| Lesson 6 | Stateful and Stateless Session Beans |
| Objective | Describe the difference between stateful and stateless session beans in Jakarta EE 11. |
Jakarta Enterprise Beans provides managed components for implementing business operations inside a Jakarta EE 11 runtime. Two important component types are the stateless session bean and the stateful session bean. Their names describe how they relate to a client conversation. A stateless bean does not preserve client-specific conversational data between calls. A stateful bean keeps conversational data for one client across multiple calls.
This distinction does not determine whether an application uses a database. Both types can participate in transactions, invoke other services, and read or update persistent data. The design question is whether a sequence of calls must remain associated with one managed component instance. State ownership should always be explicit in the application design.
It helps to separate three kinds of state before choosing a component model:
A session bean is not a persistent database record. A stateful bean can hold conversational fields, but those fields are temporary unless the application deliberately saves their values. Jakarta Persistence manages durable entities, while Jakarta Transactions defines bounded units of work that update persistent resources.
Jakarta Enterprise Beans also defines a singleton session bean. A singleton supplies one shared instance for an application and addresses application-wide coordination or shared state. It is not a replacement for either a pooled stateless service or a per-client stateful conversation.
A class annotated with @Stateless has no client-specific conversational state between business method calls. The container normally creates a pool of instances and assigns an available instance to each invocation. The same client may receive different instances on successive calls, and one instance may serve many clients over its lifetime.
Stateless does not mean that the bean exists for only one method call. An instance can remain in the pool and process many invocations before the container destroys it. It also does not mean that a stateless operation uses no data. The method can receive request data, query a database, call another service, and commit durable changes. What it must not do is require a client-specific instance field to be present on a later call.
Stateless beans are well suited to independent application operations such as tax calculation, inventory validation, payment authorization orchestration, notification dispatch, and order submission. Pooling allows a smaller collection of instances to serve many concurrent clients, although actual throughput still depends on database capacity, remote services, transaction duration, and runtime configuration.
import jakarta.ejb.Stateless;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
@Stateless
public class CartService {
@PersistenceContext
private EntityManager entityManager;
public void checkout(String cartId) {
Cart cart = entityManager.find(Cart.class, cartId);
cart.validateForCheckout();
cart.markSubmitted();
}
}
The service does not retain a cart in an instance field between calls. It loads authoritative data for the requested operation. Container-managed transaction rules can make the method a bounded transaction, and Jakarta Persistence tracks the durable entity changes.
A class annotated with @Stateful represents one client conversation. The container preserves the association between the client reference and a particular bean instance. Instance fields can therefore hold temporary selections or intermediate workflow data across several business method calls.
A stateful bean is useful when a conversation itself is an application concept. Examples include an assisted checkout, a multi-step product configurator, or a workflow that collects and validates choices before committing a final result. This model costs more runtime resources than a pooled stateless service because each active conversation requires an associated instance or a passivated representation.
The conversation has a lifecycle. The container creates the instance, performs dependency injection, and invokes lifecycle callbacks when present. An idle, passivation-capable instance may be passivated and later activated. A timeout declared with @StatefulTimeout can make an idle instance eligible for removal. A method annotated with @Remove can explicitly complete the conversation.
import jakarta.ejb.Remove;
import jakarta.ejb.Stateful;
import jakarta.ejb.StatefulTimeout;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Stateful
@StatefulTimeout(value = 20, unit = TimeUnit.MINUTES)
public class CheckoutConversation implements Serializable {
private final List<String> itemIds = new ArrayList<>();
public void addItem(String itemId) {
itemIds.add(itemId);
}
@Remove
public void complete() {
// Validate the conversation and persist the final order.
}
}
The example is intentionally abbreviated. Production code must validate identity, prices, availability, and business rules, then persist the final result in a transaction. Implementing Serializable and using serializable fields can support lifecycle requirements, but serialization does not make the conversation durable or guarantee recovery after a server failure.
| Concern | Stateless Session Bean | Stateful Session Bean |
|---|---|---|
| Client-specific conversational state | Not retained between calls | Retained across calls for one client conversation |
| Instance affinity | No client affinity between invocations | Client reference remains associated with its conversation |
| Pooling and reuse | Instances are normally pooled and reused | Instances are dedicated to active conversations |
| Resource cost | Usually lower per client because instances are shared over time | Usually higher because conversations require retained state |
| Passivation | Not passivated | Eligible instances may be passivated and activated |
| Transactions | Can use container-managed transactions | Can use container-managed transactions |
| Scaling | Often easier to distribute when durable data is shared | May require affinity, replication, or conversation recovery planning |
| Failure recovery | Reloads needed data for each operation | Temporary fields may be lost unless recovery is provided separately |
| Typical use | Independent service operations | Multi-call conversational workflows |
| Durable business data | Stored through persistence or another durable service | Stored through persistence or another durable service |
A shopping cart is a useful example because it can mean different things. A short-lived guest cart, an authenticated cart shared across devices, and a checkout workflow have different recovery and scaling requirements. No single component model is correct for every cart.
| Approach | State Location | Best Fit | Main Tradeoff |
|---|---|---|---|
@Stateful Enterprise Bean | Managed conversation fields | Bounded server-side workflow in a full Jakarta EE runtime | Client affinity and recovery planning |
CDI @SessionScoped bean | HTTP session context | Server-rendered web application | Session replication and expiration semantics |
| Stateless REST service with Jakarta Persistence | Database | Recoverable carts, multiple devices, and horizontal scaling | Database access and consistency design |
| Client-held draft | Browser or client application | Temporary user-interface convenience | Server must distrust and recalculate checkout values |
| External distributed store | Optional infrastructure | High-scale state sharing with explicit operational requirements | Additional product, consistency, and operations complexity |
A browser can retain draft item identifiers, but it must not be authoritative for prices, discounts, inventory, taxes, or payment totals. The server must authenticate the caller and validate every checkout decision. Products such as distributed caches may support a chosen architecture, but they are infrastructure decisions rather than Jakarta EE 11 requirements.
A CDI bean annotated with @SessionScoped belongs to the HTTP session context. It is a CDI contextual instance, not a stateful Enterprise Bean. This approach is natural for a server-rendered web application when the desired lifetime matches the HTTP session. CDI session scope is passivating, so the bean and its dependencies must satisfy passivation requirements.
import jakarta.enterprise.context.SessionScoped;
import jakarta.inject.Named;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
@Named
@SessionScoped
public class ShoppingCart implements Serializable {
private final List<Item> items = new ArrayList<>();
public void add(Item item) {
items.add(item);
}
public List<Item> getItems() {
return List.copyOf(items);
}
}
Domain types, validation, identity handling, serialization compatibility, and durable storage are omitted for clarity. An HTTP session can expire, and clustered session replication depends on runtime configuration. Important cart data should be persisted when the business requires recovery.
A Jakarta REST resource can remain request-oriented and delegate to a stateless application service. The request carries a cart identifier or uses the authenticated identity. The server then loads the authoritative cart, validates it, and commits a bounded operation.
import jakarta.inject.Inject;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.core.Response;
@Path("/carts")
public class CartResource {
@Inject
private CartService cartService;
@POST
@Path("/{cartId}/checkout")
public Response checkout(@PathParam("cartId") String cartId) {
cartService.checkout(cartId);
return Response.noContent().build();
}
}
The resource and service normally reside in separate source files. This design does not require the HTTP client to hold a server-side component reference between requests. It also makes durable recovery and multi-device access easier because the source of truth is independent of one web session or bean instance.
Both stateless and stateful session beans can use declarative transaction management. A transaction should protect one bounded business operation, such as validating a cart and creating an order. It should not remain open while a person moves through a workflow across multiple HTTP requests.
A stateful conversation may collect temporary choices over time and call a transactional method when the user completes the workflow. A stateless service can perform the same final operation after loading a persisted cart. In both designs, Jakarta Persistence stores durable entities and Jakarta Transactions controls the commit or rollback boundary.
Passivation, persistence, and failover solve different problems. Passivation allows the container to remove an idle stateful instance from active memory and restore it later. Persistence stores business data independently of a component instance. Failover or replication attempts to continue service after a runtime or node failure and depends on the selected product and configuration.
Neither passivation nor an in-memory HTTP session guarantees disaster recovery. Stateful components and web sessions may add affinity or replication costs in a cluster. Stateless services backed by shared durable data are often easier to scale horizontally, but a compact stateful conversation can still be appropriate when its lifecycle closely matches the business workflow.
Choose @Stateless when each operation can receive or load everything it needs. Choose @Stateful when one managed conversation must retain temporary, client-specific values across calls. Choose CDI @SessionScoped when the state naturally follows an HTTP session. Choose Jakarta Persistence when the data must survive restarts, support recovery, or remain available across devices and application instances.
These Enterprise Bean examples require a full Jakarta EE runtime or another compatible runtime that provides Jakarta Enterprise Beans. Tomcat 11 supplies the web container used by this site, but it does not by itself provide the complete Enterprise Beans component model.