Performance Tuning

Optimize Spring Boot application performance — JVM tuning, connection pool sizing, caching, async processing, virtual threads, and common performance anti-patterns.

Last reviewed: January 2025

·Spring Boot 2.7Spring Boot 3.xJava 17Java 21
✅
Quick Understanding: Spring Boot performance starts with measuring, not guessing. Profile first with Actuator metrics and APM tools. Common wins: add database indexes, enable caching (@Cacheable), use async processing (@Async), fix N+1 queries, tune HikariCP pool size, and use virtual threads (Java 21). Never optimize without measuring.

🔍 What Is It?

Performance tuning in Spring Boot covers multiple layers:

Application layer:

  • →Caching with @Cacheable
  • →Async processing with @Async
  • →Lazy loading vs eager loading
  • →Pagination for large datasets

Database layer:

  • →Index optimization
  • →N+1 query elimination
  • →Connection pool tuning
  • →Read replicas
  • →Query optimization

JVM layer:

  • →Heap size tuning (-Xmx, -Xms)
  • →GC algorithm selection
  • →Virtual threads (Java 21)
  • →GraalVM native compilation

Infrastructure layer:

  • →Horizontal scaling
  • →Load balancing
  • →CDN for static assets
  • →Async messaging for non-critical operations

🎯 Why Do We Need It?

Untuned Spring Boot applications can handle 100 requests/second. Well-tuned ones handle 10,000+. The difference is usually: missing indexes, N+1 queries, no caching, and synchronous operations that should be async. Performance tuning is the difference between a service that crashes under load and one that scales gracefully.

🏗️ What Happens in Real Projects?

Common performance wins in enterprise Spring Boot:

  1. Add index on queried column: 10s query → 10ms
  2. Fix N+1 with JOIN FETCH: 100 queries → 1 query
  3. Add @Cacheable on product catalog: 10ms DB call → 0.1ms cache hit
  4. Make email sending @Async: 500ms response → 50ms response
  5. Virtual threads (Java 21): 200 concurrent requests → 10,000+

⚙️ How It Works

Performance optimization is a cycle: measure → identify bottleneck → fix → measure. Use Actuator metrics to identify slow endpoints, APM tools (Dynatrace, New Relic) to trace slow requests, and EXPLAIN ANALYZE in the database to identify slow queries.

💻 Example

Performance Optimization Techniques — Caching, async processing, and virtual threads.

java
@Configuration
@EnableCaching
public class CacheConfig {

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(10))
            .disableCachingNullValues()
            .serializeValuesWith(
                RedisSerializationContext.SerializationPair
                    .fromSerializer(new GenericJackson2JsonRedisSerializer()));

        return RedisCacheManager.builder(factory)
            .cacheDefaults(config)
            .withCacheConfiguration("products",
                config.entryTtl(Duration.ofHours(1)))  // products cached 1 hour
            .withCacheConfiguration("user-sessions",
                config.entryTtl(Duration.ofMinutes(30)))
            .build();
    }
}

@Service
public class ProductService {

    // Cache result — subsequent calls return cached value without DB
    @Cacheable(value = "products", key = "#id")
    public ProductDto getProduct(Long id) {
        return productRepository.findById(id)  // Only called on cache miss
            .map(ProductDto::from)
            .orElseThrow(() -> new ResourceNotFoundException("Product", id));
    }

    // Evict cache when product is updated
    @CacheEvict(value = "products", key = "#id")
    @Transactional
    public ProductDto updateProduct(Long id, UpdateProductRequest request) {
        // Cache cleared — next getProduct call hits DB
        return productRepository.findById(id)
            .map(p -> { p.update(request); return productRepository.save(p); })
            .map(ProductDto::from)
            .orElseThrow(() -> new ResourceNotFoundException("Product", id));
    }
}

🤔 Common Confusions

❓ Should I add caching to everything?

