EJB (Enterprise JavaBeans)

Server-Side Components with Built-In Enterprise Features

← Back to Index

What are EJBs?

Think of EJB like a premium hotel service:

EJB (Enterprise JavaBeans) are server-side components that provide built-in support for transactions, security, concurrency, and more. They're CDI beans with superpowers!

Modern EJB: Simplified with annotations (no more XML hell from EJB 2.x days!)

EJB vs CDI Bean

// CDI Bean - Basic functionality
@RequestScoped
public class OrderService {
    @Inject
    private PaymentService payment;

    public void placeOrder(Order order) {
        // You handle transactions manually
        payment.process(order);
    }
}

// EJB - Built-in features!
@Stateless  // EJB annotation
public class OrderService {
    @Inject
    private PaymentService payment;

    // Automatic transaction! Commits on success, rolls back on exception
    public void placeOrder(Order order) {
        payment.process(order);
    }
}

Types of EJBs

1. @Stateless - No Client State (Most Common)

@Stateless  // Pool of interchangeable instances
public class ProductService {
    @PersistenceContext
    private EntityManager em;

    public Product findProduct(Long id) {
        return em.find(Product.class, id);
    }

    public void saveProduct(Product product) {
        em.persist(product);
    }
}

Characteristics:

2. @Stateful - Maintains Client State

@Stateful  // One instance per client
public class ShoppingCart {
    private List<Product> items = new ArrayList<>();
    private double total = 0.0;

    public void addItem(Product product) {
        items.add(product);
        total += product.getPrice();
    }

    public void removeItem(Product product) {
        items.remove(product);
        total -= product.getPrice();
    }

    public List<Product> getItems() {
        return items;
    }

    public double getTotal() {
        return total;
    }

    @Remove  // Call this to destroy the bean
    public void checkout() {
        System.out.println("Checkout complete. Cart destroyed.");
    }
}

Characteristics:

3. @Singleton - One Instance for All (Shared)

@Singleton  // Only ONE instance in entire application
@Startup   // Initialize at application startup
public class CacheManager {
    private Map<String, Object> cache = new ConcurrentHashMap<>();

    @PostConstruct
    public void init() {
        System.out.println("Loading cache at startup...");
        // Load data
    }

    @Lock(LockType.READ)  // Multiple threads can read
    public Object get(String key) {
        return cache.get(key);
    }

    @Lock(LockType.WRITE)  // Only one thread can write
    public void put(String key, Object value) {
        cache.put(key, value);
    }
}

Characteristics:

Type Instances State Use Case
@Stateless Pool (many) No state Services, repositories
@Stateful One per client Maintains state Shopping carts, wizards
@Singleton One (shared) Shared state Caches, config

Transaction Management

The killer feature of EJBs!

Automatic Transactions (Default)

@Stateless
public class BankService {
    @PersistenceContext
    private EntityManager em;

    // Automatic transaction!
    // If method completes: COMMIT
    // If exception thrown: ROLLBACK
    public void transfer(Long fromId, Long toId, double amount) {
        Account from = em.find(Account.class, fromId);
        Account to = em.find(Account.class, toId);

        from.withdraw(amount);  // Subtract from account
        to.deposit(amount);     // Add to account

        // Both updates happen together or not at all!
        if (from.getBalance() < 0) {
            throw new IllegalStateException("Insufficient funds");
            // Exception = automatic ROLLBACK!
        }
    }
}

Transaction Attributes

@Stateless
public class OrderService {

    // REQUIRED (default): Use existing transaction or create new one
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void placeOrder(Order order) {
        // Part of a transaction
    }

    // REQUIRES_NEW: Always create a new transaction
    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    public void logActivity(String message) {
        // Separate transaction - commits even if parent fails
    }

    // NOT_SUPPORTED: Run without transaction
    @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
    public List<Order> searchOrders(String query) {
        // No transaction needed for read-only
        return null;
    }

    // MANDATORY: Must be called within a transaction
    @TransactionAttribute(TransactionAttributeType.MANDATORY)
    public void updateInventory(Product product) {
        // Throws exception if no transaction exists
    }
}

Security with EJBs

Role-Based Access Control

@Stateless
@DeclareRoles({"ADMIN", "USER", "MANAGER"})
public class ProductService {

    // Only ADMINs can call this
    @RolesAllowed("ADMIN")
    public void deleteProduct(Long id) {
        System.out.println("Deleting product " + id);
    }

