Jakarta EE
Jakarta EE rules and best practices for Claude Code.
16.9k
- Jakarta EE
- Java
- MicroProfile
- Quarkus
- Spring
- Hibernate
- Jpa
13 TEMPLATES
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.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.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.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.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.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.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.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.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.
Have a CLAUDE.md template that works for you?
Send it in — we’ll credit you and publish it under the right tags.