This guide shows you how to create a multi-module project with Spring Boot. The project will have a library jar and a main application that uses the library. You could also use it to see how to build a library (that is, a jar file that is not an application) on its own.
You will set up a library jar that exposes a service for simple “Hello, World” messages and then include the service in a web application that uses the library as a dependency.
This guide walks through building two projects, one of which is a dependency to the other.
Consequently, you need to create two child projects under a root project. But first, create
the build configuration at the top level. For Maven you will want a pom.xml
with <modules>
listing the subdirectories:
link:complete/pom.xml[role=include]
For Gradle, you will want a settings.gradle
including the same directories:
link:complete/settings.gradle[role=include]
and (optionally) you could include an empty build.gradle
(to help IDEs identify the root directory).
In the directory that you want to be your root directory, create the following
subdirectory structure (for example, with mkdir library application
on *nix systems):
└── library └── application
In the root of the project, you will need to set up a build system, and this guide shows you how to use Maven or Gradle.
One of the two projects serves as a library that the other project (the application) will use.
In a the library
directory, create the following subdirectory structure (for example,
by using mkdir -p src/main/java/com/example/multimodule/service
on *nix systems):
└── src └── main └── java └── com └── example └── multimodule └── service
Now you need to configure a build tool (Maven or Gradle). In both cases, note that the Spring Boot plugin is not used in the library project at all. The main function of the plugin is to create an executable “über-jar”, which we neither need nor want for a library.
Although the Spring Boot Maven plugin is not being used, you do want to take advantage of
Spring Boot dependency management, so that is configured by using the
spring-boot-starter-parent
from Spring Boot as a parent project. An alternative would be
to import the dependency management as a Bill of Materials
(BOM)
in the <dependencyManagement/>
section of the pom.xml
file.
For the Library project, you need not add dependencies. The basic spring-boot-starter
dependency provides everything you need.
You can get a Maven build file with the necessary dependencies directly from the Spring Initializr.
The following listing shows the pom.xml
file that is created when you choose Maven:
link:initial/library/pom.xml[role=include]
You can get a Gradle build file with the necessary dependencies directly from the Spring Initializr.
The following listing shows the build.gradle
file that is created when you choose Gradle:
link:initial/library/build.gradle[role=include]
If you generated the Library project from start.spring.io
it will contain a wrapper script
for the build system (mvnw
or gradlew
depending on the choice you made). You can move that
script and its associated configuration up to the root directory:
$ mv mvnw* .mvn ..
$ mv gradlew* gradle ..
It is better that the library depends on the most narrowed dependencies, and not a
starter. For our own use there org.springframework.boot:spring-boot
has all the
code that we need. Removing the -starter
of the existing entry makes sure the
library doesn’t bring up too much dependencies.
The Library project has no class with a main method (because it is not an application). Consequently, you have to tell the build system to not try to build an executable jar for the Library project. (By default, the Spring Initializr builds executable projects.)
To tell Maven to not build an executable jar for the Library project, you must remove the
following block from the pom.xml
created by the Spring Initializr:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
The following listing shows the final pom.xml
file for the Library project:
link:complete/library/pom.xml[role=include]
To tell Gradle to not build an executable jar for the Library project, you must add the
following blocks to the build.gradle
created by the Spring Initializr:
plugins {
id 'org.springframework.boot' version '3.2.2' apply false
id 'io.spring.dependency-management' version '1.1.4'
// ... other plugins
}
dependencyManagement {
imports {
mavenBom org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES
}
}
The bootJar
task tries to create an executable jar, and that requires a main()
method.
As a result, you need to disable it by disabling the the Spring Boot plugin, while keeping it for its dependency management features.
Also, now that we have disabled the Spring Boot plugin, it no longer automatically configures
the JavaCompiler
task to enable the -parameters
option. This is important if you are
using an expression that refers to a parameter name. The following enables this option:
tasks.withType(JavaCompile).configureEach {
options.compilerArgs.add("-parameters")
}
The following listing shows the final build.gradle
file for the Library project:
link:complete/library/build.gradle[role=include]
The library will provide a MyService
class that can be used by applications. The
following listing (from
library/src/main/java/com/example/multimodule/service/MyService.java
) shows the
MyService
class:
link:complete/library/src/main/java/com/example/multimodule/service/MyService.java[role=include]
To make it configurable in the standard Spring Boot idiom (with application.properties
),
you can also add a @ConfigurationProperties
class. The ServiceProperties
class (from
library/src/main/java/com/example/multimodule/service/ServiceProperties.java
) fills that
need:
link:complete/library/src/main/java/com/example/multimodule/service/ServiceProperties.java[role=include]
You need not do it this way. A library might merely provide pure Java APIs and no Spring features. In that case, the application that consumes the library would need to provide the configuration itself.
You will want to write unit tests for your library components. If you provide re-usable
Spring configuration as part of the library, you might also want to write an integration
test, to make sure that the configuration works. To do that, you can use JUnit and the
@SpringBootTest
annotation. The following listing (from
library/src/test/java/com/example/multimodule/service/MyServiceTest.java
) shows how to
do so:
link:complete/library/src/test/java/com/example/multimodule/service/MyServiceTest.java[role=include]
Note
|
In the preceding listing, we have configured the service.message for the test by
using the default attribute of the @SpringBootTest annotation. We do not recommend
putting application.properties in a library, because there might be a clash at runtime
with the application that uses the library (only one application.properties is ever
loaded from the classpath). You could put application.properties in the test classpath
but not include it in the jar (for instance, by placing it in src/test/resources ).
|
The Application project uses the Library project, which offers a service that other projects can use.
In the application
directory, create the following subdirectory structure (for
example, with mkdir -p src/main/java/com/example/multimodule/application
on *nix
systems):
└── src └── main └── java └── com └── example └── multimodule └── application
Do not use the same package as the library (or a parent of the library package) unless you
want to include all Spring components in the library by @ComponentScan
in the
application.
For the Application project, you need the Spring Web and Spring Boot Actuator dependencies.
You can get a Maven build file with the necessary dependencies directly from the Spring Initializr.
The following listing shows the pom.xml
file that is created when you choose Maven:
link:initial/application/pom.xml[role=include]
You can get a Gradle build file with the necessary dependencies directly from the Spring Initializr.
The following listing shows the build.gradle
file that is created when you choose Gradle:
link:initial/application/build.gradle[role=include]
You can delete the mvnw
and/or gradlew
wrappers and their associated configuration files:
$ rm -rf mvnw* .mvn
$ rm -rf gradlew* gradle
The Application project needs to have a dependency on the Library project. You need to modify your Application build file accordingly.
For Maven, add the following dependency:
<dependency>
<groupId>com.example</groupId>
<artifactId>library</artifactId>
<version>${project.version}</version>
</dependency>
The following listing shows the finished pom.xml
file:
link:complete/application/pom.xml[role=include]
For Gradle, add the following dependency:
implementation project(':library')
The following listing shows the finished build.gradle
file:
link:complete/application/build.gradle[role=include]
The main class in the application can be a @RestController
that uses the Service
from
the library to render a message. The following listing (from
application/src/main/java/com/example/multimodule/application/DemoApplication.java
)
shows such a class:
link:complete/application/src/main/java/com/example/multimodule/application/DemoApplication.java[role=include]
Because DemoApplication
is inside a different package
(com.example.multimodule.application
) than MyService
(com.example.multimodule.service
), @SpringBootApplication
cannot automatically detect
it. There are different ways to let `MyService be picked up:
-
Import it directly with
@Import(MyService.class)
. -
Fetch everything from its package by using
@SpringBootApplication(scanBasePackageClasses={…})
. -
Specifying the parent package by name:
com.example.multimodule
. (This guide uses this method)
Note
|
If your application also uses JPA or Spring Data, the @EntityScan and
@EnableJpaRepositories (and related) annotations inherit only their base package from
@SpringBootApplication when not explicitly specified. That is, once you specify
scanBasePackageClasses or scanBasePackages , you might also have to also explicitly use
@EntityScan and @EnableJpaRepositories with their package scans explicitly configured.
|
You need to provide a message for the service in the library in application.properties
.
In the source folder, you need to create a file named
src/main/resources/application.properties
. The following listing shows a file that would
work:
link:complete/application/src/main/resources/application.properties[role=include]
Test the end-to-end result by starting the application. You can start the application in
your IDE or use the command line. Once the application is running, visit the client
application in the browser, at http://localhost:8080/
. There, you should see
Hello, World
reflected in the response.
If you use Gradle, the following command (really two commands run in sequence) will first build the library and then run the application:
$ ./gradlew build && ./gradlew :application:bootRun
If you use Maven, the following command (really two commands run in sequence) will first build the library and then run the application:
$ ./mvnw install && ./mvnw spring-boot:run -pl application
Congratulations! You have used Spring Boot to create a re-usable library and then used that library to build an application.
The following guides may also be helpful: