Should we use <dependencyManagement> or <properties> to manage versions in Maven? - maven

There are 2 ways to manage versions in multi-project solutions, e.g. Java project with microservices:
Dependency management
In parent pom.xml you define a long list of depencies used across child projects
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>3.0.1</version>
</dependency>
</dependencies>
</dependencyManagement>
and in child pom.xml you just don't specify versions
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
Version placeholders
In parent pom.xml you define properties like
<properties>
<driver.version>4.9.0-scylla-1</driver.version>
<spring-boot.version>3.0.1</spring-boot.version>
<spring-cloud.version>2022.0</spring-cloud.version>
<lombok.version>1.18.24</lombok.version>
</properties>
then in child pom.xml you must specify version in a way
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>${spring-boot.version}</version>
</dependency>
Which is the preferred way to keep dependency versions consistent in a microservice environment? What are pros and cons of these two ways? The second way seems more elastic because each of child projects may select other version of dependency and you may build project with another version of some dependency from commandline.

Related

How to force maven dependency version via dependencyManagement in parent pom?

Let's say B:1.0.1 has transitive dependency A:1.0.1, but the child project is supposed to depend on A:1.0.2 (with intentional overriding transitive dependencies).
It is easy to discover that the order of dependencies in <dependencyManagement> affect versions overriding, so adding A:1.0.2 in the child pom just before B:1.0.1 would force using A:1.0.2 even as a dependency for B:1.0.1.
In this case I'm looking for a way to declare A:1.0.2 in the parent pom, and remove boilerplate from all its children. Unfortunately, the following setup leads to using both versions in the final artifact: A:1.0.1 (comes as a dependency of B:1.0.1) and A:1.0.2 (comes from the explicit declaration in the parent pom).
How to force using version A:1.0.2 in all child projects, keeping the declaration in the parent?
Parent pom:
<groupId>my-group</groupId>
<artifactId>my-parent</artifactId>
<version>0.0.1</version>
<packaging>pom</packaging>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>g</groupId>
<artifactId>A</artifactId>
<version>1.0.2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Child pom:
<parent>
<groupId>my-group</groupId>
<artifactId>my-parent</artifactId>
<version>0.0.1</version>
</parent>
<artifactId>my-child</artifactId>
<version>0.0.1</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>g</groupId>
<artifactId>A</artifactId>
<!-- version 1.0.2 comes from the parent pom -->
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>g</groupId>
<artifactId>B</artifactId>
<version>1.0.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
You are using the dependencyManagement incorrectly.
If A and B are jar artifacts, you should not have the tags
<type>pom</type>
<scope>import</scope>
These are for BOMs only.

Maven doesn't run unit tests in a multi-module project with Spring Boot

Maven supports multi-module applications by having the child modules point to the parent module, but Spring Boot wants it's parent to be
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
According to the Spring docs I can still do this (use a maven parent-child relationship) if I add dependency management like this:
<dependencyManagement>
<dependencies>
<dependency>
<!-- Import dependency management from Spring Boot -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
instead of the Spring Boot parent.
I've done this, but now my unit tests don't run when I run mvn from the command line. The unit tests still work from Intellij, but something in the new parent-child / changes is confusing mvn and keeping it from running tests.
Here's a pom.xml from a module that no longer runs its tests:
<project xmlns=...>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>abcd</groupId>
<artifactId>abcd.parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>entities</artifactId>
<name>entities</name>
<description>Entities and Repositories</description>
<properties>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.source>11</maven.compiler.source>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<!-- Import dependency management from Spring Boot -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
This module is a library module that does not contain a #SpringBootApplication but the tests ran before converting to the maven parent-child structure.
Edit:
This project has both junit 4 and junit-jupiter-api 5.5.2 in its dependencies. When using org.junit.jupiter.api.Test (5.5.2) no tests run. When using org.junit.Test (4.12) the test runs but the Autowired repository doesn't get injected (null). (The Autowired repository is in another module.)
Spring doesn't handle dependencies correctly when using a normal maven child-parent structure.

Can't add dependency to pom.xml file

