Spring Boot, and the Spring Framework that Spring Boot is based on, is a great framework for developing microservices in Java.
When the Spring Framework v1.0 was released back in 2004, one of its main goals was to address the overly complex J2EE standard (short for Java 2 Platform, Enterprise Edition) with its infamous and heavyweight deployment descriptors. The Spring Framework provided a much more lightweight development model based on the concept of dependency injection. The Spring Framework also used far more lightweight XML configuration files compared to the deployment descriptors in J2EE.
To make things even worse with the J2EE standard, the heavyweight deployment descriptors actually came in two types:
- Standard deployment descriptors, describing the configuration in a standardized way
- Vendor-specific deployment descriptors, mapping the configuration to vendor-specific features in the vendor’s application server
In 2006, J2EE was renamed Java EE, short for Java Platform, Enterprise Edition. In 2017, Oracle submitted Java EE to the Eclipse Foundation. In February 2018, Java EE was renamed Jakarta EE. The new name, Jakarta EE, also affects the names of the Java packages defined by the standard, requiring developers to perform package renaming when upgrading to Jakarta EE, as described in the Migrating a Spring Boot 2 application section. Over the years, while the Spring Framework gained increasing popularity, the functionality in the Spring Framework grew significantly. Slowly, the burden of setting up a Spring application using the no-longer-so-lightweight XML configuration file became a problem.
In 2014, Spring Boot v1.0 was released, addressing these problems!
Convention over configuration and fat JAR files
Spring Boot targets the fast development of production-ready Spring applications by being strongly opinionated about how to set up both core modules from the Spring Framework and third-party products, such as libraries that are used for logging or connecting to a database. Spring Boot does that by applying a number of conventions by default, minimizing the need for configuration. Whenever required, each convention can be overridden by writing some configuration, case by case. This design pattern is known as convention over configuration and minimizes the need for initial configuration.
Configuration, when required, is, in my opinion, written best using Java and annotations. The good old XML-based configuration files can still be used, although they are significantly smaller than before Spring Boot was introduced.
Added to the usage of convention over configuration, Spring Boot also favors a runtime model based on a standalone JAR file, also known as a fat JAR file. Before Spring Boot, the most common way to run a Spring application was to deploy it as a WAR file on a Java EE web server, such as Apache Tomcat. WAR file deployment is still supported by Spring Boot.
A fat JAR file contains not only the classes and resource files of the application itself but also all the JAR files the application depends on. This means that the fat JAR file is the only JAR file required to run the application; that is, we only need to transfer one JAR file to an environment where we want to run the application instead of transferring the application’s JAR file along with all the JAR files the application depends on.
Starting a fat JAR requires no separately installed Java EE web server, such as Apache Tomcat. Instead, it can be started with a simple command such as java -jar app.jar
, making it a perfect choice for running in a Docker container! If the Spring Boot application, for example, uses HTTP to expose a REST API, it will also contain an embedded web server.
Code examples for setting up a Spring Boot application
To better understand what this means, let’s look at some source code examples.
We will only look at some small fragments of code here to point out the main features. For a fully working example, you’ll have to wait until the next chapter!
The magic @SpringBootApplication annotation
The convention-based autoconfiguration mechanism can be initiated by annotating the application class (that is, the class that contains the static main
method) with the @SpringBootApplication
annotation. The following code shows this:
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
The following functionality will be provided by this annotation:
- It enables component scanning, that is, looking for Spring components and configuration classes in the package of the application class and all its sub-packages.
- The application class itself becomes a configuration class.
- It enables autoconfiguration, where Spring Boot looks for JAR files in the classpath that it can configure automatically. For example, if you have Tomcat in the classpath, Spring Boot will automatically configure Tomcat as an embedded web server.
Component scanning
Let’s assume we have the following Spring component in the package of the application class (or in one of its sub-packages):
@Component
public class MyComponentImpl implements MyComponent { ...
Another component in the application can get this component automatically injected, also known as auto-wired, using the @Autowired
annotation:
public class AnotherComponent {
private final MyComponent myComponent;
@Autowired
public AnotherComponent(MyComponent myComponent) {
this.myComponent = myComponent;
}
I prefer using constructor injection (over field and setter injection) to keep the state in my components immutable. An immutable state is important if you want to be able to run the component in a multithreaded runtime environment.
If we want to use components that are declared in a package outside the application’s package, for example, a utility component shared by multiple Spring Boot applications, we can complement the @SpringBootApplication
annotation in the application class with a @ComponentScan
annotation:
package se.magnus.myapp;
@SpringBootApplication
@ComponentScan({"se.magnus.myapp","se.magnus.util" })
public class MyApplication {
We can now auto-wire components from the se.magnus.util
package in the application code, for example, a utility component named MyUtility
, as follows:
package se.magnus.util;
@Component
public class MyUtility { ...
This utility component can be auto-wired in an application component like so:
package se.magnus.myapp.services;
public class AnotherComponent {
private final MyUtility myUtility;
@Autowired
public AnotherComponent(MyUtility myUtility) {
this.myUtility = myUtility;
}
Java-based configuration
If we want to override Spring Boot’s default configuration or we want to add our own configuration, we can simply annotate a class with @Configuration
and it will be picked up by the component scanning mechanism we described previously.
For example, if we want to set up a filter in the processing of HTTP requests (handled by Spring WebFlux, which is described in the following section) that writes a log message at the beginning and the end of the processing, we can configure a log filter, as follows:
@Configuration
public class SubscriberApplication {
@Bean
public Filter logFilter() {
CommonsRequestLoggingFilter filter = new
CommonsRequestLoggingFilter();
filter.setIncludeQueryString(true);
filter.setIncludePayload(true);
filter.setMaxPayloadLength(5120);
return filter;
}
We can also place the configuration directly in the application class since the @SpringBootApplication
annotation implies the @Configuration
annotation.
That’s all for now about Spring Boot, but before moving to the next component, let’s see what is new in Spring Boot 3.0 and how to migrate a Spring Boot 2 application.
What’s new in Spring Boot 3.0
For the scope of this book, the most important new items in Spring Boot 3.0 are the following:
- Observability
Spring Boot 3.0 comes with improved support for observability, adding built-in support for distributed tracing to the already existing support for metrics and logging in previous Spring Boot releases. The new distributed tracing support is based on a new Observability API in Spring Framework v6.0 and a new module named Micrometer Tracing. Micrometer Tracing is based on Spring Cloud Sleuth, which is now deprecated. Chapter 14, Understand Distributed Tracing, covers how to use the new support for observability and distributed tracing.
- Native compilation
Spring Boot 3.0 also comes with support for compiling Spring Boot applications to native images, which are standalone executable files. A native-compiled Spring Boot application starts significantly faster and consumes less memory. Chapter 23, Native-Compiled Java Microservices, describes how to native compile microservices based on Spring Boot.
- Virtual threads
Finally, Spring Boot 3.0 comes with support for lightweight threads called virtual threads from the OpenJDK Project Loom. Virtual threads are expected to simplify the programming model for developing reactive non-blocking microservices, for example, compared to the programming model used in Project Reactor and various Spring components. Virtual threads are currently only available as a preview in Java 19. They also currently lack support for composability features, for example, required to build microservices that concurrently aggregate information from other microservices. Therefore, virtual threads will not be covered in this book. Chapter 7, Developing Reactive Microservices, covers how virtual threads can be implemented using Project Reactor and Spring WebFlux.
Migrating a Spring Boot 2 application
If you already have applications based on Spring Boot 2, you might be interested in understanding what it takes to migrate to Spring Boot 3.0. Here is a list of actions you need to take:
- Pivotal recommends first upgrading Spring Boot 2 applications to the latest v2.7.x release since their migration guide assumes you are on v2.7.
- Ensure you have Java 17 or later installed, both in your development and runtime environments. If your Spring Boot applications are deployed as Docker containers, you need to ensure that your company approves the usage of Docker images based on Java 17 or newer releases.
- Remove calls to deprecated methods in Spring Boot 2.x. All deprecated methods are removed in Spring Boot 3.0, so you must ensure that your application does not call any of these methods. To see exactly where calls are being made in your application, you can enable the
lint:deprecation
flag in the Java compiler using (assuming the use of Gradle):
tasks.withType(JavaCompile) {
options.compilerArgs += ['-Xlint:deprecation']
}
- Rename all imports of
javax
packages that are now part of Jakarta EE to jakarta
.
- For libraries that are not managed by Spring, you need to ensure that you are using versions that are Jakarta compliant, that is, using
jakarta
packages.
- For breaking changes and other important migration information, read through:
https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.0-Migration-Guide
https://docs.spring.io/spring-security/reference/migration/index.html
- Ensure that you have end-to-end black-box tests that verify the functionality of your application. Run these tests before and after the migration to ensure that the application’s functionality has not been affected by the migration.
When migrating the source code of the previous edition of this book to Spring Boot 3.0, the most time-consuming part was figuring out how to handle breaking changes in the Spring Security configuration; see Chapter 11, Securing Access to APIs, for details. As an example, the following configuration of the authorization server in the previous edition needed to be updated:
@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorizeRequests -> authorizeRequests
.antMatchers("/actuator/**").permitAll()
This configuration looks like the following with Spring Boot 3.0:
@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorizeRequests -> authorizeRequests
.requestMatchers("/actuator/**").permitAll()
The end-to-end test script, test-em-all.bash
, that comes with each chapter turned out to be indispensable in verifying that the functionality was unaffected after the migration of each chapter.
Now that we have learned about Spring Boot, let’s talk about Spring WebFlux.