On this page
1· Concept & Definition
Enterprise JavaBeans is a server-side component architecture in which business components — enterprise beans — run inside an EJB container. Clients never call bean instances directly: they call a container-provided view (proxy), and the container interposes services such as transactions, security, concurrency control, instance pooling, timers and asynchronous execution before delegating to an instance.
2· Why It Matters
- Explains the behaviour of many existing enterprise systems (transaction boundaries, exceptions, security).
- The interposition model is the foundation of today's CDI interceptors and platform services.
- Understanding services informs migration decisions and parity testing.
3· How It Works
Container
| Container responsibility | Detail |
|---|---|
| Instance management | Creates, pools, passivates and destroys bean instances |
| Client views | Generates proxies for no-interface, local and remote views |
| Interposition | Applies services on every business method invocation |
| Naming | Registers beans in JNDI (java:global/app/module/Bean!Interface) |
| Resource management | Injects data sources, SessionContext, other beans |
| Lifecycle callbacks | @PostConstruct, @PreDestroy, @PrePassivate, @PostActivate |
Enterprise beans

| Bean type | Annotation | State | Instances |
|---|---|---|---|
| Stateless session | @Stateless | None between calls | Pool; any instance per call |
| Stateful session | @Stateful | Per client (conversational) | One per client reference |
| Singleton session | @Singleton | Shared application state | Exactly one per application |
| Message-driven | @MessageDriven | None | Pool; triggered by messages |
Container services

