Reputation: 131
I have a multi-module maven application which uses Spring boot:
- spring boot parent
- myproject parent (both parent and module pom)
- module1
- module2
- module-it (integration tests)
In my module-it, I add the other modules as dependencies.
When I build my project with maven, I get "Build Success":
mvn clean install
So far so good.
Yet I would like each of my modules to be an executable jar at the end of the build. With the above settings, the manifest is not defined and the jar is not executable.
To fix this issue, I've added the following in my module1 and module2 pom files:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
With this setting, my jar file is executable but I cannot build anymore. Classes that I use in my module-it are not found.
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:testCompile (default-testCompile) on project module-it: Compilation failure: Compilation failure:
[ERROR] /home/user/<path-to-project>/testing/module-it/src/test/java/com/mycompany/testing/greeting/GreetingControllerIT.java:[20,17] cannot find symbol
[ERROR] symbol: class GreetingController
[ERROR] location: class com.mycompany.testing.greeting.GreetingControllerIT
[ERROR] /home/user/<path-to-project>/testing/module-it/src/test/java/com/mycompany/testing/hello/HelloControllerIT.java:[20,17] cannot find symbol
[ERROR] symbol: class HelloController
[ERROR] location: class com.mycompany.testing.hello.HelloControllerIT
[ERROR] /home/user/<path-to-project>/testing/module-it/src/test/java/com/mycompany/testing/greeting/GreetingControllerIT.java:[16,27] cannot find symbol
[ERROR] symbol: class GreetingController
[ERROR] /home/user/<path-to-project>/testing/module-it/src/test/java/com/mycompany/testing/hello/HelloControllerIT.java:[16,27] cannot find symbol
[ERROR] symbol: class HelloController
Can you please help me understand why spring-boot-maven-plugin makes my build fail and how I can solve the issue?
Thanks in advance for your help.
Upvotes: 7
Views: 5060
Reputation: 7872
Late answer to an old question but i've just been working on this for the past hours. Even though the previous answers were helpfull they don't explain why this problem is happening.
For others that may comes accross this issue here is a detailed explnation why it's not working
Usualy Maven package your application as a regular .jar with all compiled class being in a well known location in the .jar file. So it's pretty straighforward for a compiler to import the .jar as a library and to load the available .class.
But the spring-boot-maven-plugin
is actually modifying the .jar structure to leverage spring-boot logic when you start the .jar application.
In short, the .class are not available to be imported as a "library" from the resulting .jar, because the spring class have took the well known location for itself.
Let's explore the problem with an example
Let's imagine a project with multiple maven modules like so
my-app/ -- The parent project
├─ pom.xml
├─ application/
│ ├─ pom.xml
├─ integration-tests/
│ ├─ pom.xml
Given the following pom.xml files:
my-app/pom.xml:
<project [...]>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.0</version>
<relativePath/>
</parent>
<groupId>com.me</groupId>
<artifactId>my-app</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>application</module>
<module>integration-tests</module>
</modules>
</project>
my-app/application/pom.xml:
<project [...]>
<parent>
<groupId>com.me</groupId>
<artifactId>my-app</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>application</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
my-app/integration-tests/pom.xml:
<project [...]>
<parent>
<groupId>com.me</groupId>
<artifactId>my-app</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>integration-tests</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>com.me</groupId>
<artifactId>application</artifactId>
</dependency>
</dependencies>
</project>
Let's try to package our app
/my-app$ mvn package
Of course it will miserably fail with an error cannot find symbol
, but why is it so?
Let's take a look at our architecture after the failed build:
my-app/ -- The parent project
├─ pom.xml
├─ application/
│ ├─ pom.xml
│ ├─ target/
│ │ ├─ application-1.0.0-SNAPSHOT.jar
│ │ ├─ application-1.0.0-SNAPSHOT.jar.original
├─ integration-tests/
│ ├─ pom.xml
│ ├─ target/
The spring-boot-maven-plugin
has done several things to the application module output:
application-1.0.0-SNAPSHOT.jar
to application-1.0.0-SNAPSHOT.jar.original
application-1.0.0-SNAPSHOT.jar
Let's explore the structure of the application-1.0.0-SNAPSHOT.jar
:
BOOT-INF/
├─ classes/
│ ├─ com/
│ │ ├─ me/ -- The compiled .class of your project reside here
META-INF/
org/
├─ springframework/
│ ├─ boot/ -- contains the spring boot loader classes
As you can see the .class files at the root of your .jar are the spring boot loader classes, not our own .class that are relegated to the BOOT-INF/classes/
folder.
This is not conventional, and when the .jar is imported as a dependency it won't search here for class to import.
Because of that, when maven try to package the integration-tests module, it fails because the class present in the application-1.0.0-SNAPSHOT.jar
are actually a bunch of spring class instead of the one you are trying to import from application module.
If you were to look at the structure of the application-1.0.0-SNAPSHOT.jar.origial
it would be something like so:
META-INF/
com/
├─ me/ -- The compiled .class of your project reside here
Getting rid of spring-boot-maven-plugin
is not an acceptable solution; Of course your project will be buildable, but the resulting .jar won't be a spring boot standalone running .jar.
Instead you can instruct the spring-boot-maven-plugin
to not replace the original jar and to build to spring boot jar with another name.
To do so you'll need to configure the spring-boot-maven-plugin
in the application module:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<id>repackage</id>
<configuration>
<classifier>exec</classifier>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
Now when you build your project you'll have something like that:
my-app/ -- The parent project
├─ pom.xml
├─ application/
│ ├─ pom.xml
│ ├─ target/
│ │ ├─ application-1.0.0-SNAPSHOT.jar -- the original untouched .jar
│ │ ├─ application-1.0.0-SNAPSHOT-exec.jar -- the spring boot executable .jar
├─ integration-tests/
│ ├─ pom.xml
│ ├─ target/
│ │ ├─ integration-tests-1.0.0-SNAPSHOT.jar
Upvotes: 2
Reputation: 131
To solve this issue, we can add a classifier as described in the documentation custom repackage classifier
The plugin then becomes:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
<configuration>
<classifier>exec</classifier>
</configuration>
</execution>
</executions>
</plugin>
Upvotes: 4
Reputation: 1
At least for the spring-boot-starter-parent:2.6.0
pom the configuration for the spring-boot-maven-plugin
contains an <id>repackage</id>
for the execution of the repackage
goal
So I had to add the line <id>repackage</id>
too.
Full configuration:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
<configuration>
<classifier>exec</classifier>
</configuration>
</execution>
</executions>
</plugin>
Upvotes: 0
Reputation: 21
Also you could set repackage goal parameter attach to false:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
<configuration>
<attach>false</attach>
</configuration>
</execution>
</executions>
</plugin>
Upvotes: 2