Optimize Spring Boot applications. Lazy initialization, Undertow vs Tomcat, actuator endpoints, database query optimization, and caching strategies.
JOptimize Team
Situation: Your Spring Boot microservice takes 15 seconds to start up. Kubernetes keeps restarting it, causing cascading failures. Meanwhile, competitor's app boots in 2 seconds.
Root causes:
The fix: Disciplined configuration + smart defaults = lightning-fast apps.
Spring Boot 3.1 + H2 + JPA + Actuator Startup time: 8.2 seconds Memory: 256MB JAR size: 45MB
Component scanning (20% of startup)
Bean initialization (40% of startup)
Database initialization (15% of startup)
Other (25%)
JOptimize detects this automatically
Scan your Java project in 30 seconds — free, no credit card.
# application.yml spring: main: lazy-initialization: true # Only create beans when needed
Effect:
@Configuration public class BeanConfig { @Bean @Lazy // Only create when injected public ExpensiveService expensiveService() { return new ExpensiveService(); } @Bean public FastService fastService() { return new FastService(); // Eager: always loaded } }
Startup: 3.2s Memory: 145MB Throughput: 8,500 req/sec Latency: 45ms p99 Connections: 10 threads per connection
Startup: 1.8s (44% faster) Memory: 89MB (39% less) Throughput: 12,000 req/sec (41% more) Latency: 28ms p99 (38% better) Connections: Non-blocking, scales to 100k+
<!-- pom.xml --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency>
Or exclude Tomcat:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-undertow</artifactId> </dependency>
spring: datasource: hikari: maximum-pool-size: 10 # Default 10, good for most minimum-idle: 2 # Min connections to keep alive connection-timeout: 30000 # 30s before timeout leak-detection-threshold: 60000 # Warn on 60s old connections max-lifetime: 1800000 # 30min max age per connection
Benchmarks:
Default pool (10): 5,000 req/sec Optimized (20): 8,200 req/sec (64% more) Too high (50): 6,800 req/sec (connection overhead)
// ❌ SLOW: Loads entire User + all Orders @Entity public class User { @OneToMany(fetch = FetchType.EAGER) private List<Order> orders; } // ✅ FAST: Load User only @Entity public class User { @OneToMany(fetch = FetchType.LAZY) // Default private List<Order> orders; } // ✅ FASTER: Explicit JOIN FETCH when needed @Query("SELECT DISTINCT u FROM User u LEFT JOIN FETCH u.orders WHERE u.id = ?1") User findByIdWithOrders(Long id);
@Configuration @EnableCaching public class CacheConfig { @Bean public CacheManager cacheManager() { return new ConcurrentMapCacheManager("users", "products"); } } @Service public class UserService { @Cacheable("users") // Cache by ID public User findById(Long id) { return repository.findById(id); } }
Effect:
server: compression: enabled: true min-response-size: 1024 # Only compress > 1KB mime-types: - application/json - application/xml - text/html - text/xml - text/plain
Effect:
10MB JSON response: Uncompressed: 45MB wire transfer Gzip: 4.2MB (91% reduction) Brotli: 3.8MB (92% reduction) Transfer time: 450ms → 15ms
management: endpoints: web: exposure: include: health,metrics,prometheus # Only needed endpoints exclude: env,configprops # Hide sensitive info endpoint: health: show-details: when-authorized # Only to authenticated users metrics: enabled: true
Effect:
| Optimization | Startup | Memory | P99 Latency | Effort |
|---|---|---|---|---|
| Lazy init | -74% | -12% | - | Easy |
| Switch to Undertow | -44% | -39% | -38% | Medium |
| DB pool tuning | -5% | -3% | -25% | Easy |
| Query caching | - | +2% | -46% | Medium |
| Compression | - | - | -96% | Easy |
| Actuator tuning | -13% | -14% | - | Easy |
| Total | -89% | -45% | -96% | Medium |
Before: 15.2s (Kubernetes timeouts) After: 1.8s (instant scaling) Improvement: 88% faster
Before: 512MB heap After: 256MB heap Improvement: 50% less memory
Before: 2.1s (complaints from customers) After: 45ms (sub-100ms target met) Improvement: 46x faster
Before: 50 pod replicas (each 512MB) After: 8 pod replicas (each 256MB) Monthly cloud cost: $12,000 → $2,500 (79% savings)
spring: jpa: properties: hibernate: generate_statistics: true # Enable query statistics
Monitor these:
@RestController public class PerformanceController { private final MeterRegistry meterRegistry; @GetMapping("/health") public ResponseEntity<Health> health() { // Auto-tracked: request count, response time, errors return ResponseEntity.ok(Health.up().build()); } }
JOptimize detects:
Result: Catch performance issues before they hit production.
Fast Spring Boot apps don't just "happen"—they're designed:
Result: 88% faster startup, 46x faster requests, 79% lower cloud costs.
Start optimizing: joptimize.io/spring-boot
JOptimize finds N+1 queries, EAGER collections, and 70+ other issues in your Java codebase — in under 30 seconds.