---
description: Spring Boot Patterns
alwaysApply: false
---

# Spring Boot Patterns

Production-grade Spring Boot. Convention over configuration.

## REST Controller

```java
@RestController @RequestMapping("/api/v1/orders") @RequiredArgsConstructor
public class OrderController {
    private final OrderService svc;
    @GetMapping("/{id}")
    public ResponseEntity<OrderResponse> getById(@PathVariable UUID id) {
        return svc.findById(id).map(ResponseEntity::ok).orElse(ResponseEntity.notFound().build());
    }
    @PostMapping
    public ResponseEntity<OrderResponse> create(@Valid @RequestBody CreateOrderRequest req) {
        var order = svc.create(req);
        return ResponseEntity.created(URI.create("/api/v1/orders/" + order.id())).body(order);
    }
}
```

## Service Layer

```java
@Service @RequiredArgsConstructor @Transactional(readOnly = true)
public class OrderService {
    private final OrderRepository repo;
    @Transactional
    public OrderResponse create(CreateOrderRequest req) {
        var order = Order.create(req);
        repo.save(order);
        return OrderResponse.from(order);
    }
}
```

## Configuration

```java
@Validated @ConfigurationProperties(prefix = "app.orders")
public record OrderProperties(@NotNull Duration timeout, @Min(1) int maxItems) {}
```

## Exception Handling

Use `@RestControllerAdvice` with `ProblemDetail` (RFC 7807). Map domain exceptions to HTTP status codes. Handle `MethodArgumentNotValidException` for validation errors.

## Security

```java
http.authorizeHttpRequests(a -> a
    .requestMatchers("/api/public/**", "/actuator/health").permitAll()
    .anyRequest().authenticated())
.oauth2ResourceServer(o -> o.jwt(Customizer.withDefaults()))
.sessionManagement(s -> s.sessionCreationPolicy(STATELESS));
```

## Key Settings

- `spring.jpa.open-in-view=false` — always
- `spring.threads.virtual.enabled=true` — Java 21+

## Anti-Patterns

- **Never** field injection — use constructor injection
- **Never** business logic in controllers — delegate to services
- **Never** `@Transactional` on private methods — proxy can't intercept
