1. Background
My maven project has a lot of modules and submodules with jars
and wars
and everything works. I also can dep
change pom.xml in company-any-artifact to below and it will work .
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.company</groupId>
<artifactId>any-artifact</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<name>${project.groupId}</name>
<modules>
<module>any-module</module>
</modules>
<!-- if remove finalName, maven will not throw StackOverflow error -->
<build>
<finalName>${project.groupId}-${project.version}</finalName>
</build>
</project>
change pom.xml in submodule to below
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>any-artifact</artifactId>
<groupId>com.company</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<groupId>com.company.any-artifact</groupId>
<artifactId>any-module</artifactId>
<packaging>pom</packaging>
<!-- <name>${project.parent.name}-${project.artifactId}</name> -->
<modules>
<module>any-submodule</module>
</modules>
<build>
<finalName>${project.parent.name}-${project.artifactId}</finalName>
</build>
</project>
change submodule pom.xml to below
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>any-module</artifactId>
<groupId>com.company.any-artifact</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<groupId>com.company.any-artifact.any-module</groupId>
<artifactId>any-submodule</artifactId>
<!-- <name>${project.parent.name}-${project.artifactId}-${project.version}</name> -->
<build>
<finalName>company-${project.parent.name}-${project.artifactId}-${project.version}</finalName>
</build>
</project>
then the output was : company-any-module-any-submodule-1.0-SNAPSHOT
Interesting! I started off cloning the repo and reproducing the error. I would appreciate any leads that can be taken from any of the steps mentioned below that helped me debug the problem -
Maven Life Cycle Phases
The phase where the issue occurred was the package
phase of the lifecycle. Meaning mvn package
reproduces the issue with your project.
Went through the stack trace lines in the error. Getting to know its the expression evaluation where it's failing -
@Override
public Object evaluate( String expr ) throws ExpressionEvaluationException {
return evaluate( expr, null ); // Line 143
}
It's also not the finalName
attribute which was causing it. Since specifying the default value of the same
<finalName>${artifactId}-${version}</finalName>
works fine with the same project configs.
Then tried changing the packaging of the any-submodule
as
<packaging>pom</packaging>
and the error went away. Meaning while packaging as jar
, war
etc the expression evaluation is different and results in an overflow.
Modifying the any-module
or the any-submodule
pom.xml
content I can say with some confidence that it's the project.parent.name
that is causing a recursion in evaluating the expression and causing the stack overflow(How? - is something I am still looking for..). Also, changing
<name>${project.parent.name}-${project.artifactId}</name>
to
<name>${parent.name}-${project.artifactId}</name>
works for me in the sense that I do not get an error but the jar generated is of type -
${parent.name}-any-module-any-submodule-1.0-SNAPSHOT.jar
and
${parent.name}-any-submodule-1.0-SNAPSHOT
respectively with the change.
Looking for the solution according to the requirement, I am seeking a tail to the recursion that you are using.
Note - Still working on finding an appropriate solution to this problem.
This is issue with attribute inheritance.
Try to use ${parent.name}
instead of ${project.parent.name}
.
Look at: Project name declared in parent POM isn't expanded in a module filtered web.xml.
---UPDATE---
Benjamin Bentmann (maven committier) said: "In general though, expressions of the form ${project.parent.*}
are a bad practice as they rely on a certain build state and do not generally work throughout the POM, giving rise to surprises".
https://issues.apache.org/jira/browse/MNG-5126?jql=text%20~%20%22parent%20name%22
Maybe you should consider is using ${project.parent.*}
is a good way.
The strict answer to your question is that ${project.parent.name}
will not be resolved as part the model interpolation process. And in turn, you have a StackOverflowError
, in a completely different place of the code, namely when... building the final JAR of your project.
Here's what happens. When you're launching a Maven command on a project, the first action it takes is creating the effective model of the project. This means reading your POM file, reasoning with activated profiles, applying inheritance, performing interpolation on properties... all of this to build the final Maven model for your project. This work is done by the Maven Model Builder component.
The process of building the model is quite complicated, with a lot of steps divided in possibly 2 phases, but the part we're interested in here in the model interpolation part. This is when Maven will replace in the model all tokens denoted by ${...}
with a calculated value. It happens after profiles are injected, and inheritance is performed. At that point in time, the Maven project, as represented by a MavenProject
object, doesn't exist yet, only its Model
is being built. And it is only after you have a full model that you can start constructing the Maven project from it.
As such, when interpolation is done, it only reasons in terms of the information present in the POM file, and the only valid values are the ones mentioned in the model reference. (This replacement is performed by the StringSearchModelInterpolator class, if you want to look at the source code.) Quite notably, you will notice that the <parent>
element in the model does not contain the name of the parent model. The class Model
in Maven is actually generated with Modello from a source .mdo
file, and that source only defines groupId, artifactId, version and relativePath (along with a custom id
) for the <parent>
element. This is also visible in the documentation.
The consequence of all that, is that after model interpolation is performed, the token ${project.parent.name}
will not be replaced. And, further, the MavenProject
constructed from it will have a name containing ${project.parent.name}
unreplaced. You can see this in the logs, in your sample project, we have
[INFO] Reactor Build Order:
[INFO]
[INFO] company-any-artifact
[INFO] ${project.parent.name}-any-module
[INFO] ${project.parent.name}-any-submodule
Meaning that Maven consider the actual name of the project any-module
to be ${project.parent.name}-any-module
.
We're now at a time when all of the projects in the reactor were correctly created and even compiled. Actually, everything should theoretically work just fine, but with only completely borked names for the projects themselves. But you have a strange case, where it fails at the creation of the JAR with the maven-jar-plugin
. The build fails in your example with the following logs:
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ any-submodule ---
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO]
[INFO] company-any-artifact ............................... SUCCESS [ 0.171 s]
[INFO] ${project.parent.name}-any-module .................. SUCCESS [ 0.002 s]
[INFO] ${project.parent.name}-any-submodule ............... FAILURE [ 0.987 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
meaning that something went wrong well after the model was built. And the reason is that the plugin injects the name of the project as a parameter:
/** * Name of the generated JAR. * * @parameter alias="jarName" expression="${jar.finalName}" default-value="${project.build.finalName}" * @required */ private String finalName;
Notice project.build.finalName
as the default value of the generated JAR name for the submodule. This injection, and the interpolation of the variables are done by another class called PluginParameterExpressionEvaluator.
So what happens in this:
any-submodule
injects the final name of the project, named ${project.parent.name}-any-submodule
.<finalName>
in your top-most POM project, it inherits <finalName>${project.name}-${project.version}</finalName>
.${project.name}
for any-submodule
.${project.parent.name}-any-submodule
, due to Part 1.${project.parent.name}
for any-submodule
. This works correctly: the MavenProject
is built and getParent() will be called on the project instance, returning the concrete Maven parent project. As such, ${project.parent.name}
will try to resolve the name of any-module
, which is actually ${project.parent.name}-any-module
.${project.parent.name}-any-module
, but still on the any-submodule
project instance. For PluginParameterExpressionEvaluator
, the root "project"
on which to evaluate tokens hasn't changed.${project.parent.name}
on any-submodule
, which, again, works correctly and returns ${project.parent.name}-any-module
.${project.parent.name}
on any-submodule
... which works and returns ${project.parent.name}-any-module
so it tries to evaluate ${project.parent.name}
...And you can see the endless recursion happening here, which results in the StackOverflowError
you have. Is this a bug in PluginParameterExpressionEvaluator
? This is unclear: it reasons on model values that were not correctly replaced in the first place. In theory, it could handle the special case of evaluating ${project.parent}
and create a new PluginParameterExpressionEvaluator
working on this parent project, instead of always working on the current project. If you feel strongly about this, feel free to create a JIRA issue.
With what has been said above, you could now deduce why it works in this case. Let's reason with what Maven needs to do to evaluate the final name, as has to be injected in the Maven Jar Plugin:
any-module
injects the final name of the project, named ${project.parent.name}-any-module
.<finalName>
in your top-most POM project, it inherits <finalName>${project.name}-${project.version}</finalName>
.${project.name}
for any-module
.${project.parent.name}-any-module
, same as before.${project.parent.name}
for any-module
. Just like before, this works correctly: the MavenProject
is built and getParent() will be called on the project instance, returning the concrete Maven parent project. As such, ${project.parent.name}
will try to resolve the name of any-artifact
, which is actually company-any-artifact
.And you don't have any errors.
As I stated in my answer to Difference between project.parent.name and parent.name ans use of finalName in pom.xml
Let's first look at the basics:
as stated in POM Reference:
finalName: This is the name of the bundled project when it is finally built (sans the file extension, for example: my-project-1.0.jar). It defaults to ${artifactId}-${version}.
name: Projects tend to have conversational names, beyond the artifactId.
So these two have different uses.
name
is purely informational and mainly used for generated documentation and in the build logs. It is not inherited nor used anywhere else. It is a human readable String and can thus contain any character, i.e. spaces or characters not allowed in filenames. So, this would be valid: <name>My Turbo Project on Speed!</name>
. Which is clearly at least a questionable file name for an artifact.
as stated above, finalName
is the name of the generated artifact. It is inherited, so it should usually rely on properties. The only two really useful options are the default ${artifactId}-${version}
and the versionless ${artifactId}
. Everything else leads to confusion (such as a project named foo
creating an artifact bar.jar
). Actually, My turbo Project! would be valid, since this is a valid filename, but in reality, filenames like that tend to be rather unusable (try adressing a filename containing ! from a bash, for example)
So, as to why the Stackoverflow happens:
name
is not inheritedproject.parent.name
also is not evaluated during interpolation, since the name is one of the few properties which are completey invisible to the childrenparent.name
actually used to work in older Maven versions, but more due to a bug (also it is deprecated to access properties without the leading project
).any-submodule
, the value for finalName
is (try it with mvn help:effective-pom
) still: ${project.parent.name}-any-submodule
So far so bad. Now comes the reason for the StackOverflow
Maven has an addtional feature called late interpolation that evaluates values in plugin parameters when they are actually used. This allows a pluing to use properties that are not part of the model, but are generated by plugins earlier in the lifecycle (this allows, for instance plugins to contribute a git revision to the final name).
So what happens is this:
edit: made the actual reason for the error clearer (see comments):
@Parameter( defaultValue = "${project.build.finalName}", readonly = true )
PluginParameterExpressionEvaluator
kicks in and tries to evaluate the final name (${project.parent.name}-any-submodule
, which contains a property expression ${project.parent.name}.${project.parent.name}-any-module
.${project.parent.name}-any-module
(again), since a property is always resolved against the current project, the cycle begins again.Sadly, you can't.
You need to explicitly specify name
(as well as artifactId
) for every project. There is no workaround.
Then, you could let finalName
rely on it. I would however advise against it (see my answer to Difference between project.parent.name and parent.name ans use of finalName in pom.xml)
The problem in changing the final name that way is that the name of the locally build artifact and the one in the repository would differ, so locally your artifact is named any-artifact-any-module-any-submodule.jar
, but the artifact name in your repository would be still any-submodule.jar
<artifactId>artifact-anymodule-anysubmodule</artifactId>
.anymodule
, is does not need to be the actual artifactId of the module!name
for what it was intended, to be human readable, so you might consider something more visually appealling (since this is the name that appears in the build log): <name>Artifact :: AnyModule :: AnySubModule</name>
.