What are EJBs?
Think of EJB like a premium hotel service:
- 🏨 Regular bean = Basic apartment (you handle everything)
- 🌟 EJB = 5-star hotel (concierge handles transactions, security, room service)
- 📦 You just focus on your business - the "hotel" handles the rest
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:
- ✅ No state stored between method calls
- ✅ Container pools instances (efficient)
- ✅ Any instance can handle any request
- ✅ Thread-safe (container manages concurrency)
- ✅ Automatic transaction management
- ✅ Use for: Services, repositories, business logic
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:
- ✅ Maintains state across method calls
- ✅ One instance dedicated to one client
- ✅ Can be passivated to disk if idle (saves memory)
- ✅ Use @Remove to destroy when done
- ✅ Use for: Shopping carts, wizards, conversational flows
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:
- ✅ Single instance shared by all clients
- ✅ Can be eagerly initialized with @Startup
- ✅ Built-in concurrency control with @Lock
- ✅ Use for: Caches, configuration, app-wide state
| 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)