Java
Java rules and best practices for Claude Code.
61k
- Java
- Spring
- Spring Boot
- Quarkus
- Jakarta EE
- MicroProfile
- GraalVM
- Vert.x
- Kotlin
- Gradle
- Maven
40 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.Modern Java Rules
# CLAUDE.md — Modern Java ## Target version - Java 21+ (LTS). Java 25 if the runtime supports it. - `--release 21` in the compiler. Don't ship code that compiles on a newer JDK but doesn't run on the target. - Pin the JDK in `.tool-versions` / `.sdkmanrc` / `.java-version` so every machine builds against the same toolchain. ## Records, sealed types, pattern matching - Use `record` for value objects. They give you constructor, accessors, `equals`, `hashCode`, `toString` for free. - `sealed interface` + permitted `record` subtypes for discriminated-union-style modeling: ```java sealed interface Result<T> permits Result.Ok, Result.Err { record Ok<T>(T value) implements Result<T> {} record Err<T>(String message) implements Result<T> {} } ``` - `switch` expressions with pattern matching for exhaustive dispatch: ```java return switch (result) { case Result.Ok<T>(var v) -> v; case Result.Err<T>(var m) -> throw new IllegalStateException(m); }; ``` - Compiler-checked exhaustiveness on sealed types. Don't use `default` if you can list the cases. ## Language defaults - `var` for local variables when the right-hand side makes the type obvious. Don't use `var` for ambiguous returns (`var x = service.get();`). - Text blocks (`"""`) for multi-line strings — never `+ "\n"` chains. - `String.format` for one-off formatting; `String.join` for concatenation; `StringBuilder` only in tight loops. - `Optional<T>` as a return type, never as a field or parameter. ## Null handling - Treat `null` as a code smell. Prefer `Optional`, sentinel values, or sealed types. - Annotate parameters and returns with `@NonNull` / `@Nullable` (JSpecify or JetBrains). - `Objects.requireNonNull(arg, "arg")` at the top of public methods that don't tolerate null. ## Concurrency - Virtual threads (`Thread.startVirtualThread`, `Executors.newVirtualThreadPerTaskExecutor()`) for I/O-bound work. They scale to millions. - Structured concurrency (`StructuredTaskScope`) for fan-out/fan-in. Cleanup is automatic on scope exit. - `ConcurrentHashMap` over `Collections.synchronizedMap`. Read the JavaDoc on the iteration semantics. - Don't share mutable state across threads without a memory-barrier guarantee. ## Build & tooling - **Maven** or **Gradle**. Pick one; commit to it. Wrapper script (`./mvnw`, `./gradlew`) is committed. - Set `--enable-preview` only if you actually use preview features. Preview features may change between releases. - Format with **google-java-format** or **palantir-java-format**. Lint with **error-prone**, **PMD**, or **SpotBugs**. ## Don't - Don't use `Optional.get()` without `isPresent()`. Use `orElse`, `orElseThrow`, or `ifPresent`. - Don't catch `Exception` to "be safe". Catch the narrowest type or let it propagate. - Don't write `for (int i = 0; i < list.size(); i++)`. Use enhanced-for, streams, or `IntStream.range`. - Don't use `synchronized` on `String` literals or boxed primitives — pool aliasing creates shared locks.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.Modern Quarkus Rules
# CLAUDE.md — Modern Quarkus ## Version baseline - Quarkus 3.6+ (3.15+ if you can adopt). Java 21. - Pin Quarkus platform BOM in `pom.xml` / `quarkusPlatformVersion` in Gradle. - Single source of truth for all Quarkus extensions — never mix versions. ## Dev mode - `./mvnw quarkus:dev` (or `./gradlew --console=plain quarkusDev`) is the way you develop. - Live reload on file change. The dev UI runs at `localhost:8080/q/dev-ui`. - **Continuous testing** (press `r` in the dev console) runs tests on every save. - Don't deploy dev mode to production. It's not optimized. ## Build-time vs runtime - Quarkus does most work at **build time**: classpath scanning, reflection registration, dependency wiring. - Application startup is fast because there's nothing to discover at runtime. - Don't try to defer config to runtime if you don't need it. Build-time `@ConfigMapping` is faster and statically typed. ## Configuration - `application.properties` (or `.yml` with the `quarkus-config-yaml` extension) for app config. - `@ConfigProperty(name = "...")` for individual properties. - `@ConfigMapping(prefix = "...")` for grouped, type-safe access: ```java @ConfigMapping(prefix = "app.email") interface EmailConfig { String fromAddress(); @WithDefault("smtp.gmail.com") String smtpHost(); int smtpPort(); } ``` - Profiles: `%dev`, `%test`, `%prod`. Override per profile: `%prod.app.email.smtp-host=...`. ## CDI - Quarkus uses **ArC** — a build-time CDI implementation. Most Jakarta CDI APIs work; some runtime-only patterns don't. - `@ApplicationScoped` for singletons. `@RequestScoped` per HTTP request. `@Dependent` for shared cheap beans. - Constructor injection via `@Inject`. Field injection works but constructor is preferred. - Producers (`@Produces`) for third-party types you can't annotate. ## Logging - Use `org.jboss.logging.Logger` or just SLF4J — both work via the same Quarkus logging. - `quarkus.log.console.json=true` for structured logs in production. - Per-package log levels: `quarkus.log.category."com.app".level=DEBUG`. ## Health - Built-in `/q/health`, `/q/health/live`, `/q/health/ready`. - Auto-detects DB and message-broker health via extensions. Custom checks implement `HealthCheck`. ## Don't - Don't use reflection-heavy frameworks without Quarkus extensions. They won't work in native mode and probably won't optimize well in JVM either. - Don't store mutable state in `@ApplicationScoped` beans without synchronization. - Don't put config in `META-INF/microprofile-config.properties` — use `application.properties` for project consistency. - Don't ignore the dev UI. It surfaces config, beans, endpoints, and metrics for free.Modern Jakarta EE Rules
# CLAUDE.md — Modern Jakarta EE ## Version baseline - Jakarta EE 10 (or 11). Java 21. - All `javax.*` packages are renamed to `jakarta.*`. Do the migration once, completely. Mixed namespaces won't link. - Pick a runtime: WildFly, Open Liberty, Payara, Glassfish. Stay current — old Java EE versions are not Jakarta EE 10. ## Architecture - Layered: presentation (REST, Servlets) → service (CDI beans) → persistence (JPA). - Use **CDI** as the central glue. Don't reach for EJBs in new code unless you specifically need transactions, timers, or remoting. - Keep beans single-responsibility. A `@Stateless` bean with 30 methods is a refactor candidate. ## Modules - Jakarta EE specs are now Maven artifacts under `jakarta.*` group IDs: - `jakarta.platform:jakarta.jakartaee-api:10.0.0` (compile-only API) - Don't bundle the API in your WAR. The runtime provides it. - Use **MicroProfile** alongside Jakarta EE for cloud-native concerns (config, health, metrics) — it complements rather than replaces. ## Packaging - WAR for web apps. EAR only when you genuinely need multiple modules with isolated classloaders. - Skinny WAR + provided dependencies — let the runtime supply the platform classes. - `pom.xml`: ```xml <dependency> <groupId>jakarta.platform</groupId> <artifactId>jakarta.jakartaee-api</artifactId> <version>10.0.0</version> <scope>provided</scope> </dependency> ``` ## Deployment descriptors - Prefer annotations over `web.xml` / `ejb-jar.xml`. They survive refactors better. - Use descriptors only for declarative things annotations can't express well (security roles in some contexts, JNDI overrides). - Don't have both — pick a primary source. `metadata-complete="true"` to disable annotation scanning if descriptors are authoritative. ## Logging - **java.util.logging** is the platform default but inconvenient. Use **SLF4J + Logback** (or whatever your runtime supplies, e.g., WildFly's logging). - Configure structured JSON output for production observability stacks. ## Testing - **Arquillian** for integration tests against a real container. Slower than unit tests but realistic. - For unit tests, use plain JUnit 5 + Mockito. Most beans don't need a container to test. ## Don't - Don't write new code against Java EE 8. Migrate to Jakarta EE 10+. - Don't deploy multiple WARs that share state via static singletons. Use a JNDI-bound resource or a shared library. - Don't `@PersistenceContext` into a `@RequestScoped` bean and expect lazy loading to work after the request ends. - Don't pick Jakarta EE for a single-purpose microservice that doesn't use the platform features. Spring Boot or Quarkus is usually a better fit.GraalVM Native Image Rules
# CLAUDE.md — GraalVM Native Image ## What native image gives you - Fast startup (10–100ms vs 1–5s on JVM). - Lower memory (often 1/3 to 1/5 of JVM). - A single self-contained binary — no JRE on the target. ## What it costs you - Slow build (1–5 minutes). - No reflection except what's registered ahead of time. - No dynamic class loading at runtime. - Closed-world assumption: every code path must be reachable from `main`. Use it for serverless, CLIs, scale-to-zero. Avoid it for throughput-bound long-running services where JIT eventually wins. ## Distributions - **GraalVM Community Edition (CE)** — open source, MIT. - **Mandrel** — Red Hat's downstream of GraalVM CE, focused on native-image; pairs well with Quarkus. - **Oracle GraalVM (formerly Enterprise)** — adds JIT optimizations (G1 GC, advanced compiler). Free for production now. ## Build basics - Maven: `org.graalvm.buildtools:native-maven-plugin`. - Gradle: `org.graalvm.buildtools.native` plugin. - Run: `mvn -Pnative package` or `gradle nativeCompile`. - For a Docker build: `mvn -Pnative -Dquarkus.native.container-build=true` (Quarkus) or use the official `gcr.io/distroless/static`. ## Reflection config Native image needs to know at build time which classes use reflection. Two ways: 1. **Tracing agent (preferred)** — run your app on the JVM with `-agentlib:native-image-agent=config-output-dir=...`, then bundle the generated config: ```sh java -agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image/ -jar app.jar ``` 2. **Manual hints** — add `reflect-config.json`, `resource-config.json` files in `META-INF/native-image/`. ## Resources - Files accessed via `getResource` / `getResourceAsStream` need to be in `resource-config.json`: ```json { "resources": [{ "pattern": "config/.*\\.properties" }] } ``` - Or use the `@RegisterResource` annotation if your framework supports it. ## Class initialization - Default policy: classes initialize at **runtime**. - Some classes need to initialize at **build time** for performance — frameworks declare this via `--initialize-at-build-time=...`. - Framework integrations (Spring, Quarkus, Micronaut) handle this for you. Custom code rarely needs to opt in. ## Image size - Strip unused code: `--no-fallback`, `--gc=epsilon` (only for short-lived workloads), `-H:+RemoveUnusedSymbols`. - Compress the binary: `--enable-monitoring=heapdump,jfr` only when needed (each adds size). - For Docker, use `gcr.io/distroless/static` (~2 MB) as the runtime base. ## Debugging - Build with `-g` for debug symbols: `--debug-info`. - Profile with **JFR** (`-XX:+FlightRecorder` works on native). - For deep diagnosis, run on the JVM and reproduce — native errors are often "this didn't work in JVM either, you just got lucky". ## Don't - Don't expect every Java library to work in native. Test the integration in native mode early. - Don't skip the tracing agent run — manual reflection config is tedious and error-prone. - Don't disable build-time initialization to "fix" a class init bug. Find the actual cause. - Don't ship `--report-unsupported-elements-at-runtime` to production. It hides bugs.Modern Vert.x Rules
# CLAUDE.md — Modern Vert.x ## Mental model - Vert.x is an **event-driven, non-blocking** toolkit. Code runs on a small pool of event-loop threads. - The **golden rule**: never block an event loop. If you need to block, use `executeBlocking` to run on a worker pool. - Code that violates the rule is the #1 source of Vert.x bugs. Profile early and often. ## Verticles - A **Verticle** is a unit of deployment. Like a fiber/actor with its own lifecycle. - Two flavors: - Standard: runs on the event loop. Default for HTTP servers, clients. - Worker: runs on a worker pool. Use for blocking work. - Deploy with `vertx.deployVerticle(MyVerticle.class.getName(), options)`. ## Async patterns Three styles in modern Vert.x — pick **one** per project: 1. **Callback style** — original API. Avoid in new code; nesting gets painful. 2. **Future-based** — `Future<T>` with `.compose`, `.map`, `.recover`. Cleaner than callbacks. 3. **Mutiny** — `Uni<T>` / `Multi<T>`. Reactive Streams compatible. The recommended style for new code, especially with Quarkus. ```java // Future style client.get("/users").send() .compose(resp -> validate(resp)) .map(body -> body.toJsonObject()) .onSuccess(json -> log.info(json)) .onFailure(err -> log.error("failed", err)); ``` ## Event loop discipline - Never call `Thread.sleep`, `Object.wait`, or any blocking I/O on an event loop thread. - Vert.x detects long-running event-loop callbacks and warns: `Thread Thread[vert.x-eventloop-thread-0] has been blocked for X ms`. - For genuinely blocking work, use: ```java vertx.executeBlocking(() -> doExpensiveThing(), false, ar -> { ... }); ``` - The `ordered=false` flag means Vert.x can run blocking calls in parallel. ## Configuration - `JsonObject` config passed to verticles via `DeploymentOptions.setConfig(...)`. - For env-driven config, use Vert.x Config (`io.vertx:vertx-config`) with sources: env, files, HTTP. - Parse to a typed POJO at boot — don't pass `JsonObject` through your code. ## Logging - Vert.x uses **java.util.logging** by default. Switch to SLF4J via system property: `-Dvertx.logger-delegate-factory-class-name=io.vertx.core.logging.SLF4JLogDelegateFactory`. - Always configure structured JSON logs in production. ## Threading model - Default: 2 × CPU event loops. Tune with `VertxOptions.setEventLoopPoolSize(...)`. - Worker pool: 20 threads default. For heavy blocking work, increase or split into custom worker executors. - Each verticle is pinned to one event loop. Don't share state across verticles via static fields — use the event bus. ## Don't - Don't `Thread.sleep`. Use `vertx.setTimer(ms, ...)`. - Don't hold mutable state across `.compose` boundaries. The continuation may run on a different thread. - Don't catch and ignore Future failures. Always handle them. - Don't block the event loop for any reason. If unsure, profile.MicroProfile Core Rules
# CLAUDE.md — MicroProfile Core ## What MicroProfile is - A set of cloud-native specs that complement Jakarta EE: Config, Health, Metrics, OpenAPI, REST Client, Fault Tolerance, JWT, OpenTracing. - Implemented by Quarkus (SmallRye), Open Liberty, Helidon, Payara Micro, WildFly. - Use it on top of Jakarta EE for the "operability" layer that Jakarta EE proper doesn't cover. ## Configuration (MicroProfile Config) - `@ConfigProperty(name = "...")` to inject config values into beans: ```java @Inject @ConfigProperty(name = "app.email.from") String fromAddress; ``` - Sources by precedence: system properties → env vars → `microprofile-config.properties` → user-defined sources. - For grouped, type-safe config, use `@ConfigMapping` (Quarkus / SmallRye) or roll a `@ApplicationScoped` config bean. - Default values: `@ConfigProperty(name = "app.timeout", defaultValue = "30")`. ## Health (MicroProfile Health) - `/health` for combined, `/health/live` for liveness, `/health/ready` for readiness. - One `HealthCheck` per dependency: ```java @Liveness @ApplicationScoped public class AppLive implements HealthCheck { public HealthCheckResponse call() { return HealthCheckResponse.up("app"); } } ``` - Liveness checks must not depend on external systems (DB, Redis). They check process health only. - Readiness checks include external systems. Failure means "remove from load balancer". ## Metrics (MicroProfile Metrics / Micrometer) - MicroProfile Metrics API → SmallRye Metrics or Micrometer (Quarkus 3 prefers Micrometer). - Standard metrics: HTTP requests, JVM, GC, threads. Custom: ```java @Counted(name = "checkout_attempts") public void checkout(...) { ... } ``` - Tag with low-cardinality dimensions (`endpoint`, `status`, `tenant_id` only if bounded). Never `user_id`. - Expose at `/metrics` in Prometheus format. ## REST Client - Type-safe HTTP clients defined as interfaces (see the dedicated REST Client guide). ## Fault tolerance - Retries, circuit breaker, fallback, timeout, bulkhead — declarative annotations on methods (see the dedicated Fault Tolerance guide). ## OpenAPI - `@OpenAPIDefinition` on the application class for global metadata. - Most documentation is auto-generated from JAX-RS + Bean Validation. - See dedicated OpenAPI guide for details. ## JWT - `@LoginConfig(authMethod = "MP-JWT")` on the application class enables JWT auth. - Inject claims with `@Inject @Claim("groups") Instance<String> groups`. - See the dedicated JWT auth guide. ## Don't - Don't reach for a separate config library when MicroProfile Config is on the platform. Use what's there. - Don't mix MicroProfile Metrics and Micrometer in the same project — pick one. - Don't use `Liveness` checks that depend on the DB. The pod will be restarted unnecessarily on DB blips. - Don't expose `/metrics` to the public internet without auth.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.Java Testing with JUnit 5
# CLAUDE.md — Java Testing with JUnit 5 ## Stack - **JUnit 5** (Jupiter) for the test runner. No more JUnit 4 in new code. - **AssertJ** for fluent assertions. Replaces Hamcrest and JUnit's `assertEquals`. - **Mockito** for mocks. `@Mock` + `@InjectMocks`, or `Mockito.mock(Class)` for fine control. - **Testcontainers** for real Postgres/Redis/Kafka in tests. SQLite/H2 lie. ## File layout - Test classes live in `src/test/java`, mirroring `src/main/java` package by package. - `XxxTest.java` for unit tests, `XxxIT.java` (or `XxxIntegrationTest.java`) for integration. Maven Failsafe runs `*IT` separately. - One test class per production class. Group by behavior with nested `@Nested` classes. ## JUnit 5 idioms - `@DisplayName` for human-readable names — they show up in IDE and CI reports. - `@Nested` to group related cases: ```java @Nested @DisplayName("when user is admin") class WhenAdmin { @Test void canDelete() { ... } } ``` - `@ParameterizedTest` with `@MethodSource`, `@ValueSource`, or `@CsvSource`. Don't write copy-paste tests differing only by input. - `@TestMethodOrder` only when you actually need ordering. Random by default. ## AssertJ - `assertThat(actual).isEqualTo(expected)`. Don't mix in `assertEquals(...)`. - Chain assertions: `assertThat(list).hasSize(3).contains("a", "b").doesNotContain("c");`. - For exceptions, use `assertThatThrownBy(() -> ...)` — message and type in one statement. - `as("context")` adds a description that shows up in failure messages. ## Mockito - `@ExtendWith(MockitoExtension.class)` on the class. Use `@Mock`, `@Spy`, `@InjectMocks`. - Stub with `when(...).thenReturn(...)`. Verify with `verify(mock).method(...)`. - `lenient()` only when a stub legitimately isn't always called. Otherwise strict mode catches dead stubs. - Don't mock value types or DTOs. Build them with `new` or a builder. ## Testcontainers - Start containers with `@Testcontainers` + `@Container`. JUnit 5 manages lifecycle. - Reuse containers across tests with `withReuse(true)` and the singleton pattern. Boot cost amortizes fast. - Per-test database: each test runs in a transaction that rolls back. Or truncate in `@BeforeEach`. ## Speed - Run tests in parallel: `junit.jupiter.execution.parallel.enabled=true`. - Tag slow tests `@Tag("slow")` and exclude from default runs. - Profile with `--scan-classpath` and Surefire's `time` reporter. ## Don't - Don't `Thread.sleep` to wait for async results. Use `CompletableFuture.get(timeout)`, Awaitility, or test scheduling APIs. - Don't depend on test execution order. Assume any order. - Don't mock the framework (Spring, Hibernate). Use real instances or thin slice tests. - Don't write tests that pass with `@Disabled`. Either fix the test or delete it.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.Java Virtual Threads & Concurrency
# CLAUDE.md — Java Virtual Threads & Concurrency ## When to use virtual threads - Java 21+. Virtual threads scale to millions of concurrent tasks for I/O-bound work. - Use them when you'd previously have used a thread pool sized in the hundreds. They're cheaper than OS threads. - **Don't** use them for CPU-bound work. They run on a fixed pool of carrier threads — bottleneck is the same. - **Don't** use them inside `synchronized` blocks that span I/O. Pinning to a carrier thread negates the benefit. Use `ReentrantLock` instead. ## How to spawn - One-off: `Thread.startVirtualThread(() -> work())`. - Pool-style: `Executors.newVirtualThreadPerTaskExecutor()` — every task gets its own virtual thread, no reuse. - Don't `setMaxThreads` — there's no useful upper bound. ## Structured concurrency - `StructuredTaskScope` for fan-out/fan-in. Lifetime is tied to the enclosing scope: ```java try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { var a = scope.fork(() -> fetchA()); var b = scope.fork(() -> fetchB()); scope.join().throwIfFailed(); return new Pair<>(a.get(), b.get()); } ``` - `ShutdownOnFailure` cancels siblings on first failure. `ShutdownOnSuccess` returns first success. - Always call `scope.join()` — the try-with-resources only closes the scope, not the work. ## CompletableFuture - Still useful for callback-style composition. - Prefer `thenApply`, `thenCompose`, `thenCombine` over chained `whenComplete`. - Always specify the executor (`*Async` variants take one). The common pool surprises you. ## Locks - `ReentrantLock` for non-trivial locking. `synchronized` is fine for short critical sections. - `ReadWriteLock` only when reads dominate writes by an order of magnitude. Otherwise it's slower than `Lock`. - `StampedLock` for optimistic reads. Read the JavaDoc — it's not a drop-in replacement. ## Atomics - `AtomicInteger`, `AtomicReference` for lock-free counters and references. - `LongAdder` over `AtomicLong` when many threads write and few read. Better contention behavior. - Use `volatile` only for write-once-publish or visibility-only flags. Compound operations need atomics. ## Cancellation - Always honor `Thread.interrupted()` in long loops. - `InterruptedException`: catch, restore the flag (`Thread.currentThread().interrupt()`), and bail out. - Don't swallow interrupts. They're a cancellation signal from the caller. ## Don't - Don't sleep in I/O code. If you need a timeout, use `CompletableFuture.orTimeout(...)`. - Don't pin virtual threads inside `synchronized` blocks holding I/O calls. - Don't share mutable collections across threads. Use `ConcurrentHashMap` or copy-on-write variants. - Don't use `Thread.stop()`. Ever.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.Jakarta Persistence (JPA)
# CLAUDE.md — Jakarta Persistence (JPA) ## Entities - One `@Entity` class per concept. Use a `@MappedSuperclass` for shared fields like timestamps and audit columns. - Use surrogate keys (`@Id @GeneratedValue`) for new tables. Natural keys are valid only when they're truly stable. - For UUIDs: `@Id @GeneratedValue(strategy = UUID) UUID id`. - Annotate the table and column names explicitly when defaults disagree with your DB convention: `@Table(name = "users")`, `@Column(name = "created_at")`. ## Relationships - Default to `@ManyToOne(fetch = LAZY)`. Eager fetch is a recipe for N+1 surprises. - For `@OneToMany` and `@ManyToMany`, lazy is the default — keep it. - Use `@JoinColumn` to control the foreign key column name. Use `@JoinTable` only for genuine many-to-many (no extra columns). - For "association entities" (a join table with extra fields), promote it to its own `@Entity`. ## Fetching - `JOIN FETCH` in JPQL when you need an association eagerly: ```java TypedQuery<Order> q = em.createQuery("select o from Order o join fetch o.items where o.id = :id", Order.class); ``` - `@EntityGraph` for declarative fetch plans, attached per-query. - Don't use `OpenSessionInView` or `OpenEntityManagerInView`. They turn N+1 from a bug into a slow default. ## Queries - **JPQL** by default. Type-safe via `TypedQuery<T>`. - **Criteria API** for dynamic queries. Verbose but type-safe. - **Native SQL** (`createNativeQuery`) only when JPQL doesn't express what you need. Document why. - Use named parameters (`:name`) — never positional in new code. ## Transactions - `@Transactional` (Jakarta or Spring style depending on container) on service methods. - Read-only transactions for queries — Hibernate skips dirty-checking. - One unit-of-work per use case. Don't span transactions across HTTP requests. ## Caching - **L1 (persistence context)** is per-transaction; you can't disable it. - **L2 (shared cache)**: opt in per entity (`@Cacheable`). Use for read-heavy reference data. - **Query cache** is rarely worth it — hit rates are low. Profile before enabling. ## Migrations - **Flyway** or **Liquibase**. Don't let Hibernate auto-generate schema in anything beyond dev. - `hibernate.hbm2ddl.auto=validate` in production to catch drift between schema and entities. ## Performance - `@Where(clause = "deleted_at IS NULL")` for soft-delete filtering — applied to every query for the entity. - Add indexes for every column you filter or sort on. JPA doesn't do it for you. - For batch inserts, set `hibernate.jdbc.batch_size=50` and call `flush()` periodically. ## Don't - Don't return entities from REST endpoints. Map to DTOs at the boundary so DB renames don't break the API. - Don't lazy-load outside a transaction. `LazyInitializationException` is the most common JPA bug. - Don't ignore the SQL log in dev (`hibernate.show_sql=true`). It's the fastest way to spot N+1. - Don't share `EntityManager` instances across threads. They're not thread-safe.Quarkus REST (RESTEasy Reactive)
# CLAUDE.md — Quarkus REST (RESTEasy Reactive) ## Extension choice - Use **`quarkus-rest`** (RESTEasy Reactive). It's the modern default — built-in reactive, faster, smaller native image. - The classic `quarkus-resteasy` (non-reactive) is legacy. Don't start new projects with it. - For a fully reactive stack, pair with `quarkus-rest-jackson` (or `quarkus-rest-jsonb`) for JSON. ## Resource classes - `@Path("/users")` on a class. Methods use JAX-RS annotations: `@GET`, `@POST`, `@Path("/{id}")`. - Don't use `@ApplicationScoped` on resources unless you understand the lifecycle. RESTEasy Reactive handles this for you. - Inject services via constructor (`@Inject` on the constructor params is implicit in Quarkus). ## Reactive vs imperative - Methods can return `T` (blocking, runs on the worker thread) or `Uni<T>` / `Multi<T>` (reactive, runs on the event loop). - For purely reactive workloads, return `Uni<T>`. For mixed code, blocking is fine — Quarkus dispatches automatically. - Annotate with `@RunOnVirtualThread` (Quarkus 3.10+) to opt in to virtual-thread dispatch — best of both worlds. ## Request/response shaping - Records as DTOs. Use `@JsonbProperty` or Jackson's `@JsonProperty` to rename fields when client contracts diverge. - Validate with Bean Validation: `@Valid` on the body parameter, `@NotNull` / `@Email` / `@Size` on fields. - For errors, throw `WebApplicationException` with a status — or define an `ExceptionMapper`. ## Exception mapping - One `ExceptionMapper<X>` per error type. Quarkus auto-discovers them. - Use `RestResponse<T>` to return typed responses with status: ```java RestResponse<UserDto> get(@PathParam("id") String id) { var user = service.find(id); return user.map(RestResponse::ok) .orElseGet(() -> RestResponse.status(404)); } ``` ## Validation errors - `ConstraintViolationException` is auto-mapped to a 400 with field-level errors. Customize via a custom mapper. - For consistent error envelopes, define a `ProblemDetail`-like DTO and use it everywhere. ## OpenAPI - Add `quarkus-smallrye-openapi`. The spec is exposed at `/q/openapi`, Swagger UI at `/q/swagger-ui`. - Annotate with `@Operation`, `@APIResponse` for richer docs. Most generation is automatic from JAX-RS + Bean Validation. ## CORS - Enable globally: `quarkus.http.cors=true`, `quarkus.http.cors.origins=https://app.example.com`. - Don't set `*` with credentials. The browser will refuse. ## Don't - Don't mix RESTEasy Reactive and non-reactive in the same project. - Don't return `Response.ok().entity(x).build()` when `RestResponse.ok(x)` does the same with type safety. - Don't put logic in `ExceptionMapper`. They translate; logic belongs in services. - Don't disable native compilation tests for new endpoints. RESTEasy works in native, but reflection-heavy custom code might not.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.Java Build (Maven & Gradle)
# CLAUDE.md — Java Build (Maven & Gradle) ## Pick one - **Maven** for stability, predictability, and convention. Best for teams that don't want to debug their build tool. - **Gradle** for performance and configurability. Best for monorepos and complex build logic. - Don't mix. One per repo. - Always commit the wrapper (`mvnw` or `gradlew`) — every machine builds the same way. ## Maven defaults - `pom.xml`: - `<maven.compiler.release>21</maven.compiler.release>` - `<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>` - Pin plugin versions explicitly. Don't rely on plugin version inheritance from the super-POM. - Use **BOM imports** (`<dependencyManagement>`) for consistent versions across deps (Spring Boot BOM, JUnit BOM). - Multi-module: parent POM declares `<modules>`, each module is its own pom inheriting from parent. ## Gradle defaults - **Kotlin DSL** (`build.gradle.kts`) over Groovy. Better IDE support and type safety. - Toolchains: `java { toolchain { languageVersion = JavaLanguageVersion.of(21) } }`. Gradle downloads the JDK automatically. - `versionCatalog` (`gradle/libs.versions.toml`) for dep versions. One source of truth across modules. - Build cache + configuration cache: `org.gradle.caching=true`, `org.gradle.configuration-cache=true`. Order-of-magnitude faster builds. ## Dependencies - Pin every direct dependency. Use `mvn dependency:tree` / `gradle dependencies` to audit transitives. - `provided` / `compileOnly` for deps the runtime supplies (servlet API in a WAR, BOM imports). - `test` / `testImplementation` for test-only deps. They don't pollute the runtime classpath. - Don't use SNAPSHOT versions in CI. Pin to a release. ## Multi-module structure ``` parent-pom.xml app/ ← deployable api/ ← interfaces domain/ ← pure model infrastructure/ ← external system adapters ``` - Domain and api have minimal deps. Infrastructure pulls in Postgres, HTTP clients, etc. - App wires everything together. Should be the smallest module. ## CI - One job per stage: compile, test, integration test, package. - Use Maven's `-T` (parallel) or Gradle's parallelism for multi-module speedup. - Cache `~/.m2` and `~/.gradle` between runs. Saves minutes per build. - Fail on any new SpotBugs / SonarLint warnings — `--failOnWarning` style flags. ## Don't - Don't commit the local Maven settings or Gradle init scripts to the repo. - Don't put credentials in `pom.xml` or `build.gradle.kts`. Use `~/.m2/settings.xml` or env vars. - Don't run plugins from `mvn clean install` you don't recognize. Audit `pom.xml` periodically. - Don't use `version-range` syntax (`[1.0,2.0)`) in dependencies — it makes builds non-deterministic.Quarkus + Hibernate ORM with Panache
# CLAUDE.md — Quarkus + Hibernate ORM with Panache ## Pick a style - **Active record**: entity extends `PanacheEntity` and exposes `find()`, `persist()`, `count()` directly on the class. - **Repository**: separate `PanacheRepository<E>` class; entities stay POJOs. - Pick one per project. Mixing is confusing. - For DDD-style codebases, repositories are usually the right call. For CRUD apps, active record is faster to write. ## Entities - Use records-friendly entity patterns: ```java @Entity public class User extends PanacheEntity { public String email; public String name; public Instant createdAt = Instant.now(); } ``` - Public fields are fine — Panache rewrites them to property accessors at build time. - Use `@Id` + `@GeneratedValue` only if you need a custom ID strategy. Default is `Long id`. ## Queries - Derived queries via static methods on the entity (active record): ```java public static List<User> findByEmail(String email) { return list("email", email); } ``` - Or via repository methods: ```java @ApplicationScoped public class UserRepository implements PanacheRepository<User> { public List<User> findByEmail(String email) { return list("email", email); } } ``` - Use `find(...)` for lazy queries, `list(...)` for eager. `firstResult()` / `singleResult()` to materialize. - Pagination: `find("...").page(Page.of(0, 20)).list()`. ## Transactions - `@Transactional` on the service method. Don't put it on entities or repositories. - Reads don't need a transaction unless they touch lazy collections — use `@Transactional` defensively if you're not sure. - For **reactive Panache**, use `@WithTransaction` on `Uni`-returning methods. ## Migrations - **Flyway** (`quarkus-flyway`) or **Liquibase** (`quarkus-liquibase`). Pick one. - `quarkus.flyway.migrate-at-start=true` for dev. In production, run migrations as a separate step. - Don't rely on `quarkus.hibernate-orm.database.generation=update` outside dev — it's not safe for production. ## DTO projection - For list views, project to a DTO instead of returning entities: ```java public static List<UserListDto> listUsers() { return find("select new com.app.UserListDto(id, email) from User").project(UserListDto.class).list(); } ``` - Avoid loading associations you don't render. ## Reactive variant - Use `quarkus-hibernate-reactive-panache` for non-blocking DB access. - Pair with `vertx-pg-client` driver. SQL drivers are blocking and won't work. - Methods return `Uni<T>` and `Multi<T>`. Lifecycle integrates with RESTEasy Reactive endpoints. ## Don't - Don't use Panache active record for CRUD-heavy domain models with rich behavior. They get crowded fast. - Don't put query logic in resources. Push it to the entity (active record) or repository. - Don't share the same entity class between two databases. Use separate persistence units. - Don't use the blocking and reactive Panache variants in the same project.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.Jakarta REST (JAX-RS)
# CLAUDE.md — Jakarta REST (JAX-RS) ## Resources - One resource class per top-level path. `@Path("/users")` on the class. - Sub-resources for hierarchical paths. Methods can return another resource for nested routing: ```java @Path("/{userId}/orders") public OrderResource orders(@PathParam("userId") String userId) { return orderResource.forUser(userId); } ``` - Don't put 30 endpoints in one class. Split by sub-resource. ## Methods - `@GET`, `@POST`, `@PUT`, `@PATCH`, `@DELETE`. Use the verb that matches semantics. - `@Path("/{id}")` for path params. Use `@PathParam`, `@QueryParam`, `@HeaderParam`, `@FormParam` for binding. - `@Produces(MediaType.APPLICATION_JSON)` and `@Consumes(MediaType.APPLICATION_JSON)` at the class level when consistent. ## Request bodies - Pass POJOs as method parameters — JAX-RS uses the message body reader to deserialize. - Validate with `@Valid` + Bean Validation: ```java @POST public Response create(@Valid CreateUserRequest req) { ... } ``` - Don't accept `String` and parse JSON yourself. Let the runtime do it. ## Responses - Return `Response` for full control: ```java return Response.status(201).entity(user).header("Location", uri).build(); ``` - Return the entity directly when the status is always 200 and headers don't vary. Cleaner. - For "not found", return `Response.status(404).build()` or throw a `NotFoundException`. ## Exception mappers - `@Provider` class implementing `ExceptionMapper<X>`. The runtime auto-discovers it. - One mapper per error type. Translate domain exceptions to HTTP responses here, not in resources. ```java @Provider public class NotFoundMapper implements ExceptionMapper<NotFoundDomainException> { public Response toResponse(NotFoundDomainException e) { return Response.status(404).entity(Map.of("error", e.getMessage())).build(); } } ``` ## Filters & interceptors - `ContainerRequestFilter` / `ContainerResponseFilter` for cross-cutting: auth, logging, request id. - `@PreMatching` filters run before the resource is matched (useful for auth). - Use **interceptors** (`ReaderInterceptor`, `WriterInterceptor`) for body transformation. ## Content negotiation - Default to JSON. Add other media types only when a real client needs them. - Don't use `produces` to "version" APIs (`application/vnd.app.v2+json`). Use URL versioning for client clarity. ## Async - `@Suspended AsyncResponse response` for non-blocking responses: ```java @GET public void list(@Suspended AsyncResponse response) { service.fetchAsync().thenAccept(response::resume); } ``` - For reactive/CompletionStage, return it directly — JAX-RS 2.1+ resumes automatically. ## Don't - Don't put business logic in resources. Parse → call service → respond. - Don't return JPA entities. Map to DTOs. - Don't catch `Exception` in a resource method to translate. Use an `ExceptionMapper`. - Don't use `@QueryParam` with a complex object expecting magic binding. JAX-RS handles primitives, strings, and types with a `valueOf(String)` constructor.MicroProfile Fault Tolerance
# CLAUDE.md — MicroProfile Fault Tolerance ## Annotations - `@Retry` — retry the method on failure. - `@Timeout` — fail fast if the method runs too long. - `@CircuitBreaker` — open the circuit on cascading failure. - `@Bulkhead` — limit concurrent invocations. - `@Fallback` — alternative path on failure. - `@Asynchronous` — run on a separate thread pool. Apply on methods that call external systems (HTTP, DB, message broker). Don't put them on every method "just in case". ## Retry ```java @Retry(maxRetries = 3, delay = 200, jitter = 100, retryOn = IOException.class) ChargeResponse charge(ChargeRequest req) { ... } ``` - Always set `delay` and `jitter` — synchronized retries cause thundering herds. - Specify `retryOn` for transient errors only. Don't retry on `IllegalArgumentException` or domain failures. - `abortOn = NotFoundException.class` — give up immediately for non-retryable errors. ## Timeout ```java @Timeout(value = 5, unit = ChronoUnit.SECONDS) ChargeResponse charge(...) { ... } ``` - The annotation works by interrupting the thread. The method must respect interruption. - Timeout must be less than downstream's read timeout, or the client gives up before the server responds. ## Circuit breaker ```java @CircuitBreaker( requestVolumeThreshold = 10, // window of last 10 requests failureRatio = 0.5, // 50% failure → open delay = 5000 // half-open after 5s ) ChargeResponse charge(...) { ... } ``` - Three states: closed (normal), open (failing fast), half-open (testing recovery). - Use for downstreams that occasionally fail hard. Pointless for fast, reliable calls. - Combine with `@Fallback` for graceful degradation when the circuit is open. ## Bulkhead ```java @Bulkhead(value = 10) // max 10 concurrent calls ChargeResponse charge(...) { ... } ``` - Limits resource exhaustion when one slow downstream blocks all threads. - Pair with `@Asynchronous` for queue-based bulkhead with `waitingTaskQueue`. ## Fallback ```java @Retry(maxRetries = 2) @Fallback(fallbackMethod = "cachedCharge") ChargeResponse charge(ChargeRequest req) { ... } ChargeResponse cachedCharge(ChargeRequest req) { return cache.lookup(req); } ``` - The fallback method must have the same signature. - For class-based fallbacks, implement `FallbackHandler<T>`. - Don't use fallback to hide bugs. It's for graceful degradation, not error suppression. ## Stacking - Order: `@Bulkhead` → `@Timeout` → `@CircuitBreaker` → `@Retry` → `@Fallback`. - Retry is outermost: each retry counts as a fresh circuit-breaker call. - Fallback is innermost: only invoked after all other strategies fail. ## Don't - Don't retry POST requests without idempotency keys. You'll create duplicates. - Don't set `maxRetries=Integer.MAX_VALUE`. Bound it. - Don't use `@Asynchronous` for short calls. The thread-pool overhead exceeds the benefit. - Don't use one big `@CircuitBreaker` for many distinct downstreams. One per logical dependency.Jakarta CDI (Contexts & Dependency Injection)
# CLAUDE.md — Jakarta CDI ## Scopes - `@ApplicationScoped` — one instance for the whole app. Use for stateless services. - `@RequestScoped` — one per HTTP request. Use for request-tied state. - `@SessionScoped` — one per HTTP session. Avoid in stateless APIs. - `@Dependent` — same lifecycle as the consumer. Default for unannotated beans. - `@Singleton` is **not the same** as `@ApplicationScoped` — `@Singleton` skips proxying. Stick with `@ApplicationScoped` unless you measure. ## Injection - Constructor injection over field injection: ```java @ApplicationScoped public class CheckoutService { private final OrderRepository orders; private final PaymentGateway payments; @Inject CheckoutService(OrderRepository orders, PaymentGateway payments) { this.orders = orders; this.payments = payments; } } ``` - Field injection (`@Inject` on a field) is allowed but harder to test and hides dependencies. - Don't inject in `@PostConstruct` — too late; bean state is already half-initialized. ## Qualifiers - When you have multiple beans of the same type, use a **qualifier annotation**: ```java @Qualifier @Retention(RUNTIME) @Target({FIELD, PARAMETER}) public @interface Stripe {} ``` - Inject with `@Inject @Stripe PaymentGateway gateway`. - Don't use `@Named("stripe")` for non-EL contexts. Qualifiers are type-checked; names are strings. ## Producers - For third-party types you can't annotate, write a `@Produces` method: ```java @Produces @ApplicationScoped HttpClient httpClient() { return HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(5)).build(); } ``` - Producer methods can take parameters that themselves get injected. - For cleanup, write a `@Disposes` method. ## Events - CDI events decouple producers from consumers: ```java @Inject Event<UserCreated> userCreated; userCreated.fire(new UserCreated(user.id())); ``` - Observers: `void on(@Observes UserCreated event) { ... }`. - For async: `userCreated.fireAsync(new UserCreated(...))`. Observer signature: `void on(@ObservesAsync UserCreated event)`. - Don't make synchronous observers do slow work. They block the firing thread. ## Interceptors - For cross-cutting concerns (logging, metrics, transactions). Built into the platform. - Define an `@InterceptorBinding` annotation, an `@Interceptor` class, and apply the binding to methods. - For most observability, **MicroProfile** + Jakarta EE interceptors give you what you need. ## Lifecycle hooks - `@PostConstruct` — called once after injection completes. Use for initialization. - `@PreDestroy` — called before the bean is destroyed. Use for cleanup. - These hooks must not throw. ## Don't - Don't put state in `@ApplicationScoped` beans without synchronization. - Don't use `@Inject` to inject `@RequestScoped` beans into `@ApplicationScoped` ones unless you understand the proxy semantics. - Don't use `BeanManager.getBeans` to look up beans dynamically. That's the service-locator anti-pattern. - Don't put mutable static fields on CDI beans.Java Streams & Collections
# CLAUDE.md — Java Streams & Collections ## Streams - Use streams for **transformations** (`map`, `filter`, `reduce`, `collect`). Don't use them for side effects (`forEach` mutating external state). - Stream operations should be **stateless**. The order of evaluation is not guaranteed for parallel streams. - Don't use parallel streams unless you've measured. The overhead often beats the speedup for typical workloads. ## Collectors - `toList()` (Java 16+) returns an immutable list. Prefer it over `collect(Collectors.toList())`. - `toMap(keyFn, valueFn)` throws on duplicate keys — pass a merger if duplicates are possible. - `groupingBy` for buckets. Combine with downstream collectors: ```java Map<String, Long> byCategory = items.stream() .collect(groupingBy(Item::category, counting())); ``` - `partitioningBy(predicate)` is faster than `groupingBy` for binary splits. ## Collections - Prefer `List.of(...)`, `Map.of(...)`, `Set.of(...)` for small immutable collections. - `ArrayList` for sequenced mutable lists. `LinkedList` is almost always wrong (cache-hostile). - `HashMap` for unordered maps. `LinkedHashMap` if you need insertion order. `TreeMap` for sorted. - `HashSet` / `LinkedHashSet` / `TreeSet` follow the same logic. - `EnumMap` / `EnumSet` for keys that are enum values — they're array-backed and very fast. ## Iteration - Enhanced-for is the default: `for (var item : list) { ... }`. - `IntStream.range(0, n)` for indexed iteration when you actually need the index. - `forEach` with method references is fine for terminal-only consumption. - `Iterator` only when you need explicit removal during iteration. ## Immutability - `Collections.unmodifiableList(list)` wraps a mutable list — the underlying list can still change. - `List.copyOf(list)` returns a true immutable copy. Prefer it. - For records, fields are final. For records holding collections, copy them in the canonical constructor: ```java public record Order(String id, List<Item> items) { public Order { items = List.copyOf(items); } } ``` ## Optional - `Optional` as a return type. Never as a field, parameter, or collection element. - `orElse(default)` evaluates `default` always. Use `orElseGet(() -> ...)` for lazy defaults. - `map`, `filter`, `flatMap` to chain. Don't `if (opt.isPresent()) { opt.get() }` — that's a missed `ifPresent`. - `Optional.of(null)` throws. `Optional.ofNullable(value)` doesn't. ## Performance - Use `Stream.toList()` for results that are read-only. It's faster than `Collectors.toList()`. - For large streams with many filters, the order matters: `filter` first, `map` later — cheaper. - Prefer specialized streams (`IntStream`, `LongStream`) for primitives. Boxing kills throughput. ## Don't - Don't store `null` in collections. It breaks every iteration assumption. - Don't mutate a collection during iteration via the original reference. Use `Iterator.remove()` or collect-then-replace. - Don't ignore the return value of `Map.put` if you care about uniqueness — it returns the previous value. - Don't use `stream().forEach` when an enhanced-for loop says exactly the same thing.Quarkus + GraalVM Native
# CLAUDE.md — Quarkus + GraalVM Native ## When to go native - Cold-start matters: serverless (Lambda, Cloud Run cold starts), CLI tools, scale-to-zero deployments. - Memory matters: tight container sizes, edge environments. - **Don't** go native when: - Throughput is the priority (JIT eventually beats native after warm-up) - The team isn't ready for the build complexity - You depend on libraries that need extensive reflection / dynamic class loading without Quarkus extensions ## Build - `./mvnw package -Pnative` (or `./gradlew build -Dquarkus.package.type=native`). - Use **Mandrel** (Red Hat's GraalVM distribution focused on native-image). Pin the version: `quarkus.native.builder-image=quay.io/quarkus/ubi-quarkus-mandrel-builder-image:23.1-java21`. - Container build: `quarkus.native.container-build=true` — works without GraalVM installed locally. - Build is slow (1–5 minutes). Optimize CI with cached Maven repo + builder image. ## Reflection & resources - Quarkus extensions register most reflection automatically. - For your own code that uses reflection (rare), register classes: ```java @RegisterForReflection(targets = { com.app.SomeDto.class }) public class ReflectionConfig {} ``` - Resources accessed at runtime: `quarkus.native.resources.includes=path/to/file.json,**/*.properties`. - Static initializers that touch the environment (file system, network) need `--initialize-at-run-time` hints — usually handled by Quarkus. ## Runtime gotchas - Native binaries don't support agents. JFR works but with limitations. - Reflection on classes not registered fails at runtime — catch in tests, not prod. - Class-loading is closed-world. No `Class.forName(dynamicString)` patterns. ## Testing - `@QuarkusIntegrationTest` runs against the packaged artifact (jar or native). - Use `mvn verify -Pnative` to run integration tests against the native binary in CI. - Tag native-only tests `@DisabledOnIntegrationTest` if they only make sense in JVM mode. ## Image size - Mandrel base image is a few hundred MB. Final native binary ~50–100 MB. - Strip the image with `quarkus.native.compression.level=10` for binary compression. - Use `distroless/static` or `scratch` as the runtime image — copy in the binary, run it. ## Memory - Native uses much less heap than JVM. Set `-Xmx256m` and watch — many apps run on 64–128 MB. - The savings come from no JIT, no class metadata, no interpreter overhead. ## Don't - Don't switch to native to "optimize" without measuring. JVM with Quarkus already starts in 1–2s and uses ~150 MB. - Don't expect native + reflection-heavy library to "just work". Test the integration in native mode early. - Don't skip running the native test suite in CI. The cost is real but cheaper than a prod surprise. - Don't use `--no-fallback` until you're sure your app works fully native — the fallback image is a JVM bundled into the binary, not what you want.GraalVM + Quarkus Native
# CLAUDE.md — GraalVM + Quarkus Native ## Why Quarkus + native is a sweet spot - Quarkus is **designed** for native compilation. Most extensions ship native hints out of the box. - Build time is fast (relative to other Java frameworks) thanks to Quarkus' build-time framework processing. - Cold-start on Lambda / Cloud Run drops from 5+ seconds (JVM Spring) to ~50 ms (native Quarkus). ## Mandrel vs upstream GraalVM - **Mandrel** is Red Hat's downstream of GraalVM CE — focused exclusively on `native-image`. - Quarkus tests against Mandrel by default. Pin a specific Mandrel image: ``` quarkus.native.builder-image=quay.io/quarkus/ubi-quarkus-mandrel-builder-image:23.1-java21 ``` - Use upstream GraalVM if you need polyglot, Truffle, or Oracle's advanced JIT optimizations alongside. ## Build commands - JVM mode: `./mvnw package` (still produces an optimized AOT artifact in Boot/Quarkus). - Native mode: `./mvnw package -Pnative`. - Container build (no GraalVM/Mandrel locally): `./mvnw package -Pnative -Dquarkus.native.container-build=true`. ## Configuration knobs - `quarkus.native.additional-build-args` for niche flags. - `quarkus.native.resources.includes` for resources you load via `getResource`. - `quarkus.native.compression.level=10` (Quarkus 3.5+) for smaller binaries (UPX-based). - Disable monitoring extras you don't need: `--no-fallback`, `-H:-AddAllCharsets`. ## Reflection - Most Quarkus extensions register reflection automatically via the build-time SPI. - For custom code: `@RegisterForReflection(targets = MyDto.class)`. - For JSON DTOs (Jackson, JSON-B): annotation is needed if the class is loaded reflectively. Records and standard JAX-RS DTOs usually work without it. ## Image size - Typical Quarkus native image: 50–80 MB binary, ~100 MB Docker image with distroless. - Strip what you don't use: - `quarkus.native.enable-https-url-handler=false` if not needed - `quarkus.native.enable-jni=false` if no JNI deps - `quarkus.native.add-all-charsets=false` (default) — explicit charset list via `quarkus.native.include-charsets=UTF_8` ## Tests - `@QuarkusIntegrationTest` runs against the packaged native binary. Add to CI; don't push without it. - Use the same test classes for JVM and native — Quarkus picks the artifact based on the Maven profile. - Slow native build means CI matrix: native tests on main; JVM-mode on PRs. ## Common pitfalls - A library that uses `Class.forName(stringFromConfig)` at runtime — won't work in native unless every possible class is in the reachability metadata. - Static initializers that touch the network or filesystem — they run at build time by default and can leak build-host state into the binary. - JNI without explicit configuration — needs `jni-config.json` and the library binary in the image. ## Workflow - Develop in JVM dev mode (`./mvnw quarkus:dev`). Live reload is invaluable. - Run native tests on PR merges, not every push. Build is too slow for inner loop. - Profile native binaries with `--enable-monitoring=jfr` and Java Flight Recorder. ## Don't - Don't build native locally on every save. Use JVM dev mode. - Don't skip `@QuarkusIntegrationTest` for native. Bugs that hide in JVM emerge in native. - Don't pull in Spring/Quarkus mixed-stack libraries — Quarkus' native story works because every extension is on board. - Don't deploy native binaries built on Mac for Linux containers without cross-compilation. Use the container builder image.MicroProfile REST Client
# CLAUDE.md — MicroProfile REST Client ## Type-safe clients - Define an interface annotated with `@RegisterRestClient`: ```java @RegisterRestClient(configKey = "stripe-api") @Path("/v1") public interface StripeApi { @POST @Path("/charges") @Consumes(JSON) @Produces(JSON) ChargeResponse charge(ChargeRequest req); } ``` - Inject as `@RestClient StripeApi stripe`. - Same JAX-RS annotations as resources — `@Path`, `@GET`, `@POST`, `@Header`, `@QueryParam`. ## Configuration - Configure base URL by `configKey`: ``` stripe-api/mp-rest/url=https://api.stripe.com stripe-api/mp-rest/connectTimeout=2000 stripe-api/mp-rest/readTimeout=5000 ``` - Override per environment via standard Config sources. - Don't hardcode URLs in `@RegisterRestClient(baseUri = "...")`. Use `configKey` and override via config. ## Headers - Static headers via `@ClientHeaderParam`: ```java @ClientHeaderParam(name = "Authorization", value = "Bearer ${stripe.api.key}") public interface StripeApi { ... } ``` - Dynamic headers via `@HeaderParam` on a method parameter, or via a `ClientHeadersFactory` for cross-cutting (request id, tracing). ## Async - Return `CompletionStage<T>` for non-blocking calls: ```java CompletionStage<ChargeResponse> chargeAsync(ChargeRequest req); ``` - Pair with `@Asynchronous` (MicroProfile Fault Tolerance) for thread-pool isolation. - For reactive stacks (Quarkus + Mutiny), return `Uni<T>` instead. ## Error handling - 4xx/5xx responses throw `WebApplicationException` by default. Catch and translate to domain exceptions. - Custom exception mapping via `ResponseExceptionMapper`: ```java public class StripeErrorMapper implements ResponseExceptionMapper<StripeException> { public StripeException toThrowable(Response r) { return new StripeException(r.readEntity(StripeError.class)); } } ``` - Register via `@RegisterProvider(StripeErrorMapper.class)` on the client interface. ## Filtering / interceptors - `@RegisterProvider(MyClientFilter.class)` on the interface. - Use for: request id propagation, retry hooks, request/response logging. - Avoid logging full bodies in production filters — sanitize or sample. ## Resilience - Combine with **MicroProfile Fault Tolerance**: ```java @Retry(maxRetries = 3, delay = 200) @Timeout(value = 5, unit = ChronoUnit.SECONDS) @CircuitBreaker(requestVolumeThreshold = 4) ChargeResponse charge(ChargeRequest req); ``` - Timeout < downstream's read timeout. Otherwise the client gives up before the server responds. ## Testing - For unit tests, mock the interface as you would any CDI bean. - For integration tests, use **WireMock** or **MockServer** to stand up a fake HTTP target. - Don't hit real third-party APIs in tests. Sandbox/mock environments only. ## Don't - Don't share a single client interface across many unrelated services. One client per upstream. - Don't put auth tokens in `@ClientHeaderParam(value = "Bearer xxx")` literals — use config. - Don't return `Response` from REST Client methods. Type-safe deserialization is the point. - Don't create a new client instance per call. Inject — the runtime caches.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.Vert.x Web Routing
# CLAUDE.md — Vert.x Web Routing ## Setup - Add `io.vertx:vertx-web` for routing on top of Vert.x core. - Build a `Router`: ```java Router router = Router.router(vertx); router.get("/users/:id").handler(this::getUser); router.post("/users").handler(BodyHandler.create()).handler(this::createUser); vertx.createHttpServer().requestHandler(router).listen(8080); ``` - One router per HTTP server. For multi-tenancy or sub-app patterns, use sub-routers (`Router.router(vertx)`) and mount with `router.route("/api").subRouter(apiRouter)`. ## Handlers - Each handler takes a `RoutingContext`. Read params, body, headers; write response. - Always end the response: `ctx.json(dto)` or `ctx.response().end()`. Forgetting hangs the client. - Chain handlers with `.handler(...)`. Each can short-circuit by ending the response or throwing. ## Body parsing - `BodyHandler.create()` must come before any handler that reads the body. Mount it once at the top: ```java router.route().handler(BodyHandler.create()); ``` - For large bodies, set `setBodyLimit(maxBytes)`. - JSON: `ctx.body().asJsonObject()` or `ctx.body().asPojo(MyDto.class)` (with the right codec). ## Path & query params - `ctx.pathParam("id")` for path params. - `ctx.queryParam("page")` returns `List<String>` — multi-value aware. - For typed binding, use **OpenAPI Router Builder** (`OpenAPIContract.from(...)`) to generate routers from a spec with auto-validation. ## Validation - Use `vertx-web-validation` for declarative request validation: ```java ValidationHandler validation = ValidationHandlerBuilder.create(schemaParser) .pathParameter(Parameters.param("id", Schemas.stringSchema())) .body(Bodies.json(MY_SCHEMA)) .build(); router.post("/users").handler(validation).handler(this::createUser); ``` - Errors reach a final error handler — translate to JSON. ## Error handling - `router.errorHandler(500, ctx -> { ... })` for centralized error responses. - `ctx.fail(statusCode, throwable)` from inside a handler to escalate to the error handler. - Don't leak stack traces in production. Map to a clean error envelope. ## CORS - `CorsHandler.create("https://app.example.com").allowedMethod(...)`. - Mount before route handlers. - Don't use `*` origin with credentials. The browser will refuse. ## Auth - `AuthenticationHandler` (e.g., `JWTAuthHandler`) protects routes: ```java router.route("/admin/*").handler(JWTAuthHandler.create(jwtAuth)); ``` - Authorization (role checks) via `AuthorizationProvider` + `AuthorizationHandler`. ## Static files - `StaticHandler.create()` serves files from the classpath or filesystem. - For SPAs: route `/api/*` to handlers, fallback to `StaticHandler.create("dist").setIndexPage("index.html")`. ## Don't - Don't put the body handler after the route handler that reads the body — body will be empty. - Don't write blocking code in route handlers. Use `Future`, Mutiny `Uni`, or `executeBlocking`. - Don't write to the response after calling `end()`. Vert.x will throw or silently drop. - Don't use one giant `MainVerticle` with hundreds of routes. Split by feature into multiple verticles or routers.Quarkus Testing
# CLAUDE.md — Quarkus Testing ## Test types - `@QuarkusTest` — full Quarkus app loaded. Use for integration-style tests of endpoints, services, and DB. - `@QuarkusIntegrationTest` — runs against the packaged artifact (JVM jar or native binary). Use for smoke tests of the deployed bundle. - Plain JUnit 5 — for pure logic that doesn't need Quarkus. ## Dev services - Quarkus auto-starts containers (Postgres, Redis, Kafka, etc.) for tests when the right extensions are on the classpath. - No `@Container`, no Testcontainers boilerplate — just add the extension and write the test. - Override with explicit config when you need a specific image or version. ## REST testing - Use **REST-assured** for endpoint tests: ```java given().contentType(JSON).body(req) .when().post("/users") .then().statusCode(201).body("email", is("a@b")); ``` - `@TestHTTPEndpoint(UsersResource.class)` to skip repeating the path prefix. - For reactive endpoints, REST-assured works the same — the reactive nature is server-side. ## Mocking - `@InjectMock` to replace a CDI bean with a Mockito mock for one test class: ```java @QuarkusTest class UserServiceTest { @Inject UserService service; @InjectMock EmailGateway email; } ``` - For full bean replacement (alternative implementation), use `@Alternative` + `@Priority`. - Don't `@InjectMock` value types — mock services and gateways at the bean boundary. ## Test profiles - `@TestProfile(MyProfile.class)` to override config per test class: ```java static class MyProfile implements QuarkusTestProfile { @Override public Map<String, String> getConfigOverrides() { return Map.of("app.email.smtp-host", "test.example.com"); } } ``` - A new app is started per profile. Group tests with the same profile to keep startup costs amortized. ## Database - Each test gets a transaction that rolls back via `@TestTransaction`: ```java @Test @TestTransaction void persists_user() { repository.persist(new User(...)); } ``` - For tests that need committed state (multi-thread, async), don't use `@TestTransaction` — clean up explicitly. ## Speed - Quarkus reuses the application context across `@QuarkusTest` classes when configs match. Don't fragment with unique profiles unless necessary. - Run unit tests first, integration tests later in CI. Fail fast. - Continuous testing in dev mode (`r` in the dev console) re-runs only affected tests. ## Don't - Don't use Spring's `@SpringBootTest` mental model in Quarkus. Quarkus tests are usually faster and need less setup. - Don't `@InjectMock` infrastructure beans Quarkus controls (e.g., `EntityManager`). Use `@TestTransaction` and a real DB. - Don't run native tests on every save. Save them for `mvn verify -Pnative` in CI. - Don't share state across tests via static fields. Tests run in undefined order.MicroProfile JWT Authentication
# CLAUDE.md — MicroProfile JWT Authentication ## Setup - Add the MP-JWT spec to your runtime (Quarkus: `quarkus-smallrye-jwt`; Open Liberty: bundled). - On the application class: ```java @LoginConfig(authMethod = "MP-JWT", realmName = "myapp") @ApplicationPath("/") public class App extends Application {} ``` - Configure the public key for verification: ``` mp.jwt.verify.publickey.location=https://auth.example.com/.well-known/jwks.json mp.jwt.verify.issuer=https://auth.example.com mp.jwt.verify.audiences=myapp ``` ## Securing endpoints - Use Jakarta Security annotations: ```java @GET @RolesAllowed("user") public List<Order> myOrders() { ... } @POST @RolesAllowed("admin") public void deleteUser(@PathParam("id") String id) { ... } ``` - `@PermitAll` on public endpoints — be explicit so future developers don't break it accidentally. - `@DenyAll` on placeholder methods you haven't authorized yet. ## Reading claims - Inject the full token: `@Inject JsonWebToken jwt`. - Inject specific claims: ```java @Inject @Claim("email") String email; @Inject @Claim("groups") Set<String> groups; @Inject @Claim("sub") String userId; ``` - Standard claims: `iss`, `sub`, `aud`, `exp`, `iat`, `groups`. Custom claims work the same way. ## Roles - The `groups` claim is mapped to roles by default. Configure the claim name if your provider uses a different name: ``` mp.jwt.claim.roles.path=resource_access.myapp.roles ``` - Don't trust the token blindly. Validate signature, issuer, audience, expiry. The library does this; just configure correctly. ## Token issuance - MP-JWT specifies the **verification** side — issuance is up to your auth provider (Keycloak, Auth0, Okta, AWS Cognito). - For dev, use **smallrye-jwt-build** to issue tokens locally. Never use it in production. - Pin the JWKS URL to a fixed location — don't accept arbitrary issuers. ## Token transport - Standard `Authorization: Bearer <token>` header. - For browser apps, store in HttpOnly Secure cookies. The MP-JWT extension can read from cookies if configured: ``` mp.jwt.token.header=Cookie mp.jwt.token.cookie=jwt ``` - Never store JWTs in `localStorage`. ## Refresh - MP-JWT doesn't define refresh. Implement at the auth provider — clients send refresh tokens to the auth server, get fresh access tokens. - Keep access tokens short-lived (15 min). Refresh tokens longer-lived but revocable server-side. ## Don't - Don't put sensitive data (PII, plan info) in JWT claims. The token is base64url-decodable by anyone. - Don't accept tokens without verifying the signature. - Don't use `@RolesAllowed` and ignore object-level authorization. JWT proves identity; ownership checks happen in the service. - Don't issue long-lived (months) access tokens to "avoid" refresh complexity.GraalVM JIT vs Native — When to Use Which
# CLAUDE.md — GraalVM JIT vs Native: When to Use Which ## The trade-off in one sentence Native gives fast startup and low memory. JIT (HotSpot or GraalVM JIT) gives higher peak throughput. Pick based on workload shape, not preference. ## Use native when - **Cold start matters**: AWS Lambda, Cloud Run, scale-to-zero, batch jobs that boot per invocation. - **Memory is constrained**: edge runtimes, sidecars, dense container packing. - **Single-binary distribution**: CLIs, tools shipped to users without a JRE. - **Short-lived processes**: 60-second batch jobs that never reach JIT warmup don't benefit from JIT. ## Use JIT (HotSpot or GraalVM JIT) when - **Peak throughput is the SLO**: long-running services with steady traffic. - **The codebase relies heavily on reflection / dynamic class loading**: ORMs with proxy generation, frameworks doing runtime weaving. - **Memory is not the bottleneck**: pods sized 1 GB+ where the JIT compiler's overhead is noise. - **The team has more JVM ops experience than native**: tooling, profilers, debugging — JVM is the better-known path. ## Hybrid: Tiered approach - Some services run native in **dev/staging** for fast feedback, JVM in **production** for throughput. - Or: native for the **gateway / edge** (cold-start sensitive), JVM for the **monolith** behind it. - Don't optimize prematurely. Most services are fine on JVM with `-XX:+AlwaysActAsServerClassMachine` and a sane GC. ## Numbers (rough, your mileage will vary) | Metric | JVM (HotSpot) | GraalVM JIT | Native | |--------|---------------|-------------|--------| | Cold start | 1–5s | 1–5s | 50–200 ms | | Memory (small app) | 200–400 MB | 200–400 MB | 50–100 MB | | Peak throughput | 100% (baseline) | 105–115% | 70–85% | | Build time | 30s | 30s | 2–5 min | ## Migration path If you might want native later: 1. Start with Spring Boot 3 / Quarkus / Micronaut. They generate AOT-friendly code by default. 2. Avoid runtime reflection in your code. Use generated code (records, sealed types) where possible. 3. Run native compilation in CI from day one — even if you don't deploy native, you'll catch incompatibilities early. ## Cost considerations - Native is cheaper per invocation on serverless (faster cold start = less wall time). - Native is **more expensive to build** in CI (longer pipelines). - For 24/7 services, the JIT throughput advantage usually wins on cost. ## Don't - Don't go native because "it's faster". Cold start ≠ peak throughput. - Don't go native to save 100 MB if your container is 1 GB. Premature optimization. - Don't build native in every PR. Build JVM with AOT processing for fast feedback; build native nightly or on release branches. - Don't assume third-party libraries work in native. Test integrations early.Jakarta Bean Validation
# CLAUDE.md — Jakarta Bean Validation ## Constraint annotations - `@NotNull` — field must be present. - `@NotBlank` — string must be non-null and contain at least one non-whitespace character. - `@NotEmpty` — collection or string must be non-null and have ≥1 element/character. - `@Size(min, max)` — bounds on collections, strings, arrays. - `@Min`, `@Max`, `@DecimalMin`, `@DecimalMax` — numeric bounds. - `@Pattern(regexp)` — regex match. Anchor with `^` and `$` if you mean the full string. - `@Email`, `@URL` (Hibernate Validator) — common formats. Apply on fields of DTOs: ```java record CreateUser( @NotBlank @Email String email, @NotBlank @Size(min = 8, max = 100) String password, @NotNull @Min(13) Integer age ) {} ``` ## Triggering validation - In JAX-RS / Spring MVC, add `@Valid` to the request body parameter. - For method parameters / return types, add `@Validated` to the class (Spring) or use `@ExecutableType` (Jakarta EE) with a CDI interceptor. - Validate manually: ```java Set<ConstraintViolation<T>> violations = validator.validate(obj); if (!violations.isEmpty()) throw new ConstraintViolationException(violations); ``` ## Groups - For different validation rules at different stages (create vs update), use validation groups: ```java interface OnCreate {} interface OnUpdate {} record User( @Null(groups = OnCreate.class) @NotNull(groups = OnUpdate.class) String id, @NotBlank @Email String email ) {} ``` - Apply with `@Validated(OnCreate.class)` or `validator.validate(obj, OnCreate.class)`. - Don't over-design groups. Most apps need 0–2. ## Cross-field validation - Custom constraint annotation + a `ConstraintValidator<MyAnnotation, MyClass>`. - Apply at class level (`@Target(TYPE)`) when the rule depends on multiple fields: ```java @PasswordsMatch record SignupRequest(@NotBlank String password, @NotBlank String confirmPassword) {} ``` ## Method-level validation - Annotate method parameters and return: ```java @NotNull User findById(@NotBlank String id); ``` - Requires CDI interceptor or Spring's method validation enabled. - Throws `ConstraintViolationException` on violation. ## Error responses - In JAX-RS, write a `ConstraintViolationExceptionMapper` to translate to a 400 with field-level errors. - In Spring, `@RestControllerAdvice` with `@ExceptionHandler(MethodArgumentNotValidException.class)` is the equivalent. - Return a structured error body — clients should know which field failed. ## Localization - `@NotNull(message = "{user.name.required}")` references a message key. - Provide `ValidationMessages.properties` per locale. - Don't hard-code English messages in annotations for products with i18n. ## Don't - Don't validate inside services if the same rule applies at the request boundary. Catch invalid input early. - Don't use Bean Validation for business rules ("user can't have more than 3 active subscriptions"). That's domain logic, not input validation. - Don't write `@NotNull String` next to `@NotBlank String`. `@NotBlank` already implies non-null and non-empty. - Don't put validation annotations on getters in addition to fields — they're applied twice.Vert.x with Mutiny (Reactive)
# CLAUDE.md — Vert.x with Mutiny ## Why Mutiny - **Mutiny** (`io.smallrye.mutiny:mutiny`) is a reactive library tightly integrated with Vert.x and Quarkus. - Cleaner than callback Vert.x. More fluent than Vert.x `Future`. Reactive Streams compatible (interop with Reactor, RxJava). - For new Vert.x code, prefer Mutiny or `Future`. Avoid the original callback API. ## Uni and Multi - `Uni<T>` — 0..1 element with possible failure. Most use cases. - `Multi<T>` — 0..N elements (a stream). For event streams, paginated reads, message subscriptions. ## Mutiny-flavored Vert.x - Use `io.vertx.mutiny.*` packages — every Vert.x API has a Mutiny version returning `Uni`/`Multi`: ```java io.vertx.mutiny.core.Vertx vertx = io.vertx.mutiny.core.Vertx.vertx(); io.vertx.mutiny.core.http.HttpClient client = vertx.createHttpClient(); Uni<String> body = client.request(GET, "https://example.com").flatMap(req -> req.send().flatMap(HttpClientResponse::body)).map(Buffer::toString); ``` - Don't import the non-Mutiny `io.vertx.core.*` types in Mutiny code. The signatures don't compose. ## Common operators - `.map(fn)` — synchronous transform. - `.onItem().transformToUni(fn)` — async transform returning a `Uni`. Equivalent to `flatMap`. - `.onFailure().recoverWithItem(default)` — fallback on error. - `.onFailure().retry().atMost(3)` — retry the upstream up to N times. - `.ifNoItem().after(Duration.ofSeconds(5)).fail()` — timeout. ## Combining Unis ```java Uni<Result> combined = Uni.combine().all().unis(uniA, uniB) .with(Result::new); ``` - `combine().all()` for "wait for all". `combine().any()` for "first that completes". - For collections: `Uni.combine().all().unis(listOfUnis).combinedWith(...)`. ## Subscribers - Mutiny is **lazy**. The pipeline doesn't run until subscribed. - In Vert.x Web with Mutiny: return `Uni<T>` from a handler — the framework subscribes for you. - Manual: `uni.subscribe().with(item -> ..., failure -> ...)`. ## Backpressure (Multi) - `Multi.createFrom().items(...)` for known finite streams. - `Multi.createFrom().publisher(reactivePublisher)` for streams from another reactive lib. - For backpressure: `.onOverflow().buffer(size)`, `.onOverflow().drop()`, etc. Choose based on what loss is acceptable. ## Error handling - `onFailure()` selectors: `.onFailure(IOException.class).recoverWithUni(fallback)`. - Always handle failures explicitly. Unhandled failures in `Uni` log a warning and disappear. - Don't catch in the underlying lambda — let Mutiny carry the failure. ## Concurrency - Mutiny respects the Vert.x event loop. Operators don't switch threads unless you ask. - `.runSubscriptionOn(executor)` to subscribe on a specific executor. - For blocking work inside a Uni chain: `.onItem().transformToUni(item -> Uni.createFrom().item(() -> blockingCall(item)).runSubscriptionOn(workerPool))`. ## Don't - Don't mix Mutiny with raw Vert.x callbacks in the same chain. Convert at the boundary. - Don't subscribe inside a handler — return the `Uni`. - Don't `await().atMost(...)` on Unis in production code. That's blocking, defeating the purpose. - Don't log inside `.map` lambdas — switch to `.invoke(item -> log.info(...))` for side-effect-only operators.MicroProfile OpenAPI
# CLAUDE.md — MicroProfile OpenAPI ## Setup - Add the MP-OpenAPI implementation: SmallRye OpenAPI in Quarkus, Open Liberty's bundled implementation, etc. - Spec is exposed at `/openapi`. Many runtimes also expose Swagger UI at `/openapi/ui` or similar. - Most documentation generates from JAX-RS + Bean Validation. Annotate only when the inferred docs are wrong or insufficient. ## Application metadata ```java @OpenAPIDefinition( info = @Info( title = "My API", version = "1.0.0", contact = @Contact(name = "Team", email = "team@example.com"), license = @License(name = "Apache 2.0", url = "https://www.apache.org/licenses/LICENSE-2.0") ), servers = { @Server(url = "https://api.example.com", description = "Production"), @Server(url = "https://staging.example.com", description = "Staging") } ) @ApplicationPath("/") public class App extends Application {} ``` ## Operation annotations - `@Operation(summary = "...", description = "...")` on resource methods. - `@APIResponse(responseCode = "200", description = "...", content = @Content(...))` for documented responses. - `@APIResponses` to group multiple. Always document at least the success status and the most common error. - `@Parameter(description = "...", example = "abc-123")` on path/query params for clarity. ## Schemas - DTOs are auto-discovered from JAX-RS method signatures. - Customize with `@Schema` on classes or fields: ```java @Schema(description = "Order item with quantity and product reference") record OrderItem( @Schema(required = true, example = "prod_123") String productId, @Schema(minimum = "1", maximum = "100", example = "1") int quantity ) {} ``` - Bean Validation annotations (`@NotBlank`, `@Size`) flow through automatically — don't duplicate as `@Schema(required = true)` everywhere. ## Tags - Group endpoints with `@Tag(name = "Users", description = "User management")`. - One tag per resource class — keeps the rendered UI navigable. - Order tags via `@OpenAPIDefinition(tags = { @Tag(name = "..."), ... })` for consistent display. ## Security schemes ```java @SecurityScheme( securitySchemeName = "bearer", type = SecuritySchemeType.HTTP, scheme = "bearer", bearerFormat = "JWT" ) @OpenAPIDefinition(security = @SecurityRequirement(name = "bearer")) ``` - Apply per operation with `@SecurityRequirement` if the global default doesn't fit. - Public endpoints: `@SecurityRequirements` (empty) to override. ## Examples - Concrete examples make the spec actually usable for client developers: ```java @APIResponse(responseCode = "200", content = @Content( schema = @Schema(implementation = User.class), examples = @ExampleObject(name = "user", value = "{\"id\":\"1\",\"email\":\"a@b\"}") )) ``` ## Workflow - Generate the spec at build time (`mvn package` produces `META-INF/openapi.yaml`). - Diff the spec in CI to catch unintended API changes. - Publish to a developer portal (Swagger Hub, Stoplight, your own). ## Don't - Don't write the OpenAPI spec by hand alongside annotations. Pick one source of truth. - Don't expose `/openapi` to the public internet without auth if your spec leaks internal details. - Don't use `@Schema(implementation = Object.class)` to escape strict typing — fix the type. - Don't ship a spec that doesn't match the implementation. Validate in tests.Vert.x Event Bus
# CLAUDE.md — Vert.x Event Bus ## What it is - An in-process (and optionally cluster-wide) message bus baked into Vert.x. - Decouples senders from receivers — verticles communicate by **address** (a string), not by Java references. - Works in three modes: - **Point-to-point** — one consumer receives. - **Pub/Sub** — all consumers receive. - **Request/Reply** — sender gets a response. ## Sending messages ```java EventBus eb = vertx.eventBus(); // Point-to-point eb.send("orders.create", new JsonObject().put("userId", "123")); // Pub/Sub eb.publish("user.created", new JsonObject().put("id", "123")); // Request/Reply eb.request("orders.find", "order-123").onSuccess(reply -> { log.info("got order: {}", reply.body()); }); ``` - Addresses are simple strings. Use a hierarchical convention: `orders.create`, `orders.cancel`, `users.update`. - Don't put PII or large payloads in event-bus messages — they're broadcast across the cluster. ## Consuming ```java eb.consumer("orders.create", message -> { JsonObject body = (JsonObject) message.body(); OrderId id = createOrder(body); message.reply(new JsonObject().put("id", id.toString())); }); ``` - Always reply on a request — if you don't, the sender gets a timeout. - Use `message.fail(code, msg)` to signal an error to the sender. - Unsubscribe via the `MessageConsumer` returned by `consumer(...)` when shutting down. ## Codecs - Default codecs: `String`, `JsonObject`, `JsonArray`, `Buffer`, primitive boxed types. - For custom POJOs, register a codec: ```java eb.registerDefaultCodec(MyDto.class, new MyDtoCodec()); ``` - For cluster mode, all nodes must register the same codecs. Otherwise messages won't deserialize. ## Clustering - Vert.x can cluster across nodes via Hazelcast, Infinispan, Ignite, or ZooKeeper. - The same `EventBus` API works locally and in cluster mode. - For cluster mode: `Vertx.clusteredVertx(options, ar -> ...)`. - Network split-brain handling: configure the underlying clustering layer carefully. Defaults are not production-ready. ## Patterns - **Service-locator alternative**: register handlers as services on the bus instead of injecting them. - **Saga orchestration**: each step publishes an event; coordinator listens and dispatches the next step. - **Cache invalidation**: publish on `cache.invalidate.{key}`; every node consumes and updates its local cache. ## Performance - In-process: ~1–10 µs per message. - Clustered: depends on the cluster manager. Hazelcast adds ~1 ms per hop. - For high throughput, batch messages or use direct verticle calls instead of the event bus. ## Service proxies - Vert.x can generate type-safe proxies for an interface — calls become event-bus messages under the hood: ```java @ProxyGen public interface UserService { void findById(String id, Handler<AsyncResult<JsonObject>> handler); } ``` - Generated code (annotation processor) creates the proxy and the codec. - Useful for cross-language clients in clustered setups. ## Don't - Don't use the event bus for high-throughput hot paths between two verticles in the same JVM. Direct method calls are faster. - Don't broadcast sensitive data on a clustered bus without encryption (`SSLContext` on the cluster manager). - Don't forget to reply on `request` consumers. Senders will time out. - Don't use mutable shared objects as message bodies. Use immutable JSON or POJOs with codecs.Vert.x + Reactive Postgres
# CLAUDE.md — Vert.x + Reactive Postgres ## Stack - **vertx-pg-client** — fully reactive, non-blocking Postgres client. No JDBC. - For Mutiny-flavored API: `io.vertx.mutiny.pgclient.PgPool`. - Pair with **Flyway** or **Liquibase** for schema migrations (those are blocking; run as a separate startup step). ## Pool setup ```java PgConnectOptions connect = new PgConnectOptions() .setHost("localhost").setPort(5432) .setDatabase("app").setUser("app").setPassword(secret); PoolOptions pool = new PoolOptions().setMaxSize(10); Pool client = PgPool.pool(vertx, connect, pool); ``` - Pool size: start at 10. Tune based on `EXPLAIN`-driven query duration and concurrent users. - Reuse the `Pool` for the app's lifetime. Don't create per-request. ## Queries ```java client.query("SELECT id, email FROM users").execute() .onSuccess(rows -> rows.forEach(row -> log.info(row.getString("email")))) .onFailure(Throwable::printStackTrace); ``` - `query(...)` for simple queries. `preparedQuery(...)` for parameterized — use it always except for static SQL. - Parameterized: ```java client.preparedQuery("SELECT id FROM users WHERE email = $1") .execute(Tuple.of(email)) .onSuccess(rs -> ...); ``` - Postgres uses `$1`, `$2`, ... — not `?`. ## Mapping rows - For typed access, use the row mapper: ```java RowMapper<User> mapper = row -> new User(row.getUUID("id"), row.getString("email")); ``` - Or define a function and `.map(mapper)` over the results. - Don't return raw `Row` to callers. Wrap into a domain type. ## Transactions ```java client.withTransaction(conn -> { return conn.query("INSERT INTO orders ...").execute() .compose(__ -> conn.query("UPDATE inventory ...").execute()); }).onComplete(ar -> ...); ``` - `withTransaction` commits on success and rolls back on failure automatically. - Use it for any multi-statement write. - Read-only transactions: skip — postgres handles them efficiently without explicit BEGIN. ## Prepared statements - The pool caches prepared statements per connection. Reuse query strings for free benefit. - Avoid string-concatenating SQL — that defeats the cache and exposes you to injection. - For dynamic IN clauses, use `ARRAY[$1::uuid[]]` patterns rather than building lists. ## Migrations - Use Flyway for migrations. It's JDBC-based, runs at startup, blocking — that's fine because it's a one-time bootstrap step. - After migrations succeed, the app boots Vert.x and uses the reactive client. - Don't `executeBlocking(flywayMigrate)` from inside a verticle. ## Bulk operations - For large inserts: `executeBatch(batchedTuples)` — single round-trip. - For COPY: use the `pgPool.connection()` API to access raw COPY support if needed. ## Connection limits - Postgres default is 100 connections. With pgbouncer, can be much higher. - Set `setIdleTimeout` to release idle connections eventually. - Don't size the pool larger than `(server max conns / replicas)` minus overhead. ## Don't - Don't use JDBC drivers in Vert.x apps. They block the event loop. - Don't share a connection across coroutines/handlers. Use the pool — it manages connection lifecycle. - Don't put migrations in the same `Pool` lifecycle as queries — JDBC vs reactive are different APIs. - Don't ignore the `onFailure` callback. Postgres errors silently disappearing is a common bug.GraalVM Polyglot Programming
# CLAUDE.md — GraalVM Polyglot Programming ## What polyglot is - GraalVM can run JavaScript, Python, Ruby, R, and LLVM-based languages from inside a Java program. - Languages share a single VM, GC, and compiler. Calls between languages are zero-copy where possible. - Use cases: embedding a scripting language, running user-supplied snippets, mixing ML libraries with a Java service. ## Setup - Add the polyglot SDK and the language you want: ```xml <dependency> <groupId>org.graalvm.polyglot</groupId> <artifactId>polyglot</artifactId> <version>23.1.0</version> </dependency> <dependency> <groupId>org.graalvm.polyglot</groupId> <artifactId>js</artifactId> <version>23.1.0</version> <type>pom</type> </dependency> ``` - For Python, use `python-community`. Each language is a separate dependency. ## Basic API ```java try (Context ctx = Context.newBuilder("js") .allowAllAccess(true) .build()) { Value result = ctx.eval("js", "1 + 2 * 3"); System.out.println(result.asInt()); // 7 } ``` - `Context` is the runtime. Reuse it for many evaluations — creation is expensive. - `Value` is the polyglot representation of any JS/Python/Ruby value. Convert with `asInt`, `asString`, `as(T.class)`. - `Source.create("js", code)` lets you name the source for stack traces. ## Calling Java from a guest language ```java ctx.getBindings("js").putMember("repo", userRepository); ctx.eval("js", "repo.findById('123').getEmail()"); ``` - `allowHostAccess(HostAccess.ALL)` to expose Java methods. Use `HostAccess.EXPLICIT` and `@HostAccess.Export` to allow only annotated methods — safer for user-supplied code. ## Calling guest from Java ```java Value func = ctx.eval("js", "(x) => x * 2"); int result = func.execute(21).asInt(); ``` - Treat `Value` as a function with `.execute(...)`. - For complex types, define a Java interface and `Value.as(MyInterface.class)` to bridge. ## Sandboxing - For executing **untrusted** code (user scripts, plugins): ```java Context ctx = Context.newBuilder("js") .allowHostAccess(HostAccess.NONE) .allowIO(false) .allowCreateThread(false) .resourceLimits(ResourceLimits.newBuilder().statementLimit(10_000).build()) .build(); ``` - Set CPU and statement limits — otherwise an infinite loop hangs the host. - Don't allow `allowAllAccess(true)` for untrusted input. ## Performance - First execution warms up the compiler. Reuse `Context` and pre-parsed `Source` objects. - For hot loops, write the kernel in Java and call it from the guest. - Polyglot has overhead — don't use it for things you could do in a single language. ## Native image - Polyglot **does** work with native image, but each language adds significantly to image size. - Build with `--language:js` (and similar) flags. - Test in native mode early — some language features may not be supported in native. ## Don't - Don't reach for polyglot to "use a Python library" without measuring. JNI to a native lib or shelling out is sometimes simpler. - Don't eval user input without sandboxing. JS can do everything Java can if you give it `HostAccess.ALL`. - Don't create a new `Context` per request. The boot cost is real. - Don't ship polyglot to production without resource limits — one runaway script will hang the JVM.
Have a CLAUDE.md template that works for you?
Send it in — we’ll credit you and publish it under the right tags.