Migrating from Java 8/11 to Java 21, and Spring Boot 2 to the latest Spring Boot 3.2
Pratik Dwivedi
December 26, 2023
Introduction
Hey folks, If your work profile is even remotely related to Java, you must have noticed a lot of buzz being created by the latest stable release of Java, Java 21.
This new release introduces some futuristic features, improves upon some path-breaking features introduced/incubated earlier, deprecates redundant features, and removes a few bugs. It makes Java more competitive and capable, keeping pace with other popular programming languages.
Modern software applications and their usage patterns demand a very high level of efficiency, security, throughput, and scalability. As software development paradigms evolve to cater to these demands (and threats), so do the prevalent languages, and Java is not lagging.
If you are concerned about making, and keeping your software performing at its best, and making good use of cutting-edge technology, this post is for you. Even if you’re not planning any upgrades, you might need a reality check or take stock of your current software stack. As the environments in which we run our software and businesses keep changing, new requirements and threats unveil themselves; it's always better to plan, modify, and correct a few things in advance.
Leveraging the latest features and advancements keeps us competitive, compatible, and nimble. In this article, we discuss the migration from Java 8 to Java 21; and Spring Boot 2 to Spring Boot3.2/Spring Framework 6.1 We keep our discussion focused on Java 21 and Spring Boot 3.2
New and exciting features of Java 21:
Java 21 is an attempt to keep Java competitive, boost performance and security, and enhance its capabilities and features. Some notable features are:
Lightweight Virtual threads - Java 21 enables thread-per-request style programming to achieve near-optimal hardware utilization via Virtual threads. Earlier, each new thread created had to correspond to a new operating system thread, which constrained the creation of new threads after a certain limit.But now, Virtual threads allow the creation of many virtual threads that share the same operating system thread, easing thread-per-request style programming.
Structured Concurrency API - to coordinate, orchestrate and observe the above virtual threads, to deliver even more reliable and maintainable concurrent code.
Scoped Values - Scoped values allow sharing of variables/data values between threads, safely and efficiently. So, threads that emanate from the same parent can share information/context between them, eliminating the need to pass method parameters to achieve the same effect. A scoped value is declared final and static, and is available only for a bounded period during the execution of the thread, clearly defining the scope and rules under which variables can be shared by virtual threads.
Vector API - Another path-breaking feature in Java21, which was incubated in Java 16. Vector API allows specifying Vector computations that compile to vector instructions on supported CPUs, thereby providing a manifold increase in computational speed. Vector instructions can process several pieces of data, e.g. 8 float or 16 short values on a 256-bit processor, in just 1 CPU instruction.
In contrast, a scalar computation would need 8 repetitions of a loop to do so.
Record Patterns To make Java even more suitable for the interpretation and analysis of diverse data in huge volumes, the concept of record patterns exists in Java 21.
Record patterns can process nested object graphs, determine the exact type of a record, and then fetch the constituent data in records.
Enhanced SWITCH statement - Switch statements can now match record patterns and regex expressions, and then execute specific actions on a match.Coupled with Record patterns to deconstruct record values for better data queries, this feature enables superior data navigation and processing.
Sequenced collections Collection objects like List/Set have a defined order now, there can be first/last/next/previous elements and they can be reversed. Java 21 offers a uniform set of operations for accessing this well-defined order, adding power to the hands of the developer.
Generational ZGC - This is the improved garbage collector that can prudently deallocate memory when not needed, easing optimal resource utilization and system performance.
Foreign Function & Memory API To interoperate with code and data outside of the JVM, in case you think some other language would do a particular task better.
Many more - There are many more such features, including Unnamed classes and Unmanned Patterns, String Templates, Key encapsulation mechanisms, etc., which add to Java 21’s power and versatility.
Some of the above features are inspired by good artifacts in other languages like Python, Go, and Rust. Some of them were introduced keeping in mind the future of computing and the areas where Java is being widely used. A lot of these features were highly anticipated as they were requested by experienced Java developers.
The adoption of, and migration to Java 21 will likely be monumental.
Significance of your current setup, Java8 and/or SpringBoot2.
Java 8 was a long-term support(LTS) version introduced in 2011 with some cool functions at that time, like Lambda functions, Stream APIs, Data and Time APIs, default/static functions in Interfaces, and the Nashorn JavaScript engine.
Java 8 lets you write readable and maintainable code, which interfaces well with other parts of your software stack. Adding to Java8’s popularity is the fact that it was the last free LTS version.
Support for Java 8 ended in 2019, and only limited options are available for commercial support till 2030. License changes from Java 9 are a bit misunderstood, we think subsequent changes in Java are good for the community as well as the language. You should think about upgrading from Java 8 as moving ahead Java has advanced, matured, and added many useful features.
While initial versions of Spring Framework focused on eliminating boilerplate code and easing development by providing basic infrastructure and auto-configuration, SpringBoot3.2/Spring Framework 6.1 moves a few steps ahead to add reactive support (Webflux as well as MongoDB and Cassandra, etc.), improved Actuator, enhanced performance, and better auto-configuration, etc.
Understanding the Starting Point: Java 8 and Spring Boot 2; and their limitations w.r.t. current scenario
Java 8 and Spring Boot 2 were good, probably the best choices about a decade back.
While Spring Boot 2 offered improved features of the Spring-Hibernate platform, Java 8 was a path-defining version with powerful features added. As cloud-based applications and mobile apps were exploding, this stack had the capabilities to cater to the demands of both. Java 8 is still loved by some developers but Java itself has undergone many changes through the 11, 17, and 21 LTS releases.
Some limitations and constraints of Java 8, as compared to their respective latest versions are:-
Java 8 lacks features like vastly improved garbage collector and memory/resource management.
Advanced APIs for Security and Encryption, Foreign Memory access, Streams and Vector computations, etc.
Modern data types like Records, Virtual Threads, Sequenced Collections, etc.
Improvements and enhancements to Classes, Interfaces and Packaging
Improved class loaders, system monitors, and tools
Spring Boot 2/Spring Framework 4 or 5 lack features like:-
Features and Improvements of Spring Framework 6
Better Performance, Scalability, Security, and Resource Utilization
Latest features of Jakarta EE like Validation, Transactions, and JSON/XML support
Better Servlet containers and GraalVM images
Better Observability, Tracing, and Micrometer Metrics
Overall, Java 8 with Spring Boot 2 is a formidable composition, but with advancements in computing and changed expectations in the last decade, it now seems a bit archaic.
Why even bother Migrating to Java 21 and Spring Boot 3.2 ? - We’re happy with Java 8!
The direction Java 21 is taking is futuristic and path-breaking. Java 21 is keeping abreast with changes in other languages, like GO/Rust/JavaScript(ECMAScript2022)/Python etc., in its ecosystem. A lot of enhancements we see in these languages endorse/facilitate AI and machine learning. Newer, better, and more relevant tools are being added for enhanced observability and measurement.
Also, other APIs/software/libraries/tools that your programs make use of and coexist with, are evolving and advancing. You might not want to lag by using an obsolete or ineffective software stack. Sooner or later, the demands and expectations around your software might change too. It's prudent to consider upgrading to Java 21 and Spring Boot 3. This could also mean harvesting potential improvements in performance, security, maintainability and most importantly observability.
Hence, our discussion further will be focused on easing the transition and how to approach it**.**
SpringBoot3.2 and Spring Framework 6.1 offer the following remarkable features:-
SpringBoot3.2 can run on GraalVm along with Java 21.
GraalVM is a fascinating JDK distribution that offers ahead-of-time (AOT) compilation of Java programs, it prunes your code of unnecessary parts and compiles the stuff that you need into operating-system and architecture-specific native code, which runs as fast as native C code!
The compiled code loads quickly, takes a lot less RAM, and optimizes CPU cycle usage on the platform that it runs. Imagine running a Java program with blazing fast speed, as if it were a GO or C program !!.
You just need to add the following to your gradle configuration:-
Spring Boot 3.2 offers inbuilt support for Virtual Threads(Project Loom) if running Java 21, so you can get the benefits of lightweight concurrency constructs by upgrading. You just need to SET a single property: <span class="pink">spring.threads.virtual.enabled=true</span>; and Spring Boot 3.2 will start using virtual executors. As an icing on the cake, virtual thread executors are auto-configured for many technologies like RabbitMQ listener/Kafka listener/Spring Data Redis' ClusterCommandExecutor/Apache Pulsar etc.
CRaC (Coordinated Restore at Checkpoint) is a mechanism in Java that allows you to take a snapshot of your JVM+programs while they are running, and save the image. Later, in case your Java program runs into problems, you can restore it to this snapshot to reach a "last known stable state", and work further to solve the problems. This image can also be very useful if you encounter slow startups or long warm-up times. So, to take advantage of the above useful features, you must consider upgrading to Spring Boot 3.2 with Java 21.
Has support for Spring Framework's RestClient, in the form of a RestClientCustomizer and RestClientAutoConfiguration. RestClient offers the fluent API of WebClient with the infrastructure of RestTemplate, making HTTP programming easy with configurable templates, message converters, request factories and interceptors, etc.
Observability, as the name suggests, is the ability to measure the internal state of a system by measuring its external outputs only.
With Spring 6.1/SpringBoot3.2, a new initiative called Spring Observability is introduced where the Spring framework itself tracks its internal metrics, like logs/traces/other metrics, providing quicker/better information as it comes from the framework itself.
You can now use Micrometer’s <span class="pink">@Timed</span>, <span class="pink">@Counted</span>, <span class="pink>"@NewSpan</span>, <span class="pink">@ContinueSpan</span> and <span class="pink>"@Observed</span> annotations.
Also, Observability for R2DBC and AspectJ has been added, and the auto-configuration for Open Telemetry has been improved.
Using our Unlogged plugin in light of the above enhancements can add holistic tracking/visualizations/proactive intervention/code optimization to your observations.You can try the plugin right now.
Auto-configuration for JdbcClient has been added, based on the presence of a NamedParameterJdbcTemplate.
SSL improvements - SSL bundles can now be automatically reloaded when the trust material changes, just by setting its reload-on-update property to true.
There are many other useful features and enhancements that you can check here.
Preparing for the Transition
One way is to move upwards stepwise and transition to Java 11, then to Java 17, and finally to Java 21. Though time-consuming, this allows for a smoother transition.
The second way is to jump from Java 8 to Java 21 directly.
Whichever approach you acquire, below are some preparations and key issues you should consider while planning your transition.
Migration Analysis Reports Tool - Java 21 provides a cool and effective tool to analyze your existing applications(s) and provides detailed reports to help you plan and strategize your migration process.
This tool is fine-grained to such an extent that it identifies the line numbers in your source code where modifications are needed and highlights both mandatory and recommended changes !!
Migration analysis reports detail the migration effort and risks involved in migrating applications from older JDK versions to the required newer JDK version.
This tool really helps you quantify and measure the effort required for the migration, provides a clear picture of the operations/exercises needed, and helps you make informed decisions.
Starting from Java 17, stricter encapsulation is enforced blocking unauthorized reflection on private/protected(non-public) fields and methods , if you code was using the permissive reflection till now, you will need to add the “--add-exports" **and "--add-opens" while compiling your code.
<span class="pink">--add-exports</span> allows runtime/compile time access to encapsulated internal APIs, and <span class="pink">--add-opens</span> allows reflection into “external” Java APIs.
If you’re using <span class="teal>"javax.util.regex.Pattern</span> class, remember it behaves a bit differently after Java 8. The negation operator <span class="teal>^</span> , will now work on nested character classes also apart from the outermost class just after the opening <span class="teal">“[“</span>braces.
So, <span class="teal">[^u-v[w-x]y-z]</span> will now not match w and x also as the operator now works on nested classes too.
Earlier till Java 8, it would match w and x (did not work on nested classes earlier), but not u-v and y-z as they are not nested.
You can look into the official documentation for more details and fine grained examples including <span class="teal">&&</span> and <span class="teal">||</span> etc.
Planning the migration effort, using the tools and issues mentioned above will make your process smoother and drive you to a successful migration. Thorough preparation will ease your process and make it productive.
The checklist for the migration
Below are two general checklists for the migration:-
A. Java 8 to Java 21
In addition to the steps detailed in “Preparing for them migration” section, the following steps can work as a checklist for your migration:-
If your code relies on the version-string format to distinguish major, minor, security, and patch update releases, then you may need to update it.
The format of the new version-string is: <span class="pink">$FEATURE.$INTERIM.$UPDATE.$PATCH</span>
Class loader changes - The application class loader is an internal class now, its no longer an instance of URLClassLoader. The extension class loader has been renamed to be called the platform class loader. The bootstrap classloader now loads very few classes, so Java 8 applications that are deployed with Xbootclasspath/a OR that create class loaders with null as the parent may need to change.
AppleScript, some Apple–specific packages like com.apple.eawt and com.apple.eio are replaced by platform independent counterparts in java.awt.Desktop package. You cannot compile your code that uses them, but existing code that is compiled to old versions continues to run as they(older packages) are available to Runtime only.
You cannot select the JRE version, you will get error messages if you use the version option on the command line OR a JRE-Version manifest entry is found in a JAR file.
Deprecated:- For a full list of deprecated features, please refer to this link
The method <span class="teal">javax.management.remote.JMXConnector.getMBeanServerConnection(Subject)</span> , supported the legacy Subject Delegation feature, and is only useful in conjunction with other deprecated APIs, hence its now deprecated.
The Applet API and many interfaces and classes in the AWT package are deprecated now.
Constructor methods for parsing String to get Byte/Double/Float etc. are deprecated, you must use the static alternatives instead.
Thread methods for suspending/resuming/stopping Threads are deprecated, you may need to write code that simply modifies some variable to indicate that the target thread should stop running.
B. Spring Boot 2 to Spring Boot 3.2
Spring Boot 3 requires at least Java 17, and we will be installing Java21 , so the following issues should be kept in mind.
Since Java EE has been changed to Jakarta EE, Spring Boot 3.0 has also migrated from Java EE to Jakarta EE APIs for all dependencies. Some major dependencies/package names need to be changed to :- e.g
Spring Boot 3 is a major upgrade so several configuration properties need to be changed, and we can use the <span class="teal">spring-boot-properties-migrator</span> in our pom.xml. It's available here.
This will handle most of the required changes to dependencies and naming changes. Still, some important configurations that need a mention are listed below.
1. Since we're upgrading Java as well as Spring, You should also update 3rd party jars to their latest versions e.g. Hibernate 6.3 , Hibernate Validator 8.0, Jackson 2.15, Jersey 2.41, SLF4J 2.0.6, Tomcat 10.1 etc.
2. Latest observation APIs introduced in Micrometer 1.10 with Micrometer Tracing a.) Since the new Spring Boot release deprecates the option to configure trailing slash matching, if you need to enable the trailing slash matching, define your own configuration class that
public class WebConfiguration implements WebMvcConfigurer {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseTrailingSlashMatch(true);
}
}
3. The property server.max.http.header.size is deprecated in favour of server.max-http-request-header-size, and it now only checks the request header size. So you may want to define a new bean that implements the WebServerFactoryCustomizer interface, to define a limit for the response header size. E.g.
@Configuration
public class MyServerConfiguration implements
WebServerFactoryCustomizer {
@Override
public void customize(TomcatServletWebServerFactory defFactory)
{
defFactory.addConnectorCustomizers(new TomcatConnectorCustomizer() {
@Override
public void customize(Connector myConnector) {
connector.setProperty("maxHttpResponseHeaderSize", "200000");
} });
}
}
4. Spring Boot 3.0 uses Spring Security 6.0. , so you may want to add your fine-grained security settings here.
As we do not know the specific business logic that your application implements, nor its internal architecture, we recommend going through the migration guides at least once.
Best Practices and Final Steps for Java 21 Migration
Once Java 21 is working fine for you, the following measures can be useful to get the most out of Java 21:-
To cross-compile to an older release of the platform using the new -–release flag in the javac tool, if required.
Use the static analysis tool jdeprscan , to find out if you're still using any deprecated APIs
Your IDE's suggestions are worth considering while upgrading your code
Add the power of our IDE plugin; an Open source record and replay for Java with assertions, mocking, and code coverage; by installing Unlogged.
Conclusion:
We have discussed some key benefits of upgrading to Java 21, and the path-breaking features in Java 21. To ease your migration process we have discussed how to prepare for the transition, use tools made specifically for this purpose, checklists, and issues to be aware of. In addition, we also recommend upgrading to Spring Boot 3.2, and how to best go about the Spring Boot 2 to Spring Boot 3.2/ Spring Framework 6 migration.
All those who want to make the best use of the latest technologies, keep their applications nimble and scalable, enhance security and resilience, and be well prepared for future enhancements; should read this blog.
Java is serious about keeping up with modern languages and popular usage patterns, hence Java 21 is not only trail blazing but it will also keep you in alignment for harnessing future innovations.