Migration to Spring Boot 2
In recent two PowerAuth stack releases we migrated all of our backend projects to Spring boot 2.0 and later to version 2.1 from version 1.5. I’d like to share our experience with the migration.
Why Migrate to Spring Boot 2?
Any migration to a new major library version can be difficult, especially if the library provides so much functionality, such as Spring does. For us, the decision to migrate to Spring boot 2 was actually quite straightforward. We needed to start using Java 9 and later Java 11 given that Java 8 is getting EOL’ed. We simply forgot about the Java 10 release, because its life span is so short and eventually jumped to Java 11 as our main runtime. Note that version 2.1.x of Spring boot is required for Java 11.
Besides Java 9/10/11 support, there are many nice small improvements in Spring boot 2, such as:
- Cleanup of configuration classes/interfaces.
- Usage of Optional in JPA repositories.
- Hikari CP integration — a faster JDBC connection pool.
- improved security configuration.
- … and much more.
We also started using Spring WebFlux, the reactive stack for Spring. We started with the migration of REST clients to the reactive interface, using the WebClient component. In the future releases, we plan to use the reactive stack more. However, it will require quite a large codebase refactoring because its a completely different approach to developing services.
What went well
The Hikari CP migration went really well, we did not notice any issues. Our PowerAuth stack runs on Oracle, PostgreSQL and MySQL. Our clients can benefit from improved connection pool performance.
The migration to Optional in JPA repositories was also pretty straightforward. Instead of returning null values for non-existent entities, an Optional<MyEntity> is returned and the isPresent() method is called to check whether entity exists or not. Definitely a good step forward for getting rid of some of those pesky NullPointerExceptions.
The Spring configuration changes were mostly just changes of packages or minor refactorings. In our case, we also use the OAuth 2 stack from Spring, which required dependency updates.
We also had to deal with rename of some of the CrudRepository methods, which was not a big deal, just method renames.
The migration of clients to Web Flux went rather smoothly and we can now benefit from better performance and scalability. The most difficult thing was configuring proxy with authentication, which is, well, non-trivial, as you can see:
HttpClient httpClient = HttpClient.create()
.tcpConfiguration(tcpClient -> {
tcpClient = tcpClient.option(
ChannelOption.CONNECT_TIMEOUT_MILLIS,
pushServiceConfiguration.getFcmConnectTimeout());
if (proxyHost != null) {
tcpClient = tcpClient.proxy(proxySpec -> {
ProxyProvider.Builder builder = proxySpec
.type(ProxyProvider.Proxy.HTTP)
.host(proxyHost)
.port(proxyPort);
if (proxyUsername != null) {
builder.username(proxyUsername);
builder.password(s -> proxyPassword);
}
builder.build();
});
}
return tcpClient;
});
webClient = WebClient.builder().clientConnector(new ReactorClientHttpConnector(httpClient)).build();
Minor Surprises During Migration
There were also some unexpected changes, which were quite easy to deal with.
- spring.jpa.open-in-view — in Spring boot 2 there is a new warning about this property. Open-Session-In-View is an antipattern, luckily we do not depend on it, so we could just set the property value to false to get rid of the warning.
- spring.jmx.default-domain — some of our backends services did not start because the default JMX bean name caused a naming clash. The solution was to set the spring.jmx.default-domain property for each application. We also disabled JMX in production by default to improve startup time and improve security using property spring.jmx.enabled=false.
- Migration to new Java Date and Time APIs — in several cases, Spring started using the new API, so we had to migrate our code, too. The migration was not difficult, just a bit unexpected.
Hurdles with ID Generators
The main issue we had during migration to Spring boot 2 was something that was totally unexpected. In one of our integration projects, new records suddenly could not be inserted into the database because of ID collisions. We had no idea what was going on. After all, we did not change anything related to ID generators.
After a lot of head-scratching, we found out that Spring boot 2 migrated to enhanced ID generators. The change was documented in the migration guide shortly after 2.0.0 release. However, the impact of the change is quite significant.
The change of ID generators in Hibernate 5 basically throws away compatibility for any database tables which use sequence generators. For new projects, it is a good idea to start using the enhanced sequence generators. The enhanced ID generator algorithm performance is much better especially in case of SequenceStyleGenerator because instead of asking the database for each new ID, the database is queried only once in x new records depending on the configuration of allocationSize parameter.
The default setting of allocationSize is following in SequenceGenerator annotation:
int allocationSize() default 50;
It means that with enhanced ID generators the database is queried only once in every 50 new IDs generated. You can change this value to obtain even better performance.
Other unexpected things started to happen next to the ID collisions (which were easy to understand once we found out about the change of ID generator algorithm). We started to see negative ID numbers in the database. How could this happen?
Turns out that the enhanced ID generators break compatibility in another way. Instead of setting up sequences like this:
START WITH 1 INCREMENT BY 1
The sequences need to be defined with allocationSize parameter and preferred boundary in mind for the enhanced ID generator algorithm:
START WITH 50 INCREMENT BY 50
In case of our original sequence definition, the enhanced algorithm would take 1 as the high value and -49 as the low value and generate IDs between these two numbers. It was quite mysterious to suddenly see negative IDs in the database, but it’s just how the algorithm works when the sequence is set up incorrectly (for the simple/naive ID generation strategy).
Another thing to consider when migrating to enhanced ID generators is that new records may not have a nice linear sequence of IDs like in case of the original algorithm when generators are used from multiple different components. It is only guaranteed that the IDs will increase and there will be no ID collision when the sequence is set up correctly.
Although performance is important for us, given that PowerAuth uses cryptography extensively, most of the time is spent during the execution of cryptographic algorithms. Database queries take a very small percentage of the processing time. Migrating to new algorithms would be a major headache for existing projects, so we currently decided to use:
spring.jpa.hibernate.use-new-id-generator-mappings=false
This configuration disables the new ID generators. We describe the enhanced ID generator investigation in detail in this issue.
In the future, we may revisit this decision when database performance becomes more critical. However, a difficult database migration would be required for all of our existing clients.
Are you still on Spring Boot 1.5? Or have you already moved to Spring Boot 2? :) Let us know in the comments and share your experience with migration to Spring Boot 2!