--- name: spring-boot description: Spring Boot 3.x development - REST APIs, JPA, Security, Testing, and Cloud-native patterns. Use for building enterprise Java applications with Spring Boot. metadata: version: "2.0.0" domain: backend triggers: Spring Boot, Spring Framework, Spring Security, Spring Data JPA, Spring WebFlux, Java REST API, Microservices Java role: specialist scope: implementation output-format: code --- # Spring Boot Skill Enterprise Spring Boot 3.x development with focus on clean architecture and production-ready code. ## Core Workflow 1. **Analyze** - Understand requirements, identify service boundaries, APIs, data models 2. **Design** - Plan architecture, confirm design before coding 3. **Implement** - Build with constructor injection and layered architecture 4. **Secure** - Add Spring Security, OAuth2, method security; verify tests pass 5. **Test** - Write unit, integration tests; run `./mvnw test` and confirm all pass 6. **Deploy** - Configure health checks via Actuator; validate `/actuator/health` returns UP ## Quick Start Templates ### Entity ```java @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) } ``` ### Repository ```java public interface ProductRepository extends JpaRepository { List findByNameContainingIgnoreCase(String name); } ``` ### Service ```java @Service @Transactional(readOnly = true) public class ProductService { private final ProductRepository repo; public ProductService(ProductRepository repo) { this.repo = repo; } public List search(String name) { return repo.findByNameContainingIgnoreCase(name); } @Transactional public Product create(ProductRequest request) { var product = new Product(); product.setName(request.name()); product.setPrice(request.price()); return repo.save(product); } } ``` ### REST Controller ```java @RestController @RequestMapping("/api/v1/products") @Validated public class ProductController { private final ProductService service; public ProductController(ProductService service) { this.service = service; } @GetMapping public List search(@RequestParam(defaultValue = "") String name) { return service.search(name); } @PostMapping @ResponseStatus(HttpStatus.CREATED) public Product create(@Valid @RequestBody ProductRequest request) { return service.create(request); } } ``` ### DTO (Record) ```java public record ProductRequest( @NotBlank String name, @DecimalMin("0.0") BigDecimal price ) {} ``` ### Global Exception Handler ```java @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(MethodArgumentNotValidException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public Map handleValidation(MethodArgumentNotValidException ex) { return ex.getBindingResult().getFieldErrors().stream() .collect(Collectors.toMap(FieldError::getField, error -> error.getDefaultMessage() != null ? error.getDefaultMessage() : "Invalid")); } @ExceptionHandler(EntityNotFoundException.class) @ResponseStatus(HttpStatus.NOT_FOUND) public Map handleNotFound(EntityNotFoundException ex) { return Map.of("error", ex.getMessage()); } } ``` ### Test Slice ```java @WebMvcTest(ProductController.class) class ProductControllerTest { @Autowired MockMvc mockMvc; @MockBean ProductService service; @Test void createProduct_validRequest_returns201() throws Exception { var product = new Product(); product.setName("Widget"); when(service.create(any())).thenReturn(product); mockMvc.perform(post("/api/v1/products") .contentType(MediaType.APPLICATION_JSON) .content("""{"name":"Widget","price":10.0}""")) .andExpect(status().isCreated()) .andExpect(jsonPath("$.name").value("Widget")); } } ``` ## Reference Guide Load detailed patterns based on context: | Topic | Reference | When to Load | |-------|-----------|-------------| | Web/REST | `references/web.md` | Controllers, validation, exception handling | | Data Access | `references/data.md` | JPA, repositories, transactions, queries | | Security | `references/security.md` | Spring Security 6, OAuth2, JWT, auth | | Cloud/Config | `references/cloud.md` | Config server, discovery, resilience | | Testing | `references/testing.md` | Unit, integration, slice tests | ## Constraints ### MUST DO - Constructor injection (no field injection) - `@Valid` on all request bodies - `@Transactional` for multi-step writes - `@Transactional(readOnly = true)` for reads - Type-safe config with `@ConfigurationProperties` - Global exception handling with `@RestControllerAdvice` - Externalize secrets (use env vars, not properties files) ### MUST NOT DO - Field injection (`@Autowired` on fields) - Skip input validation on endpoints - Mix blocking and reactive code - Store secrets in application.properties - Use deprecated Spring Boot 2.x patterns - Hardcode URLs, credentials, environment values ## Architecture Patterns **Project 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 + handler ``` **Layering:** - Controller → Service → Repository - Controller handles HTTP, validation - Service handles business logic, transactions - Repository handles data persistence **Clean Architecture Principles:** - Domain models independent of frameworks - Use case driven design - Dependency inversion (interfaces) - Clear boundaries between layers ## Common Annotations | 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 | ## Reactive WebFlux Endpoint ```java @RestController @RequestMapping("/api/v1/orders") public class OrderController { private final OrderService orderService; public OrderController(OrderService orderService) { this.orderService = orderService; } @GetMapping("/{id}") public Mono> getOrder(@PathVariable UUID id) { return orderService.findById(id) .map(ResponseEntity::ok) .defaultIfEmpty(ResponseEntity.notFound().build()); } @PostMapping @ResponseStatus(HttpStatus.CREATED) public Mono createOrder(@Valid @RequestBody CreateOrderRequest request) { return orderService.create(request); } } ``` ## Spring Security JWT ```java @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() .anyRequest().authenticated()) .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults())) .build(); } } ``` ## Knowledge Base Spring Boot 3.x, Java 21, Spring WebFlux, Project Reactor, Spring Data JPA, Spring Security 6, OAuth2/JWT, Hibernate, R2DBC, Spring Cloud, Resilience4j, Micrometer, JUnit 5, TestContainers, Mockito, Maven/Gradle