I am following a Springboot tutorial on a website I found. The site tells us to add various dependencies to our pom.xml file. I added the web and thymeleaf dependencies through the Spring Initializr. Hwoever, I realized that I forgot to add the security dependency. When I try to edit my code and add the security dependency by typing:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</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-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
You should either inherit your project from spring-boot-starter-parent:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7.RELEASE</version>
</parent>
The parent project provides the following features:
Java 1.8 as the default compiler level.
UTF-8 source encoding.
A Dependency Management section, inherited from the spring-boot-dependencies pom, that manages the versions of common dependencies. This dependency management lets you omit <version> tags for those dependencies when used in your own pom.
An execution of the repackage goal with a repackage execution id.
Sensible resource filtering.
Sensible plugin configuration (exec plugin, Git commit ID, and shade).
Sensible resource filtering for application.properties and application.yml including profile-specific files (for example, application-dev.properties and application-dev.yml)
Or use the spring-boot-dependencies BOM:
<dependencyManagement>
<dependencies>
<dependency>
<!-- Import dependency management from Spring Boot -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.7.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Using the BOM is the way to go when you would not like to inherit from the spring-boot-starter-parent POM. You may have your own corporate standard parent that you need to use or you may prefer to explicitly declare all your Maven configuration. You would still keep the benefit of the dependency management, but not the plugin management.
Both solutions will allow you to omit versions of Spring dependencies.

Spring Boot: The managed version is 1.3.2.RELEASE The artifact is managed in org.springframework.boot:spring-boot-dependencies:1.3.2.RELEASE

I create a skeleton application use Spring boot. This is my pom.xml
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.lynas</groupId>
<artifactId>SpringMVCHibernate</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>SpringMVCHibernate</name>
<description>SpringMVCHibernate</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.2.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.1.0.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
I stuck at this step:
Spring Boot: The managed version is 1.3.2.RELEASE The artifact is
managed in
org.springframework.boot:spring-boot-dependencies:1.3.2.RELEASE
When I try add Hiberante 5.1.0.Final manually, this notice appear:
Overriding managed version 4.3.11.Final for hibernate-core
Help me resolve these problem.
Spring Boot provides dependency management for Hibernate. The warning is Eclipse telling you that you've overridden this dependency management by declaring a version directly on a dependency. That's a risky thing to do as you may end up with a mixture of Hibernate versions on the classpath. In fact, looking at your pom, you've overridden the version of hibernate-core but not of hibernate-entitymanager. This means you'll have 5.1.0.Final of the former and 4.3.11.Final of the latter on the classpath. That will almost certainly lead to problems at runtime.
A safer way to use Hibernate 5 is to override Boot's dependency management. As you are using spring-boot-starter-parent as your pom's parent you can do that by overriding the hibernate.version property:
<properties>
<hibernate.version>5.1.0.Final</hibernate.version>
</properties>
This will ensure that all Hibernate modules for which Spring Boot provides dependency management will have the desired version.
Finally, a note of caution. Hibernate 5.1 is very new and contains some breaking changes, even from 5.0.x. As a result, you may run into some incompatibility problems. If you don't want to be right on the bleeding edge, 5.0.x may be a safer choice. It will become the default Hibernate version in Spring Boot 1.4.
Spring Boot automatically defines version for dependencies as listed in this appendix.
http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#appendix-dependency-versions
Eclipse is just reminding about it. You can ignore the warning if you really want to change the version for that dependency.
Update:
See Andy's answer: https://stackoverflow.com/a/35385268/1433665
In addition to the answers above. My issue was an old version of STS/Eclipse. After reinstalling with the latest and greatest Spring Tools the error was resolved.
https://spring.io/tools

Should you use the parent if you are using the spring platform bom?

Some dependency versions are not in so I've added the spring platform BOM, is the parent declaration still useful?
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.1.RELEASE</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.spring.platform</groupId>
<artifactId>platform-bom</artifactId>
<version>1.1.1.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
I personally prefer to use platform-bom as a parent, i.e.
<parent>
<groupId>io.spring.platform</groupId>
<artifactId>platform-bom</artifactId>
<version>1.1.1.RELEASE</version>
<relativePath />
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
In this way I don't have to define spring-boot version number and it is automatically updated with newer version of spring platform and I don't have to worry about any inconsistencies.
See http://docs.spring.io/platform/docs/1.1.1.RELEASE/reference/htmlsingle/#appendix-dependency-versions for complete list of all managed dependencies.
EDIT: As pointed out by Andy Wilkinson, spring platform inherits spring-boot-starter-parent so all "sensible defaults" as described in http://docs.spring.io/spring-boot/docs/1.2.1.RELEASE/reference/htmlsingle/#using-boot-maven apply as well.
There is a important difference between importing a BOM (in the dependencyManagement section) and using a parent
The BOM imported in dependencyManagement only provides defaults for dependencies, but a Parent-way include the other sections too (plugins, plugin-managent, dependencies, dependencyManagement...)
So when you remove the parent spring-boot-starter-parent then you have to copy the the plugin-managent stuff you need first.

Resources