# Create a multi-module application (Spring Boot & Maven)

In 
Published 2024-01-11

This tutorial explains how we can create a multi-module Spring Boot Java application with Maven. My example will contain a simple service and a library which is used by the service.

The steps for creating a such a project are:

  • Create a Root Project
  • Create the Library Module
  • Create the Service Module

# Prerequisites

Java and Maven are already installed.

# Create a Root Project

1. Create the folder which will keep the project

This folder in my case is "D:\1-maven-lab\MyApplication". This is considered the root folder.

2. Add the pom.xml file

POM = Project Object Model

The pom.xml file

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

    <groupId>com.multi-module-app</groupId>
    <artifactId>my-application</artifactId>
    <version>0.1.0</version>
    <packaging>pom</packaging>

    <modules>
        <module>library</module>
        <module>service</module>
    </modules>

</project>

3. Create the directory structure

In "D:\1-maven-lab\MyApplication" we create 2 directory : "library" and "service".

# Create the Library Module

1. Create the folder for the library

In "D:\1-maven-lab\MyApplication\library" we create the following directories: src -> main -> java -> com -> example -> mylib.

2. Add/Create the pom.xml file

In "D:\1-maven-lab\MyApplication\library" I add the following pom.xml file:

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.2.0</version>
		<relativePath/>  
	</parent>
	<groupId>com.example</groupId>
	<artifactId>library</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>library</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>21</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

3. Create a service to read a value from application.properties

package com.example.mylib;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("custom")
public class ServiceProperties {

  private String message;

  public String getMessage() {
    return message;
  }

  public void setMessage(String message) {
    this.message = message;
  }
}

4. Create the main service in the library

package com.example.mylib;

import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Service;

@Service
@EnableConfigurationProperties(ServiceProperties.class)
public class MyService {

    private final ServiceProperties serviceProperties;

    public MyService(ServiceProperties serviceProperties) {
        this.serviceProperties = serviceProperties;
    }

    public String message() {
        return this.serviceProperties.getMessage();
    }
}

5. Create a simple unit test for MyService

For this we need to create the package (see the class definition) and we will add the following code:

package com.example.mylib;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest("custom.message=Hello")
public class MyServiceTest {

    @Autowired
    private MyService myService;

    @Test
    public void contextLoads() {
        assertThat(myService.message()).isNotNull();
    }

    @SpringBootApplication
    static class TestConfiguration {
    }
}

5. Test the library

Run the command mvn clean install in "D:\1-maven-lab\MyApplication\library".

When I run it I receive the following result:

D:\1-maven-lab\MyApplication\library>mvn clean install
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------< com.example:library >-------------------------
[INFO] Building library 0.0.1-SNAPSHOT
[INFO]   from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- clean:3.3.2:clean (default-clean) @ library ---
[INFO] Deleting D:\1-maven-lab\MyApplication\library\target
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ library ---
[INFO] skip non existing resourceDirectory D:\1-maven-lab\MyApplication\library\src\main\resources
[INFO] skip non existing resourceDirectory D:\1-maven-lab\MyApplication\library\src\main\resources
[INFO]
[INFO] --- compiler:3.11.0:compile (default-compile) @ library ---
[INFO] Changes detected - recompiling the module! :source
[INFO] Compiling 2 source files with javac [debug release 21] to target\classes
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ library ---
[INFO] skip non existing resourceDirectory D:\1-maven-lab\MyApplication\library\src\test\resources
[INFO]
[INFO] --- compiler:3.11.0:testCompile (default-testCompile) @ library ---
[INFO] Changes detected - recompiling the module! :dependency
[INFO] Compiling 1 source file with javac [debug release 21] to target\test-classes
[INFO]
[INFO] --- surefire:3.1.2:test (default-test) @ library ---
[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
[INFO]
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.example.mylib.MyServiceTest

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.2.0)

2024-01-11T22:40:44.141+02:00  INFO 19136 --- [           main] com.example.mylib.MyServiceTest          : Starting MyServiceTest using Java 21 with PID 19136 (started by Catalin in D:\1-maven-lab\MyApplication\library)
2024-01-11T22:40:44.144+02:00  INFO 19136 --- [           main] com.example.mylib.MyServiceTest          : No active profile set, falling back to 1 default profile: "default"
2024-01-11T22:40:44.578+02:00  INFO 19136 --- [           main] com.example.mylib.MyServiceTest          : Started MyServiceTest in 0.665 seconds (process running for 1.419)
WARNING: A Java agent has been loaded dynamically (C:\Users\Catalin\.m2\repository\net\bytebuddy\byte-buddy-agent\1.14.10\byte-buddy-agent-1.14.10.jar)
WARNING: If a serviceability tool is in use, please run with -XX:+EnableDynamicAgentLoading to hide this warning
WARNING: If a serviceability tool is not in use, please run with -Djdk.instrument.traceUsage for more information
WARNING: Dynamic loading of agents will be disallowed by default in a future release
OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.832 s -- in com.example.mylib.MyServiceTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO]
[INFO] --- jar:3.3.0:jar (default-jar) @ library ---
[INFO] Building jar: D:\1-maven-lab\MyApplication\library\target\library-0.0.1-SNAPSHOT.jar
[INFO]
[INFO] --- install:3.1.1:install (default-install) @ library ---
[INFO] Installing D:\1-maven-lab\MyApplication\library\pom.xml to C:\Users\Catalin\.m2\repository\com\example\library\0.0.1-SNAPSHOT\library-0.0.1-SNAPSHOT.pom
[INFO] Installing D:\1-maven-lab\MyApplication\library\target\library-0.0.1-SNAPSHOT.jar to C:\Users\Catalin\.m2\repository\com\example\library\0.0.1-SNAPSHOT\library-0.0.1-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  5.292 s
[INFO] Finished at: 2024-01-11T22:40:45+02:00
[INFO] ------------------------------------------------------------------------

All looks good so far.

# Create the Service Module

Now let's create the main service.

1. Create the folder for the library

In "D:\1-maven-lab\MyApplication\service" we create the following directories: src -> main -> java -> com -> example -> service.

2. Add/Create the pom.xml file

In "D:\1-maven-lab\MyApplication\service" I add the following pom.xml file:

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.2.0</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>
  <groupId>com.example</groupId>
  <artifactId>service</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>service</name>
  <description>Demo project for Spring Boot</description>
  <properties>
    <java.version>21</java.version>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
    
    <dependency>
      <groupId>com.example</groupId>
      <artifactId>library</artifactId>
      <version>0.0.1-SNAPSHOT</version>
   </dependency>

  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>

</project>

3. Add the main class

Here is the main class for the main service:

package com.example.service;

import com.example.mylib.MyService;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication(scanBasePackages = "com.example")
@RestController
public class DemoApplication {

	private final MyService myService;

	public DemoApplication(MyService myService) {
		this.myService = myService;
	}

	@GetMapping("/")
	public String home() {
		return myService.message();
	}

	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}
}

4. Add in application.properties some content

In application.properties file I added the following content:

custom.message=Hello Everybody !

# Test the project

When I run "http://localhost:8080" in the browser I need to see "Hello Everybody !" on the browser.