/gs-multi-module

Creating a Multi Module Project :: Learn how to build a library and package it for consumption in a Spring Boot application

Primary LanguageJavaApache License 2.0Apache-2.0

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.

What You Will Build

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.

What You Need

Create a Root Project

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).

Create the Directory Structure

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.

Create the Library Project

One of the two projects serves as a library that the other project (the application) will use.

Create the Directory Structure

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.

Setting up the Library Project

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]

Adjusting the Library Project

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 ..

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 '2.5.2' apply false
  id 'io.spring.dependency-management' version '1.0.11.RELEASE'
  // ... 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.

The following listing shows the final build.gradle file for the Library project:

link:complete/library/build.gradle[role=include]

Create a Service Component

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.

Testing the Service Component

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).

Create the Application Project

The Application project uses the Library project, which offers a service that other projects can use.

Create the Directory Structure

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.

Setting up the Application Project

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

Adding the Library Dependency

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]

Write the Application

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.

Create the application.properties File

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 Application

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

Summary

Congratulations! You have used Spring Boot to create a re-usable library and then used that library to build an application.

See Also

The following guides may also be helpful: