Java Web Aufbau – Tag 1 von 10
Von Elyndra Valen, Senior Developer bei Java Fleet Systems Consulting
🗺️ Deine Position im Kurs
| Tag | Thema | Status |
|---|---|---|
| → 1 | Filter im Webcontainer | 👉 DU BIST HIER! |
| 2 | Listener im Webcontainer | 🔒 Noch nicht freigeschaltet |
| 3 | Authentifizierung über Datenbank | 🔒 Noch nicht freigeschaltet |
| 4 | Container-Managed Security & Jakarta Security API | 🔒 Noch nicht freigeschaltet |
| 5 | Custom Tags & Tag Handler (SimpleTag) | 🔒 Noch nicht freigeschaltet |
| 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
Dein Ziel: Filter professionell einsetzen
📋 Voraussetzungen
Was du schon können solltest:
- ✅ Servlets verstehen und einsetzen
- ✅ Request/Response-Zyklus kennen
- ✅ Sessions und Cookies nutzen
- ✅ Deployment Descriptor (web.xml) verstehen
- ✅ Model 2 Pattern anwenden
- ✅ Maven-Projekte aufsetzen
Was du heute lernst:
- ✅ Filter-Chain verstehen und implementieren
- ✅ Request/Response Preprocessing & Postprocessing
- ✅ Filter-Reihenfolge kontrollieren
- ✅ Thread-Safety bei Filtern beachten
- ✅ Production-Ready Filter schreiben
⚡ 30-Sekunden-Überblick
Was sind Filter? Filter sind Interceptors, die Requests VOR und Responses NACH einem Servlet verarbeiten. Sie sind die zweite Spezialklassenart in Jakarta EE (nach Servlets) und essentiell für Cross-Cutting Concerns wie Encoding, Logging und Security.
Was lernst du heute? Du verstehst die Filter-Chain, manipulierst Requests/Responses, kontrollierst die Filter-Reihenfolge und baust production-ready Filter für Encoding, Logging und Security.
Warum ist das wichtig? Filter verhindern Code-Duplikation. Ohne sie würdest du UTF-8-Encoding, Security-Checks und Logging in JEDEM Servlet wiederholen. Filter machen das zentral und wiederverwendbar.
👋 Willkommen zu Java Web Aufbau!
Hi! 👋
Elyndra hier. Willkommen im Aufbau-Kurs!
Im Basic-Kurs hast du Servlets und JSPs gelernt – die Basis. Heute steigen wir tiefer ein: Filter sind die Magie hinter professionellen Webanwendungen.
Was unterscheidet Filter von Servlets?

