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


🗺️ Deine Position im Kurs

TagThemaStatus
→ 1Filter im Webcontainer👉 DU BIST HIER!
2Listener im Webcontainer🔒 Noch nicht freigeschaltet
3Authentifizierung über Datenbank🔒 Noch nicht freigeschaltet
4Container-Managed Security & Jakarta Security API🔒 Noch nicht freigeschaltet
5Custom Tags & Tag Handler (SimpleTag)🔒 Noch nicht freigeschaltet
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
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?

Filter
  • 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:

  1. init() – Einmalig beim Start
  2. doFilter() – Pro Request
  3. destroy() – Beim Shutdown

Kennst du das?

Genau! Filter haben denselben Lifecycle wie Servlets:

Lifecycle-PhaseServletFilter
Initialisierunginit(ServletConfig)init(FilterConfig)
Request-Verarbeitungservice() / doGet() / doPost()doFilter()
Shutdowndestroy()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?

  1. @WebFilter("/*") – Gilt für ALLE URLs (jeder Request geht durch diesen Filter)
  2. init() – Wird einmal beim Start aufgerufen, liest optionale Config
  3. doFilter() – 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!
  4. 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?

  1. BEFORE-Teil läuft VOR dem Servlet
    • Logging, Zeit-Messung starten
  2. chain.doFilter() übergibt an nächsten Filter/Servlet
  3. 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:

  1. EncodingFilter läuft ZUERST (für alle URLs)
  2. LoggingFilter läuft DANACH (für alle URLs)
  3. 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?

  1. BEFORE-Teil:
    • Request-Infos sammeln (Method, URL, Query)
    • Start-Zeit merken
    • Info loggen
  2. try-finally Block:
    • chain.doFilter() in try
    • finally garantiert: Auch bei Exception wird geloggt!
  3. 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?

  1. @WebFilter("/admin/*") – Nur für Admin-URLs
  2. Session holen mit false – keine neue Session erstellen
  3. Prüfen ob User-Attribut existiert
  4. Falls NICHT eingeloggt:
    • Redirect zu /login
    • returnKEIN chain.doFilter()! Request stoppt hier.
  5. 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-Requests
  • FORWARDRequestDispatcher.forward()
  • INCLUDERequestDispatcher.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: Nur REQUEST (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:

  1. EncodingFilter – UTF-8 für alle Requests
  2. LoggingFilter – Loggt ALLE Requests mit:
    • Timestamp
    • Method
    • URL
    • Duration
    • Status
    • Performance-Warning bei > 500ms
  3. AdminSecurityFilter – Schützt /admin/*
    • Redirect zu /login falls nicht eingeloggt
  4. web.xml – Definiert Filter-Reihenfolge:
      1. Encoding
      1. Logging
      1. 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?

  1. Logging:
System.out.println("Filter: " + filterName + " - Before");
chain.doFilter(request, response);
System.out.println("Filter: " + filterName + " - After");
  1. IDE-Debugger:
  • Payara in Debug-Mode starten
  • Breakpoints in doFilter() setzen
  1. 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:

  1. @WebFilter fehlt oder falsche URL
  2. web.xml überschreibt Annotation
  3. 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:

  1. Filter läuft nicht (siehe Problem 1)
  2. Filter läuft NACH einem anderen Filter, der bereits Output schreibt
  3. 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

Frage 1 von 10

Was ist der Hauptzweck eines Servlet-Filters in Jakarta EE?

Frage 2 von 10

Welche Methode des Filter-Interface wird für die eigentliche Request-Verarbeitung aufgerufen?

Frage 3 von 10

Wie wird die Filter-Chain fortgesetzt, damit der nächste Filter oder das Servlet aufgerufen wird?

Frage 4 von 10

Für welche typischen Cross-Cutting Concerns werden Filter in Enterprise-Anwendungen eingesetzt?

Frage 5 von 10

Welche Annotation registriert einen Filter für alle URLs einer Webanwendung?

Frage 6 von 10

Was ist die richtige Reihenfolge des Filter-Lifecycles in Jakarta EE?

Frage 7 von 10

Warum müssen Filter thread-safe implementiert werden?

Frage 8 von 10

Was passiert, wenn chain.doFilter() in einem Filter NICHT aufgerufen wird?

Frage 9 von 10

Welche Parallele besteht zwischen Servlets und Filtern bezüglich ihrer Instanziierung?

Frage 10 von 10

Was ist der Unterschied zwischen der Request-Verarbeitung in Filtern und Servlets?

ProjektFü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?

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

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.