1. JSF Lifecycle (6 Phasen)

Jeder Request durchläuft grundsätzlich sechs Phasen. Bei Konvertierungs-/Validierungsfehlern oder wenn responseComplete() bzw. renderResponse() aufgerufen wird, springt JSF direkt zu Render Response.

1Restore View
2Apply Request Values
3Process Validations
4Update Model Values
5Invoke Application
6Render Response
Direktsprung zu Render Response: Tritt in Phase 2–5 ein Konvertierungs- oder Validierungsfehler auf (oder wird FacesContext.responseComplete()/renderResponse() aufgerufen), überspringt JSF die restlichen Phasen und rendert sofort mit den Fehlermeldungen.
immediate="true": Komponenten mit diesem Attribut (z. B. ein "Abbrechen"-Button) führen Konvertierung/Validierung bereits in Phase 2 (Apply Request Values) statt in Phase 3 aus – so lassen sich Validierungen gezielt umgehen.

Kurzbeschreibung der Phasen

PhaseWas passiert
1. Restore ViewDer Component Tree der angeforderten View wird aus dem State (oder neu) aufgebaut. Bei einem Erst-Request wird eine neue View erzeugt, bei einem Postback der gespeicherte View-State wiederhergestellt.
2. Apply Request ValuesDie per HTTP übermittelten Werte werden auf die jeweiligen UIComponents als "lokaler Wert" (Submitted Value) übertragen, ohne sie schon in Bean-Properties zu schreiben.
3. Process ValidationsFür jede Komponente wird der übermittelte String mittels Converter in ein Java-Objekt umgewandelt und anschließend mit registrierten Validatoren geprüft. Bei Fehlern werden FacesMessages erzeugt und direkt zu Render Response gesprungen.
4. Update Model ValuesDie konvertierten und validierten Werte werden tatsächlich in die per EL gebundenen Bean-Properties geschrieben (Aufruf der Setter).
5. Invoke ApplicationAction-Methoden (z. B. Button-Klick) und Action-Listener werden ausgeführt; das Ergebnis bestimmt über die Navigation die nächste anzuzeigende View.
6. Render ResponseDer (ggf. neue) Component Tree wird als HTML an den Client zurückgegeben; der View-State wird für den nächsten Postback gesichert.

2. Wichtige Annotationen

