Recently the Spring Boot team announced the release of Spring Boot 2.3. While I was reading the official release notes, I created a showcase application to demo some of the new changes & features. Besides the regular dependency updates, there are two great new features when it comes to deploying Spring Boot applications.
Java 14 support in Spring Boot 2.3
One of the central updates coming with Spring Boot 2.3 is the official support of Java 14. We can now create Spring Boot applications using the latest Java version and use its preview features to provide feedback about them:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | <?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.0.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>de.rieckpil.blog</groupId> <artifactId>whats-new-in-spring-boot-2-3</artifactId> <version>0.0.1-SNAPSHOT</version> <name>whats-new-in-spring-boot-2-3</name> <description>Updates with Spring Boot 2.3</description> <properties> <java.version>14</java.version> </properties> <dependencies> <!-- classic Spring Boot Starters, e.g. web, test, ... --> </dependencies> <build> <finalName>${project.artifactId}</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <jvmArguments> --enable-preview </jvmArguments> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <compilerArgs> --enable-preview </compilerArgs> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <argLine> --enable-preview </argLine> </configuration> </plugin> </plugins> </build> </project> |
With this setup, let's create our first Java 14 Record:
1 2 3 | @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) public record User(String name, LocalDate dateOfBirth, boolean isRegistered) { } |
… and use it as a plain data object for one of our endpoints:
1 2 3 4 5 | @GetMapping("/users") public List<User> getUsers() { return List.of(new User("Duke", LocalDate.now(), true), new User("Duke", LocalDate.now().minusDays(1), false)); } |
For a complete list of all new Java 14 features, take a look at this list.
Besides Java 14, Spring Boot 2.3 still supports Java 8 and 11.
Building OCI images and layered .jar files
One great new feature when it comes to deploying Spring Boot applications is to create layered .jar
files. As most of the Spring Boot applications are deployed inside a Docker container, you have to create a new Docker image on every code change. With the traditional approach of copying the fat jar to an existing Docker base image (e.g. openjdk-11
), you always have to copy the whole build artifact e.g. 10 – 50 MB .jar
file over and over again.
This is suboptimal as usually only some Java classes change whereas your dependencies change not that frequently. A better approach would be to only copy the application code (usually some KBs) and reuse existing layers of your Docker image (containing all your dependencies) to speed up subsequent Docker image builds.
With Spring Boot 2.3 you can now configure the Spring Boot plugin (works for Maven and Gradle) to create a .jar
file with the following layers:
dependencies
(includes regularly released dependencies)spring-boot-loader
(for everything underorg/springframework/boot/loader
)snapshot-dependencies
(for snapshot dependencies)application
(includes application classes and resources)
1 2 3 4 5 6 7 8 9 | <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <layers> <enabled>true</enabled> </layers> </configuration> </plugin> |
During your daily development most probably will only the application
layer change.
We can demonstrate this feature by showcasing another new feature of Spring Boot 2.3: Building OCI (Open Container Initiative) images using Cloud Native Buildpacks. By default, the Spring Boot plugin uses one of Paketo's Buildpacks and we can build this image while executing a specific Maven Goal:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <jvmArguments> --enable-preview </jvmArguments> <layers> <enabled>true</enabled> </layers> <image> <name>myregistry.com/rieckpil/${project.artifactId}</name> <env> <BP_JVM_VERSION>14.0.1</BP_JVM_VERSION> </env> </image> </configuration> <executions> <execution> <goals> <goal>build-image</goal> </goals> </execution> </executions> </plugin> |
Once we execute mvn package
, we'll get a ready-to-use Docker image. While building the image, the Builder of the Buildpack is able to detect our layered .jar
file and can reuse existing layers that did not change (e.g. our dependencies).
If you build your Spring Boot application and change the implementation of a class afterward and rebuild the image, you'll see that only the last Docker layer changed:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | mvn package docker history myregistry.com/rieckpil/whats-new-in-spring-boot-2-3:latest IMAGE CREATED CREATED BY SIZE COMMENT 4a38d5b029c1 40 years ago 12kB <missing> 40 years ago 0B <missing> 40 years ago 10.8kB <missing> 40 years ago 0B <missing> 40 years ago 224kB <missing> 40 years ago 18MB // ... // do changes to the source code mvn package docker history myregistry.com/rieckpil/whats-new-in-spring-boot-2-3:latest IMAGE CREATED CREATED BY SIZE COMMENT 7a92643ab146 40 years ago 12kB <missing> 40 years ago 0B <missing> 40 years ago 10.8kB <missing> 40 years ago 0B <missing> 40 years ago 224kB <missing> 40 years ago 18MB |
For more information about these two new features:
- take a look at the Spring Boot documentation
- watch the following video where I demonstrate both features and explain the difference to the traditional approach of creating Docker images for Spring Boot applications
Spring Boot Starter for Validation
Both spring-boot-starter-web
and spring-boot-starter-webflux
don't contain the spring-boot-start-validation
anymore. If you are relying on Bean Validation (now called Jakarta Bean Validation) e.g. at your controller level:
1 2 3 4 5 6 7 | @GetMapping("/messages") public List<String> getMessages(@RequestParam("size") @Positive @Max(6) Integer size) { return List.of("Hello", "World", "Foo", "Bar", "Duke", "Spring") .stream() .limit(size) .collect(Collectors.toList()); } |
You have to manually add the starter for validation to your project:
1 2 3 4 | <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> |
You can read about the reason for this change in the corresponding GitHub issue.
Binding error messages for the Whitelabel error page
If you already work with Spring Boot for some time, you might have seen the Whitelabel error page during development. This error page usually appears in your browser whenever there is an exception in your service that you don't handle gracefully (e.g. with @ExceptionHandler
) or happens inside the Spring Framework (e.g. Spring MVC can't find a view).
Before Spring Boot 2.3 this page contains the error message and error bindings by default. With this behavior, you'll leak internal information about the exception to the user which might contain sensitive data. Let's consider the following trivial example:
1 2 3 4 5 6 7 | @GetMapping("/api/customers") public Integer api() { if (2 % 2 == 0) { throw new RuntimeException("Exception happened and customer Duke only has 42 $ left"); } return 40 + 2; } |
When we access this endpoint with a Spring Boot < 2.3, we'll get the following output:
In such cases, you might not want to include the error message as it leaks critical information. With Spring Boot 2.3 the Whitelabel error page does not contain any error message by default anymore:
You can still configure your application to include the error message (e.g. for debugging):
1 2 | server.error.include-message=always server.error.include-binding-errors=always |
Configuration property deprecations and renamings
With the release of Spring Boot 2.3 some configurations properties are renamed or now deprecated. The Spring Boot team provides support to migrate such properties using a dependency:
1 2 3 4 5 | <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-properties-migrator</artifactId> <scope>runtime</scope> </dependency> |
If your project contains property definitions that are no longer valid, the dependency above will take care of temporarily migrating and printing them during application startup.
As an example, I'll use the following deprecated properties to configure the thread pool size for Tomcat:
1 2 | server.tomcat.max-threads=42 server.tomcat.min-spare-threads=10 |
Once we now start the application, we'll get a log output which also includes how the old property is temporarily replaced:
1 2 3 4 5 6 7 8 9 10 | WARN 32699 --- [main] PropertiesMigrationListener : The use of configuration keys that have been renamed was found in the environment: Property source 'applicationConfig: [classpath:/application.properties]': Key: server.tomcat.max-threads Line: 1 Replacement: server.tomcat.threads.max Key: server.tomcat.min-spare-threads Line: 2 Replacement: server.tomcat.threads.min-spare |
You can then use the Replacement
hint in the log message and rename all your configuration properties. Once the PropertiesMigrationListener
doesn't warn about any depreciation, you can get rid of the dependency.
Further dependency upgrades with Spring Boot 2.3
Besides other dependency upgrades, theses are one of the most important ones:
- Spring Data Neumann: Repository support for Kotlin coroutines, upgrades for MongoDB, ElasticSearch, Couchbase and more
- Spring Security 5.3: OAuth 2 client & resource server updates, enhancing the documentation and more
- Flyway 6.4
- JUnit Jupiter 5.6: find release notes here
- Kotlin 1.3.72
- etc.
If you are curious about what changed with previous versions of Spring Boot, take a look at my review of Spring Boot 2.1 and 2.2.
While this blog post contains the most important changes from my point of view, there is more to explore in the official release notes. You can find this Spring Boot 2.3 showcase application on GitHub.
Have fun using Spring Boot 2.3.
Phil