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
Gesamt-Dauer: 10 Arbeitstage (je 8 Stunden)
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.“
✅ Checkpoint: Filter Quiz
Zeit zu checken, ob du alles verstanden hast!
Die Lösungen findest du weiter unten – aber versuch’s erst selbst! 💪
Frage 1: Was passiert, wenn du chain.doFilter() in einem Filter vergisst?
a) Der Request wird normal verarbeitet
b) Der Request erreicht NIE das Servlet
c) Es gibt eine CompilerException
d) Der Filter wird übersprungen
Frage 2: Wie stellst du sicher, dass Filter in einer bestimmten Reihenfolge ausgeführt werden?
a) Mit @WebFilter(order=1)
b) Mit web.xml <filter-mapping> Reihenfolge
c) Alphabetisch nach Klassennamen
d) Filter können nicht sortiert werden
Frage 3: Wie viele Instanzen eines Filters erstellt der Webcontainer?
a) Eine pro Request
b) Eine pro Session
c) Eine pro Filter-Klasse (für alle Requests)
d) Kommt auf die Konfiguration an
Frage 4: Welche Methode hat ein Filter NICHT?
a) init()
b) doFilter()
c) service()
d) destroy()
Frage 5: Wofür steht der Dispatcher-Type FORWARD?
a) Normale Browser-Requests
b) RequestDispatcher.forward() Calls
c) Redirects (sendRedirect())
d) Error-Pages
💻 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! 🚀
Alternativ kannst du die Musterlösung im GitHub-Projekt checken: Java Web Aufbau – Tag 1 Challenge Solution
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.
📚 Quiz-Lösungen
Hier sind die Antworten zum Quiz von oben:
Frage 1: Was passiert, wenn du chain.doFilter() in einem Filter vergisst?
Antwort: b) Der Request erreicht NIE das Servlet
Wenn du chain.doFilter() vergisst, wird die Filter-Chain abgebrochen. Der Request kommt nie beim Servlet an!
Das ist das Gegenstück zu next() in Express.js – ohne chain.doFilter() stoppt die Verarbeitung.
Use-Case: Security-Filter, der unautorisierte Requests blockt:
if (!authorized) {
response.sendError(403);
return; // Kein chain.doFilter() → Request wird geblockt
}
chain.doFilter(request, response); // Nur wenn authorized
Frage 2: Wie stellst du sicher, dass Filter in einer bestimmten Reihenfolge ausgeführt werden?
Antwort: b) Mit web.xml <filter-mapping> Reihenfolge
Bei @WebFilter-Annotations ist die Reihenfolge undefiniert. Für garantierte Reihenfolge nutze web.xml:
<filter-mapping>
<filter-name>EncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>LoggingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Die Reihenfolge der <filter-mapping>-Einträge definiert die Chain-Reihenfolge!
In der Praxis bedeutet das: Production-Apps nutzen fast immer web.xml für Filter-Reihenfolge, weil es explizit und wartbar ist.
Frage 3: Wie viele Instanzen eines Filters erstellt der Webcontainer?
Antwort: c) Eine pro Filter-Klasse (für alle Requests)
Genau wie bei Servlets:
- Der Webcontainer erstellt beim Start eine Instanz
- Diese Instanz wird von allen Threads/Requests geteilt
- Deshalb sind Instance-Variablen gefährlich (Race Conditions!)
Parallele zu Servlets:
Servlet: 1 Instanz → viele Threads → Thread-Safety beachten! Filter: 1 Instanz → viele Threads → Thread-Safety beachten!
Beide Spezialklassenarten folgen demselben Prinzip!
Frage 4: Welche Methode hat ein Filter NICHT?
Antwort: c) service()
Diese Methode gibt es nur bei Servlets!
Vergleich:
| Lifecycle-Phase | Servlet | Filter |
|---|---|---|
| Initialisierung | init(ServletConfig) | init(FilterConfig) |
| Request-Verarbeitung | service() / doGet() / doPost() | doFilter() |
| Shutdown | destroy() | destroy() |
Filter haben doFilter() statt service(), aber das Konzept ist ähnlich – beide werden pro Request aufgerufen!
Frage 5: Wofür steht der Dispatcher-Type FORWARD?
Antwort: b) RequestDispatcher.forward() Calls
Dispatcher-Types definieren, wann ein Filter ausgeführt wird:
REQUEST– Normale Browser-Requests (Standard)FORWARD–RequestDispatcher.forward()(Server-internes Forward)INCLUDE–RequestDispatcher.include()(Content-Inclusion)ERROR– Error-Pages (404, 500, etc.)ASYNC– Async-Requests
Beispiel:
<filter-mapping>
<filter-name>EncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
Encoding-Filter läuft bei normalen Requests UND bei forwards!
🔧 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>
📚 Resources & Links
Offizielle Dokumentation:
Tutorials:
Best Practices:
GitHub:
💬 Feedback
Wie war Tag 1 für dich?
- 📧 elyndra@java-developer.online
- 💬 Kommentare unten im Blog
- 📱 Folge uns: @java_developer_online
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

