Jakarta EE and MicroProfile Annotations: Using @EJB, @Resource, and @Inject for Dependency Injection

Illustration for Jakarta EE and MicroProfile Annotations: Using @EJB, @Resource, and @Inject for Dependency Injection
By Last updated:

A common mistake developers make when working with Jakarta EE applications is mixing up annotation-driven dependency injection mechanisms. For example, using @EJB where @Inject is preferred or misconfiguring @Resource for JNDI lookups. This leads to fragile applications that fail at runtime with vague errors.

Annotations like @EJB, @Resource, and @Inject are cornerstones of enterprise Java development. They enable clean, declarative dependency injection without boilerplate lookup code. Whether you are injecting an EJB, looking up a resource like a DataSource, or using CDI for managed beans, annotations reduce complexity and let the container handle lifecycle and wiring.

Think of these annotations as different types of service labels in a warehouse: @EJB for enterprise services, @Resource for utilities (like database or mail), and @Inject for CDI beans. Each has its place, but using the wrong one is like labeling a box incorrectly—chaos follows.


Core Annotations

1. @EJB – Enterprise Java Beans Injection

import jakarta.ejb.EJB;
import jakarta.ejb.Stateless;

@Stateless
public class PaymentService {
    public void processPayment() {
        System.out.println("Payment processed");
    }
}

public class OrderService {
    @EJB
    private PaymentService paymentService;

    public void placeOrder() {
        paymentService.processPayment();
    }
}
  • Injects an EJB directly without JNDI lookups.
  • The container manages transactions, security, and lifecycle.

2. @Resource – Resource Injection (JNDI)

import jakarta.annotation.Resource;
import javax.sql.DataSource;

public class UserRepository {
    @Resource(lookup = "java:jboss/datasources/MyDS")
    private DataSource dataSource;

    public void saveUser(String username) {
        // use dataSource for JDBC operations
    }
}
  • Used for external resources like DataSource, JMS queues, mail sessions.
  • More low-level than CDI, directly tied to JNDI names.

3. @Inject – CDI (Contexts and Dependency Injection)

import jakarta.inject.Inject;
import jakarta.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class NotificationService {
    public void sendNotification(String msg) {
        System.out.println("Notification: " + msg);
    }
}

public class UserService {
    @Inject
    private NotificationService notificationService;

    public void registerUser(String username) {
        notificationService.sendNotification("User " + username + " registered");
    }
}
  • The modern, recommended way of injecting dependencies in Jakarta EE.
  • Type-safe, extensible, and integrates with scopes (@RequestScoped, @SessionScoped, etc.).

How Reflection Powers These Annotations

  • The container uses reflection to scan classes for these annotations.
  • At deployment, it wires fields, constructors, or methods marked with them.
  • Proxies are created for transactional, scoped, or remote objects.
  • Developers see only plain annotations, but the container dynamically injects dependencies.

📌 What's New in Java Versions?

  • Java 5 – Introduced annotations, EJB 3.0 standardized @EJB.
  • Java 6/7 – Widespread adoption of @Resource.
  • Java EE 6 / Java 7 – CDI and @Inject introduced, shifting preference from @EJB.
  • Jakarta EE 9 – Package renaming from javax.* to jakarta.*.
  • Java 17/21 – No major new annotation changes, but CDI is now the standard for DI.

Pitfalls and Best Practices

Pitfalls

  • Using @EJB where CDI @Inject is better, leading to tight coupling.
  • Hardcoding JNDI names in @Resource.
  • Not managing CDI scopes properly, causing memory leaks or unexpected behavior.

Best Practices

  • Prefer @Inject over @EJB for new code.
  • Use @Resource sparingly and document JNDI lookups.
  • Keep beans properly scoped (@ApplicationScoped, @RequestScoped).
  • Combine CDI with MicroProfile Config for portable applications.

Summary + Key Takeaways

  • @EJB, @Resource, and @Inject are fundamental for dependency injection in Jakarta EE.
  • Use @EJB for legacy EJBs, @Resource for JNDI-managed resources, and @Inject for CDI beans.
  • Reflection and proxies allow the container to wire dependencies automatically.
  • Modern Jakarta EE encourages CDI (@Inject) as the go-to annotation.

FAQ

  1. What’s the difference between @EJB and @Inject?
    @EJB is for Enterprise Java Beans, while @Inject is part of CDI and more flexible.

  2. Can I replace all @EJB with @Inject?
    Usually yes, but if you need EJB-specific services like transactions, stick with @EJB.

  3. Is @Resource still relevant?
    Yes, for injecting infrastructure resources like data sources and JMS.

  4. Does @Inject require a no-arg constructor?
    No, CDI can inject through constructors and fields.

  5. How does reflection handle injection?
    The container inspects annotations at runtime and assigns proxies or instances.

  6. Can I mix CDI and EJBs?
    Yes, CDI beans can inject EJBs and vice versa.

  7. What happens if multiple beans match @Inject?
    The container throws an ambiguity error unless you use qualifiers (@Qualifier).

  8. Does MicroProfile extend these annotations?
    MicroProfile builds on CDI and adds config, fault tolerance, health checks, etc.

  9. Is @Resource portable across servers?
    Mostly, but JNDI names may differ between application servers.

  10. Which annotation should I prefer for new applications?
    Use @Inject with CDI for clean, modern, and portable dependency injection.