- Servlets verarbeiten Business-Logic und generieren Responses
- Filter verarbeiten Requests/Responses vor und nach Servlets
- Filter sind für Cross-Cutting Concerns (Encoding, Logging, Security)
Real talk: Ohne Filter wäre jede Enterprise-App ein Chaos. Du würdest dieselbe Encoding-Logic, Security-Checks und Logging in JEDEM Servlet wiederholen. Filter machen das zentral und wiederverwendbar.
Heute lernst du die zweite Spezialklassenart in Jakarta EE kennen:
- Filter (Spezialklassenart #2) – Request/Response Interceptors
Morgen kommen dann Listener (Spezialklassenart #3) dran!
Bist du bereit? Let’s go! 🚀
🟢 GRUNDLAGEN: Filter verstehen
Was sind Filter?
Filter sind Interceptors, die Requests BEVOR sie zum Servlet kommen und Responses NACHDEM sie vom Servlet kommen verarbeiten.
Stell dir vor:
Browser → Filter 1 → Filter 2 → Filter 3 → Servlet
↓
Browser ← Filter 1 ← Filter 2 ← Filter 3 ← Response
Das ist die Filter-Chain!
In der Praxis bedeutet das:
- Filter 1 könnte UTF-8 Encoding setzen
- Filter 2 könnte Request-Logging machen
- Filter 3 könnte Security-Checks durchführen
- Das Servlet macht die Business-Logic
- Filter 3, 2, 1 können dann die Response manipulieren
Das Filter-Interface
Jeder Filter implementiert jakarta.servlet.Filter:
public interface Filter {
void init(FilterConfig filterConfig) throws ServletException;
void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain)
throws IOException, ServletException;
void destroy();
}
Lifecycle:
- init() – Einmalig beim Start
- doFilter() – Pro Request
- destroy() – Beim Shutdown
Kennst du das?
Genau! Filter haben denselben Lifecycle wie Servlets:
| Lifecycle-Phase | Servlet | Filter |
|---|---|---|
| Initialisierung | init(ServletConfig) | init(FilterConfig) |
| Request-Verarbeitung | service() / doGet() / doPost() | doFilter() |
| Shutdown | destroy() | destroy() |
Die Parallele:
- Beide werden einmal instanziiert
- Beide werden thread-safe verwendet (eine Instanz, viele Threads!)
- Beide haben denselben Lifecycle
Der Unterschied:
- Servlet verarbeitet Business-Logic
- Filter verarbeitet Request/Response vor und nach der Business-Logic
Dein erster Filter – Encoding-Filter
Problem: Umlaute sind in JEDEM Servlet kaputt, weil UTF-8 fehlt.
Lösung: Ein zentraler Encoding-Filter!
package com.javafleet.filters;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import java.io.IOException;
@WebFilter("/*") // Für ALLE URLs
public class EncodingFilter implements Filter {
private String encoding = "UTF-8";
@Override
public void init(FilterConfig config) throws ServletException {
// Optional: Encoding aus web.xml lesen
String encodingParam = config.getInitParameter("encoding");
if (encodingParam != null) {
encoding = encodingParam;
}
System.out.println("EncodingFilter initialized with: " + encoding);
}
@Override
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain)
throws IOException, ServletException {
// Request-Encoding setzen
request.setCharacterEncoding(encoding);
// Response-Encoding setzen
response.setCharacterEncoding(encoding);
response.setContentType("text/html;charset=" + encoding);
// Weiter zum nächsten Filter oder Servlet
chain.doFilter(request, response);
}
@Override
public void destroy() {
System.out.println("EncodingFilter destroyed");
}
}
Was macht dieser Code?
@WebFilter("/*")– Gilt für ALLE URLs (jeder Request geht durch diesen Filter)init()– Wird einmal beim Start aufgerufen, liest optionale ConfigdoFilter()– Wird pro Request aufgerufen:- Setzt Request-Encoding auf UTF-8
- Setzt Response-Encoding auf UTF-8
- Ruft
chain.doFilter()auf – KRITISCH! Ohne diese Zeile erreicht der Request NIE das Servlet!
destroy()– Wird beim Shutdown aufgerufen
Wichtig zu verstehen: chain.doFilter(request, response) ist das HerzStück! Es sagt: „Geh weiter zum nächsten Filter oder Servlet in der Chain.“ Ohne diese Zeile bleibt der Request stecken.
🟡 PROFESSIONALS: Filter-Chain verstehen
Die Filter-Chain im Detail