Annotation Herkunft Zweck
@NamedCDIMacht eine Klasse als CDI-Bean per Expression Language (#{beanName}) ansprechbar. Standard-Ersatz für das alte @ManagedBean.
@ManagedBeanJSF (legacy)Alte, JSF-eigene Bean-Verwaltung (javax.faces.bean). Seit JSF 2.3 deprecated – heute @Named + CDI verwenden.
@RequestScopedCDIBean-Instanz lebt nur für die Dauer eines einzelnen HTTP-Requests.
@ViewScopedCDI (jakarta.faces.view)Bean lebt so lange wie die aktuelle View angezeigt wird – bleibt über mehrere Ajax-Postbacks erhalten, wird bei Navigation auf eine andere Seite zerstört.
@SessionScopedCDIEine Instanz pro HTTP-Session des Benutzers, z. B. für Login-Status oder Warenkorb.
@ApplicationScopedCDIGenau eine Instanz für die gesamte Anwendung, geteilt von allen Benutzern (z. B. für Caches/Konfiguration).
@ConversationScopedCDIBean lebt über mehrere Views hinweg, bis die Conversation explizit beendet wird (typisch für mehrstufige Wizards).
@InjectCDIInjiziert eine andere Bean bzw. Abhängigkeit in ein Feld, Konstruktor oder Setter.
@ManagedPropertyJSF (legacy)Injiziert Werte oder andere Managed Beans in eine alte JSF-Bean. Durch @Inject ersetzt.
@FacesConverterJSFMarkiert eine Klasse als eigenen Converter, der zwischen Anzeige-String und Java-Objekt umwandelt.
@FacesValidatorJSFMarkiert eine Klasse als eigenen Validator zur Prüfung von Eingabewerten.
@FacesComponentJSFDefiniert eine eigene UIComponent-Klasse zur Erweiterung des Component Trees.
@FacesRendererJSFDefiniert einen eigenen Renderer, der bestimmt, wie eine Komponente als HTML ausgegeben wird.
@FacesBehaviorJSFDefiniert ein eigenes Client-Behavior, z. B. zur Erweiterung von <f:ajax>.

Hinweis: In neueren Jakarta-EE-Versionen liegen die Pakete unter jakarta.* statt javax.* – die Konzepte und Namen der Annotationen bleiben gleich.

3. Composite Component – Codebeispiel

Composite Components sind eigene, wiederverwendbare Facelets-Tags, die im /resources-Ordner abgelegt werden.

/resources/components/labeledInput.xhtml (Definition)
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:composite="http://xmlns.jcp.org/jsf/composite"
      xmlns:h="http://xmlns.jcp.org/jsf/html">

<composite:interface>
    <composite:attribute name="label" required="true" />
    <composite:attribute name="value" required="true" />
    <composite:attribute name="required" default="false" />
</composite:interface>

<composite:implementation>
    <h:outputLabel value="#{cc.attrs.label}" for="input" />
    <h:inputText id="input"
                 value="#{cc.attrs.value}"
                 required="#{cc.attrs.required}" />
    <h:message for="input" style="color:red" />
</composite:implementation>
</html>
Verwendung in einer beliebigen Seite
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:my="http://xmlns.jcp.org/jsf/composite/components">

    <my:labeledInput label="Name" value="#{userBean.name}" required="true" />

</html>

cc.attrs.xxx greift innerhalb der Komponente auf die per composite:attribute deklarierten Attribute zu. Der Namespace-Pfad jsf/composite/<ordnername> ergibt sich automatisch aus dem Unterordner in /resources.

4. CDI-Scopes im Detail

Lebensdauer: 1 Request

@RequestScoped

Neue Instanz bei jedem HTTP-Request. Einfachste, zustandslose Variante – geeignet für einfache Formulare/Listenanzeigen ohne Zustand über mehrere Requests hinweg.

Lebensdauer: aktuelle View

@ViewScoped

Bleibt erhalten, solange der Benutzer auf derselben Seite bleibt – auch über mehrere Ajax-Postbacks. Wird beim Navigieren zu einer anderen View zerstört. Ideal für Formulare mit mehreren Ajax-Interaktionen (z. B. Auswahl einer Tabellenzeile, mehrstufige Eingabemasken auf einer Seite).

Lebensdauer: HTTP-Session

@SessionScoped

Eine Instanz pro Benutzer-Session, bis Logout/Session-Timeout. Typisch für Login-Informationen, Spracheinstellung, Warenkorb.

Lebensdauer: gesamte Anwendung

@ApplicationScoped

Genau eine Instanz, geteilt von allen Benutzern und Sessions. Für globale Konfiguration, Referenzdaten oder Caches. Muss thread-sicher sein, da parallel von mehreren Benutzern genutzt.

Lebensdauer: explizite Conversation

@ConversationScoped

Muss aktiv gestartet (conversation.begin()) und beendet (conversation.end()) werden. Überlebt danach mehrere Views – nützlich für mehrseitige Wizards, bei denen @ViewScoped zu kurz und @SessionScoped zu lang wäre.

Lebensdauer: Dependent (Default)

@Dependent

Der CDI-Default-Scope, falls keiner angegeben ist. Es wird bei jeder Injection eine neue Instanz erzeugt, deren Lebenszyklus an das injizierende Objekt gekoppelt ist – kein geteilter Zustand.

Praxis-Tipp: Für klassische JSF-Seiten mit Formularen und Ajax ist @ViewScoped in Kombination mit @Named der mit Abstand häufigste Fall, da der Formularzustand über mehrere Ajax-Requests hinweg erhalten bleiben muss, ohne die ganze Session zu belasten.

5. JSF-Scopes (nativ)

Vor CDI hatte JSF eigene Scope-Annotationen unter javax.faces.bean.* (JSF-Managed-Beans). Technisch werden diese Scopes einfach auf die Standard-Servlet-Objekte abgebildet. Ein Sonderfall ist der Flash-Scope, den es nur in JSF gibt – er existiert in CDI nicht.

ScopeSpeicherort (technisch)Besonderheit
@RequestScopedAttribute von HttpServletRequestIdentisch zum CDI-Pendant, nur andere Paket-Herkunft (javax.faces.bean statt CDI).
@ViewScopedUIViewRoot.getViewMap() – Teil des Component TreesUrsprünglich JSF-spezifisch; wurde später als javax.faces.view.ViewScoped auch CDI-kompatibel gemacht.
@SessionScopedAttribute von HttpSessionIdentisch zum CDI-Pendant.
@ApplicationScopedAttribute von ServletContextIdentisch zum CDI-Pendant.
@NoneScopeBean wird nicht zwischengespeichert; jeder EL-Zugriff erzeugt eine neue Instanz (kein CDI-Äquivalent nötig, da @Dependent ähnlich wirkt).
Flash-ScopeExternalContext.getFlash() – Cookie-basiertes Kurzzeit-AttributÜberlebt genau einen Redirect. Ideal, um nach einem Redirect (z. B. Speichern → Weiterleitung) noch eine Erfolgsmeldung anzuzeigen. Gibt es nur in JSF, nicht in CDI.

Flash-Scope – Beispiel

OrderBean.java – Nachricht vor dem Redirect setzen
public String save() {
    // ... Bestellung speichern ...
    FacesContext.getCurrentInstance()
        .getExternalContext()
        .getFlash()
        .put("message", "Bestellung erfolgreich gespeichert!");
    return "orders?faces-redirect=true";
}
orders.xhtml – Nachricht nach dem Redirect anzeigen
<h:outputText value="#{flash.message}" rendered="#{not empty flash.message}"
              style="color:green" />

Historische Einordnung: JSF 1.x kannte nur XML-Konfiguration in faces-config.xml (<managed-bean>), JSF 2.x führte die eigenen Annotationen ein, und mit CDI-Integration (heute Standard) werden Beans über @Named + CDI-Scopes verwaltet. Native JSF-Scope-Annotationen gelten seit JSF 2.3 als deprecated.

7. Fehlerbehandlung – Beispiel

JSF-Anwendungen behandeln Fehler auf mehreren Ebenen: Feldvalidierung, programmatische Fehlermeldungen, globale Exception-Behandlung und Ajax-Fehler.

1. Feld-/Validierungsfehler (Standardfall)

Wird bereits durch Converter/Validatoren in Phase 3 des Lifecycles abgedeckt (siehe Abschnitt 1 und 9) – Ausgabe über <h:message> bzw. <h:messages>.

2. Programmatische Fehlermeldung in der Bean

public String save() {
    if (orderService.isDuplicate(order)) {
        FacesContext.getCurrentInstance().addMessage("orderForm:orderNumber",
            new FacesMessage(FacesMessage.SEVERITY_ERROR,
                "Bestellnummer existiert bereits", null));
        return null; // Seite erneut anzeigen, Meldung erscheint
    }
    return "confirmation?faces-redirect=true";
}

3. Globale Exception-Behandlung (unerwartete Fehler)

Unbehandelte Exceptions im Lifecycle lassen sich zentral über einen eigenen ExceptionHandler abfangen, z. B. um auf eine Fehlerseite umzuleiten statt einen Stacktrace anzuzeigen:

CustomExceptionHandler.java
public class CustomExceptionHandler extends ExceptionHandlerWrapper {

    private final ExceptionHandler wrapped;
    public CustomExceptionHandler(ExceptionHandler wrapped) { this.wrapped = wrapped; }

    @Override
    public ExceptionHandler getWrapped() { return wrapped; }

    @Override
    public void handle() throws FacesException {
        Iterator<ExceptionQueuedEvent> events = getUnhandledExceptionQueuedEvents().iterator();
        while (events.hasNext()) {
            Throwable t = events.next().getContext().getException();
            try {
                FacesContext fc = FacesContext.getCurrentInstance();
                fc.getExternalContext().getRequestMap().put("errorMessage", t.getMessage());
                fc.getApplication().getNavigationHandler()
                  .handleNavigation(fc, null, "/error?faces-redirect=true");
                fc.renderResponse();
            } finally {
                events.remove(); // als behandelt markieren
            }
        }
        getWrapped().handle();
    }
}
CustomExceptionHandlerFactory.java + Registrierung
public class CustomExceptionHandlerFactory extends ExceptionHandlerFactory {
    public CustomExceptionHandlerFactory(ExceptionHandlerFactory parent) { super(parent); }

    @Override
    public ExceptionHandler getExceptionHandler() {
        return new CustomExceptionHandler(getWrapped().getExceptionHandler());
    }
}
<!-- faces-config.xml -->
<factory>
    <exception-handler-factory>
        com.example.CustomExceptionHandlerFactory
    </exception-handler-factory>
</factory>

4. Fehlerseiten für unbehandelte Exceptions/HTTP-Codes (web.xml)

<error-page>
    <exception-type>java.lang.Exception</exception-type>
    <location>/error.xhtml</location>
</error-page>
<error-page>
    <error-code>404</error-code>
    <location>/notfound.xhtml</location>
</error-page>

5. Fehlerbehandlung bei Ajax-Requests

Ein serverseitiger Fehler während eines Ajax-Postbacks löst standardmäßig kein sichtbares Feedback aus. Über onerror lässt sich ein JavaScript-Callback registrieren:

<h:commandButton value="Speichern" action="#{orderBean.save}">
    <f:ajax execute="@form" render="@form" onerror="handleAjaxError" />
</h:commandButton>

<script>
function handleAjaxError(data) {
    if (data.status === "error") {
        alert("Es ist ein Fehler aufgetreten: " + data.description);
    }
}
</script>

8. Facelets & Expression Language

Was sind Facelets?

Facelets ist seit JSF 2.0 die Standard-View-Technologie (löst das ältere JSP ab). Seiten werden als normales XHTML geschrieben und dabei mit JSF-Tag-Libraries angereichert. Wichtige Eigenschaften: Templating (Master-Pages, wiederverwendbare Fragmente), Composite Components, Kompilierung des XHTML zu einem Component Tree zur Laufzeit, und – da es "nur" XHTML ist – gute Vorschau-/Editor-Unterstützung, weil die Datei auch ohne Server-Rendering syntaktisch gültiges HTML bleibt.

Expression Language (EL)

Mit EL werden Komponenten-Attribute an Bean-Properties bzw. -Methoden gebunden. JSF/Facelets verwendet dafür die geschweifte Klammer-Syntax #{...} (deferred, verzögerte Auswertung – kann sowohl gelesen als auch geschrieben werden, z. B. für value-Bindings). Die ${...}-Syntax (immediate, sofortige Auswertung, nur lesend) stammt aus JSP/JSTL und sollte in Facelets vermieden werden.

AusdruckBedeutung
#{bean.property}Value-Expression: liest/schreibt eine Bean-Property (Getter/Setter).
#{bean.doSomething}Method-Expression: ruft eine parameterlose Methode auf, z. B. als Action.
#{bean.doSomething(param)}Method-Expression mit Parametern (seit EL 2.2).
#{not empty bean.list}Operatoren: and, or, not, empty, Vergleiche wie ==, gt, lt.
#{facesContext}, #{view}, #{request}, #{session}, #{application}, #{flash}, #{resource}, #{cc}Implizite Objekte, die EL ohne weitere Deklaration zur Verfügung stellt (#{cc} nur innerhalb einer Composite Component).

Tag Libraries

Facelets-Seiten binden mehrere Standard-Tag-Libraries per XML-Namespace ein:

PrefixNamespaceZweck / Beispiel-Tags
h:jsf/htmlHTML-Render-Komponenten: h:form, h:inputText, h:commandButton, h:dataTable, h:message.
f:jsf/coreNicht-visuelle Kern-Tags: f:convertDateTime, f:validateLongRange, f:ajax, f:param, f:facet, f:viewParam.
ui:jsf/faceletsTemplating: ui:composition, ui:insert, ui:define, ui:decorate, ui:include, ui:param.
composite:jsf/compositeAufbau eigener Composite Components: composite:interface, composite:implementation, composite:attribute.
c:jsp/jstl/coreJSTL-Tags wie c:forEach, c:if – wirken zur Build-Zeit der Seite, nicht im Component Tree; nützlich, um Teile der Seite bedingt gar nicht erst zu erzeugen.
p:, o:DrittanbieterComponent-Bibliotheken wie PrimeFaces (p:) oder OmniFaces (o:) ergänzen zusätzliche Komponenten/Utilities.

Ajax-Nutzung

<f:ajax> macht praktisch jede JSF-Komponente ajaxfähig, ohne JavaScript schreiben zu müssen. Wichtige Attribute:

AttributBedeutung
executeWelche Komponenten(-Werte) an den Server gesendet und verarbeitet werden (@this, @form, @all, @none oder konkrete IDs).
renderWelche Teile des Component Trees nach der Antwort neu gerendert werden.
eventAuslösendes DOM-/JSF-Event, z. B. click, blur, valueChange (Default hängt von der Komponente ab).
listenerOptionale Bean-Methode, die bei Auslösung aufgerufen wird (zusätzlich zur normalen Action).
onevent / onerrorJavaScript-Callbacks für erfolgreiche bzw. fehlerhafte Ajax-Antworten.

Mögliche Werte für execute und render

execute steuert, welche Komponenten vor dem Request in die Verarbeitung (Phasen 2–4 des Lifecycles) einbezogen werden; render steuert, welche Teile des Component Trees nach der Antwort (Phase 6) im Browser-DOM aktualisiert werden. Beide Attribute akzeptieren dieselbe Syntax: reservierte @-Schlüsselwörter, konkrete Client-IDs oder eine mit Leerzeichen getrennte Kombination aus beidem.

WertBedeutung
@thisNur die Komponente, an der f:ajax hängt. Default für execute.
@noneGar nichts. Bei execute heißt das: keine Werte werden verarbeitet/aktualisiert. Default für render – ohne explizite Angabe wird also standardmäßig nichts neu gerendert (außer implizit ausgelöste Fehlermeldungen).
@formDas umschließende <h:form> der Komponente – der mit Abstand häufigste Wert, da damit sämtliche Felder eines Formulars mitverarbeitet bzw. aktualisiert werden.
@allDie komplette View (ganze Seite) – bei render praktisch ein "Ajax-Vollreload" des Bodys, bei execute werden alle Formularwerte der Seite verarbeitet.
Konkrete Client-ID(s)Eine oder mehrere per Leerzeichen getrennte IDs, z. B. render="dobMsg ageMsg". IDs können relativ (im selben NamingContainer) oder absolut mit führendem Doppelpunkt angegeben werden, z. B. :mainForm:summaryPanel.
@namingcontainer(seit JSF 2.3) Der nächste umschließende NamingContainer (z. B. ein h:panelGroup mit eigener id oder eine Composite Component) – praktisch, um relativ zu "meinem Container" zu rendern, ohne dessen ID hart zu verdrahten.
@composite(seit JSF 2.3) Die umschließende Composite Component, falls die aktuelle Komponente innerhalb einer solchen liegt.
@parent(seit JSF 2.3) Die direkte Elternkomponente im Component Tree.
@child(n)(seit JSF 2.3) Das n-te Kind (0-basiert) der aktuellen Komponente.
@previous / @next(seit JSF 2.3) Die vorherige bzw. nächste Geschwisterkomponente auf derselben Ebene.
@id(id)(seit JSF 2.3) Sucht eine Komponente mit dieser id unabhängig von ihrer Position im Baum – nützlich, wenn die relative Struktur unbekannt/variabel ist.

Beispiele: render="@form" aktualisiert das komplette Formular (z. B. nach Absenden mit möglichen Validierungsfehlern in mehreren Feldern); render="@this" aktualisiert nur die auslösende Komponente selbst (z. B. ein Zähler-Label); render="dobMsg ageMsg summaryPanel" aktualisiert gezielt mehrere einzelne Bereiche; render="@none" (bzw. weglassen) unterdrückt jede DOM-Aktualisierung, wenn nur eine Listener-Methode im Hintergrund laufen soll. Wird eine angegebene ID nicht gefunden, wird dieser Teil der Angabe von den meisten Implementierungen stillschweigend ignoriert statt einen Fehler zu werfen – beim Debuggen "warum aktualisiert sich mein Panel nicht" lohnt sich daher immer ein Blick auf Tippfehler in der ID bzw. auf den korrekten NamingContainer-Präfix.

Beispiel: abhängiges Auswahlfeld (Land → Stadt) per Ajax
<h:selectOneMenu value="#{orderBean.country}">
    <f:selectItems value="#{orderBean.countries}" />
    <f:ajax listener="#{orderBean.onCountryChange}" render="citySelect" />
</h:selectOneMenu>

<h:selectOneMenu id="citySelect" value="#{orderBean.city}">
    <f:selectItems value="#{orderBean.citiesForSelectedCountry}" />
</h:selectOneMenu>

9. Konkretes Facelets-Beispiel

Ein Template mit ui:insert, eine Seite, die es über ui:composition und ui:define nutzt, sowie ein Formular mit Converter, Validator und Ajax.

/WEB-INF/templates/layout.xhtml (Master-Template)
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
      xmlns:h="http://xmlns.jcp.org/jsf/html">
<h:head>
    <title><ui:insert name="title">Meine App</ui:insert></title>
</h:head>
<h:body>
    <div id="header"><ui:insert name="header">Standard-Header</ui:insert></div>
    <div id="content"><ui:insert name="content" /></div>
    <div id="footer">© 2026 – Meine App</div>
</h:body>
</html>
/register.xhtml (nutzt das Template)
<ui:composition template="/WEB-INF/templates/layout.xhtml"
      xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:f="http://xmlns.jcp.org/jsf/core">

    <ui:define name="title">Registrierung</ui:define>

    <ui:define name="content">
        <h:form>
            <h:panelGrid columns="3">

                <h:outputLabel value="Geburtsdatum" for="dob" />
                <h:inputText id="dob" value="#{registerBean.birthDate}">
                    <f:convertDateTime pattern="dd.MM.yyyy" />
                    <f:ajax event="blur" render="dobMsg" />
                </h:inputText>
                <h:message id="dobMsg" for="dob" style="color:red" />

                <h:outputLabel value="Alter" for="age" />
                <h:inputText id="age" value="#{registerBean.age}">
                    <f:validateLongRange minimum="18" maximum="120" />
                    <f:ajax event="blur" render="ageMsg" />
                </h:inputText>
                <h:message id="ageMsg" for="age" style="color:red" />

            </h:panelGrid>

            <h:commandButton value="Absenden" action="#{registerBean.submit}" />
        </h:form>
    </ui:define>
</ui:composition>
RegisterBean.java (Backing Bean)
@Named
@ViewScoped
public class RegisterBean implements Serializable {

    private LocalDate birthDate;
    private int age;

    public String submit() {
        // Geschäftslogik, z. B. Speichern in DB
        return "success?faces-redirect=true";
    }

    // getters & setters
    public LocalDate getBirthDate() { return birthDate; }
    public void setBirthDate(LocalDate birthDate) { this.birthDate = birthDate; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
}

Ablauf im Lifecycle: Beim Absenden werden die Strings aus dob/age in Phase 2 übernommen, in Phase 3 per f:convertDateTime bzw. f:validateLongRange konvertiert/validiert (bei Fehler → sofort Render Response mit h:message), in Phase 4 in die Bean-Properties geschrieben, in Phase 5 wird submit() aufgerufen, und in Phase 6 erfolgt der Redirect zur nächsten Seite.

10. GUI-Elemente ein- und ausblenden

Für das Ein-/Ausblenden von Bereichen gibt es in JSF drei gängige Varianten – mit unterschiedlichen Kompromissen bei Server-Traffic, Sicherheit und Reaktionsgeschwindigkeit.

VarianteServer-RoundtripEigenschaft
A: rendered ohne AjaxJa (voller Postback)Element wird bei rendered="false" gar nicht ins HTML geschrieben. Ganze Seite wird neu geladen.
B: rein clientseitig (CSS/JS)NeinSofortige Reaktion ohne Server-Kontakt, aber Element bleibt im DOM (nur versteckt) – nicht für sensible Inhalte geeignet.
C: rendered mit AjaxJa (nur Teilbereich)Wie A, aber nur der betroffene Panel-Bereich wird neu gerendert – kein voller Seiten-Reload, bessere UX.

A: Ohne Ajax – serverseitig über rendered

Der Klassiker: Ein Boolean-Feld in der Bean steuert das rendered-Attribut. Jeder Klick löst einen vollständigen Postback aus, die komplette Seite wird neu aufgebaut und gerendert.

form.xhtml
<h:form>
    <h:selectBooleanCheckbox value="#{formBean.showDetails}" />
    <h:outputLabel value="Zusatzinfos anzeigen" />
    <h:commandButton value="Aktualisieren" action="#{formBean.refresh}" />

    <h:panelGroup rendered="#{formBean.showDetails}" layout="block">
        <h:outputLabel value="Kommentar" for="comment" />
        <h:inputTextarea id="comment" value="#{formBean.details}" />
    </h:panelGroup>
</h:form>
FormBean.java
@Named
@ViewScoped
public class FormBean implements Serializable {
    private boolean showDetails;
    private String details;

    public String refresh() { return null; } // kein Navigationsziel, nur Postback
    // getters & setters
}

B: Ohne Ajax – rein clientseitig (kein Server-Kontakt)

Wenn keine Serverdaten beteiligt sind, reicht reines JavaScript/CSS – schneller, aber das Element bleibt (versteckt) im DOM und wird trotzdem an den Client übertragen.

form.xhtml
<h:commandButton type="button" value="Details anzeigen/ausblenden"
    onclick="var p = document.getElementById('detailsPanel');
             p.style.display = (p.style.display === 'none') ? 'block' : 'none';" />

<h:panelGroup id="detailsPanel" layout="block" style="display:none">
    <h:outputText value="Diese Zusatzinfos werden nur im Browser ein-/ausgeblendet." />
</h:panelGroup>

type="button" verhindert, dass der Button ein Formular absendet. Da hier keine rendered-Logik greift, bleibt der Inhalt immer im Component Tree und im HTML vorhanden.

C: Mit Ajax – serverseitig über rendered + f:ajax

Kombiniert das Beste aus beiden Welten: Der Zustand wird weiterhin serverseitig über die Bean gesteuert (Element also bei rendered="false" wirklich nicht im HTML), aber es wird nur der umschließende Bereich neu gerendert – kein Seiten-Reload.

form.xhtml
<h:form>
    <h:selectBooleanCheckbox value="#{formBean.showDetails}">
        <f:ajax render="detailsPanel" />
    </h:selectBooleanCheckbox>
    <h:outputLabel value="Zusatzinfos anzeigen" />

    <h:panelGroup id="detailsPanel" layout="block">
        <h:panelGrid columns="2" rendered="#{formBean.showDetails}">
            <h:outputLabel value="Kommentar" for="comment" />
            <h:inputTextarea id="comment" value="#{formBean.details}" />
        </h:panelGrid>
    </h:panelGroup>
</h:form>

Ablauf: h:selectBooleanCheckbox löst standardmäßig bei change ein Ajax-Request aus; showDetails wird in Phase 4 (Update Model Values) aktualisiert, anschließend liefert Phase 6 (Render Response) nur das detailsPanel zurück, da genau dieses per render angefordert wurde. Ein zusätzlicher listener ist hier nicht nötig, da das Value-Binding allein für die Sichtbarkeitssteuerung ausreicht.

11. Bean Validation (JSR 380)

Statt Validierung über f:validate*-Tags in der View zu deklarieren, lassen sich Regeln direkt am Model annotieren (Jakarta Bean Validation, früher JSR 303/380). JSF ruft dafür standardmäßig automatisch einen BeanValidator in Phase 3 (Process Validations) auf – ganz ohne zusätzliche Konfiguration.

UserDto.java – Validierungsregeln am Model
public class UserDto {

    @NotNull
    @Size(min = 2, max = 50)
    private String name;

    @NotNull
    @Email
    private String email;

    @Min(18)
    private int age;

    // getters & setters
}
RegisterBean.java
@Named
@ViewScoped
public class RegisterBean implements Serializable {

    private final UserDto user = new UserDto();

    public String submit() {
        // wird nur erreicht, wenn alle Bean-Validation-Constraints erfüllt sind
        return "success?faces-redirect=true";
    }

    public UserDto getUser() { return user; }
}
register.xhtml
<h:form>
    <h:inputText value="#{registerBean.user.name}" />
    <h:message for="..." />

    <h:inputText value="#{registerBean.user.email}" />
    <h:inputText value="#{registerBean.user.age}">
        <f:validateBean disabled="false" />
    </h:inputText>

    <h:commandButton value="Registrieren" action="#{registerBean.submit}" />
</h:form>

Mit <f:validateBean validationGroups="..."/> lassen sich Validierungsgruppen gezielt aktivieren, mit disabled="true" die automatische Bean Validation für einzelne Felder deaktivieren (z. B. wenn dort ausschließlich ein eigener Validator laufen soll).

12. Eigener Converter & Validator – Codebeispiel

Eigener Converter

Ein Converter wandelt zwischen dem Anzeige-String im Browser und einem Java-Objekt um – hier ein Beispiel, das eine Produkt-ID in ein vollständiges Product-Objekt auflöst (nützlich z. B. in h:selectOneMenu).

ProductConverter.java
@FacesConverter(value = "productConverter", managed = true)
public class ProductConverter implements Converter<Product> {

    @Inject
    private ProductService productService;

    @Override
    public Product getAsObject(FacesContext context, UIComponent component, String value) {
        if (value == null || value.isBlank()) return null;
        return productService.findById(Long.valueOf(value));
    }

    @Override
    public String getAsString(FacesContext context, UIComponent component, Product product) {
        return product == null ? "" : String.valueOf(product.getId());
    }
}
Verwendung
<h:selectOneMenu value="#{orderBean.selectedProduct}" converter="productConverter">
    <f:selectItems value="#{orderBean.availableProducts}" var="p"
                   itemLabel="#{p.name}" itemValue="#{p}" />
</h:selectOneMenu>

managed = true sorgt dafür, dass CDI-Injection (hier @Inject ProductService) innerhalb des Converters funktioniert – ohne dieses Attribut würde JSF den Converter selbst instanziieren, ohne CDI zu berücksichtigen.

Eigener Validator

StrongPasswordValidator.java
@FacesValidator(value = "strongPasswordValidator", managed = true)
public class StrongPasswordValidator implements Validator<String> {

    @Override
    public void validate(FacesContext context, UIComponent component, String value) {
        if (value == null || value.length() < 8 || !value.matches(".*[A-Z].*")) {
            throw new ValidatorException(new FacesMessage(FacesMessage.SEVERITY_ERROR,
                "Passwort muss mind. 8 Zeichen und einen Großbuchstaben enthalten", null));
        }
    }
}
Verwendung
<h:inputSecret value="#{registerBean.password}" validator="strongPasswordValidator" />
<h:message for="password" />

13. File Upload

Seit JSF 2.2 unterstützt <h:inputFile> Datei-Uploads direkt als Standardkomponente – das Formular benötigt dafür enctype="multipart/form-data".

upload.xhtml
<h:form enctype="multipart/form-data">
    <h:inputFile value="#{uploadBean.file}" />
    <h:commandButton value="Hochladen" action="#{uploadBean.upload}" />
</h:form>
UploadBean.java
@Named
@RequestScoped
public class UploadBean {

    private Part file; // jakarta.servlet.http.Part

    public String upload() throws IOException {
        try (InputStream in = file.getInputStream()) {
            Files.copy(in, Paths.get("/uploads/" + file.getSubmittedFileName()),
                       StandardCopyOption.REPLACE_EXISTING);
        }
        return "success?faces-redirect=true";
    }

    public Part getFile() { return file; }
    public void setFile(Part file) { this.file = file; }
}
web.xml – maximale Upload-Größe begrenzen
<servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <multipart-config>
        <max-file-size>10485760</max-file-size>      <!-- 10 MB pro Datei -->
        <max-request-size>20971520</max-request-size> <!-- 20 MB gesamt -->
    </multipart-config>
</servlet>

14. View Parameter & Post-Redirect-Get

<f:viewParam> bindet GET-Query-Parameter an Bean-Properties – damit werden Seiten bookmarkbar und per Browser-Reload sicher wiederholbar, ohne dass Formulardaten erneut gesendet werden müssen.

product.xhtml
<f:metadata>
    <f:viewParam name="id" value="#{productBean.productId}" />
    <f:viewAction action="#{productBean.loadProduct}" />
</f:metadata>

<h:body>
    <h1>#{productBean.product.name}</h1>
    <p>#{productBean.product.description}</p>
</h:body>
ProductBean.java
@Named
@ViewScoped
public class ProductBean implements Serializable {

    private Long productId;
    private Product product;

    public void loadProduct() {
        product = productService.findById(productId);
    }
    // getters & setters
}
Bookmarkbarer Link auf die Detailseite
<h:link value="Details" outcome="product">
    <f:param name="id" value="#{p.id}" />
</h:link>
<!-- erzeugt z. B.: /product.xhtml?id=42 -->

Post-Redirect-Get (PRG): Eine Action-Methode, die nach einem POST mit "...?faces-redirect=true" antwortet (siehe Abschnitt 6), verhindert, dass ein Browser-Reload das Formular erneut absendet – der Browser zeigt nach dem Redirect nur noch eine GET-Anfrage in der Historie. Mit &includeViewParams=true werden dabei zusätzlich vorhandene f:viewParam-Werte an die Ziel-URL angehängt.

15. Resource Handling

Statische Ressourcen (CSS, JavaScript, Bilder) werden in JSF über den Ordner /resources organisiert und mit eigenen Tags statt reiner HTML-Tags eingebunden. Vorteile: automatische Versionierung/Cache-Busting, korrekte Content-Type-Header und Unterstützung für "Resource Libraries" (z. B. themenabhängiger Austausch von CSS-Dateien).

Ordnerstruktur
/resources
    /css
        styles.css
    /js
        app.js
    /images
        logo.png
layout.xhtml
<h:head>
    <h:outputStylesheet name="styles.css" library="css" />
    <h:outputScript name="app.js" library="js" target="head" />
</h:head>
<h:body>
    <h:graphicImage name="logo.png" library="images" alt="Logo" />
</h:body>

JSF liefert diese Ressourcen über eine eigene URL aus (z. B. /javax.faces.resource/styles.css.xhtml?ln=css) und hängt bei Bedarf einen Versionsstempel an, damit Browser-Caches beim Deployment einer neuen Version automatisch invalidiert werden.

16. Internationalisierung (i18n)

Mehrsprachige Texte werden über Java-ResourceBundles (.properties-Dateien) verwaltet und per EL in die Seite eingebunden.

faces-config.xml
<application>
    <locale-config>
        <default-locale>de</default-locale>
        <supported-locale>en</supported-locale>
    </locale-config>
    <resource-bundle>
        <base-name>messages</base-name>
        <var>msg</var>
    </resource-bundle>
</application>
messages_de.properties / messages_en.properties
welcomeText=Willkommen bei unserer Anwendung
saveButton=Speichern

# messages_en.properties
welcomeText=Welcome to our application
saveButton=Save
Verwendung in der Seite
<h:outputText value="#{msg.welcomeText}" />
<h:commandButton value="#{msg.saveButton}" action="#{formBean.save}" />
Sprache programmatisch umschalten
FacesContext.getCurrentInstance().getViewRoot().setLocale(new Locale("en"));

Alternativ lässt sich ein Bundle auch ohne Eintrag in faces-config.xml direkt in einer Seite laden: <f:loadBundle basename="messages" var="msg" />.

17. State Saving & CSRF

JSF muss den Component Tree zwischen zwei Requests "irgendwo" vorhalten, damit ein Postback funktioniert. Wo das passiert, wird über die State-Saving-Methode gesteuert.

web.xml
<context-param>
    <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
    <param-value>server</param-value> <!-- oder: client -->
</context-param>
MethodeVerhalten
server (Standard)Der Component-Tree-State liegt im Server-Speicher (an die Session gebunden); der Client erhält im versteckten Feld javax.faces.ViewState nur einen Verweis darauf. Geringerer Client-Traffic, aber Speicherbedarf pro Session und i. d. R. Session-Affinität im Cluster nötig.
clientDer gesamte (serialisierte, i. d. R. verschlüsselte) View-State steckt direkt im javax.faces.ViewState-Feld. Server bleibt zustandslos (einfacheres Clustering), aber jeder Request überträgt mehr Daten.

CSRF-Schutz: Das javax.faces.ViewState-Feld ist an die Session bzw. eine zufällige View-ID gebunden und muss beim Postback exakt übereinstimmen – das erschwert klassische CSRF-Angriffe bereits von Haus aus. Moderne JSF-Implementierungen (Mojarra/MyFaces) randomisieren die View-State-ID zusätzlich, um Vorhersagbarkeit zu vermeiden.

Zustandslose Views (ohne jeglichen View-State)
<f:view transient="true">
    ...
</f:view>

Für reine Anzeigeseiten ohne Formulare/Postbacks spart transient="true" das Anlegen eines View-States komplett – weder Server- noch Client-State-Saving wird benötigt.

18. Faces Flows & PhaseListener

Faces Flows

Faces Flows (seit JSF 2.2) kapseln mehrseitige Abläufe (z. B. einen Checkout-Prozess) inklusive eigenem Scope – eine deklarative Alternative/Ergänzung zu @ConversationScoped. Ein Flow entsteht durch Konvention: ein Ordner mit dem Flow-Namen, der eine passende <name>-flow.xml enthält.

/checkout/checkout-flow.xml
<?xml version="1.0" encoding="UTF-8"?>
<faces-config xmlns="http://xmlns.jcp.org/xml/ns/javaee"
              version="2.2">
    <flow-definition id="checkout">
        <start-node>step1</start-node>
        <flow-return id="checkoutComplete">
            <from-outcome>/confirmation</from-outcome>
        </flow-return>
    </flow-definition>
</faces-config>
CheckoutBean.java
@Named
@FlowScoped("checkout")
public class CheckoutBean implements Serializable {
    private Cart cart;
    // lebt nur, solange sich der Benutzer innerhalb des "checkout"-Flows befindet
}
Einstieg in den Flow
<h:link outcome="checkout/step1" value="Zur Kasse" />

PhaseListener

Ein PhaseListener hakt sich global vor/nach bestimmten Lifecycle-Phasen ein – nützlich für Logging, Monitoring oder Querschnittsbelange, die nicht in einzelne Beans gehören.

LoggingPhaseListener.java
public class LoggingPhaseListener implements PhaseListener {

    @Override
    public void beforePhase(PhaseEvent event) {
        System.out.println("Vor Phase: " + event.getPhaseId());
    }

    @Override
    public void afterPhase(PhaseEvent event) {
        System.out.println("Nach Phase: " + event.getPhaseId());
    }

    @Override
    public PhaseId getPhaseId() {
        return PhaseId.ANY_PHASE; // oder gezielt z. B. PhaseId.RENDER_RESPONSE
    }
}
Registrierung in faces-config.xml
<lifecycle>
    <phase-listener>com.example.LoggingPhaseListener</phase-listener>
</lifecycle>
Hinweis zu Marken- und Produktnamen: In diesem Dokument genannte Produkt-, Firmen- und Markennamen — u. a. JavaServer Faces (JSF), Jakarta EE, Java, CDI, JSP, JSTL, PrimeFaces, OmniFaces, Mojarra, MyFaces — sind Marken bzw. eingetragene Marken der jeweiligen Rechteinhaber. Sie werden hier ausschließlich zu illustrativen und erklärenden Zwecken verwendet; eine Zugehörigkeit, Empfehlung oder Zusammenarbeit mit den jeweiligen Unternehmen ist damit nicht verbunden.