| name | performance-optimization |
| description | Identify and fix performance issues in Java/Spring Boot applications including slow queries, memory leaks, thread contention, caching strategies, and JVM tuning. Use this skill whenever the user has a slow application, performance issue, wants to add caching, needs to optimize queries, profile memory usage, or says things like "my API is slow", "optimize this", "add caching", "reduce response time", "memory leak", "high CPU usage", "this query is taking too long", "how do I cache this". Always use this skill for any Java/Spring Boot performance optimization task. |
Java Performance Optimization Skill
Diagnose and resolve performance bottlenecks in Spring Boot applications across all layers: database, application, JVM, and infrastructure.
Performance Investigation Framework
When given a performance issue, investigate in this order:
1. Database (80% of bottlenecks are here)
→ Slow queries, N+1, missing indexes, full table scans
2. Application Layer
→ Unnecessary computations, poor data structures, synchronous blocking calls
3. Caching
→ Missing cache on hot read paths, cache invalidation issues
4. Thread Pool / Concurrency
→ Thread starvation, blocking I/O on request threads, lock contention
5. JVM / Memory
→ GC pressure, memory leaks, heap sizing
Database Performance
Identify Slow Queries
logging:
level:
org.hibernate.SQL: DEBUG
spring:
jpa:
properties:
hibernate:
generate_statistics: true
session.events.log.LOG_QUERIES_SLOWER_THAN_MS: 100
Check with Spring Boot Actuator:
GET /actuator/metrics/hibernate.query.executions
GET /actuator/metrics/hibernate.query.executions.max
Index Strategy
EXPLAIN ANALYZE SELECT * FROM orders WHERE customer_id = 42 AND status = 'PENDING';
CREATE INDEX idx_orders_customer_status ON orders(customer_id, status);
CREATE INDEX idx_products_name_gin ON products USING gin(to_tsvector('english', name));
CREATE INDEX idx_orders_created_at ON orders(created_at DESC);
CREATE INDEX idx_active_users ON users(email) WHERE active = true;
Query Optimization Patterns
Use projections instead of full entity loading
List<User> findAll();
public interface UserSummary {
Long getId();
String getName();
String getEmail();
}
List<UserSummary> findAllProjectedBy();
@Query("SELECT new com.example.dto.UserSummaryDto(u.id, u.name, u.email) FROM User u")
List<UserSummaryDto> findAllSummaries();
Batch inserts / updates
spring.jpa.properties.hibernate.jdbc.batch_size=50
spring.jpa.properties.hibernate.order_inserts=true
spring.jpa.properties.hibernate.order_updates=true
userRepository.saveAll(users);
users.forEach(userRepository::save);
Caching Strategy
Spring Cache Setup
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
@SpringBootApplication
@EnableCaching
public class Application { ... }
Caching Annotations
@Cacheable(value = "products", key = "#id")
public ProductResponse findById(Long id) { ... }
@Cacheable(value = "products", key = "#filter.hashCode()",
condition = "#filter.size() < 100")
public List<ProductResponse> findByFilter(ProductFilter filter) { ... }
@CacheEvict(value = "products", key = "#id")
public ProductResponse update(Long id, UpdateRequest request) { ... }
@CacheEvict(value = "products", allEntries = true)
public void refreshAllProducts() { ... }
@CachePut(value = "products", key = "#result.id")
public ProductResponse create(CreateRequest request) { ... }
Redis Configuration for Production
spring:
cache:
type: redis
redis:
time-to-live: 600000
cache-null-values: false
redis:
host: localhost
port: 6379
@Bean
public RedisCacheConfiguration cacheConfiguration() {
return RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10))
.disableCachingNullValues()
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()));
}
What to Cache
| Cache | TTL | Eviction Trigger |
|---|
| Reference data (categories, config) | 1 hour | Manual or scheduled |
| User profile | 15 min | On user update |
| Product catalog | 10 min | On product update |
| Search results | 5 min | Time-based only |
| Authentication tokens | Token expiry | On logout |
Do NOT cache: user-specific financial data, frequently mutating entities, large collections (> 1000 items)
Async Processing
For operations that don't need to block the HTTP request:
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean(name = "taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("async-");
executor.initialize();
return executor;
}
}
@Service
public class NotificationService {
@Async("taskExecutor")
public CompletableFuture<Void> sendWelcomeEmail(String email) {
return CompletableFuture.completedFuture(null);
}
}
Connection Pool Tuning (HikariCP)
spring:
datasource:
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
leak-detection-threshold: 60000
Memory & GC Optimization
JVM Flags for Spring Boot in Production
java \
-Xms512m -Xmx2g \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/var/log/heapdump.hprof \
-jar app.jar
Common Memory Leak Patterns in Spring Boot
private static final Map<Long, UserSession> activeSessions = new HashMap<>();
@Bean
public Cache<Long, UserSession> sessionCache() {
return Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterAccess(30, TimeUnit.MINUTES)
.build();
}
Performance Profiling Checklist
Before optimizing, measure:
Golden Rule: Measure first, optimize second. Never optimize without data.