Maven - managing spring, springboot, starter parent versions in a multi module project
- Refereces
- spring-boot-starter-parent
- spring-boot-dependencies in parent pom
- What happens if we don’t manage spring-boot-dependencies in parent pom?
- Compatibility between the versions of spring-boot, spring framework and spring cloud
- Add the spring-boot-maven-plugin explicitly
- Overriding Dependency Versions
- How to exclude specific dependencies from spring-boot-starter-parent
- TODO
Refereces
spring-boot-starter-parent
For small springboot projects, spring-boot-starter-parent
is added as a parent in the pom.xml file.
e.g.
<?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.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</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>
But this does not work well in an enterprise application that is using multi-module structure. In big enterprise applications, the smaller modules will have to use the parent project for inheritance. So, it is not possible to specify <artifactId>spring-boot-starter-parent</artifactId>
as the parent. But, it will be nice to specify the versions (of spring dependencies) in one place instead of specifying the version numbers in every sub-module.
How to do this?
spring-boot-dependencies in parent pom
If we don’t make use of the parent POM, we can still benefit from dependency management by adding the spring-boot-dependencies artifact with scope=import:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>3.1.5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
In the sub-modules, we can start simply start adding Spring dependencies and making use of Spring Boot features:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
What happens if we don’t manage spring-boot-dependencies in parent pom?
If we do not manage the version numbers of spring dependencies in the parent pom, and instead, specify a version number in each of the sub-module poms, it will lead to a mess. All the sub-modules need to be on the same version of spring dependencies. If not, we will run into errors.
Another reason is, certain versions of Spring work with only certain versions of jdk. e.g. Java 8 requires Spring framework 4. It is easy to manage these compatibility issues if all of the version numbers are maintained in a single place.
BeanDefinitionStoreException Failed to read candidate component class
ERROR DispatcherServlet - Context initialization failed
org.springframework.beans.factory.BeanDefinitionStoreException: Failed to read candidate component class: file [C:\workspace-sts\.metadata\.plugins\org.eclipse.wst.server.core\tmp1\wtpwebapps\Webtest1\WEB-INF\classes\com\springweb\controller\SetHomePageController.class]; nested exception is java.lang.IncompatibleClassChangeError: class org.springframework.core.type.classreading.ClassMetadataReadingVisitor has interface org.springframework.asm.ClassVisitor as super class
Compatibility between the versions of spring-boot, spring framework and spring cloud
In the parent pom, enforce the compatibility
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.7.18</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- How would we get this version?
Go to https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-dependencies/2.7.18 and search for org.springframework.spring-framework-bom version number -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>5.3.34</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- How would we get this version?
Go to https://spring.io/projects/spring-cloud and search for Spring Cloud release train Spring Boot compatibility.
We should find something like this: 2021.0.x aka Jubilee - 2.6.x, 2.7.x (Starting with 2021.0.3) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.9</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Add the spring-boot-maven-plugin explicitly
Without the parent POM, we no longer benefit from plugin management. This means we need to add the spring-boot-maven-plugin explicitly:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
Overriding Dependency Versions
If we want to use a different version for a certain dependency than the one managed by Boot, we need to declare it in the dependencyManagement section in the parent pom.xml, before spring-boot-dependencies is declared:
Remember: just declaring the version for the dependency outside the dependencyManagement tag will no longer work.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>3.1.5</version>
</dependency>
</dependencies>
// ...
</dependencyManagement>
How to exclude specific dependencies from spring-boot-starter-parent
To summarize the questions, I want to use spring-boot-starter-parent
and spring-boot-starter-data-jpa
but not the managed transitive dependencies from spring-boot-dependencies.
How can I do it?
Instead of messing around with <excludes>
and then try to figure out what you need to include again (after figuring out what you excluded). Just override the version as explained here in the Spring Boot Reference Guide.
Assuming you are using the spring-boot-starter-parent
as the parent you can just add a <selenium.version>
to your <properties>
section to specify which version you want.
<properties>
<selenium.version>2.53.0</selenium.version>
</properties>
This will make Spring Boot use the version you want.
TODO
How to manage spring’s irregularity in version dependencies?