Jakarta CDI Interview Questions and Answers
Master Jakarta CDI with production-ready interview questions covering dependency injection, bean discovery, scopes, qualifiers, producers, alternatives, interceptors, decorators, events, lifecycle callbacks, and senior-level best practices.
Introduction
Jakarta CDI, or Contexts and Dependency Injection, provides the standard dependency-injection and contextual lifecycle model for Jakarta EE applications.
CDI allows the container to:
- Discover application beans
- Create managed objects
- Resolve dependencies
- Control object lifecycles
- Apply scopes
- Fire and observe events
- Apply interceptors
- Apply decorators
- Select implementations using qualifiers
- Produce objects through factory methods
Without CDI, application code must manually construct and connect objects.
With CDI, classes declare what they need, and the container provides the correct dependency.
CDI is commonly used with:
- Jakarta REST
- Jakarta Persistence
- Jakarta Transactions
- Jakarta Security
- Jakarta Messaging
- Jakarta Bean Validation
- MicroProfile
A strong understanding of CDI is essential because dependency resolution, scope selection, proxy behavior, bean discovery, and interception directly affect application correctness and thread safety.
This guide covers the 15 most frequently asked Jakarta CDI interview questions with code examples, Mermaid diagrams, production scenarios, common mistakes, and senior-level recommendations.
Q1. What is CDI?
Answer
CDI stands for Contexts and Dependency Injection.
It is the Jakarta EE specification responsible for:
- Dependency injection
- Bean lifecycle management
- Contextual scopes
- Qualifier-based resolution
- Events
- Interceptors
- Decorators
- Producer methods
- Alternatives
Basic Example
@ApplicationScoped
public class CustomerRepository {
public List<Customer> findAll() {
return List.of();
}
}
@ApplicationScoped
public class CustomerService {
private final CustomerRepository repository;
@Inject
public CustomerService(
CustomerRepository repository
) {
this.repository = repository;
}
public List<Customer> findCustomers() {
return repository.findAll();
}
}
The application does not create CustomerRepository manually.
The CDI container creates and injects it.
CDI Lifecycle
flowchart TD
A[Application Starts] --> B[CDI Discovers Beans]
B --> C[Builds Bean Metadata]
C --> D[Resolves Injection Points]
D --> E[Creates Contextual Instances]
E --> F[Injects Dependencies]
F --> G[Applies Interceptors and Decorators]
G --> H[Managed Beans Ready]
Why Interviewers Ask This
CDI is the foundation of dependency management in Jakarta EE.
Production Example
A claims-processing resource injects a service, which injects a repository, event publisher, and validator.
Common Mistake
Describing CDI as only field injection.
Senior Follow-up
CDI is a complete contextual component model, not merely an object factory.
Q2. Why is CDI used?
Answer
CDI is used to reduce coupling and move object creation, lifecycle management, and dependency selection into the container.
Without CDI
CustomerRepository repository =
new CustomerRepository();
AuditService auditService =
new AuditService();
CustomerService service =
new CustomerService(
repository,
auditService
);
The caller must know:
- Which implementation to create
- How long the object should live
- Which dependencies it needs
- Whether interceptors should apply
- Whether the object is thread-safe
With CDI
@Inject
private CustomerService customerService;
The container resolves the complete dependency graph.
Benefits
- Loose coupling
- Better testability
- Centralized lifecycle management
- Scope support
- Easier implementation switching
- Interceptor support
- Event-based decoupling
- Reusable decorators
- Cleaner enterprise architecture
Dependency Graph
flowchart LR
A[CustomerResource] --> B[CustomerService]
B --> C[CustomerRepository]
B --> D[AuditService]
B --> E[CustomerEventPublisher]
C --> F[EntityManager]
CDI creates and connects the graph.
Common Mistake
Using CDI only to avoid new.
Senior Follow-up
The real value of CDI is contextual lifecycle, extensibility, interception, and decoupled component composition.
Q3. What is dependency injection?
Answer
Dependency injection is a design technique in which an object receives its dependencies from an external container rather than creating them internally.
Constructor Injection
@ApplicationScoped
public class OrderService {
private final OrderRepository repository;
private final PaymentGateway paymentGateway;
@Inject
public OrderService(
OrderRepository repository,
PaymentGateway paymentGateway
) {
this.repository = repository;
this.paymentGateway =
paymentGateway;
}
}
Field Injection
@Inject
private OrderRepository repository;
Method Injection
@Inject
public void configure(
OrderRepository repository
) {
this.repository = repository;
}
Injection Flow
sequenceDiagram
participant Container
participant Repository
participant Gateway
participant Service
Container->>Repository: Create bean
Container->>Gateway: Create bean
Container->>Service: Inspect constructor
Container->>Service: Inject Repository and Gateway
Container-->>Service: Managed instance ready
Common Mistake
Calling dependency injection a Jakarta EE-specific concept.
It is a general software design principle implemented by CDI.
Senior Follow-up
Required dependencies should usually be represented through constructor injection.
Q4. Constructor injection vs field injection?
Answer
Constructor injection provides dependencies through the constructor.
Field injection injects directly into fields.
Constructor Injection
@ApplicationScoped
public class NotificationService {
private final EmailGateway emailGateway;
@Inject
public NotificationService(
EmailGateway emailGateway
) {
this.emailGateway =
Objects.requireNonNull(
emailGateway
);
}
}
Field Injection
@ApplicationScoped
public class NotificationService {
@Inject
private EmailGateway emailGateway;
}
Comparison
| Constructor Injection | Field Injection |
|---|---|
| Dependencies are explicit | Dependencies are hidden in fields |
| Easier unit testing | Often requires reflection or container |
| Supports immutable fields | Fields cannot normally be final |
| Prevents incomplete construction | Bean exists before field injection completes |
| Better for required dependencies | Shorter syntax |
| Reveals excessive dependencies | Can hide large dependency lists |
Decision Diagram
flowchart TD
A[Dependency Required for Valid Object?] -->|Yes| B[Constructor Injection]
A -->|No or Optional| C[Consider Method or Instance Lookup]
B --> D[Immutable Explicit Dependency]
Common Mistake
Choosing field injection only because it is shorter.
Senior Follow-up
Constructor injection improves design visibility. A constructor with too many parameters often reveals that a class has too many responsibilities.
Q5. What are CDI scopes?
Answer
A CDI scope defines how long a contextual bean instance lives and where the same instance is reused.
Common Scopes
| Scope | Lifetime |
|---|---|
@Dependent |
Bound to injection target |
@RequestScoped |
One HTTP request |
@SessionScoped |
One HTTP session |
@ApplicationScoped |
Entire application |
@ConversationScoped |
One explicit conversation |
Scope Diagram
flowchart TD
A[CDI Bean] --> B{Scope}
B --> C[Dependent]
B --> D[Request]
B --> E[Session]
B --> F[Application]
B --> G[Conversation]
C --> H[Created for owning injection point]
D --> I[Shared during one request]
E --> J[Shared during one user session]
F --> K[Shared across application]
G --> L[Shared during explicit conversation]
Scope Selection Questions
- Is the bean stateful?
- Is the state request-specific?
- Is the bean shared across users?
- Is it thread-safe?
- How expensive is creation?
- Does it need serialization?
- Does it hold mutable data?
Common Mistake
Using @ApplicationScoped as the default for every bean without considering thread safety.
Senior Follow-up
Scope is both a lifecycle and concurrency decision.
Q6. What is @ApplicationScoped?
Answer
@ApplicationScoped creates one contextual bean instance for the lifetime of the application.
@ApplicationScoped
public class CurrencyConfiguration {
private final Map<String, Integer>
decimalPlaces =
new ConcurrentHashMap<>();
public int decimalPlaces(
String currency
) {
return decimalPlaces.getOrDefault(
currency,
2
);
}
}
Lifecycle
flowchart LR
A[Application Starts] --> B[ApplicationScoped Bean Created]
B --> C[Request 1 Uses Bean]
B --> D[Request 2 Uses Same Bean]
B --> E[Request N Uses Same Bean]
E --> F[Application Stops]
F --> G[Bean Destroyed]
Good Use Cases
- Stateless services
- Shared configuration
- Thread-safe caches
- Reusable gateways
- Repository components
- Application metrics
Risks
Because the bean is shared across requests:
- Mutable fields need synchronization
- Request data must not be stored
- User-specific information must not be stored
- Non-thread-safe collaborators can cause failures
Unsafe Example
@ApplicationScoped
public class CurrentCustomerHolder {
private Long customerId;
}
Concurrent users can overwrite one another's data.
Common Mistake
Assuming one application-scoped bean instance is automatically thread-safe.
Senior Follow-up
CDI manages lifecycle, not the thread safety of mutable application state.
Q7. What is @RequestScoped?
Answer
@RequestScoped creates one contextual instance for one HTTP request.
@RequestScoped
public class RequestContext {
private String correlationId;
private Long authenticatedUserId;
public String getCorrelationId() {
return correlationId;
}
public void setCorrelationId(
String correlationId
) {
this.correlationId =
correlationId;
}
}
Request Lifecycle
sequenceDiagram
participant Client
participant Container
participant RequestBean
participant Resource
Client->>Container: HTTP request
Container->>RequestBean: Create instance
Container->>Resource: Inject request bean
Resource->>RequestBean: Read or write request data
Resource-->>Container: HTTP response
Container->>RequestBean: Destroy instance
Good Use Cases
- Correlation IDs
- Request metadata
- Current tenant context
- Request-level security information
- Per-request data aggregation
Risks
- Longer-running asynchronous tasks may outlive the request
- Request-scoped proxies should not be assumed usable after request completion
- Large objects increase per-request memory usage
Common Mistake
Using request scope for stateless services that could safely be application-scoped.
Senior Follow-up
Use request scope for request-specific mutable state, not by default for all resource dependencies.
Q8. What is @SessionScoped?
Answer
@SessionScoped creates one contextual instance for one HTTP session.
Session-scoped beans must usually be serializable because application servers may passivate or replicate sessions.
@SessionScoped
public class ShoppingCart
implements Serializable {
private static final long
serialVersionUID = 1L;
private final List<CartItem>
items =
new ArrayList<>();
public void add(CartItem item) {
items.add(item);
}
public List<CartItem> getItems() {
return List.copyOf(items);
}
}
Session Lifecycle
flowchart TD
A[User Session Created] --> B[SessionScoped Cart Created]
B --> C[Request 1 Uses Cart]
B --> D[Request 2 Uses Same Cart]
B --> E[Request N Uses Same Cart]
E --> F[Session Expires or Invalidates]
F --> G[Cart Destroyed]
Good Use Cases
- Small temporary user preferences
- Multi-step UI state
- Shopping-cart presentation state
- Conversation-like web workflows
Risks
- Large session memory usage
- Cluster replication overhead
- Stale state
- Serialization problems
- Multiple browser tabs sharing state
- Session fixation concerns
Common Mistake
Storing complete entities, large files, or database connections in a session-scoped bean.
Senior Follow-up
Important business state should usually be persisted in a database rather than trusted only to an HTTP session.
Q9. What is @Dependent scope?
Answer
@Dependent is the default CDI pseudo-scope.
A dependent bean's lifecycle is tied to the object into which it is injected.
@Dependent
public class OrderMapper {
public OrderResponse map(
Order order
) {
return new OrderResponse(
order.getId(),
order.getStatus()
);
}
}
Lifecycle
flowchart TD
A[Create CustomerService] --> B[Create Dependent Mapper]
B --> C[Inject Mapper into Service]
C --> D[Service Uses Mapper]
D --> E[Service Destroyed]
E --> F[Dependent Mapper Destroyed]
Characteristics
- New dependent instance per injection target
- No normal scoped client proxy
- Lifecycle owned by receiving bean
- Useful for lightweight stateless helpers
- Destruction follows the parent contextual object
Risk
Injecting a dependent bean into a long-lived application-scoped bean effectively gives the dependent object a long lifetime.
Common Mistake
Assuming @Dependent always means a new instance per method invocation.
Senior Follow-up
It means dependent on the lifecycle of the injection target, not necessarily short-lived.
Q10. What are qualifiers?
Answer
Qualifiers allow CDI to choose among multiple implementations of the same interface.
Interface
public interface PaymentGateway {
PaymentResult charge(
PaymentRequest request
);
}
Custom Qualifier
@Qualifier
@Retention(RUNTIME)
@Target({
TYPE,
FIELD,
PARAMETER,
METHOD
})
public @interface CardPayment {
}
Implementations
@CardPayment
@ApplicationScoped
public class StripePaymentGateway
implements PaymentGateway {
@Override
public PaymentResult charge(
PaymentRequest request
) {
return PaymentResult.success();
}
}
@ApplicationScoped
public class WalletPaymentGateway
implements PaymentGateway {
@Override
public PaymentResult charge(
PaymentRequest request
) {
return PaymentResult.success();
}
}
Injection
@ApplicationScoped
public class CheckoutService {
private final PaymentGateway
paymentGateway;
@Inject
public CheckoutService(
@CardPayment
PaymentGateway paymentGateway
) {
this.paymentGateway =
paymentGateway;
}
}
Resolution Flow
flowchart TD
A[Injection Point: PaymentGateway] --> B[Find Candidate Beans]
B --> C[Stripe Gateway]
B --> D[Wallet Gateway]
A --> E[Qualifier CardPayment]
E --> F[Select Stripe Gateway]
Built-In Qualifiers
@Default@Any@Named
Common Mistake
Using string names instead of type-safe custom qualifiers for core business implementation selection.
Senior Follow-up
Qualifiers should represent semantic meaning such as region, channel, provider type, or business capability.
Q11. What are producer methods?
Answer
Producer methods allow CDI to create objects that cannot be directly discovered or constructed as normal CDI beans.
Producer Example
@ApplicationScoped
public class ClockProducer {
@Produces
public Clock clock() {
return Clock.systemUTC();
}
}
Injection
@ApplicationScoped
public class AuditService {
private final Clock clock;
@Inject
public AuditService(
Clock clock
) {
this.clock = clock;
}
public Instant now() {
return clock.instant();
}
}
Qualified Producer
@Qualifier
@Retention(RUNTIME)
@Target({
FIELD,
PARAMETER,
METHOD
})
public @interface BusinessClock {
}
@Produces
@BusinessClock
public Clock businessClock() {
return Clock.system(
ZoneId.of("America/Chicago")
);
}
Producer Flow
sequenceDiagram
participant Container
participant Producer
participant Clock
participant AuditService
Container->>Producer: Invoke @Produces method
Producer-->>Container: Clock instance
Container->>AuditService: Inject Clock
Container-->>AuditService: Bean ready
Common Use Cases
- Third-party library objects
- Configured clients
- Clocks
- Formatters
- Provider-specific resources
- Legacy classes
- Objects selected through configuration
Disposal Method
public void closeClient(
@Disposes ApiClient client
) {
client.close();
}
Common Mistake
Creating expensive objects from an unscoped producer without considering how often the producer is invoked.
Senior Follow-up
Producer methods need deliberate scope, disposal, thread-safety, and configuration behavior.
Q12. What are CDI interceptors?
Answer
Interceptors apply cross-cutting behavior around method invocations or lifecycle events.
Common uses include:
- Logging
- Auditing
- Metrics
- Authorization
- Retry
- Transaction handling
- Performance timing
Interceptor Binding
@InterceptorBinding
@Retention(RUNTIME)
@Target({
TYPE,
METHOD
})
public @interface Monitored {
}
Interceptor
@Monitored
@Interceptor
@Priority(
Interceptor.Priority.APPLICATION
)
public class MonitoringInterceptor {
@AroundInvoke
public Object monitor(
InvocationContext context
) throws Exception {
long start =
System.nanoTime();
try {
return context.proceed();
} finally {
long duration =
System.nanoTime() - start;
System.out.println(
context.getMethod().getName()
+ " took "
+ duration
);
}
}
}
Usage
@Monitored
@ApplicationScoped
public class ClaimService {
public void processClaim() {
}
}
Invocation Flow
sequenceDiagram
participant Client
participant Interceptor
participant Service
Client->>Interceptor: Invoke service method
Interceptor->>Interceptor: Before logic
Interceptor->>Service: context.proceed()
Service-->>Interceptor: Result
Interceptor->>Interceptor: After logic
Interceptor-->>Client: Return result
Common Mistake
Putting core business behavior inside interceptors.
Senior Follow-up
Interceptors should implement cross-cutting concerns that are independent of one business domain.
Q13. What are CDI decorators?
Answer
Decorators wrap an implementation of an interface to add or modify business behavior while preserving the same interface.
Interface
public interface NotificationService {
void send(
Notification notification
);
}
Base Implementation
@ApplicationScoped
public class EmailNotificationService
implements NotificationService {
@Override
public void send(
Notification notification
) {
// Send email
}
}
Decorator
@Decorator
public abstract class AuditedNotificationDecorator
implements NotificationService {
@Inject
@Delegate
private NotificationService delegate;
@Inject
private AuditService auditService;
@Override
public void send(
Notification notification
) {
auditService.record(
notification
);
delegate.send(notification);
}
}
Decorator Flow
flowchart LR
A[Caller] --> B[Audit Decorator]
B --> C[Record Audit]
B --> D[Delegate Notification Service]
D --> E[Send Notification]
Interceptor vs Decorator
| Interceptor | Decorator |
|---|---|
| Cross-cutting technical concern | Business-interface enhancement |
| Uses interceptor binding | Uses @Decorator and @Delegate |
| Can apply across unrelated types | Implements specific business interface |
| Logging, metrics, security | Auditing or enriching business behavior |
Common Mistake
Treating decorators and interceptors as interchangeable.
Senior Follow-up
Use decorators when behavior is directly tied to the business contract.
Q14. What are CDI events?
Answer
CDI events allow one component to publish an event without directly depending on the observing components.
Event Model
public record CustomerCreatedEvent(
Long customerId,
String email
) {
}
Publisher
@ApplicationScoped
public class CustomerService {
@Inject
private Event<CustomerCreatedEvent>
customerCreatedEvent;
public void createCustomer(
Customer customer
) {
customerCreatedEvent.fire(
new CustomerCreatedEvent(
customer.getId(),
customer.getEmail()
)
);
}
}
Observer
@ApplicationScoped
public class WelcomeEmailObserver {
public void sendWelcomeEmail(
@Observes
CustomerCreatedEvent event
) {
// Send email
}
}
Event Flow
sequenceDiagram
participant Service
participant CDI
participant EmailObserver
participant AuditObserver
Service->>CDI: Fire CustomerCreatedEvent
CDI->>EmailObserver: Notify observer
CDI->>AuditObserver: Notify observer
EmailObserver-->>CDI: Completed
AuditObserver-->>CDI: Completed
Asynchronous Event
customerCreatedEvent.fireAsync(
event
);
Observer:
public void handleAsync(
@ObservesAsync
CustomerCreatedEvent event
) {
}
Important Limitation
CDI events are usually in-process events.
They do not automatically provide:
- Durable delivery
- Cross-service communication
- Retry persistence
- Dead-letter handling
- Broker-based scalability
Common Mistake
Using CDI events as a replacement for durable enterprise messaging.
Senior Follow-up
Use CDI events for in-process decoupling and Jakarta Messaging or an external broker for durable distributed events.
Q15. What are senior-level CDI best practices?
Answer
Senior developers should use CDI to make dependency graphs explicit, scopes safe, and cross-cutting concerns predictable.
Best Practices
- Prefer constructor injection.
- Keep required dependencies explicit.
- Select scopes based on lifecycle and thread safety.
- Keep application-scoped beans stateless or thread-safe.
- Avoid request state in shared beans.
- Use semantic custom qualifiers.
- Use producers for third-party or configured objects.
- Apply disposal methods to managed resources.
- Use interceptors for technical cross-cutting concerns.
- Use decorators for business-contract enhancement.
- Use CDI events for local decoupling.
- Do not use CDI events for guaranteed distributed delivery.
- Avoid manually constructing managed beans.
- Keep beans cohesive.
- Avoid circular dependencies.
- Use lifecycle callbacks carefully.
- Test alternative implementations and qualifiers.
- Document custom scopes and extensions.
- Understand normal-scope proxies.
- Keep implementation-specific behavior isolated.
CDI Design Flow
flowchart TD
A[New Component] --> B{Has Required Dependencies?}
B -->|Yes| C[Use Constructor Injection]
B -->|No| D[Simple Managed Bean]
C --> E{Which Lifetime?}
D --> E
E --> F[Request Scope]
E --> G[Application Scope]
E --> H[Session Scope]
E --> I[Dependent Scope]
F --> J[Validate State and Thread Safety]
G --> J
H --> J
I --> J
J --> K{Multiple Implementations?}
K -->|Yes| L[Use Qualifiers]
K -->|No| M[Direct Resolution]
L --> N[Add Interceptors Events or Decorators Only When Needed]
M --> N
Senior Follow-up
Good CDI design keeps business services simple while allowing the container to provide lifecycle, composition, interception, and implementation selection.
CDI Bean Discovery
Bean Archive
CDI discovers beans in an application archive according to bean-discovery rules and metadata.
A beans.xml file may be used depending on discovery mode and application requirements.
Example location:
WEB-INF/beans.xml
or:
META-INF/beans.xml
Discovery Flow
flowchart TD
A[Application Archive] --> B[Container Reads Bean Metadata]
B --> C[Scans Bean-Defining Annotations]
C --> D[Builds Bean Set]
D --> E[Validates Injection Points]
E --> F{Resolution Valid?}
F -->|Yes| G[Deploy Application]
F -->|No| H[Deployment Failure]
Common Bean-Defining Annotations
@ApplicationScoped@RequestScoped@SessionScoped@ConversationScoped- Custom normal scopes
- Stereotypes containing a scope
Unsatisfied and Ambiguous Dependencies
Unsatisfied Dependency
No bean matches the injection point.
@Inject
private PaymentGateway paymentGateway;
If there is no matching implementation, deployment fails.
Ambiguous Dependency
Multiple beans match without enough qualifier information.
flowchart TD
A[Injection Point PaymentGateway] --> B[Stripe Implementation]
A --> C[PayPal Implementation]
B --> D{No Qualifier}
C --> D
D --> E[Ambiguous Dependency]
E --> F[Deployment Failure]
Resolution
Use a qualifier:
@Inject
@CardPayment
private PaymentGateway paymentGateway;
CDI Client Proxies
Normal-scoped beans such as @RequestScoped and @ApplicationScoped are commonly injected through proxies.
The proxy resolves the correct contextual instance when a method is invoked.
Proxy Flow
flowchart LR
A[Injected Reference] --> B[CDI Client Proxy]
B --> C[Resolve Active Context]
C --> D[Find Contextual Bean Instance]
D --> E[Invoke Actual Bean]
Why Proxies Matter
An application-scoped bean may inject a request-scoped bean safely through a proxy.
@ApplicationScoped
public class AuditService {
@Inject
private RequestContext requestContext;
}
The proxy resolves the current request instance during each invocation.
Common Mistake
Assuming the request-scoped instance is created permanently when injected into an application-scoped bean.
Lifecycle Callbacks
CDI supports lifecycle callbacks.
Initialization
@PostConstruct
public void initialize() {
// Initialization after injection
}
Destruction
@PreDestroy
public void shutdown() {
// Release resources
}
Lifecycle
stateDiagram-v2
[*] --> Constructed
Constructed --> DependenciesInjected
DependenciesInjected --> PostConstruct
PostConstruct --> Ready
Ready --> PreDestroy
PreDestroy --> Destroyed
Destroyed --> [*]
Best Practices
- Keep initialization fast.
- Avoid slow remote calls at startup unless required.
- Release owned resources.
- Do not duplicate container-managed resource cleanup.
- Handle partial initialization failures.
Alternatives
Alternatives allow a different implementation to be selected for a deployment.
Example
@Alternative
@Priority(1)
@ApplicationScoped
public class MockPaymentGateway
implements PaymentGateway {
@Override
public PaymentResult charge(
PaymentRequest request
) {
return PaymentResult.success();
}
}
Use Cases
- Testing
- Environment-specific implementations
- Migration paths
- Feature replacement
- Fallback implementations
Selection Flow
flowchart TD
A[Interface Injection Point] --> B[Default Implementation]
A --> C[Enabled Alternative]
C --> D[Alternative Priority Selected]
D --> E[Inject Alternative]
Common Mistake
Using alternatives for dynamic per-request selection.
Qualifiers or programmatic lookup may be better for runtime selection.
Programmatic Lookup with Instance
Instance<T> supports programmatic selection and optional resolution.
@Inject
@Any
private Instance<PaymentGateway>
gateways;
PaymentGateway gateway =
gateways.select(
new PaymentProviderLiteral(
providerName
)
).get();
Use Cases
- Optional dependencies
- Dynamic implementation selection
- Iterating through plugins
- Lazy bean resolution
Risks
- Moves resolution logic into application code
- Can hide dependency requirements
- Requires careful qualifier handling
CDI Events vs Jakarta Messaging
| CDI Event | Jakarta Messaging |
|---|---|
| In-process | Broker-based |
| Usually same application | Cross-process and cross-service |
| Fast local communication | Durable asynchronous messaging |
| No broker required | Requires messaging provider |
| Limited delivery guarantees | Acknowledgement and redelivery |
| Good for local observers | Good for enterprise integration |
Interceptor Ordering
Multiple interceptors may apply to one method.
@Priority helps define ordering.
sequenceDiagram
participant Client
participant SecurityInterceptor
participant MetricsInterceptor
participant TransactionInterceptor
participant Service
Client->>SecurityInterceptor: Invoke
SecurityInterceptor->>MetricsInterceptor: Continue
MetricsInterceptor->>TransactionInterceptor: Continue
TransactionInterceptor->>Service: Business method
Service-->>TransactionInterceptor: Result
TransactionInterceptor-->>MetricsInterceptor: Commit
MetricsInterceptor-->>SecurityInterceptor: Record metrics
SecurityInterceptor-->>Client: Result
Senior developers should understand that interceptor order can affect:
- Transactions
- Security
- Retry
- Logging
- Metrics
- Exception handling
Thread Safety by Scope
| Scope | Shared Across Threads? | Main Concern |
|---|---|---|
@Dependent |
Depends on owner | Owner lifecycle |
@RequestScoped |
Usually one request | Async continuation |
@SessionScoped |
May be accessed concurrently | Multiple browser requests |
@ApplicationScoped |
Yes | Mutable shared state |
@ConversationScoped |
Potentially | Conversation concurrency |
Even session-scoped beans may receive concurrent requests from the same user.
Unsafe Application Scope
@ApplicationScoped
public class ReportService {
private final List<String>
currentRows =
new ArrayList<>();
public List<String> generate() {
currentRows.clear();
currentRows.add("row");
return currentRows;
}
}
Multiple requests can modify the same list.
Safer Pattern
@ApplicationScoped
public class ReportService {
public List<String> generate() {
List<String> rows =
new ArrayList<>();
rows.add("row");
return rows;
}
}
Keep request-specific mutable data in local variables.
CDI Architecture
flowchart TD
A[REST Resource] --> B[Application Service]
B --> C[Repository]
B --> D[Event Publisher]
B --> E[External Client]
F[CDI Container] --> A
F --> B
F --> C
F --> D
F --> E
F --> G[Scope Management]
F --> H[Dependency Resolution]
F --> I[Interceptors]
F --> J[Decorators]
F --> K[Events]
Scope Selection Matrix
| Bean Type | Recommended Scope |
|---|---|
| Stateless service | @ApplicationScoped |
| Repository | @ApplicationScoped |
| Request metadata | @RequestScoped |
| User session UI state | @SessionScoped |
| Lightweight mapper | @Dependent or application scope |
| Thread-safe client | @ApplicationScoped |
| Non-thread-safe formatter | Producer with suitable lifecycle |
| Temporary conversation state | @ConversationScoped |
Constructor Injection Example
@ApplicationScoped
public class ClaimService {
private final ClaimRepository repository;
private final FraudService fraudService;
private final Event<ClaimCreatedEvent>
claimCreatedEvent;
@Inject
public ClaimService(
ClaimRepository repository,
FraudService fraudService,
Event<ClaimCreatedEvent>
claimCreatedEvent
) {
this.repository = repository;
this.fraudService =
fraudService;
this.claimCreatedEvent =
claimCreatedEvent;
}
public Long createClaim(
CreateClaimCommand command
) {
fraudService.validate(command);
Claim claim =
Claim.create(command);
repository.persist(claim);
claimCreatedEvent.fire(
new ClaimCreatedEvent(
claim.getId()
)
);
return claim.getId();
}
}
Qualifier Example with Members
@Qualifier
@Retention(RUNTIME)
@Target({
TYPE,
FIELD,
PARAMETER,
METHOD
})
public @interface Channel {
ChannelType value();
}
@Channel(ChannelType.EMAIL)
@ApplicationScoped
public class EmailNotificationService
implements NotificationService {
}
@Inject
public NotificationCoordinator(
@Channel(ChannelType.EMAIL)
NotificationService service
) {
this.service = service;
}
Producer with Configuration
@ApplicationScoped
public class ApiClientProducer {
@Produces
@ApplicationScoped
public ApiClient apiClient(
Configuration configuration
) {
return ApiClient.builder()
.baseUrl(
configuration.apiBaseUrl()
)
.connectTimeout(
Duration.ofSeconds(5)
)
.build();
}
public void dispose(
@Disposes ApiClient client
) {
client.close();
}
}
CDI Event with Qualifier
@Qualifier
@Retention(RUNTIME)
@Target({
FIELD,
PARAMETER,
METHOD
})
public @interface HighPriority {
}
Publisher:
@Inject
@HighPriority
private Event<ClaimEvent> event;
Observer:
public void handle(
@Observes
@HighPriority
ClaimEvent event
) {
}
Qualifiers can categorize event channels.
Common CDI Interview Mistakes
- Saying CDI is only dependency injection.
- Using field injection by default.
- Making every bean application-scoped.
- Storing request state in shared beans.
- Ignoring thread safety.
- Assuming session scope receives no concurrent access.
- Using
@Namedinstead of semantic qualifiers. - Forgetting producer disposal.
- Manually creating CDI beans with
new. - Confusing decorators with interceptors.
- Using CDI events for durable messaging.
- Creating circular dependencies.
- Misunderstanding dependent scope.
- Ignoring client proxies.
- Performing slow work in
@PostConstruct. - Injecting non-serializable dependencies into passivating scopes.
- Hiding optional dependencies behind runtime failures.
- Overusing programmatic lookup.
- Putting domain logic in interceptors.
- Failing to test bean resolution during deployment.
Senior CDI Checklist
Explicit Constructor Dependencies
│
▼
Correct Scope Selection
│
▼
Thread-Safe Shared Beans
│
▼
Semantic Qualifiers
│
▼
Controlled Producers and Disposal
│
▼
Thin Interceptors
│
▼
Business-Focused Decorators
│
▼
Appropriate Local Events
│
▼
Deployment-Time Resolution Tests
Interview Quick Revision
| Concept | Purpose |
|---|---|
| CDI | Contexts and dependency injection |
@Inject |
Inject dependency |
| Constructor Injection | Explicit required dependency |
@ApplicationScoped |
Application-wide contextual instance |
@RequestScoped |
Per-request instance |
@SessionScoped |
Per-session instance |
@Dependent |
Lifecycle tied to owner |
| Qualifier | Select implementation |
| Producer | Create injectable object |
| Disposer | Release produced resource |
| Interceptor | Cross-cutting method behavior |
| Decorator | Enhance business interface |
| Event | In-process decoupling |
| Observer | Receives CDI event |
| Alternative | Replace implementation |
Key Takeaways
- CDI is Jakarta EE's standard contextual dependency-injection and component-lifecycle model.
- CDI manages bean discovery, dependency resolution, scopes, events, interceptors, decorators, producers, and alternatives.
- Constructor injection makes required dependencies explicit and improves testability.
- Field injection is concise but hides dependencies and weakens immutability.
- CDI scopes define both bean lifetime and sharing behavior.
- Application-scoped beans are shared and must remain stateless or thread-safe.
- Request-scoped beans are appropriate for request-specific mutable state.
- Session-scoped beans should remain small, serializable, and safe for concurrent session requests.
- Dependent beans inherit the effective lifetime of their injection target.
- Qualifiers provide type-safe selection among multiple implementations.
- Producer methods integrate third-party, configured, or factory-created objects into CDI.
- Disposer methods should release resources created by producers.
- Interceptors handle technical cross-cutting concerns such as metrics, logging, and authorization.
- Decorators enhance behavior tied to a specific business interface.
- CDI events support in-process decoupling but do not provide durable distributed delivery.
- Bean-resolution failures are detected during deployment, helping expose ambiguous or unsatisfied dependencies.
- Client proxies allow beans with different scopes to interact while resolving the active contextual instance.
- Scope selection must consider state, serialization, thread safety, and resource cost.
- Managed CDI beans should not be created manually when injection, interception, or contextual behavior is required.
- Senior developers use CDI to create explicit, cohesive, testable, and operationally predictable component graphs.