Spring Framework is one of the most popular and mature application development frameworks available in the Java ecosystem, and Spring Boot simplifies the process of creating Spring-powered applications by providing pre-configured modules, automatic configuration, starter dependencies, and more. This simplicity, popularity, and maturity have caused many systems out there to be implemented with Spring Boot, and it is quite possible that some of them are not optimized and performant.
In this article, we will first discuss what performance is in general, and then we will discuss 10 Spring Boot Performance Best Practices to make our Spring Boot fast and resource-efficient.
What do we mean when we say performance?
Traditionally, Performance refers to how effectively an application performs its tasks (Runtime efficiency), which we also call speed, and how efficiently it uses system resources (Resource efficiency).
In modern software development, Performance has different aspects that are somehow related to these two (Runtime & Resource efficiency):
- Scalability: Capability to sustain performance and functionality under higher workloads, user numbers, or data amounts.
- Reliability: Capability to be consistent and execute its intended functions without disruptions or errors.
- Throughput: Capability to manage data and tasks smoothly for a specific duration.
Typically, the performance of a software application is all about making sure it runs smoothly, quickly, and effectively. It also involves managing resources efficiently and meeting the expectations of users.
How to improve Spring Boot performance?
In the coming sections, we will address ten best practices that will help us to have a performant Spring Boot application. These are not listed in any particular order, and some of them might be applicable to the newer version of Spring Boot.
1- Using the latest version of Spring Boot as much as possible
In every version of Spring Framework and Spring Boot, besides introducing new features and functionalities, there are also several improvements in performance by optimizing the Spring core container and modules, fixing bugs, and more. So, it is highly recommended that you use as many of the latest versions of Spring Boot as possible.
2- JVM version and tuning
Similar to Spring Boot, every Java LTS release has a lot of performance improvements. Sometimes, to leverage the performance improvements in the latest version of Spring Boot, we need to use the latest version of Java.
Although the latest version of JVM will improve the performance of your Spring Boot application, JVM tuning can further improve the performance of our Spring Boot application. JVM tuning is out of the scope of this article, but I can mention some areas that we need to pay attention to:
- Initial and maximum heap size using
-Xms
and-Xmx
- Other JVM memory settings
- Choose the proper GC implementation
Optimal JVM and GC settings differ based on your application and environment. Testing various configurations and monitoring performance are essential to identify the best settings.
3- Using Virtual Threads in Web MVC stack on JDK 21
Since version 3.2, Spring Boot has started to support Virtual Threads (Project Loom) in different ways. You can find more information about it in my previous article about this feature: Read more: Here
To use this feature, you need to use JDK 21 or higher,
The most important advantage of Virtual Threads is that they improve the Scalability and Performance of Spring Boot applications by introducing a lightweight threading model while keeping backward compatibility and decreasing the complexity of asynchronous programming without sacrificing performance.
We can easily enable Virtual Threads in Spring Boot using this config:
spring.threads.virtual.enabled=true
By existing in this config, Spring Boot Web MVC is handling a web request, such as a method in a controller, which will run on a virtual thread.
4- Spring AOT and Spring GraalVM Native Image
GraalVM Native Images offers a new approach to running Java applications with reduced memory usage and significantly faster startup times compared to the JVM
It is ideal for container-based deployments and Function-as-a-Service platforms, GraalVM Native Images require ahead-of-time processing to create an executable through static code analysis. The result is a platform-specific, standalone executable that doesn’t need a JVM for runtime.
Since GraalVM is not directly aware of dynamic elements of our Java code, such as reflection, resources, serialization, and dynamic proxies, we need to provide some information about those elements for GraalVM. On the other hand, Spring Boot applications are dynamic, with configuration performed at runtime.
Spring AOT (Ahead-of-Time) processing is a feature provided by the Spring Framework that enables the transformation of Spring applications to make them more compatible with GraalVM.
Spring AOT performs ahead-of-time processing during build time and generates additional assets such as Java codes, bytecodes, and GraalVM JSON hint files that help GraalVM for ahead-of-time compilation.
[image for ahead-of-the-time processing and ahead-of-the-time compilation]
To achieve this, we need to use the GraalVM build tools plugin in our Spring Boot project:
<plugin> <groupId>org.graalvm.buildtools</groupId> <artifactId>native-maven-plugin</artifactId> </plugin>
Then, to build the GraalVM Native Image, we can run the spring-boot:build-image
goal with the native
profile active:
mvn -Pnative spring-boot:build-image
It will create a docker image using Buildpacks for us. If you want to know more about the Cloud Native Buildpacks and how Spring Boot is using it, read this article.
To generate a native executable instead of a Docker image, we can run this command (make sure that a GraalVM distribution is installed on your machine):
mvn -Pnative native:compile
5- JVM Checkpoint Restore feature (Project CRaC)
This feature was added to Spring Boot from version 3.2 and provided by the JVM to enable a running Java application to save its state, called a “checkpoint,” and then restore that state at a later time. Spring Boot integrated this feature with its ApplicationContext
lifecycle for the automatic checkpoint, and it can be used simply by adding the –Dspring.context.checkpoint=onRefresh
parameter.
This feature can improve the performance of a Spring Boot application in the following ways:
- Faster startup times: To enhance performance, the Spring Boot app can be optimized by saving a warmed-up JVM state and skipping time-consuming initialization on restarts.
- Improved stability: Enhance the Spring Boot app’s stability by restoring the JVM state from a reliable checkpoint and bypassing initialization-related issues for a smoother, more dependable experience.
- Reduced resource usage: Optimize resource usage for the Spring Boot app by reusing existing JVM resources from checkpoints and reducing overall resource consumption.
To use this feature, we need to install a CRaC-enabled
version of the JDK and use Spring Boot 3.2 or later.
To know more about this feature, you can read our previous deep article about this feature: read more: Here
6- Class Data Sharing (CDS)
Class Data Sharing (CDS) is a feature of the Java Virtual Machine (JVM) that can help decrease Java applications’ startup time and memory footprint. Starting in version 3.3, it was integrated into Spring Boot.
The CDS feature comprises two main steps: 1—Creating the CDS Archive, which creates an archive file (with .jsa
format) containing the application classes when the application exits 2—Using the CDS Archive, which loads the .jsa
file into memory.
CDS reduces the Spring Boot application startup time because accessing the shared archive is faster than loading the classes when the JVM starts. It is also a memory-efficient solution because:
A portion of the shared archive on the same host is mapped as read-only and shared among multiple JVM processes, and also the shared archive contains class data in the form that the Java Hotspot VM uses it.
7- Configuring threads for Spring MVC and Database centric app
Spring Boot application can handle multiple requests in parallel, and the key factor to achieve high throughput is to have enough thread to handle requests. Two important layers that can cause bottlenecks and need to be configured carefully are the Controller and Database access layers.
🌐 Controller layer threads configuration:
If you cannot use the Virtual Thread feature in your Spring MVC application for any reason, it is very important to properly configure the thread pool for the controller layer.
Since Spring MVC applications run on servlet containers like Tomcat, Jetty, or Undertow, we need to know specific configuration keys for our servlet containers to configure the thread pool. For example, in the case of Tomcat, we have two important keys for thread configuration:
server.tomcat.threads.max
: Maximum number of worker threads for request processing.server.tomcat.threads.min-spare
: The minimum number of worker threads kept alive, which is also equal to how many threads are created at startup time.
server: tomcat: connection-timeout: 2s keep-alive-timeout: 10s threads: max: 500 min-spare: 100
Depending on the servlet container you are using, there are more configurations that can help us configure it for better performance. For example, for Tomcat, there are two timeout-related configs (server.tomcat.connection-timeout and server.tomcat.keep-alive-timeout) that might need to be adjusted to improve performance.
🗄️ Database access layer threads configuration:
Since JDBC is a connection-oriented standard for communicating with a database, it is very important to use a connection pool. By default, Spring Boot uses HikariCP as the connection pool. Similar to servlet containers, each connection pool library has its own configuration key to configure it, for HikariCP the most important config for the connection pool are:
spring.datasource.hikari.maximum-pool-size
: Maximum number of database connections in the pool.spring.datasource.hikari.minimum-idle
: Minimum number of idle database connections in the pool.
spring: datasource: username: deli password: p@ssword url: jdbc:postgresql://localhost:5432/sample_db hikari: maximum-pool-size: 400 minimum-idle: 100 connection-timeout: 2000
8- Use caching strategies
Choosing a proper caching strategy for our Spring Boot application can greatly improve its performance. These days, we have many microservices with data spread between them. By leveraging a proper caching strategy, we can improve performance and cut several round trips.
Based on the application’s scale, data access patterns, and performance requirements, we need to decide about two important aspects of caching:
1- What do we cache? What piece of information in our application do we want to keep in the cache?
2- How do we cache? What mechanism or approach of caching do we use? Do we want to use local cache, distributed cache, or both? For how long do we want to keep a piece of data in the cache?
Fortunately, Spring Boot provides a set of useful libraries to help us with caching. We have a dedicated article about caching strategies in Spring Boot. You can read it: Here.
9- Adopting resiliency patterns and best practices
I believe that this is a very important topic, especially these days, because of the increasing number of applications based on the microservices architecture.
Following resiliency patterns such as Circuit Breaker, Timeouts, Fallback, and Retries helps Spring Boot applications better handle failures, allocate resources efficiently, and provide consistent performance, ultimately leading to an improved overall application performance.
Spring Boot supports resiliency patterns through several built-in features and integrations with popular libraries like Resilience4j. Spring Boot simplifies the integration of Resilience4j by auto-configuring the required beans and providing a convenient way to configure resilience patterns through properties or annotations.
Spring Cloud Circuit Breaker provides an abstraction layer for circuit breakers, and Resilience4j can be configured to be used under the hood.
@Service public static class DemoControllerService { private RestTemplate rest; private CircuitBreakerFactory cbFactory; public DemoControllerService(RestTemplate rest, CircuitBreakerFactory cbFactory) { this.rest = rest; this.cbFactory = cbFactory; } public String slow() { return cbFactory.create("slow").run( () -> rest.getForObject("/slow", String.class), throwable -> "fallback" ); } }
10- Monitoring and Profiling
By combining monitoring and profiling, you can gain a deep understanding of your Spring Boot application’s performance and make data-driven decisions to improve it. In our last article, we went deep into Static and Dynamic code analysis and its relation with finding performance problems using Monitoring and Profiling: Read more: Here.
Spring Boot, out of the Box, has many libraries and tools for this purpose. Spring Boot Actuator can monitor application health, gather metrics, and identify performance bottlenecks. On the other hand, from Spring Boot 3.x, there is very good integration with Micrometer using Spring autoconfiguration, which helps us have better metrics and distributed tracing, which, lastly, leads to better application monitoring.
How Digma can help to improve Spring Boot app performance
Interestingly, Digma is a tool that allows us to profile and monitor the Spring Boot applications without needing to deploy the application.
Digma can help us find bottlenecks, slow queries, cache misses, and more insight during the development inside the IDE. These features make Digma a hidden weapon in our toolkit to improve the Spring Boot application performance.
Final Thought: 10 Spring Boot Performance Best Practices
In this article, we reviewed ten ways to improve Spring Boot application performance. Some improve runtime performance, and some optimize the application’s consumed resources. However, some approaches require using a specific Java and Spring Boot version. Of course, there are other ways to improve Spring programs, especially best practices that can apply at the coding level. I would be happy if you mentioned other best practices in the comment section.
Download Digma: Here