From java-services
Enforces Spring Boot 3.x patterns for DI, REST controllers, configuration, JPA, security, events, virtual threads, and observability. Use when working with Spring Boot, @SpringBootApplication, @RestController, @Service, @Repository, @ConfigurationProperties, Spring Security, Spring Data JPA, or Micrometer.
npx claudepluginhub andercore-labs/claudes-kitchen --plugin java-servicesThis skill uses the workspace's default tool permissions.
Spring Boot 3.x patterns + hexagonal architecture compliance.
Implements structured self-debugging workflow for AI agent failures: capture errors, diagnose patterns like loops or context overflow, apply contained recoveries, and generate introspection reports.
Monitors deployed URLs for regressions in HTTP status, console errors, performance metrics, content, network, and APIs after deploys, merges, or upgrades.
Provides React and Next.js patterns for component composition, compound components, state management, data fetching, performance optimization, forms, routing, and accessible UIs.
Spring Boot 3.x patterns + hexagonal architecture compliance.
// DI — constructor injection ONLY
@Service
public class OrderService {
private final OrderRepository repo;
private final ApplicationEventPublisher events;
public OrderService(OrderRepository repo, ApplicationEventPublisher events) {
this.repo = repo;
this.events = events;
}
}
// Controller
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@PostMapping
public ResponseEntity<OrderResponse> create(@Valid @RequestBody CreateOrderRequest req) {
return ResponseEntity.status(HttpStatus.CREATED).body(service.create(req));
}
}
// Config
@ConfigurationProperties(prefix = "app.order")
@Validated
public record OrderConfig(
@NotNull Duration timeout,
@NotBlank String currency
) {}
// Domain event
record OrderPlacedEvent(UUID orderId, Instant occurredAt) {}
@SpringBootApplication | @RestController | @Service | @Repository | @ConfigurationProperties
Spring Data JPA | Spring Security | ApplicationEventPublisher | @EventListener | Micrometer
| Rule | ✓ Do | ✗ Avoid |
|---|---|---|
| Annotation specificity | @Service, @Repository, @Controller | Generic @Component |
| Injection style | Constructor injection | @Autowired field injection |
| Lombok shorthand | @RequiredArgsConstructor | Manual constructor for DI boilerplate |
| Third-party beans | @Configuration + @Bean | Subclassing external classes |
| Disambiguation | @Qualifier("name") | @Primary (use sparingly) |
| Circular deps | Restructure first | @Lazy (last resort) |
| Bean scope | Singleton (default) | @Scope("prototype") |
// ✓ Lombok shorthand — equivalent to explicit constructor
@Service
@RequiredArgsConstructor
@Slf4j
public class OrderService {
private final OrderPort orderPort;
private final ApplicationEventPublisher events;
}
// Third-party bean
@Configuration
@EnableConfigurationProperties(OrderConfig.class)
public class AppConfig {
@Bean
public ObjectMapper objectMapper() {
return new ObjectMapper().registerModule(new JavaTimeModule());
}
}
// Qualifier disambiguation
@Bean @Qualifier("primary") public DataSource primaryDataSource() { ... }
@Bean @Qualifier("replica") public DataSource replicaDataSource() { ... }
@RestController
@RequestMapping("/api/v1/orders")
public class OrderController {
@GetMapping("/{id}")
public ResponseEntity<OrderResponse> findById(@PathVariable UUID id) {
return ResponseEntity.ok(service.findById(id));
}
@PostMapping
public ResponseEntity<OrderResponse> create(@Valid @RequestBody CreateOrderRequest req) {
return ResponseEntity.status(HttpStatus.CREATED).body(service.create(req));
}
@PatchMapping("/{id}/status")
public ResponseEntity<OrderResponse> updateStatus(
@PathVariable UUID id,
@RequestParam @NotBlank String status) {
return ResponseEntity.ok(service.updateStatus(id, status));
}
}
| Mapping | Usage |
|---|---|
@GetMapping | Read |
@PostMapping | Create → 201 |
@PutMapping | Full replace |
@PatchMapping | Partial update |
@DeleteMapping | Delete → 204 |
Global exception handler:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(NotFoundException.class)
public ProblemDetail handleNotFound(NotFoundException ex) {
ProblemDetail pd = ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, ex.getMessage());
pd.setType(URI.create("https://api.example.com/errors/not-found"));
log.warn("Resource not found: {}", ex.getMessage());
return pd;
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ProblemDetail handleValidation(MethodArgumentNotValidException ex) {
ProblemDetail pd = ProblemDetail.forStatus(HttpStatus.UNPROCESSABLE_ENTITY);
pd.setProperty("violations", ex.getBindingResult().getFieldErrors()
.stream().map(f -> f.getField() + ": " + f.getDefaultMessage()).toList());
return pd;
}
@ExceptionHandler(Exception.class)
public ProblemDetail handleUnexpected(Exception ex) {
log.error("Unexpected error", ex);
return ProblemDetail.forStatusAndDetail(HttpStatus.INTERNAL_SERVER_ERROR, "Unexpected error");
}
}
DTO validation (controller layer ONLY):
public record CreateOrderRequest(
@NotNull UUID customerId,
@NotBlank String currency,
@Size(min = 1, max = 100) List<@Valid OrderItem> items
) {}
@ConfigurationProperties(prefix = "app.payment")
@Validated
public record PaymentConfig(
@NotBlank String apiKey,
@NotNull URI baseUrl,
@Min(1) @Max(10) int maxRetries,
@NotNull Duration timeout
) {}
application.yml:
app:
payment:
api-key: ${PAYMENT_API_KEY}
base-url: https://api.payment.example.com
max-retries: 3
timeout: PT5S
spring:
threads:
virtual:
enabled: true # Boot 3.2+ virtual threads
| Rule | ✓ | ✗ |
|---|---|---|
| Complex config | @ConfigurationProperties | @Value("${prop}") |
| Env values | ${ENV_VAR} in YAML | Hardcode |
| Profiles | application-{profile}.yml | @Profile on beans (except infra) |
| Validation | @Validated + JSR-303 | Manual null checks |
// ✓ MapStruct mapper as Spring-managed bean
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.ERROR)
public interface OrderMapper {
OrderResponseDto toDto(OrderJpaEntity entity);
@Mapping(target = "id", ignore = true)
OrderJpaEntity toEntity(CreateOrderRequest request);
}
// ✓ Injected automatically via @RequiredArgsConstructor
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderMapper orderMapper;
}
| Rule | Status |
|---|---|
@Mapper(componentModel = "spring") | ✓ mandatory for Spring DI |
unmappedTargetPolicy = ReportingPolicy.ERROR | ✓ catches unmapped fields at compile-time |
Manual new XxxDto(entity.getX(), entity.getY()) | ✗ Use MapStruct |
// Immutable event record
record OrderPlacedEvent(UUID orderId, UUID customerId, Instant occurredAt) {}
// Publishing (service layer)
@Service
public class OrderService {
private final ApplicationEventPublisher events;
public Order place(PlaceOrderCommand cmd) {
Order order = Order.create(cmd);
repo.save(order);
events.publishEvent(new OrderPlacedEvent(order.id(), order.customerId(), Instant.now()));
return order;
}
}
// Sync listener
@Component
public class OrderNotificationListener {
@EventListener
public void onOrderPlaced(OrderPlacedEvent event) { ... }
}
// Async-safe listener (fires after TX commits)
@Component
public class OrderAuditListener {
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
@Async
public void onOrderPlaced(OrderPlacedEvent event) { ... }
}
| Listener Type | Annotation | When |
|---|---|---|
| Sync, any TX state | @EventListener | Notifications, caches |
| After commit | @TransactionalEventListener(AFTER_COMMIT) | Outbox, audit |
| Async | + @Async | Non-blocking side effects |
Require @EnableAsync on @Configuration for @Async.
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
show-details: when_authorized
// Custom health indicator
@Component
public class PaymentGatewayHealthIndicator implements HealthIndicator {
@Override
public Health health() {
return gateway.isReachable()
? Health.up().withDetail("latency", gateway.latencyMs()).build()
: Health.down().withDetail("reason", "gateway unreachable").build();
}
}
// Micrometer metrics
@Service
public class OrderService {
private final Counter ordersPlaced;
private final Timer orderProcessingTime;
public OrderService(MeterRegistry registry) {
this.ordersPlaced = Counter.builder("orders.placed").register(registry);
this.orderProcessingTime = Timer.builder("orders.processing.time").register(registry);
}
}
Logging: LogstashEncoder or EcsEncoder → never System.out.println.
spring.threads.virtual.enabled: true
| Rule | ✓ | ✗ |
|---|---|---|
| Blocking I/O | Safe with virtual threads | Never in platform threads pool |
| Synchronization | ReentrantLock | synchronized blocks |
| Thread locals | Request-scoped beans | Thread-local caches |
| Source | Status | Log Level |
|---|---|---|
| Validation failure | 422 | WARN |
| Not found | 404 | WARN |
| Auth failure | 401/403 | WARN |
| Domain rule violation | 409 | WARN |
| Unexpected exception | 500 | ERROR |
Map domain exceptions → HTTP in @RestControllerAdvice ONLY. Services throw domain exceptions; never return HTTP types.
| Forbidden | Reason | Use Instead |
|---|---|---|
@Autowired field injection | Hidden deps, untestable | Constructor injection |
WebSecurityConfigurerAdapter | Removed in Boot 3 | SecurityFilterChain bean |
FetchType.EAGER | N+1 queries | @EntityGraph / JOIN FETCH |
@Transactional on controllers | Wrong layer | Service layer only |
| Spring annotations in Domain | Framework coupling | Pure Java domain |
HttpServletRequest in services | Layer violation | Extract data in controller |
@Value("${x}") for complex config | Type-unsafe | @ConfigurationProperties |
System.out.println | Not structured | Logger (SLF4J) |
| Topic | File |
|---|---|
| Spring Data JPA | data-jpa.md |
| Spring Security | security.md |
CHECK:
1. @Service/@Repository/@Controller used (not generic @Component)?
2. Constructor injection ONLY — no @Autowired fields?
3. @Configuration + @Bean for third-party classes?
4. @Qualifier over @Primary for disambiguation?
5. No circular dependencies (restructure before @Lazy)?
6. @RestController + class-level @RequestMapping?
7. ResponseEntity<T> with explicit status codes?
8. @Valid on all @RequestBody / @RequestParam in controllers?
9. @RestControllerAdvice → ProblemDetail (RFC 9457)?
10. DTO validation in controller layer ONLY (not service)?
11. @ConfigurationProperties (not @Value) for complex config?
12. @Validated on @ConfigurationProperties class?
13. No hardcoded env values — use ${ENV_VAR}?
14. Events are immutable records?
15. @TransactionalEventListener(AFTER_COMMIT) for post-TX events?
16. management.endpoints exposed: health, info, metrics, prometheus?
17. Micrometer (Counter/Timer) for custom metrics?
18. No System.out.println — structured logging?
19. Virtual threads: ReentrantLock (not synchronized)?
20. Domain layer free of Spring annotations?
21. Lombok: @RequiredArgsConstructor used for DI; @Data absent on @Entity?
22. MapStruct: @Mapper(componentModel = "spring") for all entity↔DTO mappings?
ALL pass → Generate code
ANY fail → REJECT with violation report
| Phase | Action |
|---|---|
| 1. SCOPE | Extract files, mode (informative | executive), sessionId |
| 2. VERIFY | Run ALL 20 verification checks |
| 3. VIOLATIONS | Collect: file:line, check name, severity |
| 4. REPORT | ✓ ALL pass → Done | ✗ ANY fail → Return violations JSON |
| 5. METRICS | Call mcp__agent-orchestrator__store-skill-metrics |
Output format:
{
"discipline": "springboot",
"timestamp": "ISO8601",
"di_beans": {"violations": [], "status": "pass"},
"rest_controllers": {"violations": [], "status": "pass"},
"configuration": {"violations": [], "status": "pass"},
"data_jpa": {"violations": [], "status": "pass"},
"security": {"violations": [], "status": "pass"},
"events": {"violations": [], "status": "pass"},
"observability": {"violations": [], "status": "pass"},
"virtual_threads": {"violations": [], "status": "pass"},
"error_handling": {"violations": [], "status": "pass"},
"prohibitions": {"violations": [], "status": "pass"},
"summary": {"critical": 0, "errors": 0, "warnings": 0}
}
Metrics call:
mcp__agent-orchestrator__store-skill-metrics({
sessionId: "[from context]",
skill: "java-services:springboot-recipe",
initialViolations: N,
iterations: N,
fixesApplied: N,
finalViolations: 0,
validationPassed: true,
durationSeconds: N,
metadata: { files: [...] }
})