Boost Spring Boot Performance: Proven Tips & Code Samples
This article provides a comprehensive guide to optimizing Spring Boot applications, covering application‑level tweaks, JVM tuning, database connection and query improvements, caching strategies, asynchronous and reactive programming, as well as monitoring and diagnostics, all illustrated with practical code examples.
Application Layer Optimization (Spring Boot & Spring Framework)
1. Proper Use of Spring Core Features
@Transactional Transaction Optimization
Limit transaction scope to the smallest necessary unit and avoid opening transactions for read‑only operations.
Specify readOnly = true for read‑only transactions.
Avoid time‑consuming operations (e.g., remote calls) inside transactional methods.
@Service
public class OrderService {
private final OrderRepository orderRepository;
// constructor injection
@Transactional(readOnly = true)
public List<Order> findAllOrders() {
return orderRepository.findAll();
}
@Transactional
public Order createOrder(Order order) {
Order savedOrder = orderRepository.save(order);
return savedOrder;
}
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED)
public void updateOrderStatus(Long orderId, OrderStatus status) {
Order order = orderRepository.findById(orderId)
.orElseThrow(() -> new ResourceNotFoundException("Order not found"));
order.setStatus(status);
orderRepository.save(order);
}
}Lazy Loading and Bean Scope Optimization
Use @Lazy for large beans that are not needed at startup.
Choose appropriate bean scopes (singleton, prototype, request, session) based on usage.
@Configuration
public class AppConfig {
// Lazy bean example
@Bean
@Lazy
public HeavyComponent heavyComponent() {
return new HeavyComponent(); // expensive to initialize
}
// Prototype scoped bean
@Bean
@Scope("prototype")
public RequestScopedComponent requestScopedComponent() {
return new RequestScopedComponent();
}
}AOP Aspect Optimization
Define precise pointcut expressions to avoid unnecessary interceptions and evaluate the performance impact of AOP.
@Aspect
@Component
public class OptimizedAspect {
@Pointcut("execution(* com.yourcompany.service.*Service.*(..)) && !execution(* com.yourcompany.service.*Service.get*(..))")
public void serviceMethodPointcut() {}
@Around("serviceMethodPointcut()")
public Object optimizeServiceCalls(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
try {
return joinPoint.proceed();
} finally {
long duration = System.currentTimeMillis() - startTime;
if (duration > 500) {
log.warn("Slow method: {} took {}ms", joinPoint.getSignature(), duration);
}
}
}
}2. Component Scanning
Use @SpringBootApplication(scanBasePackages = "com.yourcompany") or @ComponentScan to limit the scanned packages and reduce startup time and memory usage.
@SpringBootApplication(scanBasePackages = {"com.yourcompany.service", "com.yourcompany.controller.api"})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}3. Startup Optimization Configuration
Enable global lazy initialization (Spring Boot 2.2+), clean classpath, and use the spring-boot-maven-plugin for executable JAR packaging.
# application.yml
spring:
main:
lazy-initialization: true
web-application-type: servlet
autoconfigure:
exclude:
- org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration
- org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfigurationJVM Layer Optimization
1. JVM Parameter Examples
Typical settings for a 4‑core, 8 GB server:
java -jar app.jar \
-Xms6g -Xmx6g \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-XX:InitiatingHeapOccupancyPercent=75 \
-XX:+UseStringDeduplication \
-XX:+UseContainerSupport \
-XX:MaxRAMPercentage=75.0 \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/var/log/app/heapdump.hprofFor small‑memory microservices:
java -jar app.jar \
-Xms2g -Xmx2g \
-XX:+UseSerialGC \
-XX:NewRatio=3 \
-XX:SurvivorRatio=4 \
-XX:MaxTenuringThreshold=15 \
-XX:+UseCompressedOops \
-XX:+UseCompressedClassPointers2. Virtual Thread Configuration (Java 21+)
Spring Boot 3.2+ supports virtual threads:
server:
tomcat:
threads:
virtual:
enabled: trueDatabase Layer Optimization
1. HikariCP Connection Pool Settings
Choose HikariCP (default) for best performance; adjust pool size, idle settings, and timeouts according to workload.
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
url: jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
username: root
password: secret
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
maximum-pool-size: 15
minimum-idle: 5
idle-timeout: 300000
connection-timeout: 20000
max-lifetime: 1800000
connection-test-query: SELECT 1
pool-name: MyAppHikariCP
auto-commit: false
leak-detection-threshold: 600002. JPA/Hibernate Optimizations
spring:
jpa:
hibernate:
ddl-auto: validate
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
properties:
hibernate:
jdbc.batch_size: 30
order_inserts: true
order_updates: true
jdbc.batch_versioned_data: true
show_sql: false
format_sql: false
generate_statistics: false
cache:
use_second_level_cache: true
region.factory_class: org.hibernate.cache.jcache.JCacheRegionFactory
use_query_cache: true
open-in-view: false3. SQL Optimization Examples
Use JOIN FETCH or @Fetch(FetchMode.JOIN) to avoid N+1 queries.
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
@Query("SELECT o FROM Order o JOIN FETCH o.items WHERE o.id = :id")
Optional<Order> findByIdWithItems(@Param("id") Long id);
@Query("SELECT o FROM Order o WHERE o.status = :status ORDER BY o.createTime DESC")
Page<Order> findByStatus(@Param("status") OrderStatus status, Pageable pageable);
@Query("SELECT new com.example.dto.OrderSummaryDTO(o.id, o.orderNo, o.createTime, o.status) FROM Order o WHERE o.customerId = :customerId")
List<OrderSummaryDTO> findOrderSummariesByCustomerId(@Param("customerId") Long customerId);
}MyBatis Batch Operations
<!-- OrderMapper.xml -->
<insert id="batchInsertOrders">
INSERT INTO orders (order_no, customer_id, status, create_time)
VALUES
<foreach collection="orders" item="order" separator=",">
(#{order.orderNo}, #{order.customerId}, #{order.status}, #{order.createTime})
</foreach>
</insert>Cache Optimization
Local caches (Caffeine, Ehcache) for fast access to small, rarely changing data.
Distributed caches (Redis, Memcached) for shared, large‑scale data with high availability.
1. Redis Cache Integration
@Configuration
@EnableCaching
public class RedisCacheConfig {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration defaultConfig = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
.disableCachingNullValues();
Map<String, RedisCacheConfiguration> configs = new HashMap<>();
configs.put("products", defaultConfig.entryTtl(Duration.ofHours(24)));
configs.put("users", defaultConfig.entryTtl(Duration.ofHours(1)));
configs.put("hotData", defaultConfig.entryTtl(Duration.ofMinutes(10)));
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(defaultConfig)
.withInitialCacheConfigurations(configs)
.transactionAware()
.build();
}
}2. Cache Usage Example
@Service
public class ProductService {
private final ProductRepository productRepository;
@Cacheable(value = "products", key = "#id", unless = "#result == null")
public Product findById(Long id) {
return productRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Product not found"));
}
@CachePut(value = "products", key = "#product.id")
public Product updateProduct(Product product) {
return productRepository.save(product);
}
@CacheEvict(value = "products", key = "#id")
public void deleteProduct(Long id) {
productRepository.deleteById(id);
}
@CacheEvict(value = "products", allEntries = true)
@Scheduled(fixedRate = 86400000)
public void clearProductCache() {
log.info("Clearing product cache");
}
}Cache Protection Techniques
Cache‑penetration guard with null caching and Bloom filter, cache‑avalanche mitigation using random expiration.
@Service
public class ProductServiceImpl implements ProductService {
private final ProductRepository productRepository;
private final BloomFilter<Long> productIdBloomFilter;
@Cacheable(value = "products", key = "#id", unless = "#result == null")
public Product findById(Long id) {
if (!productIdBloomFilter.mightContain(id)) {
return null;
}
Product product = productRepository.findById(id).orElse(null);
if (product == null) {
redisTemplate.opsForValue().set("products::" + id, null, 5, TimeUnit.MINUTES);
}
return product;
}
}Asynchronous & Concurrency Optimization
1. @Async Methods and Thread‑Pool Configuration
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean(name = "taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
int core = Runtime.getRuntime().availableProcessors() + 1;
executor.setCorePoolSize(core);
executor.setMaxPoolSize(core * 2);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("Async-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.setAwaitTerminationSeconds(60);
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.initialize();
return executor;
}
}
@Service
public class NotificationService {
private final EmailClient emailClient;
@Async("taskExecutor")
public CompletableFuture<Void> sendOrderConfirmationEmail(Order order) {
try {
Thread.sleep(1000);
emailClient.send(order.getCustomerEmail(), "Order Confirmation", "Thank you for your order #" + order.getId());
return CompletableFuture.completedFuture(null);
} catch (Exception e) {
log.error("Failed to send email", e);
return CompletableFuture.failedFuture(e);
}
}
}2. Reactive Programming (WebFlux)
@RestController
@RequestMapping("/api/orders")
public class OrderController {
private final OrderService orderService;
@GetMapping("/{id}")
public Mono<ResponseEntity<OrderDTO>> getOrder(@PathVariable Long id) {
return orderService.findById(id)
.map(order -> ResponseEntity.ok(convertToDTO(order)))
.defaultIfEmpty(ResponseEntity.notFound().build());
}
@GetMapping
public Flux<OrderSummaryDTO> getOrdersByCustomer(@RequestParam Long customerId) {
return orderService.findByCustomerId(customerId)
.map(this::convertToSummaryDTO)
.sort((o1, o2) -> o2.getCreateTime().compareTo(o1.getCreateTime()));
}
}
@Service
public class OrderService {
private final OrderRepository orderRepository;
public Mono<Order> findById(Long id) {
return Mono.fromCallable(() -> orderRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Order not found")));
}
public Flux<Order> findByCustomerId(Long customerId) {
return Flux.fromIterable(orderRepository.findByCustomerId(customerId));
}
}Monitoring & Diagnostics
Enable Spring Boot Actuator for endpoints such as /actuator/metrics, /actuator/health, and /actuator/prometheus.
Integrate Prometheus + Grafana, ELK stack, or APM tools (New Relic, Dynatrace, SkyWalking, etc.) for metrics, logs, and distributed tracing.
Use profilers like VisualVM, YourKit, JProfiler, or Arthas for CPU, memory, and thread analysis.
Conclusion
Spring Boot performance tuning is a systematic effort that spans multiple layers:
Application layer – leverage Spring features, optimize beans, AOP, and transactions.
JVM layer – choose appropriate GC and tune parameters.
Database layer – fine‑tune connection pools, SQL, and ORM settings.
Cache layer – implement multi‑level caching and protect against penetration, avalanche, and stampede.
Concurrency – use async methods, reactive programming, and proper thread‑pool sizing.
Monitoring – establish comprehensive observability and continuously iterate based on data.
Key recommendations: drive optimizations with data, address the biggest bottlenecks first, avoid premature or excessive tuning, maintain performance test baselines, continuously monitor production metrics, and stay updated with Spring’s latest performance improvements.
Java Architect Essentials
Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
