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

Custom Tags

📋 Deine Position im Kurs

TagThemaStatus
1Filter im Webcontainer✅ Abgeschlossen
2Listener im Webcontainer✅ Abgeschlossen
3Authentifizierung über Datenbank✅ Abgeschlossen
4Container-Managed Security & Jakarta Security API✅ Abgeschlossen
→ 5Custom Tags & Tag Handler (Teil-1 SimpleTag)👉 DU BIST HIER!
6Custom Tag Handler mit BodyTagSupport🔒 Noch nicht freigeschaltet
7JPA vs JDBC – Konfiguration & Provider🔒 Noch nicht freigeschaltet
8JPA Relationen (1): @OneToOne & @ManyToOne🔒 Noch nicht freigeschaltet
9JPA Relationen (2): @OneToMany & @ManyToMany🔒 Noch nicht freigeschaltet
10JSF Ü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 SimpleTagSupport
SimpleTagSupport 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 Output
getJspContext().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 → Methode setType()
  • Attribut message → Methode setMessage()
  • Attribut userName → Methode setUserName()

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:

  1. JSP enthält <fleet:alert type="danger" message="Error!" />
  2. Container erstellt neue Instanz von AlertTag
  3. Container ruft setType("danger") auf
  4. Container ruft setMessage("Error!") auf
  5. Container ruft doTag() auf
  6. 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 werden
  • false = 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 – Text
  • java.lang.Integer – Ganzzahlen
  • java.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() Methode
getJspBody() 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:

  1. JSP parst <fleet:uppercase>Hello ${name}!</fleet:uppercase>
  2. Container evaluiert EL → „Hello Nova!“
  3. Container ruft doTag() auf
  4. Wir fangen Body ab → „Hello Nova!“
  5. Wir transformieren → „HELLO NOVA!“
  6. 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: &lt;fleet:formatDate date="${order.date}" pattern="dd.MM.yyyy" /&gt;
    </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 SimpleTagSupport als Basis
  • Denk an Getter UND Setter für alle Attribute
  • Setze <rtexprvalue>true</rtexprvalue> für EL-Support
  • Bei repeat brauchst du eine Loop in doTag()

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 TagSupport oder BodyTagSupport
  • ❌ 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:

Oracle/Legacy (noch relevant):

🎓 Tutorials & Guides

Baeldung (sehr gut!):

Tutorialspoint:

Javatpoint:

Mkyong (mit Code-Beispielen):

📖 Praktische Beispiele

GitHub:

Stack Overflow:

🔍 Best Practices & Advanced

IBM Developer:

DZone:


💬 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:

  1. Sie funktionieren
  2. Migration zu React wäre teuer
  3. 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:

  1. Erstelle new AlertTag()
  2. Rufe setType("success") auf
  3. Rufe setMessage("Done!") auf
  4. Rufe doTag() auf

Ohne Setter: Container kann Attribut nicht setzen → Fehler!

Naming ist kritisch:

  • Attribut type → Setter setType()
  • Attribut userName → Setter setUserName()

</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:

  1. Prüfe TLD-Datei liegt in WEB-INF/tlds/
  2. Prüfe <uri> in TLD stimmt mit uri="..." in JSP überein
  3. Server neu starten (TLD wird beim Start geladen!)
  4. 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:

  1. Server neu starten (nicht nur redeploy!)
  2. Lösche work/ oder generated/ Ordner
  3. Clean & Build Projekt
  4. 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

Autor

  • Elyndra Valen

    28 Jahre alt, wurde kürzlich zur Senior Entwicklerin befördert nach 4 Jahren intensiver Java-Entwicklung. Elyndra kennt die wichtigsten Frameworks und Patterns, beginnt aber gerade erst, die tieferen Zusammenhänge und Architektur-Entscheidungen zu verstehen. Sie ist die Brücke zwischen Junior- und Senior-Welt im Team.