    // ADMIN or MANAGER can call this
    @RolesAllowed({"ADMIN", "MANAGER"})
    public void updatePrice(Long id, double newPrice) {
        System.out.println("Updating price");
    }

    // Anyone authenticated can call this
    @RolesAllowed({"ADMIN", "USER", "MANAGER"})
    public Product getProduct(Long id) {
        return null;
    }

    // No authentication required
    @PermitAll
    public List<Product> getAllProducts() {
        return null;
    }

    // Nobody can call this (useful for internal methods)
    @DenyAll
    private void internalMethod() {
        System.out.println("Internal only");
    }
}

Asynchronous Methods

Fire and Forget

@Stateless
public class EmailService {

    @Asynchronous  // Runs in separate thread
    public void sendEmail(String to, String subject, String body) {
        System.out.println("Sending email to " + to + "...");
        // Simulate slow email sending
        try {
            Thread.sleep(3000);
            System.out.println("Email sent!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

// Usage
@Inject
private EmailService emailService;

public void registerUser(User user) {
    // Save user to database
    database.save(user);

    // Send email asynchronously (doesn't block!)
    emailService.sendEmail(user.getEmail(), "Welcome!", "Thanks for joining!");

    System.out.println("User registered! Email sending in background...");
    // Method returns immediately, email sent in background
}

With Return Value (Future)

@Stateless
public class ReportService {

    @Asynchronous
    public Future<String> generateReport() {
        // Simulate long-running task
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) { }

        String report = "Sales Report: $1,234,567";
        return new AsyncResult<>(report);
    }
}

// Usage
Future<String> futureReport = reportService.generateReport();

System.out.println("Report generation started...");

// Do other work while report generates
doOtherWork();

// Wait for result when needed
String report = futureReport.get();  // Blocks until ready
System.out.println(report);

Scheduled Tasks (Timers)

Cron-Style Scheduling

@Singleton
public class ScheduledTasks {

    // Run every day at 2:00 AM
    @Schedule(hour = "2", minute = "0", persistent = false)
    public void dailyCleanup() {
        System.out.println("Running daily cleanup at 2 AM...");
    }

    // Run every 30 minutes
    @Schedule(minute = "*/30", hour = "*")
    public void checkSystemHealth() {
        System.out.println("Health check...");
    }

    // Run every Monday at 9 AM
    @Schedule(dayOfWeek = "Mon", hour = "9", minute = "0")
    public void weeklyReport() {
        System.out.println("Generating weekly report...");
    }

    // Multiple schedules on same method
    @Schedules({
        @Schedule(hour = "8", minute = "0"),   // 8 AM
        @Schedule(hour = "12", minute = "0"),  // 12 PM
        @Schedule(hour = "18", minute = "0")   // 6 PM
    })
    public void syncData() {
        System.out.println("Syncing data three times daily...");
    }
}

Programmatic Timers

@Singleton
public class DynamicTimers {
    @Resource
    private TimerService timerService;

    public void scheduleTask(long delayMs) {
        // Create a timer that fires once after delay
        timerService.createSingleActionTimer(delayMs, new TimerConfig());
    }

    @Timeout  // Called when timer fires
    public void handleTimeout(Timer timer) {
        System.out.println("Timer fired!");
    }
}

Best Practices

✅ DO:

  • Use @Stateless for most services - Default choice
  • Use @Stateful sparingly - Memory intensive
  • Let container manage transactions - Don't use manual begin/commit
  • Use @Asynchronous for long tasks - Keep UI responsive
  • Leverage security annotations - Cleaner than manual checks
  • Use @Schedule for background jobs - Built-in scheduler
  • Call @Remove on @Stateful beans - Clean up resources

❌ DON'T:

  • Don't use EJBs if CDI beans suffice - EJBs add overhead
  • Don't store heavy objects in @Stateful - Memory issues
  • Don't create threads manually - Use @Asynchronous instead
  • Don't catch RuntimeException in transactions - Prevents rollback
  • Don't call local methods directly - Transaction/security won't work
  • Don't make EJBs final - Container needs to create proxies

Summary

  • EJBs are CDI beans with built-in enterprise features
  • @Stateless: Pool of instances, no state (most common)
  • @Stateful: Maintains state per client
  • @Singleton: One instance shared by all
  • Automatic transactions: Commit on success, rollback on exception
  • Security: @RolesAllowed, @PermitAll, @DenyAll
  • Asynchronous: @Asynchronous for background tasks
  • Scheduling: @Schedule for cron-style timers
  • When to use: Need transactions, security, async, or timers
  • When not to use: Simple services (use CDI beans instead)