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 @Named instead 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.