Die Chain ist essentiell!
@Override
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain)
throws IOException, ServletException {
// BEFORE: Preprocessing
System.out.println("Before Servlet");
long startTime = System.currentTimeMillis();
// Weiter zur Chain!
chain.doFilter(request, response);
// AFTER: Postprocessing
long duration = System.currentTimeMillis() - startTime;
System.out.println("After Servlet - Duration: " + duration + "ms");
}
Was macht dieser Code?
- BEFORE-Teil läuft VOR dem Servlet
- Logging, Zeit-Messung starten
- chain.doFilter() übergibt an nächsten Filter/Servlet
- AFTER-Teil läuft NACH dem Servlet
- Duration berechnen, Response manipulieren
In der Praxis bedeutet das: Du kannst Request-Daten loggen BEVOR das Servlet läuft, und Response-Daten loggen NACHDEM das Servlet fertig ist. Das ist mit normaler Servlet-Vererbung nicht möglich!
Was passiert wenn du chain.doFilter() vergisst? → Request erreicht NIE das Servlet! 💥
Filter-Reihenfolge kontrollieren
Problem: Mehrere Filter, aber Reihenfolge ist wichtig!
@WebFilter("/*")
public class EncodingFilter implements Filter { ... }
@WebFilter("/*")
public class LoggingFilter implements Filter { ... }
@WebFilter("/*")
public class SecurityFilter implements Filter { ... }
Problem: Bei Annotations ist die Reihenfolge undefiniert!
Lösung: web.xml für garantierte Reihenfolge:
<?xml version="1.0" encoding="UTF-8"?>
<web-app 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-app_6_0.xsd"
version="6.0">
<!-- Filter-Definitionen -->
<filter>
<filter-name>EncodingFilter</filter-name>
<filter-class>com.javafleet.filters.EncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter>
<filter-name>LoggingFilter</filter-name>
<filter-class>com.javafleet.filters.LoggingFilter</filter-class>
</filter>
<filter>
<filter-name>SecurityFilter</filter-name>
<filter-class>com.javafleet.filters.SecurityFilter</filter-class>
</filter>
<!-- Filter-Mappings - REIHENFOLGE DEFINIERT HIER! -->
<filter-mapping>
<filter-name>EncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
<filter-mapping>
<filter-name>LoggingFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
<filter-mapping>
<filter-name>SecurityFilter</filter-name>
<url-pattern>/admin/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
</web-app>
Was macht dieser Code?
Die Reihenfolge der <filter-mapping> Einträge definiert die Chain-Reihenfolge:
- EncodingFilter läuft ZUERST (für alle URLs)
- LoggingFilter läuft DANACH (für alle URLs)
- SecurityFilter läuft ZULETZT (nur für
/admin/*)
Wichtig zu verstehen: Die Position im web.xml bestimmt die Reihenfolge! Annotations können das nicht garantieren.
Praktisches Beispiel – Logging-Filter
Use-Case: Alle Requests loggen – URL, Methode, Duration, Status.
package com.javafleet.filters;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.*;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@WebFilter(filterName = "LoggingFilter", urlPatterns = "/*")
public class LoggingFilter implements Filter {
private static final DateTimeFormatter TIME_FORMATTER =
DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
@Override
public void init(FilterConfig config) throws ServletException {
System.out.println("LoggingFilter initialized");
}
@Override
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
// Request-Infos sammeln
String method = httpRequest.getMethod();
String uri = httpRequest.getRequestURI();
String queryString = httpRequest.getQueryString();
String fullUrl = uri + (queryString != null ? "?" + queryString : "");
// Start-Zeit
long startTime = System.currentTimeMillis();
String timestamp = LocalDateTime.now().format(TIME_FORMATTER);
System.out.println("\n=== REQUEST START ===");
System.out.println("Time: " + timestamp);
System.out.println("Method: " + method);
System.out.println("URL: " + fullUrl);
try {
// Weiter zur Chain
chain.doFilter(request, response);
} finally {
// End-Zeit (auch bei Exception!)
long duration = System.currentTimeMillis() - startTime;
int status = httpResponse.getStatus();
System.out.println("Status: " + status);
System.out.println("Duration: " + duration + "ms");
// Performance-Warning
if (duration > 1000) {
System.err.println("⚠️ SLOW REQUEST: " + fullUrl
+ " took " + duration + "ms");
}
System.out.println("=== REQUEST END ===\n");
}
}
@Override
public void destroy() {
System.out.println("LoggingFilter destroyed");
}
}
Was macht dieser Code?
- BEFORE-Teil:
- Request-Infos sammeln (Method, URL, Query)
- Start-Zeit merken
- Info loggen
- try-finally Block:
chain.doFilter()in try- finally garantiert: Auch bei Exception wird geloggt!
- AFTER-Teil:
- Duration berechnen
- Status-Code aus Response holen
- Performance-Warning bei > 1000ms
Output-Beispiel:
=== REQUEST START === Time: 14:32:15.342 Method: GET URL: /FilterDemo/products?category=electronics Status: 200 Duration: 47ms === REQUEST END ===
Production-ready Logging! 📊
Security-Filter – Authentication-Check
Use-Case: Alle /admin/* URLs brauchen Login.
package com.javafleet.filters;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.*;
import java.io.IOException;
@WebFilter("/admin/*")
public class AdminSecurityFilter implements Filter {
@Override
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
// Session holen (ohne neue zu erstellen!)
HttpSession session = httpRequest.getSession(false);
// Ist User eingeloggt?
Object user = (session != null) ? session.getAttribute("user") : null;
if (user == null) {
// NICHT eingeloggt → Redirect zu Login
String loginURL = httpRequest.getContextPath() + "/login";
httpResponse.sendRedirect(loginURL);
return; // ← WICHTIG! Kein chain.doFilter()!
}
// User ist eingeloggt → Weiter zur Chain
chain.doFilter(request, response);
}
@Override
public void init(FilterConfig config) throws ServletException {}
@Override
public void destroy() {}
}
Was macht dieser Code?
@WebFilter("/admin/*")– Nur für Admin-URLs- Session holen mit
false– keine neue Session erstellen - Prüfen ob User-Attribut existiert
- Falls NICHT eingeloggt:
- Redirect zu
/login return– KEINchain.doFilter()! Request stoppt hier.
- Redirect zu
- Falls eingeloggt:
chain.doFilter()– Request geht weiter
Wichtig zu verstehen: Durch das return ohne chain.doFilter() wird die Filter-Chain abgebrochen. Der Request erreicht nie das Admin-Servlet. Das ist Security! 🔒
🔵 BONUS: Dispatcher-Types
Was sind Dispatcher-Types?
Filter können auf verschiedene Request-Typen reagieren:
Dispatcher-Types:
REQUEST– Normale Browser-RequestsFORWARD–RequestDispatcher.forward()INCLUDE–RequestDispatcher.include()ERROR– Error-Pages (404, 500, etc.)ASYNC– Async-Requests
Beispiel in web.xml:
<filter-mapping>
<filter-name>EncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
In der Praxis bedeutet das:
- Ohne
<dispatcher>Tags: NurREQUEST(Standard) - Mit expliziten Tags: Filter läuft auch bei forwards/includes/errors
Use-Case: Encoding-Filter sollte auch bei forwards laufen, Security-Filter nur bei direkten Requests.
Thread-Safety bei Filtern
Kritisch zu verstehen:
Filter werden einmal instanziiert, aber von vielen Threads gleichzeitig genutzt!
// ❌ FALSCH - nicht thread-safe:
public class CounterFilter implements Filter {
private int requestCount = 0; // ← Race Condition!
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain) {
requestCount++; // ← Mehrere Threads gleichzeitig!
chain.doFilter(request, response);
}
}
// ✅ RICHTIG - thread-safe:
import java.util.concurrent.atomic.AtomicInteger;
public class CounterFilter implements Filter {
private final AtomicInteger requestCount = new AtomicInteger(0);
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain) {
requestCount.incrementAndGet(); // ← Atomic Operation!
chain.doFilter(request, response);
}
}
Wichtig zu verstehen:
- Instance-Variablen sind gefährlich (Race Conditions)
- Local-Variablen in
doFilter()sind sicher (Thread-local) - AtomicInteger für thread-safe Counters
Parallele zu Servlets: Genau dasselbe Problem wie bei Servlets! Eine Instanz, viele Threads.
💬 Real Talk: „Brauchen wir wirklich Filter?“
Java Fleet Büro, 14:30 Uhr. Nova sitzt mit ihrem Laptop am Tisch, Elyndra holt sich einen Kaffee.
Nova: „Elyndra, mal ehrlich – warum Filter? Kann ich das nicht einfach in jedem Servlet machen?“
Elyndra: setzt sich „Technisch? Ja. Praktisch? Horror. Stell dir vor: Du hast 50 Servlets und willst UTF-8 Encoding für alle. Würdest du request.setCharacterEncoding("UTF-8") in jedes Servlet copy-pasten?“
Nova: „Okay, das wäre… nervig. Aber ein Base-Servlet mit der Logik?“
Elyndra: „Besser. Aber was, wenn du die Logic VOR dem Servlet UND NACH dem Servlet brauchst? Logging zum Beispiel: Start-Zeit messen BEVOR das Servlet läuft, Duration berechnen NACHDEM es fertig ist.“
Nova: nickt langsam „Ah. Das geht mit Vererbung nicht gut…“
Elyndra: „Genau! Filter sind wie Middleware in Express.js – sie wrappen die Request/Response-Pipeline. Ein EncodingFilter für ALLE Requests. Ein LoggingFilter für ALLE Requests. Ein SecurityFilter nur für /admin/*. DRY-Prinzip auf Architektur-Ebene.“
Nova: „Und morgen lernen wir Listener… die sind auch für sowas?“
Elyndra: „Nein! Listener sind für Events. Application-Start, Session Created, etc. Das sind Sidechannel-Concerns, die nicht in die Request-Verarbeitung gehören. Aber das ist Stoff für morgen.“
Nova: „Also: Filter = Request-Pipeline wrappen?“
Elyndra: grinst „Exactly. Filter sind dein Türsteher – sie checken jeden, der reinkommt, und jeden, der rausgeht. Ohne Filter hättest du Security-Checks in JEDEM Servlet. Mit Filter? Einmal zentral.“
Nova: „Das… macht total Sinn. Real talk: Ich hab‘ Filter vorher als ‚optional nice-to-have‘ gesehen. Jetzt seh‘ ich: Das ist fundamentale Architektur.“
Elyndra: „Welcome to Enterprise Java.“ trinkt Kaffee „Übrigens: Wenn du später Spring Boot lernst – der DispatcherServlet hat auch eine Filter-Chain. Gleiches Konzept, andere Wrapper.“
💻 Hands-On: Projekt aufsetzen
Maven-Projekt erstellen
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.javafleet</groupId>
<artifactId>filter-demo</artifactId>
<version>1.0</version>
<packaging>war</packaging>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- Jakarta EE 10 API -->
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-api</artifactId>
<version>10.0.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>FilterDemo</finalName>
</build>
</project>
Projekt-Struktur
filter-demo/ ├── pom.xml ├── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── javafleet/ │ │ ├── filters/ │ │ │ ├── EncodingFilter.java │ │ │ ├── LoggingFilter.java │ │ │ └── AdminSecurityFilter.java │ │ └── servlets/ │ │ ├── HomeServlet.java │ │ └── AdminServlet.java │ └── webapp/ │ ├── WEB-INF/ │ │ └── web.xml │ └── index.html
🎯 Mini-Challenge
Baue ein vollständiges Monitoring-System mit Filtern!
Requirements:
- EncodingFilter – UTF-8 für alle Requests
- LoggingFilter – Loggt ALLE Requests mit:
- Timestamp
- Method
- URL
- Duration
- Status
- Performance-Warning bei > 500ms
- AdminSecurityFilter – Schützt
/admin/*- Redirect zu
/loginfalls nicht eingeloggt
- Redirect zu
- web.xml – Definiert Filter-Reihenfolge:
- Encoding
- Logging
- Security (nur für
/admin/*)
Bonus:
- Counter-Filter: Zählt Requests (thread-safe mit AtomicInteger)
- Zeige Request-Count auf Homepage
Lösung:
Die Lösung zu dieser Challenge findest du am Anfang von Tag 2 als Kurzwiederholung! 🚀
Aber versuch’s erst selbst – learning by doing! 💪
❓ Häufig gestellte Fragen
Frage 1: Was ist der Unterschied zwischen Filter und Interceptor?
Filter sind Teil der Servlet-API:
- Arbeiten auf HTTP-Ebene
- Vor und nach Servlets
- Konfiguriert via web.xml oder @WebFilter
Interceptor sind Teil von Frameworks (CDI, EJB):
- Arbeiten auf Methoden-Ebene
- Vor und nach Business-Methods
- Konfiguriert via @Interceptor
Filter = HTTP-Layer
Interceptor = Business-Layer
Frage 2: Kann ich die Filter-Chain abbrechen?
Ja! Einfach chain.doFilter() NICHT aufrufen:
@Override
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain)
throws IOException, ServletException {
if (notAuthorized) {
response.sendError(HttpServletResponse.SC_FORBIDDEN);
return; // ← Chain abgebrochen!
}
chain.doFilter(request, response); // Nur wenn authorized
}
Frage 3: Warum nutzt man AtomicInteger statt int im Filter?
Thread-Safety!
Mehrere Requests gleichzeitig = mehrere Threads = Race Conditions!
// ❌ FALSCH - nicht thread-safe: private int count = 0; count++; // Race Condition! // ✅ RICHTIG - thread-safe: private static final AtomicInteger count = new AtomicInteger(0); count.incrementAndGet(); // Atomic Operation!
Frage 4: Kann ich Filter nur für bestimmte HTTP-Methoden einsetzen?
Nein, nicht direkt mit @WebFilter.
Aber im Filter selbst prüfen:
@Override
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String method = httpRequest.getMethod();
if ("POST".equals(method)) {
// Nur für POST
}
chain.doFilter(request, response);
}
Frage 5: Wann sollte ich Filter vs. Servlet nutzen?
Filter nutzen für:
- Cross-Cutting Concerns (Encoding, Logging, Security)
- Preprocessing/Postprocessing
- Request/Response Manipulation
- Dinge die ALLE Requests betreffen
Servlet nutzen für:
- Business-Logic
- Content-Generierung
- Spezifische Endpunkte
Faustregel: Wenn es MEHRERE Servlets betrifft → Filter!
Wenn es EINEN Endpunkt betrifft → Servlet!
Frage 6: Wie debugge ich Filter?
- Logging:
System.out.println("Filter: " + filterName + " - Before");
chain.doFilter(request, response);
System.out.println("Filter: " + filterName + " - After");
- IDE-Debugger:
- Payara in Debug-Mode starten
- Breakpoints in
doFilter()setzen
- Browser DevTools:
- F12 → Network-Tab
- Headers inspizieren
Frage 7: Bernd meinte mal, Filter wären „fancy Middleware“. Hat er recht?
Lowkey ja! 😄
Filter sind im Prinzip das Servlet-API-Äquivalent zu Middleware in Node.js/Express oder Django.
Express Middleware:
app.use((req, res, next) => {
console.log('Request received');
next();
});
Jakarta EE Filter:
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain) {
System.out.println("Request received");
chain.doFilter(request, response);
}
Same concept, different ecosystem! Real talk: Wenn du Filter verstehst, verstehst du Middleware in allen Web-Frameworks. Das Konzept ist universal. 🎯
Bernd hat auch gesagt: „In Spring Boot machst du dasselbe, nur heißt es dann OncePerRequestFilter oder HandlerInterceptor.“ Er hat nicht Unrecht – aber die Grundkonzepte bleiben gleich.
🔧 Troubleshooting
Problem 1: Filter wird nicht ausgeführt
Ursachen:
@WebFilterfehlt oder falsche URLweb.xmlüberschreibt Annotation- Filter nicht deployed
Lösung:
// Prüfe Annotation:
@WebFilter("/*") // Gilt für ALLE URLs
@WebFilter("/admin/*") // Nur für /admin/...
// Oder in web.xml:
<filter-mapping>
<filter-name>MyFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Restart Server nach Änderungen!
Problem 2: „Response already committed“ Exception
Grund: Du versuchst, Response-Header zu ändern, nachdem Output geschrieben wurde.
Lösung: Im Filter: Setze Header BEVOR du chain.doFilter() aufrufst:
// ✅ RICHTIG:
response.setContentType("text/html;charset=UTF-8");
chain.doFilter(request, response);
// ❌ FALSCH:
chain.doFilter(request, response);
response.setContentType("text/html;charset=UTF-8"); // Zu spät!
Problem 3: Filter-Reihenfolge stimmt nicht
Grund: Bei Annotations ist die Reihenfolge undefiniert.
Lösung: Nutze web.xml für explizite Reihenfolge!
Problem 4: Umlaute funktionieren nicht trotz EncodingFilter
Ursachen:
- Filter läuft nicht (siehe Problem 1)
- Filter läuft NACH einem anderen Filter, der bereits Output schreibt
- Filter setzt Encoding NACH
chain.doFilter()
Lösung: EncodingFilter muss ERSTER in der Chain sein!
<filter-mapping>
<filter-name>EncodingFilter</filter-name> <!-- ← ZUERST! -->
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>LoggingFilter</filter-name> <!-- ← DANACH -->
<url-pattern>/*</url-pattern>
</filter-mapping>
Problem 5: Filter gilt nicht für Forwards/Includes
Grund: Standard Dispatcher = REQUEST only.
Lösung:
<filter-mapping>
<filter-name>MyFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
</filter-mapping>
Jakarta Web Aufbau - Tag 1
Filter meistern - Request/Response Interceptors in Jakarta EE
| Projekt | Für wen? | Download |
|---|---|---|
| tag01-filter-demo.zip | 🟢 Einsteiger | ⬇️ Download |
📚 Resources & Links
Offizielle Dokumentation:
Tutorials:
Best Practices:
GitHub:
💬 Feedback
Wie war Tag 1 für dich?
- 📧 elyndra@java-developer.online
- 💬 Kommentare unten im Blog
Was können wir verbessern? Dein Feedback hilft uns, den Kurs besser zu machen!
🎉 Tag 1 geschafft!
Wow, das war intensiv! 💪
Du hast heute richtig was gelernt:
- ✅ Filter-Chain verstanden
- ✅ Request/Response Preprocessing gemeistert
- ✅ Filter-Reihenfolge kontrolliert
- ✅ Thread-Safety bei Filtern beachtet
- ✅ Production-Ready Filter gebaut
Real talk: Filter sind das Fundament jeder professionellen Webanwendung. Ohne sie wäre jede Enterprise-App ein Chaos aus dupliziertem Code. Du hast heute die zweite Spezialklassenart in Jakarta EE gemeistert!
Slay! Du bist jetzt ein Filter-Pro! 🎯
🚀 Wie geht’s weiter?
Morgen (Tag 2): Listener im Webcontainer
Was dich erwartet:
- ServletContextListener (Application Lifecycle)
- HttpSessionListener (Session Tracking)
- ServletRequestListener (Performance Monitoring)
- HttpSessionAttributeListener (Login/Logout Detection)
- Thread-Safety mit AtomicInteger
- 8 Listener-Typen im Detail
- Das wird dein Game-Changer für Application Monitoring! 🔥
Besonderheit: Morgen lernst du die dritte Spezialklassenart – Listener sind Event-Handler, die auf Application/Session/Request Events reagieren. Kombiniert mit Filtern baust du dann ein komplettes Monitoring-System!
Brauchst du eine Pause?
Mach sie! Filter sind komplex. Lass das heute sacken.
Tipp für heute Abend:
Spiel mit der Mini-Challenge! Bau das Monitoring-System. Teste verschiedene Szenarien:
- Was passiert bei GET vs. POST?
- Wie reagiert der LoggingFilter bei slow Requests?
- Siehst du die Filter-Chain in der Console?
Learning by doing! 🔧
Bis morgen! 👋
Elyndra
elyndra@java-developer.online
Senior Developer bei Java Fleet Systems Consulting
Java Web Aufbau – Tag 1 von 10
Teil der Java Fleet Learning-Serie
© 2025 Java Fleet Systems Consulting
Website: java-developer.online

