JavaServer Faces (JSF) – Konzepte im Überblick
Lifecycle, Annotationen, Composite Components, CDI-Scopes und ein Facelets-Beispiel auf einer Seite.
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.
FacesContext.responseComplete()/renderResponse() aufgerufen), überspringt JSF die restlichen Phasen und rendert sofort mit den Fehlermeldungen.
Kurzbeschreibung der Phasen
| Phase | Was passiert |
|---|---|
| 1. Restore View | Der 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 Values | Die 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 Validations | Fü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 Values | Die konvertierten und validierten Werte werden tatsächlich in die per EL gebundenen Bean-Properties geschrieben (Aufruf der Setter). |
| 5. Invoke Application | Action-Methoden (z. B. Button-Klick) und Action-Listener werden ausgeführt; das Ergebnis bestimmt über die Navigation die nächste anzuzeigende View. |
| 6. Render Response | Der (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 |
|---|---|---|
@Named | CDI | Macht eine Klasse als CDI-Bean per Expression Language (#{beanName}) ansprechbar. Standard-Ersatz für das alte @ManagedBean. |
@ManagedBean | JSF (legacy) | Alte, JSF-eigene Bean-Verwaltung (javax.faces.bean). Seit JSF 2.3 deprecated – heute @Named + CDI verwenden. |
@RequestScoped | CDI | Bean-Instanz lebt nur für die Dauer eines einzelnen HTTP-Requests. |
@ViewScoped | CDI (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. |
@SessionScoped | CDI | Eine Instanz pro HTTP-Session des Benutzers, z. B. für Login-Status oder Warenkorb. |
@ApplicationScoped | CDI | Genau eine Instanz für die gesamte Anwendung, geteilt von allen Benutzern (z. B. für Caches/Konfiguration). |
@ConversationScoped | CDI | Bean lebt über mehrere Views hinweg, bis die Conversation explizit beendet wird (typisch für mehrstufige Wizards). |
@Inject | CDI | Injiziert eine andere Bean bzw. Abhängigkeit in ein Feld, Konstruktor oder Setter. |
@ManagedProperty | JSF (legacy) | Injiziert Werte oder andere Managed Beans in eine alte JSF-Bean. Durch @Inject ersetzt. |
@FacesConverter | JSF | Markiert eine Klasse als eigenen Converter, der zwischen Anzeige-String und Java-Objekt umwandelt. |
@FacesValidator | JSF | Markiert eine Klasse als eigenen Validator zur Prüfung von Eingabewerten. |
@FacesComponent | JSF | Definiert eine eigene UIComponent-Klasse zur Erweiterung des Component Trees. |
@FacesRenderer | JSF | Definiert einen eigenen Renderer, der bestimmt, wie eine Komponente als HTML ausgegeben wird. |
@FacesBehavior | JSF | Definiert 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.
<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>
<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
@RequestScoped
Neue Instanz bei jedem HTTP-Request. Einfachste, zustandslose Variante – geeignet für einfache Formulare/Listenanzeigen ohne Zustand über mehrere Requests hinweg.
@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).
@SessionScoped
Eine Instanz pro Benutzer-Session, bis Logout/Session-Timeout. Typisch für Login-Informationen, Spracheinstellung, Warenkorb.
@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.
@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.
@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.
| Scope | Speicherort (technisch) | Besonderheit |
|---|---|---|
@RequestScoped | Attribute von HttpServletRequest | Identisch zum CDI-Pendant, nur andere Paket-Herkunft (javax.faces.bean statt CDI). |
@ViewScoped | UIViewRoot.getViewMap() – Teil des Component Trees | Ursprünglich JSF-spezifisch; wurde später als javax.faces.view.ViewScoped auch CDI-kompatibel gemacht. |
@SessionScoped | Attribute von HttpSession | Identisch zum CDI-Pendant. |
@ApplicationScoped | Attribute von ServletContext | Identisch zum CDI-Pendant. |
@NoneScope | – | Bean wird nicht zwischengespeichert; jeder EL-Zugriff erzeugt eine neue Instanz (kein CDI-Äquivalent nötig, da @Dependent ähnlich wirkt). |
| Flash-Scope | ExternalContext.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
public String save() {
// ... Bestellung speichern ...
FacesContext.getCurrentInstance()
.getExternalContext()
.getFlash()
.put("message", "Bestellung erfolgreich gespeichert!");
return "orders?faces-redirect=true";
}
<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:
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();
}
}
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.
| Ausdruck | Bedeutung |
|---|---|
#{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:
| Prefix | Namespace | Zweck / Beispiel-Tags |
|---|---|---|
h: | jsf/html | HTML-Render-Komponenten: h:form, h:inputText, h:commandButton, h:dataTable, h:message. |
f: | jsf/core | Nicht-visuelle Kern-Tags: f:convertDateTime, f:validateLongRange, f:ajax, f:param, f:facet, f:viewParam. |
ui: | jsf/facelets | Templating: ui:composition, ui:insert, ui:define, ui:decorate, ui:include, ui:param. |
composite: | jsf/composite | Aufbau eigener Composite Components: composite:interface, composite:implementation, composite:attribute. |
c: | jsp/jstl/core | JSTL-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: … | Drittanbieter | Component-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:
| Attribut | Bedeutung |
|---|---|
execute | Welche Komponenten(-Werte) an den Server gesendet und verarbeitet werden (@this, @form, @all, @none oder konkrete IDs). |
render | Welche Teile des Component Trees nach der Antwort neu gerendert werden. |
event | Auslösendes DOM-/JSF-Event, z. B. click, blur, valueChange (Default hängt von der Komponente ab). |
listener | Optionale Bean-Methode, die bei Auslösung aufgerufen wird (zusätzlich zur normalen Action). |
onevent / onerror | JavaScript-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.
| Wert | Bedeutung |
|---|---|
@this | Nur die Komponente, an der f:ajax hängt. Default für execute. |
@none | Gar 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). |
@form | Das umschließende <h:form> der Komponente – der mit Abstand häufigste Wert, da damit sämtliche Felder eines Formulars mitverarbeitet bzw. aktualisiert werden. |
@all | Die 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.
<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.
<!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>
<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>
@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.
| Variante | Server-Roundtrip | Eigenschaft |
|---|---|---|
A: rendered ohne Ajax | Ja (voller Postback) | Element wird bei rendered="false" gar nicht ins HTML geschrieben. Ganze Seite wird neu geladen. |
| B: rein clientseitig (CSS/JS) | Nein | Sofortige Reaktion ohne Server-Kontakt, aber Element bleibt im DOM (nur versteckt) – nicht für sensible Inhalte geeignet. |
C: rendered mit Ajax | Ja (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.
<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>
@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.
<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.
<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.
public class UserDto {
@NotNull
@Size(min = 2, max = 50)
private String name;
@NotNull
@Email
private String email;
@Min(18)
private int age;
// getters & setters
}
@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; }
}
<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).
@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());
}
}
<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
@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));
}
}
}
<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".
<h:form enctype="multipart/form-data">
<h:inputFile value="#{uploadBean.file}" />
<h:commandButton value="Hochladen" action="#{uploadBean.upload}" />
</h:form>
@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; }
}
<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.
<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>
@Named
@ViewScoped
public class ProductBean implements Serializable {
private Long productId;
private Product product;
public void loadProduct() {
product = productService.findById(productId);
}
// getters & setters
}
<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).
/resources
/css
styles.css
/js
app.js
/images
logo.png
<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.
<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>
welcomeText=Willkommen bei unserer Anwendung
saveButton=Speichern
# messages_en.properties
welcomeText=Welcome to our application
saveButton=Save
<h:outputText value="#{msg.welcomeText}" />
<h:commandButton value="#{msg.saveButton}" action="#{formBean.save}" />
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.
<context-param>
<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value>server</param-value> <!-- oder: client -->
</context-param>
| Methode | Verhalten |
|---|---|
| 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. |
| client | Der 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.
<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.
<?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>
@Named
@FlowScoped("checkout")
public class CheckoutBean implements Serializable {
private Cart cart;
// lebt nur, solange sich der Benutzer innerhalb des "checkout"-Flows befindet
}
<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.
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
}
}
<lifecycle>
<phase-listener>com.example.LoggingPhaseListener</phase-listener>
</lifecycle>