No. Cache only data that is: read frequently, expensive to compute, and acceptable to be slightly stale. Don't cache: user-specific data that changes frequently, financial data that must be real-time, or data with complex invalidation rules. Incorrect caching causes stale data bugs that are hard to debug.

❓ What are virtual threads and should I use them?

Virtual threads (Java 21, Project Loom) are lightweight threads managed by the JVM, not the OS. Tomcat can handle 10,000+ concurrent requests with virtual threads vs 200 with platform threads. Enable with spring.threads.virtual.enabled=true in Spring Boot 3.2+. Use them — it's a free performance upgrade for I/O-bound applications.

🚨 Common Production Issues

Cache stampede: many requests hitting DB simultaneously when cache expires — use probabilistic early expiration

Cache poisoning: caching error responses — use disableCachingNullValues() and don't cache exceptions

@Async methods swallowing exceptions silently — always return CompletableFuture and handle failures

Thread pool exhaustion from @Async — monitor pool queue size and tune accordingly

✅ Best Practices

Measure before optimizing — use Actuator metrics and APM tools

Fix database issues first (indexes, N+1) — biggest wins

Add caching for expensive, frequently-read, stable data

Use @Async for non-critical operations (email, notifications, logging)

Enable virtual threads on Java 21 — free performance improvement

Profile with realistic load, not just unit tests

🏛️ Architect's Note

Senior Architect's Perspective

The 80/20 rule of Spring Boot performance: 80% of performance issues come from database problems. Before tuning JVM or adding caching, check your queries. Enable slow query logging (log queries > 100ms), run EXPLAIN ANALYZE on slow queries, add missing indexes. A single missing index fix can improve performance 100x. Only then consider caching and async processing.

💬 Frequently Asked Questions

How do I identify slow endpoints in production?

Use Actuator HTTP metrics: GET /actuator/metrics/http.server.requests?tag=uri:/api/v1/orders to see count, total time, and max time for each endpoint. Or configure Micrometer to export to Prometheus and create Grafana dashboards showing p99 latency by endpoint. APM tools (Dynatrace, New Relic) provide even more detail.

What JVM flags should I use for a Spring Boot production service?

Baseline: -Xms512m -Xmx2g -XX:+UseG1GC -XX:MaxGCPauseMillis=200. For Java 21: add -XX:+UseZGC for lower GC pauses. Enable GC logging: -Xlog:gc*:file=gc.log:time. Set -Dfile.encoding=UTF-8. In containers: -XX:+UseContainerSupport automatically uses container memory limits.

🎯 Key Takeaways

1Measure first — never optimize without data
2Database optimization (indexes, N+1 fixes) gives the biggest gains
3@Cacheable reduces DB load for frequently-read stable data
4@Async offloads non-critical work to background threads
5Virtual threads (Java 21) dramatically increase concurrency for I/O-bound apps
6Enable spring.threads.virtual.enabled=true in Spring Boot 3.2+ for free throughput gains

🎯 Practice this topic in interviews

↗ Where this topic fits

Sources

Sources / References

Use these Spring Boot references to verify the concept in official docs and continue into the most relevant SplashRide pages.

Related SplashRide pages

SplashRide Spring Boot learning path

Browse the full Spring Boot topic collection and learning order on SplashRide.

Spring Boot Interview Prep

Practice Spring Boot interview questions and scenario-based answers.

Backend Engineer Career Path

A structured journey through Core Java, Spring Boot, production backend engineering, and interview preparation.

Full Stack Java Developer Career Path

A guided route through frontend foundations, Java backend engineering, delivery workflows, and interview readiness.

Actuator

Master Spring Boot Actuator — the production-ready features for health checks, metrics, info, and management endpoints that every enterprise Spring Boot application needs.

Monitoring

Implement production monitoring for Spring Boot applications — Micrometer metrics, Prometheus integration, Grafana dashboards, and distributed tracing with Micrometer Tracing.