🔸 TL;DR 🧾
▪️ Session Beans = managed business services (stateless/stateful/singleton)
▪️ @Asynchronous = easy background execution under container control
▪️ MDBs = message consumers for event-driven processing
▪️ Transactions = one of EJB’s strongest features (container-managed by default)

EJB can feel “old school”… but the ideas are timeless: server-side components, managed lifecycle, transactions, async, and messaging — all handled by the container ✅ If you work with Jakarta EE apps (or legacy Java EE), understanding EJB is still a practical skill.
🔸 SESSION BEANS: THE “SERVICE LAYER” OF EJB 🧠
▪️ Stateless: no client-specific state → best for scalable business services
▪️ Stateful: keeps conversational state per client → useful for multi-step workflows
▪️ Singleton: one instance for the app → shared state + scheduled jobs/caches
🔸 SIMPLE SESSION BEAN: THE CORE IDEA ⚙️
▪️ You write a class annotated like @Stateless / @Stateful
▪️ The container injects it with @EJB (or @Inject in CDI contexts)
▪️ The container manages pooling, lifecycle, security, and transactions
1) You write a session bean (business service)
// 1) You write a session bean (business service) // The container will manage lifecycle, pooling, security, and transactions. import jakarta.ejb.Stateless; import jakarta.annotation.security.RolesAllowed; import jakarta.transaction.Transactional; @Stateless @RolesAllowed("USER") // security handled by container @Transactional // transaction boundary handled by container public class BillingService { public String charge(String customerId, int amountCents) { // business logic here (DB calls, etc.) return "Charged " + amountCents + " cents to " + customerId; } }
2) Your web layer (Servlet here) calls the bean
// 2) Your web layer (Servlet here) calls the bean // The container injects the EJB automatically. import jakarta.ejb.EJB; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; public class BillingServlet extends HttpServlet { @EJB private BillingService billingService; // injected by container @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) { String customerId = req.getParameter("customerId"); int amount = Integer.parseInt(req.getParameter("amountCents")); String result = billingService.charge(customerId, amount); resp.setContentType("text/plain"); try { resp.getWriter().println(result); } catch (Exception ignored) { } } }
(Optional) CDI-style injection instead of @EJB
// (Optional) CDI-style injection instead of @EJB import jakarta.inject.Inject; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; @Path("/billing") public class BillingResource { @Inject BillingService billingService; @POST public String charge() { return billingService.charge("cust-42", 1999); } }
🔸 INVOKING SESSION BEANS FROM A WEB APP 🌐
▪️ Classic setup: a Servlet / JSF / JAX-RS resource calls an injected EJB
▪️ Your web layer stays thin, the EJB holds business logic
▪️ The container takes care of the “enterprise plumbing” (tx, threads, resources)
🔸 ASYNCHRONOUS METHOD CALL ⏱️
Need “fire-and-forget” or background work without blocking the request thread?
▪️ Use @Asynchronous on a method
▪️ Return void for fire-and-forget, or a Future/CompletionStage style result (depending on stack)
▪️ The container runs it in managed threads (safer than manual new Thread())
Service
import jakarta.ejb.Asynchronous; import jakarta.ejb.Stateless; import jakarta.ejb.AsyncResult; import jakarta.enterprise.concurrent.ManagedExecutorService; import jakarta.annotation.Resource; import java.util.concurrent.Future; import java.util.concurrent.CompletableFuture; // EJB async: container-managed threads (no new Thread()) @Stateless public class ReportService { // 1) Fire-and-forget ✅ @Asynchronous public void generateReport(String reportId) { // long-running work (IO, DB, remote calls...) // container manages the execution thread System.out.println("Generating report " + reportId); } // 2) Future-based result ✅ @Asynchronous public Future<String> generateReportWithStatus(String reportId) { // do work... return new AsyncResult<>("DONE:" + reportId); } // 3) CompletionStage style (common alternative in modern code) ✅ // If your stack prefers CompletionStage, you can adapt (example). @Resource ManagedExecutorService executor; public CompletableFuture<String> generateReportAsync(String reportId) { return CompletableFuture.supplyAsync(() -> "DONE:" + reportId, executor); } }
Resource
import jakarta.ejb.EJB; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; import jakarta.ws.rs.QueryParam; import java.util.concurrent.Future; @Path("/reports") public class ReportResource { @EJB ReportService reportService; // Doesn't block: returns immediately, work continues in background @POST @Path("/fire") public String fireAndForget(@QueryParam("id") String id) { reportService.generateReport(id); return "Accepted: " + id; } // Optionally wait for result (not always recommended in HTTP handlers) @POST @Path("/status") public String withFuture(@QueryParam("id") String id) throws Exception { Future<String> status = reportService.generateReportWithStatus(id); return status.get(); // blocks until done } }
🔸 MESSAGE-DRIVEN BEANS (MDB): EVENT CONSUMERS 📩
MDBs are EJBs designed to consume messages (typically JMS).
▪️ The container delivers messages to your bean automatically
▪️ Great for decoupling, buffering spikes, and async processing
🔸 CONTAINER-MANAGED VS BEAN-MANAGED (MDB & TX) 🤝
▪️ Container-managed: you rely on annotations/config → simplest, most common
▪️ Bean-managed: you explicitly control things (like transactions) → more code, more responsibility Rule of thumb: use container-managed unless you have a strong reason not to.
🔸 TRANSACTIONS IN EJB (STATEFUL VS STATELESS) 💳
EJB makes transactions boringly reliable (that’s a compliment 😄):
▪️ Stateless: each call is usually independent → clean, predictable tx boundaries
▪️ Stateful: can span multi-step workflows → tx boundaries can be more subtle
▪️ Use transaction attributes (like “required / requires new”) to express intent clearly
▪️ Keep tx short: do the DB work, commit, move on ✅
🔸 TAKEAWAYS 🎯
▪️ EJB is less about hype, more about production-grade primitives
▪️ Prefer stateless for scalability; use stateful only when workflow state is truly needed
▪️ Use container-managed transactions & messaging first — go bean-managed only when necessary
▪️ EJB concepts map well to modern patterns: services, async jobs, event consumers, tx boundaries
#java #jakartaee #ejb #enterprisejava #softwarearchitecture #transactions #messaging #jms #backend #developers #legacycode #microservices
Go further with Java certification:
Java👇
Spring👇
SpringBook👇
JavaBook👇