Spring
Spring Framework rules and best practices for Claude Code.
22.5k
- Spring
- Spring Boot
- Java
- Jakarta EE
- Postgres
- Hibernate
- Kotlin
11 TEMPLATES
Spring Boot 3 Modern Rules
# CLAUDE.md — Spring Boot 3 Modern Rules ## Version baseline - Spring Boot 3.2+ (3.4 if you can adopt). Java 21. - Migrate `javax.*` → `jakarta.*` once and for all. Boot 3 dropped `javax`. - Pin the Spring Boot parent in `pom.xml` / `plugins { id 'org.springframework.boot' version '...' }`. Don't manually align Spring deps. ## Project layout ``` src/main/java/com/app/ Application.java # @SpringBootApplication config/ domain/ api/ # controllers service/ repository/ Application.java src/main/resources/ application.yml # default config application-dev.yml # profile-specific overrides application-prod.yml ``` - Avoid splitting "common" beans across many modules. Boot's autoconfig discovers what's on the classpath. ## Configuration properties - `@ConfigurationProperties` over `@Value`. Use records: ```java @ConfigurationProperties("app.email") record EmailProperties(String fromAddress, String smtpHost, int smtpPort) {} ``` - `@EnableConfigurationProperties(EmailProperties.class)` once on a `@Configuration` class. - Validate with `@Validated` + `@NotNull` / `@NotEmpty` on fields. Boot fails fast on bad config. ## Profiles - `dev`, `test`, `prod`. Activate with `SPRING_PROFILES_ACTIVE=prod` (env var) or `--spring.profiles.active=prod`. - Don't have profile-specific Java code (`if (env.equals("dev"))`). Use `@Profile` on beans. - `application-{profile}.yml` overrides `application.yml`. Keep secrets out — use env vars or a secret manager. ## Logging - Default to **Logback** (Boot's default). Use **structured JSON** (logstash-encoder) in production. - `LOG_LEVEL_app=DEBUG` env var overrides at runtime (Boot reads logging.level.* env vars). - One logger per class: `private static final Logger log = LoggerFactory.getLogger(MyService.class);`. - Don't log sensitive data (PII, tokens). Add a redaction filter if there's any risk. ## Auto-configuration - Boot configures DataSource, JPA, Web, Security, etc. based on what's on the classpath. - Override defaults with explicit beans — they win over auto-config. - Disable specific auto-config: `@SpringBootApplication(exclude = SecurityAutoConfiguration.class)` only when you have a real reason. ## Starters - `spring-boot-starter-web` for Servlet stack, `spring-boot-starter-webflux` for reactive. Pick one. - `spring-boot-starter-data-jpa` brings Hibernate + Spring Data JPA + a connection pool. - `spring-boot-starter-actuator` for ops endpoints. Always include in production. ## Health & lifecycle - Boot's `/actuator/health` endpoint is on by default. Add component-specific checks via `HealthIndicator`. - Configure `management.endpoint.health.probes.enabled=true` for K8s liveness/readiness split. - Graceful shutdown: `server.shutdown=graceful`, `spring.lifecycle.timeout-per-shutdown-phase=30s`. Drain in-flight requests on SIGTERM. ## Don't - Don't `application.yml` your way to a 500-line config. Split by profile or extract to `@ConfigurationProperties`. - Don't disable Spring's exception handling to "see the real error". Use a `@ControllerAdvice` instead. - Don't run with `spring.jpa.hibernate.ddl-auto=update` in production. Use Flyway or Liquibase. - Don't put `@Bean` methods in your `Application.java`. Move to a dedicated `@Configuration` class.Spring Framework Core Rules
# CLAUDE.md — Spring Framework Core ## Dependency injection - **Constructor injection** for required dependencies. Final fields, no `@Autowired` needed when there's a single constructor. - Field injection (`@Autowired` on a field) is banned. It hides dependencies and breaks immutability. - Setter injection only for legitimately optional collaborators. - One bean = one responsibility. Wide constructors are a sign of a bean doing too much. ## Bean configuration - Java config (`@Configuration` + `@Bean`) over XML. XML is only for legacy. - Use `@Component` / `@Service` / `@Repository` on classes you own. `@Bean` methods for third-party types and conditional wiring. - Avoid mixing `@ComponentScan` with manual `@Bean` registration of the same type — the precedence rules surprise you. ## Configuration properties - `@ConfigurationProperties` over `@Value`. Strongly typed, auto-completed, validated. - Use records for property classes: ```java @ConfigurationProperties("app.email") record EmailProperties(String fromAddress, String smtpHost, int smtpPort) {} ``` - Validate with Bean Validation: `@Validated` on the class, `@NotNull`, `@NotEmpty` on fields. ## Profiles - One profile per environment: `dev`, `test`, `prod`. Configure with `spring.profiles.active`. - Don't put production-specific behavior behind a `if` on a profile name. Use `@Profile` on beans. - `@Profile("!prod")` for "everywhere except prod" beans (dev tools, mock integrations). ## Lifecycle - `@PostConstruct` for initialization that requires dependency injection to be complete. - `@PreDestroy` for cleanup. Triggered on graceful shutdown. - Don't do work in constructors that touches the network, database, or threads. The bean isn't fully wired yet. ## Events - `ApplicationEventPublisher` for in-process events. Decouples producers from consumers. - Listeners are `@EventListener` methods. Avoid `ApplicationListener<EventType>` — older API. - For async listeners, use `@Async` + `@EnableAsync`. Configure the executor explicitly — the default isn't safe for production. ## Transactions - `@Transactional` on service methods. Don't put it on controllers or repositories. - Default propagation `REQUIRED` is right 95% of the time. - `readOnly = true` for queries — Hibernate optimizes flush behavior. - Self-invocation doesn't trigger the proxy: a `@Transactional` method calling another `@Transactional` method on `this` won't open a new transaction. Inject `self` or split the bean. ## Don't - Don't inject the `ApplicationContext` to look up beans dynamically. That's service locator anti-pattern. - Don't `BeanFactory.getBean()` from app code. If you need conditional wiring, use `@Conditional` or factories. - Don't put initialization in static blocks. Use `@PostConstruct` or `CommandLineRunner`. - Don't catch and swallow `RuntimeException` to "make Spring happy" — the framework's exception handling is intentional.Spring Boot REST API
# CLAUDE.md — Spring Boot REST API ## Endpoint design - One controller per resource. Use kebab-case URLs and stable plurals (`/users`, `/orders`, `/order-items`). - HTTP verbs map to operations: `GET` (read), `POST` (create), `PUT` (replace), `PATCH` (partial), `DELETE` (delete). - Use `@RestController` + the verb-specific shortcuts (`@GetMapping`, `@PostMapping`). ## DTO layer - Don't expose JPA entities. Map to records: ```java record UserResponse(String id, String email, Instant createdAt) { static UserResponse from(User u) { return new UserResponse(u.getId(), u.getEmail(), u.getCreatedAt()); } } ``` - Separate request and response DTOs. They evolve independently. - Use **MapStruct** for tedious mapping when there's enough of it. Otherwise hand-write — clearer than reflection-based mappers. ## Validation - Bean Validation on request DTOs. `@Valid` on the controller parameter: ```java @PostMapping ResponseEntity<UserResponse> create(@Valid @RequestBody CreateUserRequest req) { ... } ``` - Custom constraint annotations for cross-field rules. Don't validate in services if it could be at the DTO level. ## Error responses - Use **Problem Details for HTTP APIs (RFC 7807)** — `ProblemDetail` is built into Spring 6. - One `@RestControllerAdvice` per app for centralized error mapping: ```java @ExceptionHandler(NotFoundException.class) ProblemDetail handle(NotFoundException e) { var pd = ProblemDetail.forStatus(404); pd.setTitle("Not Found"); pd.setDetail(e.getMessage()); return pd; } ``` - Don't leak stack traces to clients. Log them, return a clean problem detail. ## OpenAPI / Swagger - **springdoc-openapi-starter-webmvc-ui** generates OpenAPI 3 from your controllers. - Annotate when the inferred docs are wrong: `@Operation`, `@ApiResponse`, `@Schema`. - Pin the spec version in CI — break PRs that change the API surface unintentionally. ## Pagination & filtering - Accept `Pageable` directly. Spring binds `?page=0&size=20&sort=createdAt,desc`. - For filtered list endpoints, use a Specification or QueryDSL — don't write `if (params.get("status") != null)` chains. - Cap page size in code: `if (pageable.getPageSize() > 100) pageable = ...withMaxSize(100);`. ## Versioning - URL versioning: `/api/v1/users`. Simple, cacheable, easy for clients. - Don't version via media type unless you have a strong reason (it complicates clients). - Keep `v1` working when you add `v2`. Deprecate explicitly via headers and changelogs. ## Idempotency - For non-idempotent verbs (`POST`, `PATCH`), accept an `Idempotency-Key` header. Store key→result for retries. - `PUT` should be naturally idempotent. If it isn't, you're using the wrong verb. ## Don't - Don't return `null` from a controller. Throw a domain exception or return `ResponseEntity.notFound().build()`. - Don't accept `Map<String, Object>` as a request body. Use a typed DTO. - Don't put business logic in the controller. Parse → call service → return. - Don't expose IDs you don't want enumerated. Use UUIDs for public-facing identifiers.Spring Boot Testing
# CLAUDE.md — Spring Boot Testing ## Test layers - **Unit tests** — plain JUnit + Mockito. No Spring context. Fast. - **Slice tests** — `@WebMvcTest`, `@DataJpaTest`, `@WebFluxTest`. Load only what's needed for one layer. - **Integration tests** — `@SpringBootTest` with `@AutoConfigureMockMvc` or full HTTP via `TestRestTemplate`/`WebTestClient`. Most tests should be unit or slice tests. `@SpringBootTest` is slow — use it sparingly, for end-to-end smoke tests. ## @WebMvcTest - Loads only MVC infrastructure + the controller under test. - Mock the service layer with `@MockBean`. - `MockMvc` for assertions: ```java mvc.perform(get("/users/{id}", "123")) .andExpect(status().isOk()) .andExpect(jsonPath("$.email").value("a@b")); ``` - Don't `@SpringBootTest` to test a single controller. Use `@WebMvcTest`. ## @DataJpaTest - Loads JPA + repositories + an in-memory or testcontainers DB. - Each test runs in a transaction that rolls back. Don't `commit()`. - Use **Testcontainers Postgres**, not H2. The dialect differences matter: ```java @Testcontainers @DataJpaTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) class UserRepositoryTest { ... } ``` ## @SpringBootTest - Full app context. Use for end-to-end flows that span multiple layers. - `webEnvironment = RANDOM_PORT` and `TestRestTemplate`/`WebTestClient` for real HTTP. - Override beans with `@TestConfiguration` or `@MockBean` — don't fork production wiring with profiles like `test`. ## Testcontainers - One container per type per test run. Reuse with `@Container` static + `withReuse(true)`: ```java static PostgreSQLContainer<?> POSTGRES = new PostgreSQLContainer<>("postgres:16-alpine") .withReuse(true); static { POSTGRES.start(); } ``` - Configure via `@DynamicPropertySource`: ```java @DynamicPropertySource static void props(DynamicPropertyRegistry r) { r.add("spring.datasource.url", POSTGRES::getJdbcUrl); } ``` ## Test slice tips - `@MockBean` adds a mock to the application context. Differs from `@Mock`, which is just a Mockito mock. - Avoid `@MockBean` in unit tests — it forces context loading. Plain `@Mock` is faster. - `@SpyBean` for partial mocking. Use sparingly — usually a code smell. ## Speed - Reuse the application context across tests. Boot caches contexts by configuration; don't make every test unique. - `@DirtiesContext` invalidates the cache. Use it only when you must — it kills performance. - Run unit tests in parallel: `junit.jupiter.execution.parallel.enabled=true`. ## Don't - Don't load the full `@SpringBootTest` context for tests that touch one bean. - Don't use H2 / HSQLDB for repository tests. Use Postgres via Testcontainers. - Don't share state across tests. Random execution order is the default. - Don't `Thread.sleep` to wait for async work. Use `Awaitility` or `CompletableFuture.get(timeout)`.Spring Data JPA
# CLAUDE.md — Spring Data JPA ## Repositories - Extend `JpaRepository<Entity, Id>` for the standard CRUD surface. `CrudRepository` is the lowest common denominator. - Don't write methods you don't use. The auto-generated query is only created when called. - One repository per aggregate root. Don't share a `UserRepository` across unrelated services. ## Derived queries - Use derived query methods for simple cases: `findByEmail`, `findByStatusAndCreatedAtAfter`. - When the method name gets longer than ~50 chars, switch to `@Query`. - `@Query("...")` for explicit JPQL. Use named parameters (`:name`), not positional. - `@Query(nativeQuery = true)` only when JPQL can't express it. Document why. ## Projections - Read just the columns you need. Don't fetch full entities for a list view. - Interface projections — Spring builds the proxy from getter signatures: ```java interface UserListView { String getId(); String getEmail(); Instant getCreatedAt(); } List<UserListView> findAllProjectedBy(); ``` - DTO projections in JPQL: `select new com.app.UserDto(u.id, u.email) from User u`. ## N+1 prevention - `@EntityGraph` on repository methods to fetch associations eagerly when you need them: ```java @EntityGraph(attributePaths = {"orders", "address"}) List<User> findAll(); ``` - For collection associations, prefer `JOIN FETCH` in `@Query` over `EntityGraph` for control over the join type. - Profile every list endpoint. **Hibernate Statistics** or **DataSource Proxy** in dev catch N+1s before prod. ## Pagination - `Pageable` parameter on repository methods. Spring derives `LIMIT` / `OFFSET`. - Cursor-based pagination via slice queries (`Slice<T> findFirst20ByOrderByIdAscIdGreaterThan(String cursor)`) for large datasets. - Don't use `OFFSET` for deep pagination — performance degrades quadratically. ## Transactions - `@Transactional` on service methods, not on repositories. Repositories run inside the caller's transaction. - `readOnly = true` on queries. Hibernate skips dirty-checking and flush. - Don't lazy-load attributes outside the transaction (`LazyInitializationException`). Either fetch them eagerly with `EntityGraph` or use a projection. ## Auditing - `@CreatedDate`, `@LastModifiedDate`, `@CreatedBy`, `@LastModifiedBy` for free with `@EnableJpaAuditing`. - One `AuditorAware` bean to expose the current user. Don't repeat in every entity. - Add `@EntityListeners(AuditingEntityListener.class)` to the entity (or a `@MappedSuperclass`). ## Migrations - **Flyway** or **Liquibase**. Don't rely on `spring.jpa.hibernate.ddl-auto=update` in production. - `ddl-auto=validate` in production to assert the schema matches. - Run migrations as a separate step before app boot in multi-instance deploys. ## Don't - Don't use `findAll()` without pagination on tables that grow. - Don't return `Optional<List<T>>` — use an empty list. - Don't expose JPA entities through controllers. Map to DTOs at the boundary. - Don't catch `EntityNotFoundException` in services to translate it. Use a `Optional` return or throw a domain exception.Spring Security Rules
# CLAUDE.md — Spring Security ## Configuration - Use the modern `SecurityFilterChain` bean approach. The deprecated `WebSecurityConfigurerAdapter` is gone. - One `@Bean SecurityFilterChain` per filter chain. Multiple chains for `/api/**` vs `/admin/**` if their auth differs. - Configure with the lambda DSL: ```java http.authorizeHttpRequests(auth -> auth .requestMatchers("/public/**").permitAll() .requestMatchers("/admin/**").hasRole("ADMIN") .anyRequest().authenticated() ); ``` - Always end with `.anyRequest().authenticated()` (or `.denyAll()`). Default-allow is a foot-gun. ## Method security - Enable with `@EnableMethodSecurity`. Use `@PreAuthorize` on service methods. - `@PreAuthorize("hasRole('ADMIN')")` for role checks. Use SpEL for object-level: `@PreAuthorize("#user.id == authentication.name")`. - Don't put auth checks in controllers. They live at the service boundary so non-HTTP callers (jobs, schedulers) get them too. ## Password handling - `BCryptPasswordEncoder` with cost factor 12+. Re-evaluate yearly. - Or `Argon2PasswordEncoder` if starting fresh. - Use `DelegatingPasswordEncoder` (`PasswordEncoderFactories.createDelegatingPasswordEncoder()`) to support migration between algorithms. - Never log raw passwords. Configure your error reporter to filter. ## OAuth2 Resource Server - For JWT-protected APIs: ```java http.oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwkSetUri(jwksUri))); ``` - Configure `JwtAuthenticationConverter` to map claims to authorities. Don't roll your own JWT validation. - Validate `iss`, `aud`, and `exp` — set them in your authorization server, verify them here. ## CSRF - Default-on for browser apps. Disable only for stateless APIs: ```java http.csrf(AbstractHttpConfigurer::disable); ``` - For session-based forms, use the cookie-based token strategy (`CookieCsrfTokenRepository`). - Never disable CSRF on session-authenticated endpoints that mutate state. ## CORS - Use `CorsConfigurationSource` bean. Define an explicit allowlist — never `allowedOrigins("*")` with `allowCredentials(true)` (Spring will refuse). - Configure once globally for cross-origin APIs. Don't sprinkle `@CrossOrigin` on controllers. ## Session management - Stateless APIs: `http.sessionManagement(sm -> sm.sessionCreationPolicy(STATELESS))`. - Stateful (server-rendered): default is fine. Set `maximumSessions` and `expiredUrl` for fixed concurrency. - Always invalidate sessions on logout: `http.logout(logout -> logout.invalidateHttpSession(true).deleteCookies("JSESSIONID"))`. ## Don't - Don't put `permitAll()` on `**/*` to "fix" auth for now. The fix becomes permanent. - Don't store auth tokens in `localStorage` from server-rendered Spring apps. HttpOnly cookies are the answer. - Don't write your own `UserDetailsService` if Spring's defaults plus a JPA-backed one cover you. - Don't expose actuator endpoints (`/actuator/**`) without auth. They leak everything.Spring Boot Actuator & Observability
# CLAUDE.md — Spring Boot Actuator & Observability ## Actuator - Add `spring-boot-starter-actuator`. It's mandatory in production. - Default exposed endpoints: `/actuator/health`, `/actuator/info`. Everything else is opt-in. - Expose only what's needed: `management.endpoints.web.exposure.include=health,info,metrics,prometheus`. - **Never** expose `env`, `heapdump`, `threaddump`, or `loggers` to public traffic. Lock them behind admin auth or a separate management port. ## Health checks - Boot composes `/actuator/health` from `HealthIndicator` beans (DB, Redis, etc.). - Implement custom `HealthIndicator` for app-specific dependencies: ```java @Component class StripeHealthIndicator implements HealthIndicator { public Health health() { ... } } ``` - For Kubernetes, enable probes split: `management.endpoint.health.probes.enabled=true`. - `/actuator/health/liveness` — process is alive (don't depend on DB here) - `/actuator/health/readiness` — can serve traffic (DB + cache reachable) ## Metrics with Micrometer - Micrometer is auto-configured. Add a Prometheus registry: `io.micrometer:micrometer-registry-prometheus`. - Tag every metric — high cardinality kills Prometheus. Tag by **service**, **endpoint**, **status** — never by user_id. - Standard metrics from Boot: HTTP request timings, JDBC pool, JVM, Tomcat. Custom metrics: ```java Timer timer = Timer.builder("checkout.duration").tag("flow", "card").register(meterRegistry); ``` ## Tracing - **Micrometer Tracing** with OpenTelemetry exporter. Add `micrometer-tracing-bridge-otel` + `opentelemetry-exporter-otlp`. - Sampling: 10% in production, 100% in dev. Configure with `management.tracing.sampling.probability`. - Propagate `traceparent` across HTTP, message queues, and async hops. Spring instruments most clients automatically. ## Logging - Structured JSON logs. Use **logstash-logback-encoder** or Boot's built-in JSON encoder (Boot 3.4+). - Add `trace_id` and `span_id` to every log line via MDC. Spring populates them when tracing is enabled. - One log statement per significant decision. Don't log every method entry. ## Profiling - **Spring Boot Profiler** (Boot 3.5+) for prod-safe sampling. - For deeper diagnostics, **JFR** (Java Flight Recorder) — built into the JVM, very low overhead. - `/actuator/heapdump` on demand for OOM investigation. Lock behind admin auth. ## Alerting - Define SLOs: `availability`, `p99 latency`, `error rate`. Alert when burned. - Don't alert on every spike. Multi-window, multi-burn-rate alerts (Google SRE book) are the right pattern. - Page on user-impacting symptoms, not on every CPU spike. ## Production checklist - Actuator on a separate port (`management.server.port=8081`) so internal-only endpoints don't share routing with public traffic. - Prometheus scraping `/actuator/prometheus`. - Logs shipped to Loki/Datadog/your aggregator. - Traces shipped to Tempo/Honeycomb/your tracer. ## Don't - Don't expose unauthenticated `/actuator/env` — leaks every config value, including resolved secrets. - Don't set `management.endpoints.web.exposure.include=*` in production. - Don't tag metrics by `user_id` or any high-cardinality value. Your bill will explode. - Don't bypass tracing for "performance" — it costs <1% and saves debugging hours.Spring MVC Rules
# CLAUDE.md — Spring MVC ## Controllers - `@RestController` for JSON APIs. `@Controller` only when you actually return view names. - Constructor-inject services. Final fields, no `@Autowired`. - Map URLs at the class level: `@RequestMapping("/users")`. Methods use the verb-specific shortcuts (`@GetMapping`, `@PostMapping`). - One controller per resource. Cross-cutting endpoints (`/health`, `/metrics`) live in their own controller. ## Request handling - `@RequestBody` for JSON bodies. Add `@Valid` to trigger Bean Validation. - `@PathVariable` and `@RequestParam` are explicit — name them with the parameter name when it differs: ```java @GetMapping("/{id}") UserDto get(@PathVariable("id") String userId) ``` - Use `Optional<T>` for `@RequestParam(required = false)` instead of nullable defaults. ## Response shaping - Return `ResponseEntity<T>` when you need to set status, headers, or vary by branch: ```java return ResponseEntity.created(uri).body(dto); ``` - Return the DTO directly when status is always 200. Spring fills in the rest. - Set `produces = MediaType.APPLICATION_JSON_VALUE` when you mean it. Avoid `MediaType.ALL_VALUE` on data endpoints. ## Validation - Bean Validation on the request DTO with `jakarta.validation.constraints.*`: ```java record CreateUser(@NotBlank @Email String email, @NotBlank @Size(min = 8) String password) {} ``` - `@Valid` on the controller parameter. Spring throws `MethodArgumentNotValidException` on failure. - For groups (`Default`, `OnCreate`, `OnUpdate`), use `@Validated(OnCreate.class)`. ## Exception handling - `@RestControllerAdvice` class with `@ExceptionHandler` methods. One per error type. - Map domain exceptions to status codes here, never in controllers: ```java @ExceptionHandler(NotFoundException.class) ResponseEntity<ProblemDetail> handle(NotFoundException e) { return ResponseEntity.status(404).body(ProblemDetail.forStatusAndDetail(404, e.getMessage())); } ``` - Use `ProblemDetail` (RFC 7807) for error responses. It's the modern Spring default. ## Content negotiation - Default to JSON. Add XML/Other media types only when a client actually needs them. - Don't use `produces` to "version" APIs (`application/vnd.app.v1+json`). Use URL versioning for clarity. ## Filters & interceptors - Servlet filters for cross-cutting on the request boundary (request id, logging). - `HandlerInterceptor` for Spring-aware concerns (auth, MDC). - Don't put business logic in filters. They're for cross-cutting only. ## Don't - Don't return `null` from a controller. Use `ResponseEntity.notFound().build()` or throw. - Don't catch `Exception` in a controller method to translate it. Use `@ExceptionHandler` so the same logic applies app-wide. - Don't expose entity classes from controllers. Map to DTOs. - Don't mix `@RequestParam` and `@PathVariable` carelessly — they bind from different sources and the bug is silent.GraalVM + Spring Boot Native
# CLAUDE.md — GraalVM + Spring Boot Native ## Setup - Spring Boot 3.0+ has first-class native support via Spring AOT. - Add the GraalVM Native Build Tools: - Maven: enable the `native` profile in the parent POM (`spring-boot-starter-parent` ships it). - Gradle: apply `id 'org.graalvm.buildtools.native'`. - Build: - Maven: `./mvnw -Pnative native:compile` - Gradle: `./gradlew nativeCompile` - Container image build (no GraalVM locally needed): - Maven: `./mvnw -Pnative spring-boot:build-image` - Gradle: `./gradlew bootBuildImage` ## Spring AOT - AOT processing runs at build time. Produces source/config that bypasses runtime reflection. - Generated artifacts live in `target/spring-aot/` — don't edit, they regenerate. - AOT also runs in JVM mode (Boot 3+) for faster startup. You get the benefit without going native. ## Runtime hints - Spring auto-discovers most reflection. For your own custom code, register via `RuntimeHintsRegistrar`: ```java public class MyHints implements RuntimeHintsRegistrar { public void registerHints(RuntimeHints hints, ClassLoader cl) { hints.reflection().registerType(MyDto.class, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS); hints.resources().registerPattern("data/*.json"); } } ``` - Register via `@ImportRuntimeHints(MyHints.class)` on a `@Configuration` class. ## What works in native - Spring MVC, WebFlux, Data JPA, Security, Actuator, JDBC. Most starters just work. - Tested with H2, Postgres, MySQL, MongoDB, Redis, Kafka, RabbitMQ. - For full status, see [Spring's native compatibility matrix](https://docs.spring.io/spring-boot/reference/packaging/native-image/index.html). ## What doesn't work - Mock-based testing in native (`@MockBean`) — use it in JVM, integration-test in native. - DevTools, Spring Boot Loader's reload — JVM-only. - Runtime bytecode manipulation libraries that don't ship native hints. ## Tracing agent - For libraries without first-class native support, use the agent during a JVM test run: ```sh java -agentlib:native-image-agent=config-merge-dir=src/main/resources/META-INF/native-image/ -jar target/app.jar ``` - Run real test scenarios — the agent records reflection / resources used. ## Build optimization - Build is the bottleneck. Mitigate: - Cache `target/` and `~/.m2` in CI between runs. - Use `-march=native` only if the build host matches the deployment host architecture. Otherwise pick a target. - Build native only on release branches; PRs build JVM-mode (with AOT) for fast feedback. ## Image - Use distroless or minimal Debian: `FROM gcr.io/distroless/java-base-debian12`. - Don't bundle the full Java image — native binaries don't need a JVM. - Buildpacks produce a layered image automatically. ## Performance characteristics - Cold start: 50–200 ms vs 2–5 s on JVM. - Memory: ~80 MB vs ~250 MB for a typical Boot app. - Peak throughput: native is **slower** than JIT-warmed JVM for long-running services. Don't switch for throughput. ## Don't - Don't go native to "save memory" without measuring. JVM's `MaxRAMPercentage` + container limits often suffice. - Don't use `@MockBean` in native tests. They depend on bytecode manipulation. - Don't enable `spring.aot.enabled=false` in production. Lose AOT, lose the speedup. - Don't ignore tracing-agent output. The reflection config is doing real work.Spring Boot Deployment
# CLAUDE.md — Spring Boot Deployment ## Image strategy Three options, in order of preference: 1. **Cloud Native Buildpacks** — `mvn spring-boot:build-image` or `gradle bootBuildImage`. Layered, optimized, secure-by-default base. 2. **Layered jar + Dockerfile** — Spring's layered jar separates dependencies from app classes for better caching. 3. **GraalVM native image** — small startup, low memory; complex to debug. Use for serverless / Lambda. ## Buildpacks - One command, no Dockerfile: `./mvnw spring-boot:build-image -Dspring-boot.build-image.imageName=myapp:1.0`. - Pin the buildpack version: `<imageName>` config + Paketo builder version. - Configure JVM flags via `BPL_JVM_HEAD_ROOM`, `BPE_*` env vars at runtime. ## Layered jar Dockerfile ```dockerfile FROM eclipse-temurin:21-jre AS builder WORKDIR /app COPY target/*.jar app.jar RUN java -Djarmode=tools -jar app.jar extract --layers --launcher FROM eclipse-temurin:21-jre WORKDIR /app COPY --from=builder /app/app/dependencies/ ./ COPY --from=builder /app/app/spring-boot-loader/ ./ COPY --from=builder /app/app/snapshot-dependencies/ ./ COPY --from=builder /app/app/application/ ./ USER nobody ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"] ``` - Run as non-root. - The layer order is the cache order — dependencies change less than app code. ## Native image (GraalVM) - Add `org.graalvm.buildtools:native-maven-plugin` (or Gradle equivalent). - `./mvnw -Pnative native:compile` builds the native binary. - Reflection / dynamic proxies need hints. Spring Boot 3 generates most automatically; provide your own with `RuntimeHintsRegistrar` for edge cases. - Trade-off: 50–100ms startup, ~50 MB memory; build takes 1–5 minutes; harder to profile. ## JVM tuning - Container-aware JVM (Java 10+ does this automatically). `MaxRAMPercentage=75` is a good default. - `-XX:+ExitOnOutOfMemoryError` so the orchestrator restarts you instead of you limping along. - For Boot 3+, Spring AOT processing reduces startup time even on the JVM. Enable with `spring-boot-maven-plugin` `<process-aot>true</process-aot>`. ## Configuration - **12-factor**: every config from env vars. No files baked into the image. - Spring reads env vars natively: `SERVER_PORT=8080` overrides `server.port`. Use `SCREAMING_SNAKE_CASE`. - Secrets injected by the orchestrator (K8s Secrets, AWS Secrets Manager). Never bake. ## Migrations - Run **Flyway** or **Liquibase** as a separate one-shot job before the app starts. - For multi-replica deploys, never run migrations from `app boot` — race condition. - Schema changes are backward-compatible: add nullable column → deploy → backfill → make NOT NULL in next deploy. ## Health & graceful shutdown - Configure: `server.shutdown=graceful`, `spring.lifecycle.timeout-per-shutdown-phase=30s`. - Kubernetes: `terminationGracePeriodSeconds: 35` (slightly more than the shutdown timeout). - Liveness/readiness split, lock behind a management port. ## CI - Cache `.m2` / `.gradle` between runs. - Multi-stage CI: lint → test → build image → integration test against the image → push. - Sign images with `cosign` if your platform supports it. ## Don't - Don't run with `--add-opens` flags in production unless you know exactly which library needs them. Each one is a future migration. - Don't put `application-prod.yml` in version control with secrets in plaintext. - Don't forget `USER nobody` in your Dockerfile. Root containers are a footgun. - Don't try to fit a 1 GB JAR into a 256 MB pod. Profile memory before sizing.Spring WebFlux (Reactive)
# CLAUDE.md — Spring WebFlux (Reactive) ## When to use WebFlux - For high-concurrency I/O-bound services where you'd otherwise size a thread pool in the thousands. - When the upstream APIs are reactive (e.g., R2DBC, MongoDB Reactive, reactive HTTP clients). - **Don't** use WebFlux when: - The team isn't ready for reactive debugging (it's harder than imperative) - The DB driver is blocking (you'll wrap blocking I/O and lose every benefit) - Java 21+ is available — virtual threads on Spring MVC give similar concurrency without reactive complexity ## Mono and Flux - `Mono<T>` for 0..1 elements. `Flux<T>` for 0..N elements. - The pipeline doesn't run until something subscribes. WebFlux subscribes for you at the controller boundary. - Don't call `.block()` in production code. It defeats the reactive model. ## Operators - `.map(...)` — synchronous transform. - `.flatMap(...)` — async transform that returns a `Mono`/`Flux`. - `.zip(a, b)` — combine two parallel sources. - `.then(...)` — chain a follow-up that doesn't depend on the result. - `.switchIfEmpty(Mono.error(...))` — handle "no result" without `if`. ## Backpressure - Built into Reactor. Slow consumers signal upstream to slow down. - Configure `bufferSize`, `prefetch` on operators that aggregate. Default values are usually fine. - For unbounded sources (event streams), always provide an overflow strategy (`onBackpressureBuffer`, `onBackpressureDrop`). ## R2DBC - The reactive equivalent of JDBC. **Spring Data R2DBC** for repository-style access. - Repositories return `Mono<T>` and `Flux<T>`. Methods named the same as JPA derived queries. - No JPA. No lazy loading. Joins are explicit. Decide between `@Query` and projections per query. - Transactions: `@Transactional` on a method returning a `Mono`/`Flux`. Reactor wires the lifecycle. ## Error handling - `.onErrorResume(e -> Mono.just(fallback))` for recovery. - `.onErrorMap(IOException.class, e -> new MyDomainException(e))` to translate. - `@ExceptionHandler` in `@RestControllerAdvice` works the same as MVC. Use it for HTTP error mapping. ## Functional endpoints - Alternative to annotated controllers: `RouterFunction<ServerResponse>` + `HandlerFunction`. Useful for very dynamic routing or pure-function-style codebases. - For most apps, `@RestController` + `Mono`/`Flux` returns is simpler. ## Testing - `@WebFluxTest` for controller slice tests. Provides `WebTestClient`. - `StepVerifier` for assertions on `Mono`/`Flux`: ```java StepVerifier.create(service.find("id")) .expectNextMatches(u -> u.email().equals("a@b")) .verifyComplete(); ``` ## Don't - Don't mix blocking JDBC/JPA with WebFlux. The first thing that blocks the event loop kills throughput. - Don't subscribe inside a reactive pipeline. Return the publisher; let the framework subscribe. - Don't `Flux.fromIterable(largeList)` for streaming — use a real reactive source from the start. - Don't reach for WebFlux because "reactive is faster". It's a different model with different costs.
Have a CLAUDE.md template that works for you?
Send it in — we’ll credit you and publish it under the right tags.