Spring Boot 2.x migration analysis

Motivation

Spring Boot 2.0 was first released on March 1st and this is a major release that comes with a lot of goodies. Thus, for us, a migration to Spring Boot 2.x was inevitable in the near future. Moreover, the Spring team chooses to add the new technology on the new major releases and those are not ported on the previous branches. For instance, Spring Data has a much better support for Cassandra in the version that comes along with Spring Boot 2.x. If you need those features, trying to mix releases from different major version branches might not be the smartest idea.

However, while most of the Spring Cloud components are not released at the time of the writing, there is little chance that the final release will be postponed for too long, since most of these components are already version RC2.

Challenges

Properties change

This is a minor change didn’t affect us much, since we don’t have a lot of configuration and rely on the default configurations in most of the cases. Another important aspect to note is that starting with Spring Boot 2.x, the configuration fields follow the kebab notation for their names.

Experience. This change has to be taken care of at the early stage and correctly implemented. Despite code compiling successfully and the application runs, there might be malfunctions that don’t reveal their source at a first glance, thus from this standpoint, this makes the change a bit tricky.

Lesson. Use the advices the team gives on the matter, namely to use the spring-boot-proprieties-migrator tool they refer to in the migration guide.

The override of the Gradle Jar task

By far the most challenging one and with no previous heads-up from the migration guide. The only comment in the migration guide is:

The jar and war tasks are no longer involved.

, but this clearly underestimate the challenge this change poses to the migration process.

With this change, the Spring Boot Gradle plugin deactivates the default Jar task of the Gradle java project. As a side note, this is an important aspect in a Gradle multi-project environment where you have inter-project dependencies. When a project is defined as a compile dependency, the dependent will expose the outcome of the Jar task to the dependee. And in this case, where the Jar task produces nothing, the dependency will fail.

Experience. The ad-hoc solution, although not intuitive at a first glance, was to create a new Gradle configuration to be extended from the runtime sourceSet and to expose it to the dependee.

Code snippet to export a new configuration as a Jar task from the dependent.

configurations {
runtimeArtifact.extendsFrom runtime
}
task runtimeJar(type: Jar) {
classifier “runtime”
baseName = “${project.name}-runtime”
from sourceSets.main.output
}
artifacts {
runtimeArtifact runtimeJar
}

And this is how a dependee would import that import it.

dependencies {
compile project(path: ‘:elephant’, configuration: ‘runtimeArtifact’)
}

While this was a perfectly fine working solution, it was not exactly the most optimal, since it was not revealing why this was needed.

However, because our build pipeline is not interested in the fat jar produced by the bootJar, now the default build artifact of the project, we had to switch back to the old jar task somehow. Per Spring Boot Gradle plugin documentation, there is a way to do that by re-enabling the jar task and, if required, disable the bootJar task. In this way, the Gradle build will produce a Jar as an outcome of the build task. This, accidentally, solved the problem previously described and the exposure of an extra configuration was not needed anymore.

Lesson. Such fights are hard to avoid, since they are not very clear documented. Small baby steps will help in identifying the root problem and experimenting with different solutions.

Import a Spring Boot artifact into a non Spring Boot project

While importing a Spring Boot project into another Spring Boot project is trivial if the versions are the same, because the compile environment is already present in the dependee configuration, there still might be cases where you need to import a Spring Boot project into a non Spring Boot one, for instance, for testing purposes.

In this case, you really have to rely on exposing an extra configuration extended from the runtime in the dependent project. It has to be extended from the runtime and not just the exposure of the sourceSet, since this will export the transitive dependencies too, namely the Spring Boot framework libs.

However, there is also a nice trick that will save you from the burden of exposing that extra configuration in the dependent project and the declaration of this dependency in the dependee’s configuration. Per Spring Boot Gradle plugin documentation, you can import the plain jar (not the fat one) of the Spring Boot dependent project into the dependee and let their io.spring.dependency-management plugin to do the ceremony. All this by declaring the dependee project a Spring Boot but without really applying the plugin. Therefore, you have all the Spring Boot dependencies resolved in an elegant way.

plugins {
id ‘org.springframework.boot’ version ‘2.0.2.RELEASE’ apply false
}

One note here is that Gradle does not support variables for version in this case and you have to hardcode the Spring Boot version.

Lessons. Again, this is not something really straight forward and a baby steps approach is you friend in these cases too. This is also something we found out accidentally.

Happy Spring Booting!