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

Filter

🗺️ 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
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:

  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.“


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

  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! 🚀

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?

  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.


📚 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-PhaseServletFilter
Initialisierunginit(ServletConfig)init(FilterConfig)
Request-Verarbeitungservice() / doGet() / doPost()doFilter()
Shutdowndestroy()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)
  • FORWARDRequestDispatcher.forward() (Server-internes Forward)
  • INCLUDERequestDispatcher.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:

  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>

📚 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.