Get ahead
VMware offers training and certification to turbo-charge your progress.
Learn moreSpring Boot 2 was released recently and the production instance of Spring Initializr (start.spring.io) was upgraded to Spring Boot 2 the same day.
In this post, I'd like to walk you through the process of upgrading a Spring Boot 1.x
app to Spring Boot 2.
A good first step is to get yourself familiar with the main changes in Spring Boot 2 by reading the migration guide and the release notes.
If you are using Maven and the spring-boot-starter-parent
, you need to be aware that several plugins are going to be updated as part of the upgrade. If you're not using the parent, it is worthwhile to inspect your build and upgrade the plugins that you are using. Spring Initializr is built with Maven so the easiest way is to scan spring-boot-dependencies
and upgrade the plugins you are using if necessary.
Initializr has dedicated auto-configuration to automatically configure the service. It also exposes dedicated metrics so it goes beyond using what already exists. The first step is to review some basic compilation errors due to renames and relocations.
In the case of Initializr, the biggest change was the move to micrometer as the CounterService
and GaugeService
are no longer available. Fortunately, it was quite easy to fix the compilation errors by injecting the MeterRegistry
. We can then use the registry to retrieve a Counter
per element to increment.
We certainly need to improve our integration with Micrometer, but that basic change is enough to get us going.
We haven't looked at the test suite yet but we could give the app a go to get a first impression. Before doing so, let's add the properties migrator to the build
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-properties-migrator</artifactId>
<scope>runtime</scope>
</dependency>
Or, if you're using Gradle:
runtime("org.springframework.boot:spring-boot-properties-migrator")
If we run the app, it will identify the properties that are no longer managed by Spring Boot. If there is a replacement it will temporarily remap the property for you with a warning. If there isn't a replacement, an error report will give you more information. Either way, the configuration has to be updated and the dependency removed once you have updated the configuration.
Before you move on, it is a good idea to use the search feature of your IDE to double-check that you aren't using one of the properties you've migrated in an integration test. In the case of Initializr, management.security.enabled
was used and had to be migrated as well.
So the app starts now. Before going further, let's have a look at the test suite.
As part of the upgrade, test utilities are also upgraded (we primarily use AssertJ and Mockito). AssertJ had an interesting behaviour change: containsSequence
on a String
stopped permitting holes in the sequence (containsSubSequence
was introduced for that behavior). The Mockito upgrade was painless (ArgumentMatcher
is now an interface and the import for argThat
had to be changed).
We were also using PropertiesConfigurationFactory
to bind a POJO to a prefix of the Environment
. In 2.0, a brand new Binder
API was introduced that is more flexible and easier to use. Our binding that took 10 lines of code could be reduced to 3 simple lines.
Finally, our auto-configuration tests can now benefit from ApplicationContextRunner
, rather than creating the ApplicationContext
and managing its lifecycle ourselves. This feature is covered in detail in a separate blog post.
We can now run the tests and realize that there are a few tests failing. The home page of the service does not work anymore in the browser! Some tests exercising the creation of build scripts via the endpoint failed too.
The reason the templates did not render anymore is due to the fact that the prefix for Mustache templates has changed. Renaming our templates to .mustache
was enough to fix that problem.
The second problem was an abuse of path matching in our endpoint. The endpoint that allows you to create a pom.xml
file was as follows:
@RequestMapping("/pom")
@ResponseBody
public ResponseEntity<byte[]> pom(BasicProjectRequest request) { ... }
Users are expected to call /pom.xml
and that's what we should have mapped initially. In Spring Boot 2, the Spring MVC path matching default behavior has changed so that this endpoint does not match anymore.
An easy fix is:
@RequestMapping(path = { "/pom", "/pom.xml" })
@ResponseBody
public ResponseEntity<byte[]> pom(BasicProjectRequest request) { ... }
/info
https://start.spring.io/info is a well-known URL to retrieve the versions mapping that Spring Initializr is currently using. It typically allows you to know which Spring Cloud version matches your current Spring Boot version.
Spring Boot 2 moves actuator endpoints to /actuator
and we want to follow that default. But that does not mean we want to break a URL that users may have bookmarked. To get the best of the both worlds, we can add a simple redirect:
@Bean
public WebMvcConfigurer initializrWebMvcConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addRedirectViewController("/info", "/actuator/info");
}
}
}
Migrating Spring Initializr to Spring Boot 2 was quite easy in the end. Reading the release notes and the migration guide to know beforehand the changes that may affect your app was key. Since the Spring Boot 2 release, start.spring.io has been happily generating tens of thousands of projects a day for the community around the World.
The next step is to properly migrate metrics to Micrometer, abandoning hierarchical names and using tags instead. Subscribe to #526 if you are interested to know more about that.