Distributed Objects   «Prev  Next» 

Lesson 6Stateful and Stateless Session Beans
ObjectiveDescribe the difference between stateful and stateless session beans in Jakarta EE 11.

Stateful and Stateless Session Beans: Managing State 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.

Session Beans and Three Kinds of State

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.

Stateless Session Beans

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.

Stateless Service Example

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.

Stateful Session Beans

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.

Stateful Conversation Example

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.



Stateful and Stateless Comparison

ConcernStateless Session BeanStateful Session Bean
Client-specific conversational stateNot retained between callsRetained across calls for one client conversation
Instance affinityNo client affinity between invocationsClient reference remains associated with its conversation
Pooling and reuseInstances are normally pooled and reusedInstances are dedicated to active conversations
Resource costUsually lower per client because instances are shared over timeUsually higher because conversations require retained state
PassivationNot passivatedEligible instances may be passivated and activated
TransactionsCan use container-managed transactionsCan use container-managed transactions
ScalingOften easier to distribute when durable data is sharedMay require affinity, replication, or conversation recovery planning
Failure recoveryReloads needed data for each operationTemporary fields may be lost unless recovery is provided separately
Typical useIndependent service operationsMulti-call conversational workflows
Durable business dataStored through persistence or another durable serviceStored through persistence or another durable service

Modern Shopping-Cart Design Choices

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.

ApproachState LocationBest FitMain Tradeoff
@Stateful Enterprise BeanManaged conversation fieldsBounded server-side workflow in a full Jakarta EE runtimeClient affinity and recovery planning
CDI @SessionScoped beanHTTP session contextServer-rendered web applicationSession replication and expiration semantics
Stateless REST service with Jakarta PersistenceDatabaseRecoverable carts, multiple devices, and horizontal scalingDatabase access and consistency design
Client-held draftBrowser or client applicationTemporary user-interface convenienceServer must distrust and recalculate checkout values
External distributed storeOptional infrastructureHigh-scale state sharing with explicit operational requirementsAdditional 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.

CDI Session Scope and HTTP Sessions

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.

Stateless REST Services and Durable Cart Data

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.

Transactions, Persistence, and Conversation Boundaries

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, Failure Recovery, and Scaling

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.

Choosing Where State Should Live

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.


SEMrush Software