| Service | Declared with | Default |
|---|---|---|
| Transactions | @TransactionAttribute, @TransactionManagement | CMT, REQUIRED |
| Security | @RolesAllowed, @PermitAll, @DenyAll, @RunAs | Unchecked (permit) unless declared |
| Concurrency | @Lock, @ConcurrencyManagement, @AccessTimeout | Singleton: container-managed, @Lock(WRITE) |
| Pooling | Server configuration | Vendor-specific pool sizes |
| Timers | @Schedule, TimerService | Persistent automatic timers |
| Asynchronous | @Asynchronous | Synchronous |
| Interceptors | @Interceptors, CDI bindings | None |
4· Java / Jakarta EE Code Examples
@Stateless // stateless session bean
public class TransferService {
@PersistenceContext EntityManager em; // container-injected resources
@Resource SessionContext ctx;
@RolesAllowed("teller") // security service
// REQUIRED tx by default
public void transfer(long from, long to, BigDecimal amount) {
Account a = em.find(Account.class, from, LockModeType.PESSIMISTIC_WRITE);
Account b = em.find(Account.class, to, LockModeType.PESSIMISTIC_WRITE);
if (a.balance().compareTo(amount) < 0) { ctx.setRollbackOnly(); throw new
InsufficientFunds(); }
a.debit(amount); b.credit(amount);
}
}// Clients: injection (preferred) or JNDI lookup with portable names
// CDI injection works for session beans
@Inject TransferService transfers;
@EJB TransferService transfers2; // EJB injection
TransferService t = (TransferService) new InitialContext()
.lookup("java:global/bank/core/TransferService!com.bank.TransferService");INFO [org.jboss.as.ejb3.deployment] WFLYEJB0473: JNDI bindings for session bean named
'TransferService':
java:global/bank/core/TransferService!com.bank.TransferService
java:app/core/TransferService!com.bank.TransferService
java:module/TransferService!com.bank.TransferService5· Configuration, Defaults & Diagnostics
| Item | Note |
|---|---|
| Descriptor | ejb-jar.xml optional; can override annotations |
| Packaging | EJBs in WARs (EJB 3.1+) or EJB JARs inside EARs |
| Portable JNDI namespaces | java:global, java:app, java:module |
| Monitoring | Server consoles/JMX: invocation counts, pool usage, wait times |
6· Production Usage Patterns
- Stateless beans as transactional service facades in layered monoliths.
- EJBs packaged inside WARs alongside REST resources and CDI beans.
- Singleton beans with
@Schedulefor background jobs.
7· Common Pitfalls & Misconceptions
- Calling methods internally via
this— container services are bypassed. - Fine-grained remote EJBs — network chattiness.
- Assuming checked exceptions roll back transactions.
- Mixing EJB and CDI scopes without understanding lifecycles.
8· Best Practices
- Keep beans coarse-grained service facades.
- Inject with
@Inject; avoid manual JNDI lookups. - Declare transaction and security metadata explicitly for clarity.
- Monitor pool and invocation metrics.
9· Architect's Perspective & Trade-offs
| Criterion | EJB | CDI + Jakarta specs |
|---|---|---|
| Transactions | Built-in CMT | @Transactional interceptor |
| Pooling | Built-in | Not needed (thread-safe beans) |
| Remote access | RMI-IIOP / vendor | REST, gRPC, messaging |
| Timers | Rich, persistent | Concurrency / schedulers |
| Profile availability | Platform (full), Web (Lite) | Core/Web/Platform |
10· Interview Q&A
What is an EJB container?
An EJB container is the part of a Jakarta EE application server that manages enterprise beans. It creates bean instances and may pool them, injects their dependencies and controls their life cycle. Most importantly, it intercepts every business method call through a proxy and applies services around it: starting or joining a transaction, checking security roles, managing concurrency and running interceptors. The developer writes a plain class with annotations such as @Stateless and @TransactionAttribute, and the container provides the infrastructure behaviour declaratively. Many of these ideas later moved into CDI and are now used well beyond EJB.
What are the types of enterprise beans?
There are two main kinds. Session beans implement business logic called by clients. A stateless session bean keeps no conversational state between calls, so the container can pool instances and give any instance to any caller. A stateful session bean keeps state for one client across several calls, such as a multi-step checkout, and is removed or passivated when no longer needed. A singleton session bean has exactly one instance per application, useful for shared caches or start-up logic, with container-managed concurrency. Message-driven beans are the second kind: they consume messages asynchronously from a queue or topic, usually through JMS.
How do clients obtain EJB references?
Clients obtain an EJB reference either by injection or by lookup. Injection with @EJB, or with @Inject when CDI is used, is the usual approach inside the same application. Lookup uses JNDI with portable global names in the form java:global/app/module/BeanName!fully.qualified.Interface, which is useful from code the container does not manage. In every case the client receives a container-generated proxy, not the bean instance itself. That proxy is what lets the container apply transactions, security and interceptors on every call, and it is also why a remote client can call a bean running in another server.
Why do internal calls within an EJB bypass container services?
Container services such as transactions, security checks and interceptors are applied by the proxy that sits between the client and the bean. When one business method calls another method on the same bean through this, the call goes directly to the instance and never passes through the proxy. As a result, the second method's @TransactionAttribute, for example REQUIRES_NEW, its security roles and its interceptors are silently ignored. The fix is to call through the container: inject the bean's own reference with @Resource SessionContext and use getBusinessObject, inject itself, or move the method into a separate bean. Spring proxies behave the same way.
Which EJB concepts survived into CDI and Jakarta EE today?
Most of EJB's useful ideas were generalised so they work with any CDI bean, not just EJBs. Interceptors became a general Jakarta mechanism with @Interceptor and interceptor bindings. Declarative transactions are available through @Transactional from Jakarta Transactions. Declarative security uses @RolesAllowed and related annotations from Jakarta Security. Asynchronous methods and scheduled tasks are provided by Jakarta Concurrency, including managed executors and, in Jakarta EE 11, support for virtual threads. As a result, new Jakarta EE applications rarely need EJBs at all, while existing EJBs keep working alongside CDI beans in the same application.
How would you assess an EJB-heavy application for modernisation?
Start with an inventory: every bean and its type, remote interfaces and their clients, timers and schedules, transaction attributes, security annotations, message-driven beans and any pooling or concurrency settings. Map each to its modern equivalent, such as CDI beans with @Transactional, Jakarta Concurrency for asynchronous work and schedules, or REST or messaging for remote interfaces. Flag behaviours that change subtly, such as which exceptions cause rollback, the loss of instance pooling limits that used to throttle load, and stateful bean passivation. Then migrate incrementally, protected by behavioural tests around each area, and record the target architecture in an ADR.