Java Web Aufbau – Tag 5 von 10
Von Elyndra Valen, Senior Developer bei Java Fleet Systems Consulting

📋 Deine Position im Kurs
| Tag | Thema | Status |
|---|---|---|
| 1 | Filter im Webcontainer | ✅ Abgeschlossen |
| 2 | Listener im Webcontainer | ✅ Abgeschlossen |
| 3 | Authentifizierung über Datenbank | ✅ Abgeschlossen |
| 4 | Container-Managed Security & Jakarta Security API | ✅ Abgeschlossen |
| → 5 | Custom Tags & Tag Handler (Teil-1 SimpleTag) | 👉 DU BIST HIER! |
| 6 | Custom Tag Handler mit BodyTagSupport | 🔒 Noch nicht freigeschaltet |
| 7 | JPA vs JDBC – Konfiguration & Provider | 🔒 Noch nicht freigeschaltet |
| 8 | JPA Relationen (1): @OneToOne & @ManyToOne | 🔒 Noch nicht freigeschaltet |
| 9 | JPA Relationen (2): @OneToMany & @ManyToMany | 🔒 Noch nicht freigeschaltet |
| 10 | JSF Überblick – Component-Based UI | 🔒 Noch nicht freigeschaltet |
Modul: Java Web Aufbau
Gesamt-Dauer: 10 Arbeitstage (je 8 Stunden)
Dauer heute: 8 Stunden
Dein Ziel: Custom Tags mit SimpleTag erstellen und TLD-Dateien verstehen
🎯 Was du heute lernst
Custom Tags erstellen und nutzen:
🟢 Level 1: Simple Tags (Keine Attribute, kein Body)
🟡 Level 2: Tags mit Attributen (Konfigurierbar)
🔵 Level 3: Body Content Processing (Tags mit Inhalt)
Tag Library Descriptor (TLD):
- Was ist eine TLD-Datei?
- Wie registriere ich Custom Tags?
- Tag-URIs und Namespaces verstehen
Von JSTL lernen:
- Wie funktioniert
<c:if>wirklich? - Eigene Control-Flow-Tags erstellen
- Best Practices für wiederverwendbare Komponenten
💬 Real Talk: Warum Custom Tags?
Java Fleet Küche, 10:30 Uhr
Nova: „Elyndra, wir haben doch JSTL! Warum sollte ich eigene Tags erstellen?“
Elyndra: „Drei gute Gründe:
1. Domain-spezifische Logik
Du baust ein E-Commerce-System. Brauchst du wirklich in JEDER JSP diese 30 Zeilen Code für Preis-Formatierung mit Rabatten, Währungsumrechnung und Steuer-Berechnung?
2. Konsistenz<shop:price product="${product}" /> ist self-documenting. Jeder im Team versteht sofort, was passiert. Kein Copy-Paste-Fehler mehr.
3. UI-Komponenten kapseln
Rating-Stars, Social-Share-Buttons, komplexe Tabellen – einmal als Tag gebaut, überall nutzbar.“
Nova: „Ah! Wie React Components, aber für JSP?“
Elyndra: „Genau! JSP Custom Tags sind die Vorfahren von React Components. Das Konzept ist dasselbe – Wiederverwendbarkeit durch Kapselung.“
🟢 LEVEL 1: Simple Tags ohne Attribute
Konzept
Simple Tag = Tag ohne Parameter, das statischen Content erzeugt
Use Cases:
- Copyright-Notices
- Wiederkehrende HTML-Strukturen
- Debugging-Informationen
- Build-Nummern / Versionsnummern
Implementation
1. Tag Handler Klasse erstellen:
package com.javafleet.tags;
import jakarta.servlet.jsp.tagext.SimpleTagSupport;
import jakarta.servlet.jsp.JspException;
import java.io.IOException;
public class CopyrightTag extends SimpleTagSupport {
@Override
public void doTag() throws JspException, IOException {
getJspContext().getOut().write(
"<footer class='copyright'>" +
"© 2025 Java Fleet Systems Consulting. All rights reserved." +
"</footer>"
);
}
}
Was macht dieser Code?
Das ist unser erster Custom Tag Handler! Lass uns verstehen, wie er funktioniert:
Die Vererbung von SimpleTagSupportSimpleTagSupport ist die Basis-Klasse für einfache Tags. Sie stellt uns alle notwendigen Methoden zur Verfügung, um mit dem JSP-Container zu kommunizieren.
Die doTag() Methode
Das ist die zentrale Methode, die der Container aufruft, wenn dein Tag in einer JSP verwendet wird. Hier schreibst du die Logik, die ausgeführt werden soll.
JspContext und OutputgetJspContext().getOut() gibt uns Zugriff auf den Output-Stream der JSP. Alles, was wir dort hineinschreiben, landet im HTML-Output der Seite.
In der Praxis bedeutet das:
Jedes Mal, wenn jemand <fleet:copyright /> in einer JSP schreibt, wird diese Methode aufgerufen und unser HTML-Footer wird ins Dokument eingefügt.
2. Tag Library Descriptor (TLD) erstellen:
<?xml version="1.0" encoding="UTF-8"?>
<taglib xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/web-jsptaglibrary_3_0.xsd"
version="3.0">
<tlib-version>1.0</tlib-version>
<short-name>fleet</short-name>
<uri>http://javafleet.com/tags</uri>
<tag>
<name>copyright</name>
<tag-class>com.javafleet.tags.CopyrightTag</tag-class>
<body-content>empty</body-content>
<info>Displays copyright notice</info>
</tag>
</taglib>
Speicherort: WEB-INF/tlds/fleet-tags.tld
Was macht diese TLD-Datei?
Der Tag Library Descriptor ist die Verbindung zwischen deinen Java-Klassen und den JSP-Tags. Lass uns jeden Teil verstehen:
Die taglib-Version und Namespace
Diese Angaben sagen dem Container, welche Jakarta EE Version wir verwenden. version="3.0" entspricht Jakarta EE 10.
Der short-name
Das ist der empfohlene Prefix für deine Tag-Library. In JSPs wird das typischerweise als <%@ taglib prefix="fleet" ... %> verwendet.
Die URI – Das wichtigste Element!<uri>http://javafleet.com/tags</uri> ist der eindeutige Identifier deiner Tag-Library. Das muss KEINE echte Website sein – es ist nur ein Namespace wie bei XML.
Wichtig zu verstehen:
Diese URI verwendest du in JSPs, um deine Tags zu importieren. Sie verbindet die TLD-Datei mit dem JSP-Code.
Das <tag> Element
Hier registrieren wir jeden einzelnen Custom Tag:
<name>– Der Tag-Name wie er in JSP verwendet wird<tag-class>– Der vollständige Klassenname des Tag Handlers<body-content>– Kann das Tag Inhalt haben? (empty= nein)<info>– Beschreibung für Entwickler
In der Praxis bedeutet das:
Wenn jemand <fleet:copyright /> schreibt, sucht der Container diese TLD-Datei, findet das <tag> mit <name>copyright</name>, und ruft dann die Klasse CopyrightTag auf.
3. In JSP verwenden:
<%@ taglib prefix="fleet" uri="http://javafleet.com/tags" %>
<!DOCTYPE html>
<html>
<head>
<title>Simple Tag Demo</title>
</head>
<body>
<h1>Welcome to Java Fleet</h1>
<p>Content here...</p>
<!-- Unser Custom Tag! -->
<fleet:copyright />
</body>
</html>
Das Ergebnis:
Überall wo <fleet:copyright /> steht, wird automatisch unser Copyright-Footer eingefügt!
🟡 LEVEL 2: Tags mit Attributen
Konzept
Tags mit Attributen = Konfigurierbare, wiederverwendbare Komponenten
Use Cases:
- Formatierung (Datum, Währung, Zahlen)
- Bedingte Ausgabe
- URL-Generierung
- User-spezifische Inhalte
Implementation: Alert-Box Tag
Ziel:
Ein Tag für Bootstrap-Alerts mit verschiedenen Typen:
<fleet:alert type="success" message="Operation completed!" /> <fleet:alert type="danger" message="Error occurred!" /> <fleet:alert type="info" message="Please note..." />
1. Tag Handler mit Properties:
package com.javafleet.tags;
import jakarta.servlet.jsp.tagext.SimpleTagSupport;
import jakarta.servlet.jsp.JspException;
import java.io.IOException;
public class AlertTag extends SimpleTagSupport {
// Attribute als private Felder
private String type = "info"; // Default-Wert
private String message;
// WICHTIG: Getter UND Setter für jedes Attribut!
public void setType(String type) {
this.type = type;
}
public String getType() {
return type;
}
public void setMessage(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
@Override
public void doTag() throws JspException, IOException {
// Icon basierend auf Type
String icon = switch (type.toLowerCase()) {
case "success" -> "✓";
case "danger" -> "✗";
case "warning" -> "⚠";
default -> "ℹ";
};
// HTML generieren
String html = String.format(
"<div class='alert alert-%s' role='alert'>" +
" <span class='alert-icon'>%s</span>" +
" <span class='alert-message'>%s</span>" +
"</div>",
type, icon, message
);
getJspContext().getOut().write(html);
}
}
Was macht dieser Code?
Das ist ein Tag mit konfigurierbaren Attributen! Lass uns die wichtigen Konzepte verstehen:
Die Attribute als Properties
Jedes JSP-Attribut wird als privates Feld in der Klasse deklariert. private String type entspricht dem Attribut type="..." im JSP-Tag.
Die Setter-Methoden – Das Herzstück!
Der JSP-Container ruft automatisch die Setter auf, wenn ein Attribut gesetzt wird:
<fleet:alert type="success" ... />
→ Container ruft setType("success") auf!
Wichtig zu verstehen:
Die Setter-Namen MÜSSEN exakt zur JavaBeans-Konvention passen:
- Attribut
type→ MethodesetType() - Attribut
message→ MethodesetMessage() - Attribut
userName→ MethodesetUserName()
Groß-/Kleinschreibung ist kritisch! settype() funktioniert NICHT!
Der Switch-Expression (Java 17+)
Wir nutzen moderne Java-Syntax für sauberen Code. Die Icons werden basierend auf dem type ausgewählt.
In der Praxis bedeutet das:
- JSP enthält
<fleet:alert type="danger" message="Error!" /> - Container erstellt neue Instanz von
AlertTag - Container ruft
setType("danger")auf - Container ruft
setMessage("Error!")auf - Container ruft
doTag()auf - HTML wird generiert und ausgegeben
2. TLD-Eintrag mit Attributen:
<tag>
<name>alert</name>
<tag-class>com.javafleet.tags.AlertTag</tag-class>
<body-content>empty</body-content>
<info>Bootstrap alert box</info>
<!-- Attribute definieren -->
<attribute>
<name>type</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
<type>java.lang.String</type>
</attribute>
<attribute>
<name>message</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<type>java.lang.String</type>
</attribute>
</tag>
Was bedeuten die Attribute-Definitionen?
Jedes Tag-Attribut braucht eine Beschreibung in der TLD. Lass uns verstehen, was die einzelnen Elemente bedeuten:
<name> – Der Attribut-Name
Muss exakt zum Property-Namen in der Java-Klasse passen! <name>type</name> → setType() Methode.
<required> – Pflichtfeld?
true= Attribut MUSS angegeben werdenfalse= Attribut ist optional
In unserem Beispiel: message ist Pflicht, type ist optional (hat Default-Wert „info“).
<rtexprvalue> – Runtime Expression Value
Das ist wichtig! true bedeutet: „EL-Expressions sind erlaubt!“
Mit rtexprvalue=“true“:
<!-- EL funktioniert! -->
<fleet:alert type="${alertType}" message="${errorMsg}" />
Mit rtexprvalue=“false“:
<!-- Nur statische Strings erlaubt! --> <fleet:alert type="success" message="Done!" />
Best Practice: Setze IMMER <rtexprvalue>true</rtexprvalue> – du willst dynamische Werte nutzen!
<type> – Java-Datentyp
Definiert den erwarteten Typ. Der Container macht automatisch Type-Conversion:
java.lang.String– Textjava.lang.Integer– Ganzzahlenjava.lang.Boolean– true/false- Oder eigene Klassen!
In der Praxis bedeutet das:
Die TLD ist der „Vertrag“ zwischen deinem Tag und der JSP-Seite. Sie definiert, welche Attribute existieren, welche Pflicht sind, und welche Datentypen erwartet werden.
3. In JSP verwenden:
<%@ taglib prefix="fleet" uri="http://javafleet.com/tags" %>
<!-- Mit Default-Type (info) -->
<fleet:alert message="Welcome to Java Fleet!" />
<!-- Mit explizitem Type -->
<fleet:alert type="success" message="Login successful!" />
<fleet:alert type="danger" message="Invalid credentials!" />
<fleet:alert type="warning" message="Session expires soon!" />
<!-- Mit EL-Expressions -->
<fleet:alert type="${alertType}" message="${alertMessage}" />
🔵 LEVEL 3: Body Content Processing
Konzept
Tags mit Body = Tags, die den Content zwischen Opening- und Closing-Tag verarbeiten
<fleet:uppercase>
this will be converted to uppercase
</fleet:uppercase>
Use Cases:
- Text-Transformationen
- Content-Wrapping (Panels, Cards, Modals)
- Conditional Rendering mit Body
- Iteration über Collections
Implementation: Uppercase Tag
Ziel:
Ein Tag, das seinen Inhalt in Großbuchstaben umwandelt
1. Tag Handler mit Body Content:
package com.javafleet.tags;
import jakarta.servlet.jsp.tagext.SimpleTagSupport;
import jakarta.servlet.jsp.JspException;
import java.io.StringWriter;
import java.io.IOException;
public class UppercaseTag extends SimpleTagSupport {
@Override
public void doTag() throws JspException, IOException {
// StringWriter fängt den Body-Content auf
StringWriter bodyContent = new StringWriter();
// Body in StringWriter schreiben lassen
getJspBody().invoke(bodyContent);
// Content in Uppercase umwandeln
String uppercased = bodyContent.toString().toUpperCase();
// Transformierten Content ausgeben
getJspContext().getOut().write(uppercased);
}
}
Was macht dieser Code?
Das ist Body Content Processing – einer der mächtigsten Features von Custom Tags! Lass uns verstehen, wie das funktioniert:
Der StringWriter als Buffer
Wir erstellen einen StringWriter, der als „Zwischenspeicher“ für den Body-Content dient. Der Body wird nicht direkt ausgegeben, sondern erst gesammelt.
Die getJspBody() MethodegetJspBody() gibt uns Zugriff auf den Content zwischen <fleet:uppercase> und </fleet:uppercase>.
Das invoke() Pattern
getJspBody().invoke(bodyContent);
Das sagt: „Führe den Body-Content aus und schreibe die Ausgabe in meinen StringWriter!“
Wichtig zu verstehen:
Der Body kann beliebig komplex sein:
<fleet:uppercase>
Hello ${user.name}!
<c:if test="${showDetails}">
Details here
</c:if>
</fleet:uppercase>
ALLE EL-Expressions, JSTL-Tags, etc. werden ZUERST ausgeführt, DANN kommt der fertige String bei uns an!
Die Transformation
String uppercased = bodyContent.toString().toUpperCase();
Jetzt haben wir den kompletten Body als String und können ihn manipulieren – in diesem Fall: Uppercase.
Die Ausgabe
Am Ende schreiben wir den transformierten Content in den Output-Stream der JSP.
In der Praxis bedeutet das:
- JSP parst
<fleet:uppercase>Hello ${name}!</fleet:uppercase> - Container evaluiert EL → „Hello Nova!“
- Container ruft
doTag()auf - Wir fangen Body ab → „Hello Nova!“
- Wir transformieren → „HELLO NOVA!“
- Wir geben aus → User sieht „HELLO NOVA!“
2. TLD-Eintrag mit Body Content:
<tag>
<name>uppercase</name>
<tag-class>com.javafleet.tags.UppercaseTag</tag-class>
<body-content>scriptless</body-content>
<info>Converts body content to uppercase</info>
</tag>
Was bedeutet body-content=“scriptless“?
Das <body-content> Element definiert, was zwischen Opening- und Closing-Tag erlaubt ist:
Die drei Optionen:
1. empty
<body-content>empty</body-content>
Kein Body erlaubt! Tag muss self-closing sein:
<fleet:copyright /> <!-- OK --> <fleet:copyright></fleet:copyright> <!-- FEHLER! -->
2. scriptless
<body-content>scriptless</body-content>
Body ist erlaubt, aber KEINE Java-Scriptlets!
<!-- OK: -->
<fleet:uppercase>
Hello ${user.name}!
<c:if test="${condition}">Content</c:if>
</fleet:uppercase>
<!-- FEHLER: -->
<fleet:uppercase>
<% out.println("No scriptlets allowed!"); %>
</fleet:uppercase>
3. tagdependent
<body-content>tagdependent</body-content>
Body wird NICHT verarbeitet – raw content!
<fleet:code>
<!-- Das wird als Text ausgegeben, nicht als HTML! -->
<h1>Hello</h1>
</fleet:code>
Best Practice:
Verwende fast immer scriptless – das erlaubt EL und JSTL (modern) aber blockiert Scriptlets (veraltet).
3. In JSP verwenden:
<%@ taglib prefix="fleet" uri="http://javafleet.com/tags" %>
<h1>Text Transformation Demo</h1>
<fleet:uppercase>
this text will be converted to uppercase
</fleet:uppercase>
<!-- Mit EL -->
<fleet:uppercase>
Welcome, ${user.name}!
</fleet:uppercase>
<!-- Mit JSTL -->
<fleet:uppercase>
<c:if test="${user.isAdmin}">
admin privileges enabled
</c:if>
</fleet:uppercase>
Output:
THIS TEXT WILL BE CONVERTED TO UPPERCASE WELCOME, NOVA! ADMIN PRIVILEGES ENABLED
🔵 BONUS 1: Panel Tag mit Styling
Ein wiederverwendbarer Panel-Wrapper:
package com.javafleet.tags;
import jakarta.servlet.jsp.tagext.SimpleTagSupport;
import jakarta.servlet.jsp.JspException;
import java.io.StringWriter;
import java.io.IOException;
public class PanelTag extends SimpleTagSupport {
private String title;
private String color = "primary";
public void setTitle(String title) {
this.title = title;
}
public void setColor(String color) {
this.color = color;
}
@Override
public void doTag() throws JspException, IOException {
// Body Content verarbeiten
StringWriter bodyContent = new StringWriter();
getJspBody().invoke(bodyContent);
// Panel HTML generieren
String html = String.format("""
<div class="panel panel-%s">
<div class="panel-header">
<h3>%s</h3>
</div>
<div class="panel-body">
%s
</div>
</div>
""", color, title, bodyContent.toString());
getJspContext().getOut().write(html);
}
}
TLD-Eintrag:
<tag>
<name>panel</name>
<tag-class>com.javafleet.tags.PanelTag</tag-class>
<body-content>scriptless</body-content>
<info>Bootstrap-style panel wrapper</info>
<attribute>
<name>title</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>color</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
Verwendung:
<fleet:panel title="User Profile" color="info">
<p>Name: ${user.name}</p>
<p>Email: ${user.email}</p>
<p>Role: ${user.role}</p>
</fleet:panel>
<fleet:panel title="Statistics" color="success">
<ul>
<li>Total Users: ${stats.totalUsers}</li>
<li>Active Sessions: ${stats.activeSessions}</li>
<li>Requests Today: ${stats.requestsToday}</li>
</ul>
</fleet:panel>
🔵 BONUS 2: Conditional Tag (wie JSTL <c:if>)
Eigenes Conditional Tag erstellen:
package com.javafleet.tags;
import jakarta.servlet.jsp.tagext.SimpleTagSupport;
import jakarta.servlet.jsp.JspException;
import java.io.IOException;
public class ShowIfTag extends SimpleTagSupport {
private boolean condition;
public void setCondition(boolean condition) {
this.condition = condition;
}
@Override
public void doTag() throws JspException, IOException {
// Nur bei TRUE den Body ausführen
if (condition) {
getJspBody().invoke(null);
}
// Bei FALSE: Nichts tun (Body wird ignoriert)
}
}
Was ist anders hier?
Das ist ein Control-Flow-Tag! Lass uns verstehen, was hier passiert:
Die boolean Property
private boolean condition;
Unser Attribut ist vom Typ boolean – nicht String! Der Container macht automatisch Type-Conversion:
<fleet:showIf condition="${user.isAdmin}">
→ EL evaluiert zu true/false
→ Container konvertiert zu Java boolean
→ setCondition(true) wird aufgerufen
Die bedingte Ausführung
if (condition) {
getJspBody().invoke(null);
}
Das ist der Trick: Wir rufen invoke() nur auf, wenn die Bedingung TRUE ist!
Wichtig zu verstehen:
Bei invoke(null) wird der Body DIREKT in den JSP-Output-Stream geschrieben (kein StringWriter als Buffer).
Das Resultat:
Der Body-Content wird nur verarbeitet und ausgegeben, wenn condition == true. Sonst passiert gar nichts – der Body wird komplett ignoriert!
In der Praxis bedeutet das:
Das ist exakt so, wie JSTL’s <c:if> funktioniert! Du hast gerade dein eigenes Control-Flow-Tag gebaut!
TLD-Eintrag:
<tag>
<name>showIf</name>
<tag-class>com.javafleet.tags.ShowIfTag</tag-class>
<body-content>scriptless</body-content>
<info>Conditional rendering (like JSTL c:if)</info>
<attribute>
<name>condition</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<type>boolean</type>
</attribute>
</tag>
Verwendung:
<fleet:showIf condition="${user.isLoggedIn}">
<p>Welcome back, ${user.name}!</p>
<a href="/logout">Logout</a>
</fleet:showIf>
<fleet:showIf condition="${not user.isLoggedIn}">
<p>Please log in to continue.</p>
<a href="/login">Login</a>
</fleet:showIf>
<fleet:showIf condition="${product.stock > 0}">
<button>Add to Cart</button>
</fleet:showIf>
💡 Best Practices für Custom Tags
1. Naming Conventions
Tag-Namen:
- Lowercase mit Bindestrichen:
<fleet:show-if> - Oder camelCase:
<fleet:showIf> - Konsistent innerhalb einer Library!
Attribute-Namen:
- camelCase:
condition,userName,maxLength - NICHT snake_case: ~~
max_length~~
2. Error Handling
Validierung in Settern:
public void setMaxLength(int maxLength) {
if (maxLength <= 0) {
throw new IllegalArgumentException("maxLength must be positive");
}
this.maxLength = maxLength;
}
In doTag():
@Override
public void doTag() throws JspException, IOException {
try {
// Tag-Logik
} catch (Exception e) {
throw new JspException("Error in CustomTag: " + e.getMessage(), e);
}
}
3. Null-Safety
Defensive Programmierung:
public void setMessage(String message) {
this.message = (message != null) ? message : "";
}
@Override
public void doTag() throws JspException, IOException {
if (message == null || message.isEmpty()) {
// Fallback-Verhalten
return;
}
// Normal-Verhalten
}
4. Performance
Ressourcen wiederverwenden:
public class FormatterTag extends SimpleTagSupport {
// FALSCH: Neue Instanz bei jedem Request!
@Override
public void doTag() throws JspException, IOException {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy");
// ...
}
}
// RICHTIG: Static final!
public class FormatterTag extends SimpleTagSupport {
private static final DateTimeFormatter DATE_FORMATTER =
DateTimeFormatter.ofPattern("dd.MM.yyyy");
@Override
public void doTag() throws JspException, IOException {
String formatted = DATE_FORMATTER.format(date);
// ...
}
}
5. Dokumentation
Javadoc für Tag Handler:
/**
* Custom tag that formats dates according to user locale.
*
* Usage:
* <fleet:formatDate date="${order.date}" pattern="dd.MM.yyyy" />
*
* @author Elyndra Valen
* @since 1.0
*/
public class FormatDateTag extends SimpleTagSupport {
/**
* The date to format.
*/
private LocalDate date;
/**
* The pattern for formatting (e.g., "dd.MM.yyyy").
* Defaults to "yyyy-MM-dd" if not specified.
*/
private String pattern = "yyyy-MM-dd";
// ...
}
Info in TLD:
<tag>
<name>formatDate</name>
<tag-class>com.javafleet.tags.FormatDateTag</tag-class>
<body-content>empty</body-content>
<info>
Formats a date according to the specified pattern.
Example: <fleet:formatDate date="${order.date}" pattern="dd.MM.yyyy" />
</info>
<!-- attributes... -->
</tag>
📚 Mini-Challenge: Build Your Own Tag Library
Deine Aufgabe:
Erstelle eine vollständige Tag Library mit folgenden Tags:
1. <fleet:greeting name="..." time="morning|afternoon|evening" />
- Zeigt personalisierte Grüße abhängig von Tageszeit
- Beispiel: „Good morning, Nova!“ oder „Guten Abend, Nova!“
2. <fleet:badge value="..." color="..." />
- Bootstrap-Badge mit konfigurierbarer Farbe
- Beispiel:
<fleet:badge value="5" color="danger" />→ Roter Badge mit „5“
3. <fleet:truncate maxLength="50">...long text...</fleet:truncate>
- Kürzt Text auf maxLength Zeichen
- Fügt „…“ am Ende hinzu wenn gekürzt wurde
4. <fleet:repeat times="5">...content...</fleet:repeat>
- Wiederholt Body-Content n-mal
- Nützlich für Test-Daten oder Listen
Bonus:
- Alle Tags in eine TLD-Datei mit URI
http://javafleet.com/challenge-tags - Erstelle eine Demo-JSP die alle Tags verwendet
- Deployment auf Payara und testen!
Hinweise:
- Nutze
SimpleTagSupportals Basis - Denk an Getter UND Setter für alle Attribute
- Setze
<rtexprvalue>true</rtexprvalue>für EL-Support - Bei
repeatbrauchst du eine Loop indoTag()
Lösung: Am Anfang von Tag 6!
❓ FAQ
F1: Wann sollte ich Custom Tags erstellen statt JSTL zu nutzen?
Antwort:
Nutze Custom Tags wenn:
- ✅ Domain-spezifische Logik wiederholt wird
- ✅ Komplexe UI-Komponenten gekapselt werden sollen
- ✅ Business-Rules in wiederverwendbare Form gebracht werden
- ✅ Konsistenz über viele JSPs erzwungen werden soll
Nutze JSTL wenn:
- ✅ Standard-Control-Flow (
<c:if>,<c:forEach>) - ✅ Standard-Formatierung (
<fmt:formatDate>) - ✅ URL-Handling (
<c:url>) - ✅ I18N (
<fmt:message>)
Regel: Nicht das Rad neu erfinden! Nur Custom Tags für projektspezifische Logik.
F2: Warum heißt es „Tag Library Descriptor“ und nicht einfach „Tag Config“?
Historical Context:
„Descriptor“ kommt aus der Java EE Welt:
- Deployment Descriptor (web.xml)
- Tag Library Descriptor (TLD)
- Application Descriptor (application.xml)
„Descriptor“ = „Beschreibt die Struktur und Konfiguration“
Der Name ist historisch gewachsen und Teil der Jakarta EE Terminologie.
F3: Kann ich in einem Tag auf Session oder Request zugreifen?
Ja, absolut!
public class UserInfoTag extends SimpleTagSupport {
@Override
public void doTag() throws JspException, IOException {
// Request zugreifen
PageContext pageContext = (PageContext) getJspContext();
HttpServletRequest request =
(HttpServletRequest) pageContext.getRequest();
// Session zugreifen
HttpSession session = request.getSession();
User user = (User) session.getAttribute("user");
// Ausgabe
if (user != null) {
getJspContext().getOut().write("Welcome, " + user.getName());
}
}
}
Wichtig: Cast zu PageContext notwendig!
F4: Was ist der Unterschied zwischen SimpleTag und ClassicTag?
SimpleTag (Modern – Jakarta EE 5+):
- ✅ Einfacher zu implementieren
- ✅ Extends
SimpleTagSupport - ✅ Eine Methode:
doTag() - ✅ Keine komplexe Lifecycle-Verwaltung
ClassicTag (Legacy – vor Jakarta EE 5):
- ❌ Komplexer Lifecycle
- ❌ Extends
TagSupportoderBodyTagSupport - ❌ Viele Methoden:
doStartTag(),doEndTag(),doAfterBody() - ❌ Return-Codes:
EVAL_BODY_INCLUDE,SKIP_BODY, etc.
Empfehlung: Immer SimpleTagSupport verwenden! ClassicTag ist veraltet.
F5: Wie teste ich Custom Tags?
Unit-Test mit Mock-Objekten:
import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.*;
class AlertTagTest {
@Test
void testAlertTag() throws Exception {
// Tag erstellen
AlertTag tag = new AlertTag();
tag.setType("success");
tag.setMessage("Test message");
// Mock JspContext
JspContext jspContext = mock(JspContext.class);
JspWriter out = mock(JspWriter.class);
when(jspContext.getOut()).thenReturn(out);
tag.setJspContext(jspContext);
// Tag ausführen
tag.doTag();
// Verify Output
verify(out).write(contains("alert-success"));
verify(out).write(contains("Test message"));
}
}
Integration-Test:
- Erstelle Test-JSP mit allen Custom Tags
- Deploye auf Test-Server
- HTTP-Request senden und HTML validieren
F6: Kann ich Custom Tags mit JSF kombinieren?
Ja, aber…
JSF hat sein eigenes Component-Model (Facelets).
Custom JSP Tags funktionieren in JSF-Seiten, aber:
- ❌ Nicht optimal – verschiedene Lifecycles
- ❌ Besser: JSF Composite Components verwenden
- ❌ Custom Tags sind für JSP gedacht, nicht für JSF
Wenn du JSF nutzt: Lerne das JSF Component Model statt Custom Tags!
📚 Offizielle Dokumentation & Spezifikationen
Jakarta EE:
- Jakarta JSP Specification: https://jakarta.ee/specifications/pages/
- Jakarta Tag Library Specification: https://jakarta.ee/specifications/tags/
- Jakarta EE Tutorial – Custom Tags: https://jakarta.ee/learn/docs/jakartaee-tutorial/current/web/servlets/servlets.html
Oracle/Legacy (noch relevant):
- Java EE 8 Tutorial – Custom Tags: https://javaee.github.io/tutorial/jsf-custom.html
- JSP Custom Tag Tutorial: https://docs.oracle.com/javaee/5/tutorial/doc/bnalj.html
🎓 Tutorials & Guides
Baeldung (sehr gut!):
- Custom JSP Tags: https://www.baeldung.com/jsp-custom-tags
- JSP Tag Library (TLD): https://www.baeldung.com/jsp-tag-library
Tutorialspoint:
- JSP Custom Tags: https://www.tutorialspoint.com/jsp/jsp_custom_tags.htm
Javatpoint:
- Custom Tags in JSP: https://www.javatpoint.com/custom-tags
Mkyong (mit Code-Beispielen):
- JSP Custom Tags Example: https://mkyong.com/jsp/jsp-2-0-simple-tag-example/
📖 Praktische Beispiele
GitHub:
- Jakarta JSP Examples: https://github.com/eclipse-ee4j/jakartaee-examples
- Custom Tag Examples: https://github.com/javaee-samples/javaee7-samples/tree/master/jaspic
Stack Overflow:
- JSP Custom Tags Questions: https://stackoverflow.com/questions/tagged/jsp+custom-tags
🔍 Best Practices & Advanced
IBM Developer:
- Writing Custom JSP Tags: https://developer.ibm.com/articles/wa-jsptags/
DZone:
- JSP Custom Tags Tutorial: https://dzone.com/articles/jsp-custom-tags
💬 Real Talk: Custom Tags vs. Moderne Frameworks
Java Fleet Büro, 15:00 Uhr. Nova und Elyndra diskutieren über Legacy-Code
Nova: „Elyndra, ich habe eine Frage. Unsere alten Projekte sind voll mit Custom Tags. Aber in modernen Projekten sehe ich nur noch React Components, Vue Components, Angular Components… Sind Custom Tags nicht veraltet?“
Elyndra: „Gute Beobachtung! Lass mich dir was zeigen.“
Elyndra öffnet zwei Code-Beispiele nebeneinander
Custom Tag (2010):
<shop:productCard
product="${product}"
showPrice="true"
onSale="${product.discount > 0}" />
React Component (2025):
<ProductCard
product={product}
showPrice={true}
onSale={product.discount > 0} />
Nova: „Das… das ist fast identisch!“
Elyndra: „Genau! Das Konzept ist zeitlos: Wiederverwendbare UI-Komponenten mit Props. Nur die Technologie hat sich geändert.
Was Custom Tags waren:
- UI-Komponentisierung für JSP
- Server-side Rendering
- Wiederverwendbarkeit durch Kapselung
Was React/Vue Components sind:
- UI-Komponentisierung für SPAs
- Client-side Rendering
- Wiederverwendbarkeit durch Kapselung
Der Unterschied:
- Custom Tags → Server generiert HTML
- React Components → Browser generiert DOM
Aber das Prinzip? Exakt dasselbe!“
Nova: „Also wenn ich Custom Tags verstehe, verstehe ich auch das Konzept hinter React?“
Elyndra: „Teilweise ja! Du verstehst:
- Component-based Architecture
- Props/Attributes
- Composition (Tags in Tags = Components in Components)
- Wiederverwendbarkeit
Was du noch lernen musst für React:
- State-Management
- Virtual DOM
- Event-Handling im Browser
- Component Lifecycle (anders als Tags!)
Custom Tags sind ein exzellentes Fundament für Component-Thinking!“
Nova: „Verstehe! Also veraltet, aber das Konzept lebt weiter?“
Elyndra: „Exactly! In Legacy-Projekten wirst du Custom Tags sehen. In neuen Projekten React/Vue/Angular. Aber das Denken ist ähnlich.
Übrigens: Viele große Enterprise-Systeme laufen NOCH HEUTE mit JSP und Custom Tags. Die wurden nicht umgeschrieben, weil:
- Sie funktionieren
- Migration zu React wäre teuer
- Team kennt JSP
Legacy-Code != Schlechter Code!“
✅ Checkpoint & Quiz
Quiz:
1. Was ist ein Tag Library Descriptor (TLD)? <details> <summary>Antwort anzeigen</summary>
Eine XML-Datei in WEB-INF/tlds/, die Custom Tags registriert und beschreibt.
Enthält:
- Tag-Namen
- Tag-Handler-Klassen
- Attribute-Definitionen
- URI für Taglib-Import
Verbindet JSP-Syntax (<fleet:tag>) mit Java-Klassen (FleetTag.java). </details>
2. Was bedeutet <rtexprvalue>true</rtexprvalue>? <details> <summary>Antwort anzeigen</summary>
Runtime Expression Value = true
Bedeutet: „EL-Expressions sind in diesem Attribut erlaubt!“
<!-- Mit rtexprvalue="true": -->
<fleet:alert message="${errorMsg}" /> <!-- OK! -->
<!-- Mit rtexprvalue="false": -->
<fleet:alert message="${errorMsg}" /> <!-- FEHLER! -->
<fleet:alert message="Static text" /> <!-- OK! -->
Best Practice: Fast immer true setzen! </details>
3. Was ist der Unterschied zwischen body-content="empty" und body-content="scriptless"? <details> <summary>Antwort anzeigen</summary>
empty:
- Kein Body erlaubt
- Tag muss self-closing sein:
<tag /> - Beispiel:
<fleet:copyright />
scriptless:
- Body ist erlaubt
- EL und JSTL funktionieren
- KEINE Java-Scriptlets erlaubt
- Beispiel:
<fleet:uppercase>Content here</fleet:uppercase>
tagdependent:
- Body wird nicht verarbeitet (raw text)
- Für Code-Listings, etc.
</details>
4. Warum braucht jedes Attribut einen Setter in der Tag-Handler-Klasse? <details> <summary>Antwort anzeigen</summary>
Der JSP-Container verwendet Reflection und die JavaBeans-Konvention:
<fleet:alert type="success" message="Done!" />
Container-Verhalten:
- Erstelle
new AlertTag() - Rufe
setType("success")auf - Rufe
setMessage("Done!")auf - Rufe
doTag()auf
Ohne Setter: Container kann Attribut nicht setzen → Fehler!
Naming ist kritisch:
- Attribut
type→ SettersetType() - Attribut
userName→ SettersetUserName()
</details>
5. Wie greife ich in einem Tag auf die HTTP-Session zu? <details> <summary>Antwort anzeigen</summary>
@Override
public void doTag() throws JspException, IOException {
// Cast zu PageContext
PageContext pageContext = (PageContext) getJspContext();
// Request holen
HttpServletRequest request =
(HttpServletRequest) pageContext.getRequest();
// Session holen
HttpSession session = request.getSession();
// Session-Attribute nutzen
User user = (User) session.getAttribute("user");
}
Wichtig: getJspContext() gibt JspContext zurück, nicht PageContext!
→ Cast notwendig für Zugriff auf Request/Response/Session </details>
6. Wie kann ich den Body-Content eines Tags manipulieren? <details> <summary>Antwort anzeigen</summary>
@Override
public void doTag() throws JspException, IOException {
// StringWriter als Buffer
StringWriter bodyContent = new StringWriter();
// Body in Buffer schreiben
getJspBody().invoke(bodyContent);
// Als String holen und manipulieren
String content = bodyContent.toString();
String modified = content.toUpperCase(); // Beispiel
// Ausgeben
getJspContext().getOut().write(modified);
}
Wichtig: invoke(bodyContent) schreibt in Buffer, invoke(null) direkt in Output! </details>
🎉 Tag 5 geschafft!
Slay! Du hast es geschafft! 🚀
Das hast du heute gerockt:
- ✅ Custom Tags von Grund auf verstanden
- ✅ Tag Library Descriptors (TLD) erstellt
- ✅ Simple Tags ohne Attribute gebaut
- ✅ Tags mit konfigurierbaren Attributen implementiert
- ✅ Body Content Processing gemeistert
- ✅ Best Practices für wiederverwendbare Komponenten gelernt
Von einfachen <fleet:copyright /> Tags zu komplexen Body-Processing-Tags!
Main Character Energy: Unlocked! ✨
Dein neues Skillset:
- Tag Library Design
- Component-based UI Thinking
- TLD-Konfiguration
- Body Content Manipulation
Wie geht’s weiter?
Morgen (Tag 6): JPA – Java Persistence API
Was dich erwartet:
- Was ist JPA und warum brauchen wir es?
- Entity-Klassen erstellen
- EntityManager verstehen
- persistence.xml konfigurieren
- CRUD-Operationen mit JPA
- Relationship-Mapping (OneToMany, ManyToOne, etc.)
Von Custom Tags zu Datenbank-Persistierung – wir machen deine Anwendung stateful!
Troubleshooting
Problem: Tag wird nicht gefunden – „No tag library could be found with this URI“
Lösung:
- Prüfe TLD-Datei liegt in
WEB-INF/tlds/ - Prüfe
<uri>in TLD stimmt mituri="..."in JSP überein - Server neu starten (TLD wird beim Start geladen!)
- Prüfe TLD ist valid XML (keine Syntax-Fehler)
Problem: Attribut wird nicht gesetzt – bleibt null
Lösung:
// FALSCH:
public void settype(String type) { ... } // Lowercase!
// RICHTIG:
public void setType(String type) { ... } // Camelcase!
JavaBeans-Konvention ist case-sensitive!
Problem: „Cannot invoke method on null object“
Lösung:
// Body könnte null sein bei empty tags!
@Override
public void doTag() throws JspException, IOException {
if (getJspBody() != null) {
getJspBody().invoke(null);
}
}
Problem: TLD-Änderungen werden nicht übernommen
Lösung:
- Server neu starten (nicht nur redeploy!)
- Lösche
work/odergenerated/Ordner - Clean & Build Projekt
- Hard-Refresh im Browser (Ctrl+F5)
Bis morgen! 👋
Elyndra
Java Web Aufbau – Tag 5 von 10
© 2025 Java Fleet Systems Consulting
Website: java-developer.online

