Spring Boot 4.x development - REST APIs, JPA, Security, Testing, and Cloud-native patterns. Use for building enterprise Java applications with Spring Boot.
81%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
Enterprise Spring Boot 4.x development with focus on clean architecture and production-ready code.
ddl-auto: validate)./mvnw clean verify and confirm all pass, with integration tests running (not skipped)/actuator/health returns UP and /actuator/info shows build.version@Entity
@Table(name = "products")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank
private String name;
@DecimalMin("0.0")
private BigDecimal price;
// Getters/Setters (no Lombok)
}public interface ProductRepository extends JpaRepository<Product, Long> {
List<Product> findByNameContainingIgnoreCase(String name);
}@Service
@Transactional(readOnly = true)
public class ProductService {
private final ProductRepository repo;
public ProductService(ProductRepository repo) {
this.repo = repo;
}
public List<ProductResponse> search(String name) {
return repo.findByNameContainingIgnoreCase(name).stream()
.map(ProductResponse::from)
.toList();
}
@Transactional
public ProductResponse create(ProductRequest request) {
var product = new Product();
product.setName(request.name());
product.setPrice(request.price());
return ProductResponse.from(repo.save(product));
}
}@RestController
@RequestMapping("/api/v1/products")
@Validated
public class ProductController {
private final ProductService service;
public ProductController(ProductService service) {
this.service = service;
}
@GetMapping
public List<ProductResponse> search(@RequestParam(defaultValue = "") String name) {
return service.search(name);
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public ProductResponse create(@Valid @RequestBody ProductRequest request) {
return service.create(request);
}
}public record ProductRequest(
@NotBlank String name,
@DecimalMin("0.0") BigDecimal price
) {}
public record ProductResponse(Long id, String name, BigDecimal price) {
public static ProductResponse from(Product product) {
return new ProductResponse(product.getId(), product.getName(), product.getPrice());
}
}@RestControllerAdvice
public class ApiExceptionHandler extends ResponseEntityExceptionHandler {
public record FieldMessage(String field, String code, String message) {}
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers,
HttpStatusCode status, WebRequest request) {
ex.getBody().setProperty("errors", ex.getFieldErrors().stream()
.map(e -> new FieldMessage(e.getField(), code(e.getCode()), e.getDefaultMessage()))
.toList());
return super.handleMethodArgumentNotValid(ex, headers, status, request);
}
private static String code(String constraint) {
return switch (constraint) {
case "NotBlank", "NotNull" -> "required";
case null, default -> "invalid";
};
}
@ExceptionHandler
ProblemDetail handleOptimisticLock(ObjectOptimisticLockingFailureException ex) {
return ProblemDetail.forStatusAndDetail(HttpStatus.CONFLICT, "Record was changed by someone else; reload and retry");
}
@ExceptionHandler
ProblemDetail handleUnexpected(Exception ex) {
logger.error("Unexpected error", ex);
return ProblemDetail.forStatusAndDetail(HttpStatus.INTERNAL_SERVER_ERROR, "Unexpected server error");
}
}Every error is RFC 9457 Problem Details with field errors in errors: [{field, code, message}]. Full handler (unique constraints, bad sort, documented codes): references/web.md.
@WebMvcTest(ProductController.class)
class ProductControllerTest {
@Autowired MockMvc mockMvc;
@MockitoBean ProductService service;
@Test
void createProduct_validRequest_returns201() throws Exception {
var response = new ProductResponse(1L, "Widget", new BigDecimal("10.00"));
when(service.create(any())).thenReturn(response);
mockMvc.perform(post("/api/v1/products")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{"name":"Widget","price":10.0}
"""))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.name").value("Widget"));
}
}Load detailed patterns based on context:
| Topic | Reference | When to Load |
|---|---|---|
| Web/REST | references/web.md | Controllers, paged lists, validation, PUT and version, Problem Details (RFC 9457) with errors[] |
| API built for a UI | references/ui-contract.md | Only when a UI or generated client consumes the OpenAPI contract (see conventions): /api, springdoc 3 contract export, error codes, filters, relations, Keycloak, "Connect a UI" |
| Data Access | references/data.md | JPA, repositories, transactions, queries, Flyway migrations |
| Security | references/security.md | Spring Security 6, OAuth2, JWT, auth |
| Cloud/Config | references/cloud.md | Config server, discovery, resilience, virtual threads, HTTP interfaces, GraalVM native, OTLP tracing |
| AI | references/ai.md | Only when the app calls an LLM: Spring AI 2.x ChatClient, structured output, tool calling, RAG, chat memory, MCP server, testing, 1.x to 2.0 renames |
| Testing | references/testing.md | Unit, integration, slice tests, Testcontainers @ServiceConnection |
@Valid on all request bodies@Transactional for multi-step writes@Transactional(readOnly = true) for reads@ConfigurationProperties@RestControllerAdviceddl-auto: validate when persisting to a relational database (skip Flyway otherwise)PagedModel/actuator/health/** so the liveness and readiness probes work behind securityconventions, "REST API Built for a UI"): follow references/ui-contract.md@Autowired on fields)ddl-auto: update/create outside tests, or edit an already-applied migrationPage/PageImpl from a controller, or put @EnableSpringDataWebSupport on the application class (@WebMvcTest registers it twice)@ApiResponse on controller methods (springdoc 3 then drops the success response; use an OpenApiCustomizer)PropertyReferenceException and TypeInformation moved to org.springframework.data.core@MockBean/@SpyBean are removed: use @MockitoBean/@MockitoSpyBean@WebMvcTest creates no repositories: every repository a controller injects needs a @MockitoBean in its test./mvnw clean verify: classes compiled by the IDE in target can hide javac errorsProject Structure:
src/main/java/pl/piomin/services/
├── controller/ # REST endpoints
├── service/ # Business logic
├── repository/ # Data access
├── model/ # Entities
├── dto/ # Request/Response DTOs
├── config/ # Configuration
└── exception/ # Custom exceptions + handlerLayering:
Clean Architecture Principles:
| Annotation | Purpose |
|---|---|
@RestController | REST controller (combines @Controller + @ResponseBody) |
@Service | Business logic component |
@Repository | Data access component |
@Transactional | Transaction management |
@Valid | Trigger validation |
@ConfigurationProperties | Bind properties to class |
@EnableMethodSecurity | Enable method security |
@RestController
@RequestMapping("/api/v1/orders")
public class OrderController {
private final OrderService orderService;
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
@GetMapping("/{id}")
public Mono<ResponseEntity<OrderDto>> getOrder(@PathVariable UUID id) {
return orderService.findById(id)
.map(ResponseEntity::ok)
.defaultIfEmpty(ResponseEntity.notFound().build());
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Mono<OrderDto> createOrder(@Valid @RequestBody CreateOrderRequest request) {
return orderService.create(request);
}
}@Configuration
@EnableMethodSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(s -> s.sessionCreationPolicy(STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/actuator/health/**").permitAll() // + "/actuator/info" when built for a UI
.anyRequest().authenticated())
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
.build();
}
}Spring Boot 4.x, Java 21+, Spring WebFlux, Project Reactor, Spring Data JPA, Spring Security, OAuth2/JWT, Hibernate, R2DBC, Spring Cloud, Resilience4j, Micrometer, JUnit 5, Testcontainers, Mockito, Maven, Paketo Buildpacks, Skaffold
piomin/claude-ai-spring-boot
31fe1d5
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.