In this article, “Spring Boot Best Practices for Developers” I explained the best practices you can follow when creating an enterprise-level Spring Boot application. It covers most of the common practices that can be applied to your Spring Boot application to make it more convenient.
In this article, I will explain how to improve the performance of your Spring Boot application. First of all, you need to identify the issues. You can use monitoring and profiling tools to check the issues.
- Expose Spring Actuator endpoints to check the application's health and other metrics.
- Use the profiling tool to check memory issues and CPU usage.
Here are my points that help to improve the performance of a Spring Boot application.
Optimizing the database

Generally, database interactions could account for anywhere from 20% to 60% of the performance bottleneck in a typical Spring Boot application. Not only Spring Boot applications but also other applications as well.
- Database design — This is the most crucial aspect, similar to the foundation of a house. If it goes wrong, the end will be more terrifying. So the DB design is most important.
- Configure connection pooling — You can configure ORM tool properties to optimize for better processing time. Spring Boot offers a high-performance connection pool, HikariCP. You can configure the maximum-pool-size, minimum-idel, and connection-timeout
spring:
datasource:
url: jdbc:mysql://localhost:3306/testdb
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
pool-name: HikariPool
maximum-pool-size: 20
minimum-idle: 10
idle-timeout: 300000
max-lifetime: 1800000
connection-timeout: 30000
validation-timeout: 5000
leak-detection-threshold: 2000
Use efficient DB queries and practices.
This is not specific to the Spring Boot application, but these points will be important for you.
- Use indexing — This will be very useful for frequently used data. You can analyze queries with EXPLAIN and then identify where to put indexes.
EXPLAIN SELECT * FROM students WHERE subject = 'Science';
- Avoid N + 1 queries — If I take the most common example, suppose you have to fetch 10 authors and their books. So one query will query for authors and then execute 10 queries to fetch their books one by one. All together 10+1 queries will be executed. This occurs with ORM tools like Hibernate.
Use JOIN FETCH — This can be used to fetch data in a single query if it is EAGER loading.
@Query("SELECT a FROM Author a JOIN FETCH a.books")
List<Author> findAllAuthorsWithBooks();
Use BATCH fetching — This fetching strategy can be used for fetching in LAZY loading.
@Entity
@BatchSize(size = 10)
public class Author {
@OneToMany(mappedBy = "author", fetch = FetchType.LAZY)
private List<Book> books;
}
Use @EntityGraph() — This is a JPA-specific annotation that can be used to retrieve data in a single query.
@Entity
public class Author {
@Id
private Long id;
private String name;
@OneToMany(mappedBy = "author", fetch = FetchType.LAZY)
private List<Book> books;
}
@Repository
public interface AuthorRepository extends JpaRepository<Author, Long> {
@EntityGraph(attributePaths = "books")
List<Author> findAll();
}
Use SUBSELECT — This is also Hibernate specific method
@OneToMany(mappedBy = "author", fetch = FetchType.LAZY)
@Fetch(FetchMode.SUBSELECT)
private List<Book> books;
Enable caching

Another most common term when we talk about the performance of an application. Caching will help the application reduce the number of database hits. Frequently used data can be saved in a cache and we can reuse them if needed. Mainly there are two types of caching mechanisms.
- In-memory cache — This is suitable for simple, single-instance applications.
- Distributed cache — This is suitable for microservices-based architecture. The most popular distributed caching mechanism is Redis.
The most important thing is cache invalidation. Because the results should be updated with the database, otherwise you may retrieve old, invalid data.
You can use @CacheEvict and @CachePut annotations to remove outdated data and update with new data.
package com.example.demo.service;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
public class AuthorService {
@Cacheable(value = "authors", key = "#id")
public String getAuthorById(Long id) {
return "Author with ID: " + id;
}
@CacheEvict(value = "authors", key = "#id")
public void deleteAuthorById(Long id) {
// deleting
}
}
Use best practices

You can find more details about best practices in this article, “Spring Boot Best Practices for Developers” So I am not going to explain it here.
Tune up the JVM
- Use G1 garbage collector for better performance - G1GC is the default Garbage collector from Java 9. G1GC is suitable for applications with large heaps.
- You can adjust the heap size — -Xms is the initial heap size and -Xmx is the maximum heap size. You can set them both into the same value to avoid resizing the heap during runtime.
- You can change the thread stack size, GC thread count, native thread count…etc But these values should be changed according to your application.
Spring Boot LAZY initialization
This is a very powerful configuration because it can reduce application startup time and memory utilization. You can make it globally for specifically for the specific beans you need.
- If you need to change the initialization globally, you can change the property file to enable it like,
spring.main.lazy-initialization=true
- If you need to make it specific, you can use @Lazy( ) annotation for the specific bean.
Limit the Actuator endpoint in production.
Enabling the actuator endpoint in production may cause performance issues as well as security concerns. Since we are talking about performance, Actuator endpoints may consume a significant amount of memory and CPU usage. Some endpoints may also increase the network traffic. (Eg: /actuator/metrics)
So it is better to disable them in production. You can change the configurations in the property file.
Unused Auto-configurations
Auto-configuration is one of the key advantages of Spring Boot. But disabling unused auto-configurations will speed up the application startup time and improve the application performance. Because it will save time and memory by not creating unused beans.
These points represent the core considerations for enhancing Spring Boot application performance. Each can be expanded with additional details depending on specific use cases, but they collectively offer a solid foundation for ensuring scalability and efficiency in modern application environments.

No comments: