Agent skill
spring-boot-performance
Guide for optimizing Spring Boot application performance including caching, pagination, async processing, and JPA optimization. Use this when addressing performance issues or implementing high-traffic features.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/spring-boot-performance
SKILL.md
Spring Boot Performance Optimization
Follow these practices to optimize application performance.
Pagination for Large Datasets
NEVER load entire tables into memory:
// ❌ WRONG - Can cause OutOfMemoryError
List<User> allUsers = userRepository.findAll();
// ✅ CORRECT - Use pagination
@GetMapping("/users")
public Page<UserDTO> getUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(defaultValue = "id") String sortBy) {
Pageable pageable = PageRequest.of(page, size, Sort.by(sortBy));
return userRepository.findAll(pageable)
.map(userMapper::toDto);
}
Projections for Partial Data
Use projections when you don't need full entities:
// Interface projection
public interface UserSummary {
Long getId();
String getName();
String getEmail();
}
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
List<UserSummary> findAllProjectedBy();
@Query("SELECT u.id as id, u.name as name FROM User u")
List<UserSummary> findUserSummaries();
}
Avoiding N+1 Query Problem
// ❌ WRONG - N+1 queries
List<Order> orders = orderRepository.findAll();
for (Order order : orders) {
// This causes N additional queries
List<OrderItem> items = order.getItems();
}
// ✅ CORRECT - Use JOIN FETCH
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
@Query("SELECT o FROM Order o LEFT JOIN FETCH o.items")
List<Order> findAllWithItems();
@EntityGraph(attributePaths = {"items", "customer"})
List<Order> findAll();
}
Caching Configuration
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(Duration.ofMinutes(10))
.recordStats());
return cacheManager;
}
}
@Service
public class ServiceTypeService {
@Cacheable(value = "serviceTypes", key = "#id")
public ServiceType getServiceType(Long id) {
return serviceTypeRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Service type not found"));
}
@CacheEvict(value = "serviceTypes", key = "#serviceType.id")
public ServiceType updateServiceType(ServiceType serviceType) {
return serviceTypeRepository.save(serviceType);
}
@CacheEvict(value = "serviceTypes", allEntries = true)
public void clearCache() {
// Clears entire cache
}
}
Async Processing
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean(name = "taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(25);
executor.setThreadNamePrefix("Async-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
@Service
public class NotificationService {
@Async("taskExecutor")
public CompletableFuture<Void> sendEmailNotification(String email, String message) {
// Long-running email sending operation
// This runs in a separate thread
return CompletableFuture.completedFuture(null);
}
}
// Usage in controller
@PostMapping("/appointments")
public AppointmentDTO createAppointment(@RequestBody AppointmentRequest request) {
AppointmentDTO appointment = appointmentService.create(request);
// Fire and forget - doesn't block response
notificationService.sendEmailNotification(
appointment.getCustomerEmail(),
"Your appointment is confirmed"
);
return appointment;
}
Connection Pool Configuration
# application.yml
spring:
datasource:
hikari:
maximum-pool-size: 10
minimum-idle: 5
idle-timeout: 30000
connection-timeout: 20000
max-lifetime: 1800000
pool-name: SalonHubPool
JPA Optimization
# application.yml
spring:
jpa:
properties:
hibernate:
# Batch processing
jdbc:
batch_size: 50
order_inserts: true
order_updates: true
# Second-level cache (optional)
cache:
use_second_level_cache: true
region:
factory_class: org.hibernate.cache.jcache.JCacheRegionFactory
# Query hints
default_batch_fetch_size: 25
Batch Processing
@Service
@Transactional
public class BulkImportService {
@PersistenceContext
private EntityManager entityManager;
public void bulkInsert(List<Customer> customers) {
int batchSize = 50;
for (int i = 0; i < customers.size(); i++) {
entityManager.persist(customers.get(i));
if (i > 0 && i % batchSize == 0) {
entityManager.flush();
entityManager.clear();
}
}
entityManager.flush();
entityManager.clear();
}
}
Lazy Loading Best Practices
@Entity
public class Order {
@Id
private Long id;
// Lazy by default for collections
@OneToMany(mappedBy = "order", fetch = FetchType.LAZY)
private List<OrderItem> items;
// Consider lazy for large objects
@Basic(fetch = FetchType.LAZY)
@Lob
private String description;
}
// Initialize lazy collections when needed
@Transactional(readOnly = true)
public Order getOrderWithItems(Long id) {
Order order = orderRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Order not found"));
// Force initialization within transaction
Hibernate.initialize(order.getItems());
return order;
}
Response Compression
# application.yml
server:
compression:
enabled: true
mime-types: application/json,application/xml,text/html,text/xml,text/plain
min-response-size: 1024
Index Optimization
-- Create indexes for frequently queried columns
CREATE INDEX idx_appointments_customer_id ON appointments(customer_id);
CREATE INDEX idx_appointments_employee_id ON appointments(employee_id);
CREATE INDEX idx_appointments_date ON appointments(appointment_time);
-- Composite index for common query patterns
CREATE INDEX idx_appointments_status_date ON appointments(status, appointment_time);
Performance Monitoring
@Aspect
@Component
public class PerformanceLoggingAspect {
private static final Logger logger = LoggerFactory.getLogger(PerformanceLoggingAspect.class);
@Around("@annotation(LogExecutionTime)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object result = joinPoint.proceed();
long duration = System.currentTimeMillis() - start;
logger.info("{} executed in {} ms", joinPoint.getSignature(), duration);
return result;
}
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {}
Performance Checklist
- Use pagination for all list endpoints
- Implement caching for frequently accessed, rarely changed data
- Check for N+1 queries using logging or profiler
- Use projections when full entities aren't needed
- Add database indexes for frequently queried columns
- Configure connection pooling appropriately
- Use async processing for non-critical operations
- Enable response compression
- Monitor slow queries and optimize
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?