Jakarta CDI (Contexts & Dependency Injection)
CDI scopes, qualifiers, producers, events, and the lifecycle to actually understand.
# 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.
Other Java templates
Modern Java Rules
Java 21+ defaults: records, sealed types, pattern matching, virtual threads, var.
Java Testing with JUnit 5
JUnit 5 + AssertJ + Mockito + Testcontainers — opinionated test conventions.
Java Virtual Threads & Concurrency
Project Loom virtual threads, structured concurrency, and modern concurrent patterns.
Java Build (Maven & Gradle)
Build conventions, dependency management, multi-module projects, and CI patterns.
Java Streams & Collections
Streams API, immutable collections, Collectors, and idiomatic data manipulation.