Im Java-EE-/Jakarta-EE-Umfeld bezeichnet „Session Bean“ eine serverseitige Komponente, die vom Application Server verwaltet wird. In WildFly bedeutet das ganz konkret: Der Container erstellt Instanzen, steuert deren Lebenszyklus und führt Methodenaufrufe in einem Rahmen aus, der dir Infrastruktur abnimmt. Du schreibst fachliche Logik, der Server liefert dir Dinge wie Transaktionen, Security, kontrollierte Nebenläufigkeit und Dependency Injection.
Session-Beans gehören zur EJB-Welt (Enterprise JavaBeans). In der Praxis triffst du sie heute meist über Annotationen wie @Stateless oder @Stateful. Damit sagst du dem Container: „Diese Klasse ist eine Session-Bean, bitte manage sie.“ Du musst sie dann nicht selbst instanziieren, sondern injizierst sie, zum Beispiel per @EJB oder in vielen Projekten einfach per @Inject.
Der Vollständigkeit halber gibt es auch @Singleton. In diesem Beitrag geht es aber bewusst vor allem um die beiden typischen Fälle @Stateless und @Stateful.
Ein wichtiger Punkt, der oft untergeht: „Session“ bedeutet hier nicht „HTTP-Session“. Es geht nicht um Web-Session-Daten im Browser. Bei Session-Beans meint der Begriff eher die Beziehung zwischen Client und serverseitiger Business-Komponente. Bei @Stateful kann daraus ein echter Konversationszustand über mehrere Aufrufe entstehen; bei @Stateless gerade nicht.
Was unterscheidet sie von einer klassischen Bean?
Eine gewöhnliche Bean im Sinne eines POJOs ist erstmal nur eine normale Java-Klasse. Du erzeugst sie selbst mit new oder lässt sie von einem Framework verwalten, und du kontrollierst, wann sie entsteht und wann sie wieder weg ist. Genau da liegt der Unterschied: Eine Session-Bean lebt im Container und läuft unter dessen Regeln.
Das hat konkrete Folgen:
- Der Container darf Aufrufe abfangen und erweitern, zum Beispiel für Transaktionsgrenzen oder Security-Checks.
- Der Container regelt Nebenläufigkeit. Du bekommst keine „zufälligen“ parallelen Aufrufe auf dieselbe Bean-Instanz, sondern ein kontrolliertes Modell.
- Der Container kann Instanzen poolen, vor allem bei Stateless Session-Beans, und damit Last besser abfedern.
Du kannst dir das so merken: Eine klassische Bean ist „nur Code“. Eine Session-Bean ist „Code plus Laufzeitvertrag“ mit dem Application Server.
Wofür setzt man sie ein?
Session-Beans sitzen typischerweise in der Service-Schicht, also dort, wo fachliche Use-Cases umgesetzt werden. Du packst hinein, was aus fachlicher Sicht zusammengehört: Validierung, Regeln, Orchestrierung mehrerer Repositories oder Integrationen.
Ein paar typische Szenarien, ohne dass es künstlich wird:
- Einen Auftrag anlegen, dabei prüfen, ob ein Kunde existiert, und anschließend speichern
- Einen Benutzerstatus ändern, inklusive Berechtigungsprüfung und Audit-Log
- Einen Prozess abschließen, der mehrere Schritte umfasst und transaktional sauber bleiben muss
Was eher nicht hinein gehört: reine Datenklassen (DTOs), Entities (@Entity), Mapper oder reine Utility-Klassen. Session-Beans sind am stärksten, wenn sie fachlich geschnitten sind und die Container-Services bewusst nutzen.
Stateless und Stateful im Vergleich
Der zentrale Unterschied ist simpel, aber entscheidend: Hält die Bean clientbezogenen Zustand über mehrere Aufrufe hinweg oder nicht?
Stateless Session Bean (@Stateless)
Eine Stateless Session Bean hält keinen clientbezogenen Zustand. Der Container darf eine Instanz für verschiedene Aufrufer wiederverwenden. Genau deshalb darfst du keinen „Pro Benutzer“-Zustand in Instanzfeldern speichern. Alles, was pro Request unterschiedlich ist, gehört in lokale Variablen, Parameter oder in die Datenbank.
Stateless ist in Business-Anwendungen der Standard, weil es sehr gut skaliert: Pooling funktioniert sauber, und du bekommst weniger Überraschungen, wenn Last steigt.
Kurzes Beispiel, so wie es in vielen Projekten aussieht:
import jakarta.ejb.Stateless;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
@Stateless
public class OrderService {
@PersistenceContext
private EntityManager em;
public void placeOrder(long customerId) {
Customer customer = em.find(Customer.class, customerId);
if (customer == null) {
throw new IllegalArgumentException("Kunde nicht gefunden");
}
em.persist(new Order(customer));
}
}
Hier passiert fachliche Logik im Methodenkörper. Der Container liefert den Rahmen: Der EntityManager wird injiziert, und bei Container-managed Transactions läuft die Methode ohne weitere Konfiguration mit dem Standard-Transaktionsattribut REQUIRED.
Stateful Session Bean (@Stateful)
Eine Stateful Session Bean hält Zustand pro Client. Das heißt: Instanzfelder dürfen bewusst genutzt werden, um einen mehrstufigen Ablauf abzubilden. Das ist sinnvoll, wenn du einen Workflow hast, der sich über mehrere Aufrufe erstreckt, zum Beispiel einen Warenkorb, einen Wizard oder einen mehrstufigen Freigabeprozess.
Ein realistisches Minimalbeispiel:
import jakarta.ejb.Stateful;
import jakarta.ejb.Remove;
@Stateful
public class CheckoutFlow {
private long customerId;
public void start(long customerId) {
this.customerId = customerId;
}
public void confirmAddress(String addressId) {
// Zustandsschritt, z.B. Adresse merken oder prüfen
}
@Remove
public void finish() {
// Abschluss, z.B. Bestellung auslösen
}
}
Mit @Remove sagst du dem Container: „Diese Konversation ist fertig, die Bean kann weg.“ Genau diese Lifecycle-Frage ist bei Stateful Session Beans der Knackpunkt.
Noch ein Praxisdetail: Bei Stateful Session Beans taucht häufig das Thema „Extended Persistence Context“ auf, wenn du über mehrere Schritte hinweg mit demselben Entity-Zustand arbeiten willst. Das ist ein spezielles Werkzeug und nicht automatisch nötig. Viele Teams lösen Workflows auch über explizite Persistierung pro Schritt und bleiben dabei stateless.
Worauf du in der Praxis achten solltest
Die meisten Probleme mit Session-Beans kommen nicht daher, dass EJB kompliziert wäre, sondern weil man die Regeln ignoriert.
- Keine Zustände in Stateless-Feldern. Ein
@Stateless-Service mit Instanzfeldern als Cache oder „aktueller Benutzer“ wird früher oder später kaputtgehen. Wenn Last dazukommt, bekommst du Race-Conditions und Daten, die scheinbar „springen“. - Stateful nur bei echtem Bedarf. Stateful Session Beans binden Ressourcen, weil pro Client Zustand gehalten wird. Das kann Speicher kosten, Timeouts triggern und in Clustern zusätzliches Handling erfordern.
- Transaktionen bewusst nutzen. In Java EE und Jakarta EE sind Transaktionen oft standardmäßig aktiv, aber das heißt nicht, dass jede Methode automatisch genau so committen soll, wie du es fachlich brauchst. Gerade bei mehrstufigen Abläufen solltest du wissen, ob du pro Schritt oder erst am Ende verbindlich speichern willst.
- Keine eigenen Threads starten. In einem Container wie WildFly ist „ich starte mal eben einen Thread“ meistens ein Fehler, weil du damit aus dem Managed-Umfeld rausläufst. Wenn du parallelisieren musst, nutze Container-Mechanismen.
- Schnittstellen sauber halten. Session-Beans wirken am besten, wenn sie klar geschnittene Methoden anbieten und intern nicht zu viel Mischmasch haben. Viele kleine, fachliche Methoden schlagen eine riesige „God-Bean“.
Wenn du nur eine Faustregel mitnehmen willst: Starte mit @Stateless. Greif zu @Stateful, wenn du wirklich einen zustandsbehafteten Prozess modellieren musst und du den Lebenszyklus im Griff hast.
Fazit
Eine Session Bean ist im Java-EE-/Jakarta-EE-Kontext eine vom Application Server verwaltete Business-Komponente. Gegenüber einer klassischen Bean gewinnt sie ihre Bedeutung durch den Container: Lifecycle, Transaktionen, Security und kontrollierte Nebenläufigkeit. In der Praxis ist @Stateless der Normalfall, weil es sauber skaliert und wenig Nebenwirkungen hat. @Stateful ist das Spezialwerkzeug für mehrstufige Abläufe, bei denen Zustand zwischen Calls notwendig ist und du den Abschluss bewusst markierst.
