Java Web Basic – Tag 3 von 10
Von Elyndra Valen, Senior Developer bei Java Fleet Systems Consulting
🗺️ Deine Position im Kurs
| Tag | Thema | Status |
|---|---|---|
| 1 | Java EE Überblick & HTTP | ✅ Abgeschlossen |
| 2 | HTTP-Protokoll Vertiefung & Zustandslosigkeit | ✅ Abgeschlossen |
| → 3 | Servlets & Servlet API | 👉 DU BIST HIER! |
| 4 | Deployment Descriptor & MVC vs Model 2 | 🔒 Noch nicht freigeschaltet |
| 5 | JSP & Expression Languages (Teil 1) | 🔒 Noch nicht freigeschaltet |
| 6 | Java Beans, Actions, Scopes & Direktiven | 🔒 Noch nicht freigeschaltet |
| 7 | Include-Action vs Include-Direktive | 🔒 Noch nicht freigeschaltet |
| 8 | JSTL – Java Standard Tag Libraries | 🔒 Noch nicht freigeschaltet |
| 9 | Java Web und Datenbanken – Datasource | 🔒 Noch nicht freigeschaltet |
| 10 | Connection Pools & JDBC in Web-Umgebungen | 🔒 Noch nicht freigeschaltet |
Modul: Java Web Basic
Gesamt-Dauer: 10 Arbeitstage (je 8 Stunden)
Dauer heute: 8 Stunden
Dein Ziel: Servlets von Grund auf verstehen und professionell einsetzen
📋 Voraussetzungen
Was du schon können solltest:
- ✅ HTTP-Protokoll verstehen (GET, POST, Status-Codes)
- ✅ Request/Response-Zyklus kennen
- ✅ Sessions und Cookies verstehen
- ✅ Maven-Projekt aufsetzen können
- ✅ Payara Server nutzen können
Was du heute lernst:
- ✅ Servlet-Lifecycle im Detail
- ✅ Request und Response Objekte nutzen
- ✅ Thread-Safety in Servlets
- ✅ Filter-Chain verstehen
- ✅ Listeners einsetzen
- ✅ Production-Ready Servlets schreiben
⚡ 30-Sekunden-Überblick
Was sind Servlets? Servlets sind Java-Klassen, die HTTP-Requests verarbeiten und Responses erzeugen. Sie sind die erste Spezialklassenart in Java EE, die du lernst – und die Grundlage ALLER Java-Webanwendungen. Auch wenn du später Spring Boot nutzt, laufen unter der Haube Servlets.
Was lernst du heute? Du verstehst den kompletten Servlet-Lifecycle, arbeitest mit Request/Response-Objekten, lernst Thread-Safety kennen und baust production-ready Servlets.
Warum ist das wichtig? Ohne Servlets gibt es keine Java-Webanwendung. Punkt. Selbst moderne Frameworks basieren auf dieser Technologie. Im Java Web Basic Kurs dreht sich fast alles um Servlets – weitere Spezialklassen wie Filter, WebListener und TagHandler lernst du später im Aufbau-Kurs kennen.
👋 Willkommen zu Tag 3!
Hi! 👋
Elyndra hier. Heute wird es praktisch und technisch zugleich!
Gestern hast du HTTP verstanden – heute setzt du das um. Servlets sind das Herzstück jeder Java-Webanwendung. Auch wenn du später mit JSPs oder Spring Boot arbeitest – unter der Haube laufen immer Servlets.
Servlets – Deine erste Spezialklassenart in Java EE: Servlets sind die erste Spezialklassenart, die du in Java EE kennenlernst. Im Java Web Basic Kurs dreht sich fast alles um Servlets – sie sind dein Einstieg in die Java-Web-Welt.
Später im Java Web Aufbau Kurs lernst du weitere Spezialklassen kennen:
- Filter (Request-Preprocessing)
- WebListener (Event-Handling)
- TagHandler (Custom JSP-Tags)
- Und noch mehr…
Aber heute? Heute meisterst du Servlets von Grund auf!
Warum Servlets? Als Nova mich neulich fragte: „Kann ich nicht einfach Spring Boot lernen und Servlets überspringen?“ – musste ich lachen. Spring Boot IST Servlets! Der DispatcherServlet in Spring? Ein Servlet. Jeder Controller? Wird zu einem Servlet gemappt.
Du kannst kein Haus bauen, ohne das Fundament zu verstehen.
Los geht’s! 🔧
🟢 GRUNDLAGEN
Was ist ein Servlet genau?
Definition: Ein Servlet ist eine Java-Klasse, die:
- Von
HttpServleterbt - HTTP-Requests empfängt
- HTTP-Responses zurückgibt
- Im Webcontainer läuft
Einfachstes Beispiel:
package com.javadeveloper.hellopayara;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<!DOCTYPE html>");
out.println("<html>");
out.println("<head><title>Hello Servlet</title></head>");
out.println("<body>");
out.println("<h1>Hello from Servlet!</h1>");
out.println("</body>");
out.println("</html>");
}
}
Was passiert hier?
@WebServlet("/hello")– Registriert das Servlet unter der URL/helloextends HttpServlet– Macht deine Klasse zu einem ServletdoGet()– Wird bei GET-Request aufgerufenresponse.getWriter()– Gibt HTML zurück
URL zum Testen:
http://localhost:8080/HelloPayara/hello
Der Servlet-Lifecycle
3 Phasen – super wichtig zu verstehen:
┌─────────────────────────────────────────────────┐ │ Phase 1: INIT (einmalig beim Start) │ │ ├─ Constructor wird aufgerufen │ │ └─ init() wird aufgerufen │ ├─────────────────────────────────────────────────┤ │ Phase 2: SERVICE (bei jedem Request) │ │ ├─ service() entscheidet: GET oder POST? │ │ ├─ doGet() oder doPost() wird aufgerufen │ │ └─ Multithreading! Mehrere Requests parallel │ ├─────────────────────────────────────────────────┤ │ Phase 3: DESTROY (beim Herunterfahren) │ │ └─ destroy() wird aufgerufen │ └─────────────────────────────────────────────────┘
Lifecycle-Demo:
@WebServlet("/lifecycle")
public class LifecycleServlet extends HttpServlet {
// Phase 1: Constructor (nur einmal)
public LifecycleServlet() {
System.out.println("1. Constructor called");
}
// Phase 1: Init (nur einmal)
@Override
public void init() throws ServletException {
System.out.println("2. init() called");
// Hier: DB-Connection Pool initialisieren
// Hier: Config laden
}
// Phase 2: Service (bei jedem Request)
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
System.out.println("3. doGet() called - Thread: " +
Thread.currentThread().getName());
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<h1>Request processed by: " +
Thread.currentThread().getName() + "</h1>");
}
// Phase 3: Destroy (nur einmal)
@Override
public void destroy() {
System.out.println("4. destroy() called");
// Hier: DB-Connections schließen
// Hier: Resources freigeben
}
}
Was du sehen wirst:
1. Constructor called 2. init() called 3. doGet() called - Thread: http-thread-pool-1 3. doGet() called - Thread: http-thread-pool-2 3. doGet() called - Thread: http-thread-pool-3 ... (bei weiteren Requests) 4. destroy() called (beim Server-Shutdown)
Wichtig:
- Constructor + init() = nur 1x
- doGet()/doPost() = bei jedem Request (parallel!)
- destroy() = nur 1x beim Shutdown
Request-Objekt verstehen
Was enthält ein HttpServletRequest?
@WebServlet("/request-info")
public class RequestInfoServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// 1. URL-Parameter lesen
String name = request.getParameter("name");
String age = request.getParameter("age");
// 2. HTTP-Headers lesen
String userAgent = request.getHeader("User-Agent");
String acceptLanguage = request.getHeader("Accept-Language");
// 3. Request-Informationen
String method = request.getMethod(); // GET, POST, etc.
String uri = request.getRequestURI(); // /HelloPayara/request-info
String protocol = request.getProtocol(); // HTTP/1.1
// 4. Client-Informationen
String remoteAddr = request.getRemoteAddr(); // IP-Adresse
String remoteHost = request.getRemoteHost(); // Hostname
// 5. Session
HttpSession session = request.getSession();
// Antwort erstellen
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<!DOCTYPE html>");
out.println("<html><body>");
out.println("<h1>Request Information</h1>");
out.println("<p><strong>Name:</strong> " + name + "</p>");
out.println("<p><strong>Age:</strong> " + age + "</p>");
out.println("<p><strong>Method:</strong> " + method + "</p>");
out.println("<p><strong>URI:</strong> " + uri + "</p>");
out.println("<p><strong>User-Agent:</strong> " + userAgent + "</p>");
out.println("<p><strong>Remote Address:</strong> " + remoteAddr + "</p>");
out.println("</body></html>");
}
}
Teste mit dieser URL:
http://localhost:8080/HelloPayara/request-info?name=Anna&age=25
Response-Objekt verstehen
Was kannst du mit HttpServletResponse machen?
@WebServlet("/response-demo")
public class ResponseDemoServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// 1. Content-Type setzen (WICHTIG!)
response.setContentType("text/html;charset=UTF-8");
// 2. Status-Code setzen (optional)
response.setStatus(HttpServletResponse.SC_OK); // 200
// 3. Headers setzen
response.setHeader("X-Custom-Header", "My Value");
response.setHeader("Cache-Control", "no-cache, no-store");
// 4. Cookie setzen
Cookie cookie = new Cookie("username", "anna");
cookie.setMaxAge(3600); // 1 Stunde
response.addCookie(cookie);
// 5. HTML-Output schreiben
PrintWriter out = response.getWriter();
out.println("<!DOCTYPE html>");
out.println("<html><body>");
out.println("<h1>Response Demo</h1>");
out.println("<p>Check your browser's developer tools!</p>");
out.println("</body></html>");
}
}
Content-Types – Die wichtigsten:
// HTML
response.setContentType("text/html;charset=UTF-8");
// JSON
response.setContentType("application/json;charset=UTF-8");
// Plain Text
response.setContentType("text/plain;charset=UTF-8");
// XML
response.setContentType("application/xml;charset=UTF-8");
// Binary (Download)
response.setContentType("application/octet-stream");
Redirects und Forwards
2 Arten, den User weiterzuleiten:
1. Redirect (Client-Side)
@WebServlet("/redirect-demo")
public class RedirectServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// Client macht neuen Request an andere URL
response.sendRedirect("https://www.google.com");
// Oder intern:
// response.sendRedirect("/HelloPayara/hello");
}
}
Was passiert?
- Server sendet Status-Code 302 (Found)
- Header „Location: https://www.google.com„
- Browser macht neuen Request an neue URL
Wann nutzen?
- Nach erfolgreicher POST-Operation (PRG-Pattern!)
- Weiterleitung zu externer URL
- User soll neue URL sehen
2. Forward (Server-Side)
@WebServlet("/forward-demo")
public class ForwardServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// Daten vorbereiten
request.setAttribute("message", "Hello from ForwardServlet!");
// An anderes Servlet weiterleiten (Server-intern)
RequestDispatcher dispatcher =
request.getRequestDispatcher("/hello");
dispatcher.forward(request, response);
}
}
Was passiert?
- Request wird server-intern weitergeleitet
- Browser merkt nichts davon
- URL im Browser bleibt gleich
Wann nutzen?
- MVC-Pattern: Controller → View
- Request-Attribute weitergeben
- Server-interne Verarbeitung
🟡 PROFESSIONALS
Thread-Safety – Das MUSS du verstehen!
KRITISCH: Ein Servlet ist EINE Instanz für ALLE Requests!
Der Webcontainer erstellt beim Start genau eine Instanz deines Servlets. Alle eingehenden Requests werden von verschiedenen Threads verarbeitet, die aber alle auf dieselbe Servlet-Instanz zugreifen.
// ❌ FALSCH - NICHT THREAD-SAFE!
@WebServlet("/counter-bad")
public class CounterBadServlet extends HttpServlet {
private int counter = 0; // ❌ Shared State!
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
counter++; // ❌ Race Condition!
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<h1>Counter: " + counter + "</h1>");
}
}
Was ist das Problem?
Zeit T1: Thread 1 liest counter = 0 Zeit T2: Thread 2 liest counter = 0 Zeit T3: Thread 1 schreibt counter = 1 Zeit T4: Thread 2 schreibt counter = 1 (sollte 2 sein!)
- 1000 User = 1000 verschiedene Threads
- Alle Threads greifen auf DIESELBE Servlet-Instanz zu
- Alle teilen sich die Instance-Variable
counter - Race Conditions sind garantiert!
✅ RICHTIG – Thread-Safe Variante 1: AtomicInteger
@WebServlet("/counter-atomic")
public class CounterAtomicServlet extends HttpServlet {
private final AtomicInteger counter = new AtomicInteger(0);
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
int currentValue = counter.incrementAndGet();
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<h1>Counter: " + currentValue + "</h1>");
}
}
✅ RICHTIG – Thread-Safe Variante 2: Lokale Variablen
@WebServlet("/counter-local")
public class CounterLocalServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// Lokale Variable = jeder Thread hat seine eigene!
int counter = 42;
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<h1>Counter: " + counter + "</h1>");
}
}
✅ RICHTIG – Thread-Safe Variante 3: Session
@WebServlet("/counter-session")
public class CounterSessionServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
HttpSession session = request.getSession();
// Pro User ein eigener Counter
Integer counter = (Integer) session.getAttribute("counter");
if (counter == null) {
counter = 0;
}
counter++;
session.setAttribute("counter", counter);
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<h1>Your Counter: " + counter + "</h1>");
}
}
Regel:
- ✅ Lokale Variablen = thread-safe
- ✅ Request-Attribute = thread-safe (pro Request)
- ✅ Session-Attribute = thread-safe (pro User)
- ✅ AtomicInteger/AtomicLong = thread-safe
- ❌ Instance-Variablen = NICHT thread-safe!
POST-Request mit Formular
HTML-Formular:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>User Registration</title>
</head>
<body>
<h1>Register</h1>
<form action="/HelloPayara/register" method="POST">
<label>Username:</label>
<input type="text" name="username" required><br><br>
<label>Email:</label>
<input type="email" name="email" required><br><br>
<label>Age:</label>
<input type="number" name="age" required><br><br>
<button type="submit">Register</button>
</form>
</body>
</html>
Servlet:
@WebServlet("/register")
public class RegisterServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// 1. Form-Daten lesen
String username = request.getParameter("username");
String email = request.getParameter("email");
String ageStr = request.getParameter("age");
// 2. Validierung
if (username == null || username.trim().isEmpty()) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST,
"Username is required");
return;
}
int age = 0;
try {
age = Integer.parseInt(ageStr);
} catch (NumberFormatException e) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST,
"Invalid age");
return;
}
// 3. Business-Logic (z.B. in DB speichern)
// userService.saveUser(username, email, age);
// 4. PRG-Pattern: POST → Redirect → GET
HttpSession session = request.getSession();
session.setAttribute("message",
"Registration successful! Welcome, " + username);
response.sendRedirect("/HelloPayara/success");
}
}
Success-Servlet:
@WebServlet("/success")
public class SuccessServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
HttpSession session = request.getSession();
String message = (String) session.getAttribute("message");
session.removeAttribute("message"); // Flash-Message
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<!DOCTYPE html>");
out.println("<html><body>");
out.println("<h1>✅ " + message + "</h1>");
out.println("<a href='/HelloPayara'>Back to home</a>");
out.println("</body></html>");
}
}
PRG-Pattern (Post-Redirect-Get):
POST /register
↓
[Daten verarbeiten]
↓
[In DB speichern]
↓
Redirect → GET /success
↓
[Erfolg anzeigen]
Warum PRG?
- ✅ Kein doppeltes Submit beim F5 (Reload)
- ✅ Saubere URL nach POST
- ✅ Bessere User-Experience
🔵 BONUS
Async Servlets – Nicht-blockierend arbeiten
Normal (blockierend):
Client → Request → [Servlet wartet] → Response
↓
[Thread blockiert]
Async (nicht-blockierend):
Client → Request → [Servlet gibt Thread frei] → Async-Processing
↓ ↓
[Thread verfügbar] [Complete später]
↓
Response
Beispiel:
@WebServlet(urlPatterns = "/async", asyncSupported = true)
public class AsyncServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// 1. Async-Context starten
AsyncContext asyncContext = request.startAsync();
asyncContext.setTimeout(5000); // 5 Sekunden Timeout
// 2. Langwierige Operation in separatem Thread
asyncContext.start(() -> {
try {
// Simuliere lange Datenbank-Abfrage
Thread.sleep(3000);
// Response schreiben
ServletResponse resp = asyncContext.getResponse();
resp.setContentType("text/html;charset=UTF-8");
PrintWriter out = resp.getWriter();
out.println("<h1>Async Response after 3 seconds</h1>");
// Async-Context abschließen
asyncContext.complete();
} catch (Exception e) {
e.printStackTrace();
}
});
// doGet() endet sofort - Thread ist frei!
}
}
Wann nutzen?
- Lange DB-Queries
- API-Calls zu externen Services
- File-Processing
- Lange Berechnungen
Vorteil:
- Threads bleiben nicht blockiert
- Bessere Skalierung
- Mehr gleichzeitige Requests möglich
Error Handling – Fehler elegant behandeln
Globaler Error-Handler:
@WebServlet("/error")
public class ErrorHandlerServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// Error-Informationen aus Request
Integer statusCode =
(Integer) request.getAttribute("jakarta.servlet.error.status_code");
String message =
(String) request.getAttribute("jakarta.servlet.error.message");
Throwable throwable =
(Throwable) request.getAttribute("jakarta.servlet.error.exception");
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<!DOCTYPE html>");
out.println("<html><body>");
out.println("<h1>⚠️ Error " + statusCode + "</h1>");
out.println("<p>" + message + "</p>");
if (throwable != null) {
out.println("<h2>Stack Trace:</h2>");
out.println("<pre>");
throwable.printStackTrace(out);
out.println("</pre>");
}
out.println("</body></html>");
}
}
In web.xml registrieren:
<error-page>
<error-code>404</error-code>
<location>/error</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/error</location>
</error-page>
JSON-Response zurückgeben
Moderne Web-APIs:
@WebServlet("/api/user")
public class UserApiServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// JSON-Response
response.setContentType("application/json;charset=UTF-8");
PrintWriter out = response.getWriter();
// Manuell JSON erstellen (für einfache Fälle)
out.println("{");
out.println(" \"id\": 1,");
out.println(" \"username\": \"anna\",");
out.println(" \"email\": \"anna@example.com\",");
out.println(" \"age\": 25");
out.println("}");
// Production: Jackson oder Gson verwenden!
// ObjectMapper mapper = new ObjectMapper();
// User user = new User(1, "anna", "anna@example.com", 25);
// mapper.writeValue(out, user);
}
}
Mit Gson (Maven-Dependency):
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
@WebServlet("/api/users")
public class UsersApiServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// User-Liste erstellen
List<User> users = Arrays.asList(
new User(1, "anna", "anna@example.com", 25),
new User(2, "bob", "bob@example.com", 30),
new User(3, "charlie", "charlie@example.com", 35)
);
// JSON-Response mit Gson
response.setContentType("application/json;charset=UTF-8");
Gson gson = new Gson();
String json = gson.toJson(users);
PrintWriter out = response.getWriter();
out.print(json);
}
}
class User {
private int id;
private String username;
private String email;
private int age;
public User(int id, String username, String email, int age) {
this.id = id;
this.username = username;
this.email = email;
this.age = age;
}
// Getters (für Gson)
public int getId() { return id; }
public String getUsername() { return username; }
public String getEmail() { return email; }
public int getAge() { return age; }
}
Response:
[
{
"id": 1,
"username": "anna",
"email": "anna@example.com",
"age": 25
},
{
"id": 2,
"username": "bob",
"email": "bob@example.com",
"age": 30
},
{
"id": 3,
"username": "charlie",
"email": "charlie@example.com",
"age": 35
}
]
💬 Real Talk: Thread-Safety ist kein Nice-to-Have
Java Fleet Büro, 15:30 Uhr. Nova sitzt frustriert vor ihrem Code. Elyndra kommt mit Kaffee vorbei.
Nova: „Ely, mein Counter-Servlet macht komische Sachen. Manchmal zeigt es 5, dann plötzlich 3, dann wieder 7. Total random!“
Elyndra: schaut auf den Code „Ah. Instance-Variable, oder?“
Nova: „Ja, private int counter = 0; – wo ist das Problem?“
Elyndra: setzt sich neben Nova „Das Problem heißt Race Condition. Du hast eine Servlet-Instanz für alle Requests. 100 User = 100 Threads greifen auf dieselbe Variable zu.“
Nova: „Aber… ich dachte, Java ist thread-safe?“
Elyndra: „Java gibt dir die Werkzeuge, aber du musst sie nutzen. int counter++ ist nicht atomar. Das sind drei Operationen: lesen, incrementieren, schreiben.“
Kat kommt aus ihrem Büro
Kat: „Ich hatte das gleiche Problem letzten Monat. Hab gedacht, ich teste mit nur einem User – alles lief perfekt. Dann ging die App live und es war Chaos.“
Nova: „Was ist die Lösung?“
Elyndra: „Drei Optionen: Erstens, AtomicInteger statt int. Zweitens, lokale Variablen statt Instance-Variablen. Drittens, Session-Attribute für User-spezifische Daten.“
Kat: „Ich nutze jetzt die Regel: Instance-Variablen in Servlets = nur final und immutable. Alles andere ist Session oder Request-Scope.“
Nova: „Real talk, das hätte man mir am Anfang sagen sollen!“
Elyndra: lacht „Deshalb lernst du’s jetzt. Thread-Safety ist kein Nice-to-Have – es ist kritisch für Production.“
✅ Checkpoint: Hast du es verstanden?
Zeit zu testen, was du gelernt hast!
Quiz:
Frage 1: Wie viele Instanzen eines Servlets erstellt der Webcontainer?
Frage 2: Welche Methode wird nur einmal beim Start aufgerufen?
Frage 3: Was ist der Unterschied zwischen sendRedirect() und forward()?
Frage 4: Warum sind Instance-Variablen in Servlets problematisch?
Frage 5: Was ist das PRG-Pattern und warum ist es wichtig?
Die Lösungen findest du weiter unten! 👇
Versuch erst selbst zu antworten – das hilft beim Lernen!
🎯 Mini-Challenge
Aufgabe:
Erstelle ein vollständiges Login-System mit:
- Login-Formular (HTML)
- LoginServlet (POST) – mit statischer Userliste zur Authentifizierung
- ProtectedServlet (nur für eingeloggte User – prüft Session selbst!)
- LogoutServlet
- DashboardServlet (zeigt Username aus Session)
Anforderungen:
- Username + Password validieren gegen eine statische HashMap/Liste im Servlet
- NICHT
request.login()verwenden! Das lernen wir später - Session nutzen für User-Info
- ProtectedServlet prüft selbst ob User eingeloggt (Session-Check!)
- Redirect zu Login falls nicht eingeloggt
- Logout löscht Session
- Fehlerbehandlung bei falschen Credentials
Hinweise:
- Login-Formular sendet POST an
/login - Erstelle eine statische Map im LoginServlet:
private static final Map<String, String> USERS = Map.of( "admin", "password", "user", "test123" ); - Bei Erfolg: Session-Attribut
usersetzen - ProtectedServlet: Prüfe am Anfang ob
session.getAttribute("user")existiert - Falls nicht eingeloggt:
response.sendRedirect("/HelloPayara/login") - Logout macht
session.invalidate()
Bonus:
- Zähle fehlgeschlagene Login-Versuche in der Session
- Zeige Fehlermeldung im Login-Formular
- Nach 3 fehlgeschlagenen Versuchen: Account temporär sperren (z.B. für 5 Minuten)
Lösung:
Die Lösung zu dieser Challenge findest du am Anfang von Tag 4 als Kurzwiederholung! 🚀
Alternativ kannst du die Musterlösung im GitHub-Projekt checken: Java Fleet – Tag 3 Challenge Solution
Geschafft? 🎉
Dann bist du bereit für die FAQ-Sektion!
❓ Häufig gestellte Fragen
Frage 1: Kann ich mehrere Servlets unter derselben URL registrieren?
Nein. Jede URL kann nur zu einem Servlet gemappt werden. Wenn du mehrere @WebServlet("/test") hast, wird nur eines verwendet (welches ist undefiniert).
Lösung: Verwende verschiedene URLs oder implementiere ein zentrales Servlet mit Routing-Logic.
Frage 2: Warum funktionieren Umlaute nicht in meinem Servlet?
Du hast vergessen, das Character-Encoding zu setzen:
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
Setze das am Anfang von doGet() oder doPost() in deinem Servlet.
Besser: Globale Konfiguration in payara-web.xml
Erstelle die Datei src/main/webapp/WEB-INF/payara-web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE payara-web-app PUBLIC "-//Payara.fish//DTD Payara Server 4 Servlet 3.0//EN"
"https://docs.payara.fish/schemas/payara-web-app_4.dtd">
<payara-web-app error-url="">
<parameter-encoding default-charset="UTF-8"/>
</payara-web-app>
Jetzt wird automatisch UTF-8 für alle Request-Parameter verwendet – du musst es nicht mehr in jedem Servlet setzen! 🎯
Im Java Web Aufbau Kurs lernst du Filter kennen, die das ebenfalls für alle Requests machen können.
Frage 3: Was ist der Unterschied zwischen request.getSession() und request.getSession(false)?
getSession()→ Erstellt Session, falls keine existiertgetSession(false)→ Gibtnullzurück, falls keine Session existiert
Nutze getSession(false) in Login-Check-Logik, um nicht ungewollt Sessions zu erstellen!
Frage 4: Wie kann ich File-Uploads in Servlets verarbeiten?
Mit @MultipartConfig:
@WebServlet("/upload")
@MultipartConfig
public class UploadServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
Part filePart = request.getPart("file");
String fileName = filePart.getSubmittedFileName();
InputStream fileContent = filePart.getInputStream();
// Speichern...
}
}
HTML-Formular braucht enctype="multipart/form-data"!
Frage 5: Sollte ich HTML direkt in Servlets schreiben?
Nein! Für größere Projekte:
- ✅ Servlet = Controller (Business-Logic)
- ✅ JSP = View (Presentation)
Servlets sollten nur JSON-APIs oder kleine Responses erzeugen.
Ab Tag 5 lernst du JSPs kennen! 🚀
Frage 6: Wie debugge ich Servlets?
- Logging:
System.out.println("Debug: " + variable);
- IDE-Debugger:
- Payara Server in Debug-Mode starten
- Breakpoints setzen
- Step-through im Code
- Browser DevTools:
- F12 → Network-Tab
- Requests/Responses inspizieren
- Headers checken
Frage 7: Bernd meinte mal, Servlets wären „oldschool“ und ich sollte direkt Spring Boot lernen. Hat er recht?
Lowkey ja, aber auch nein. Real talk:
Spring Boot ist moderner und produktiver – keine Frage. Aber Spring Boot IST Servlets! Der DispatcherServlet ist das Herzstück von Spring MVC.
Wenn du Servlets verstehst:
- ✅ Du verstehst, wie Spring Boot unter der Haube funktioniert
- ✅ Du kannst Probleme besser debuggen
- ✅ Du weißt, was Filter, Listeners und Request-Scopes sind
- ✅ Du bist nicht nur „Framework-User“, sondern verstehst die Grundlagen
Für neue Projekte würde ich auch Spring Boot nehmen. Aber die Servlet-Basics zu kennen? Unverzichtbar für jeden Java-Web-Dev.
Bernd hat’s mal so gesagt: „Du kannst kein Auto tunen, wenn du nicht weißt, wie ein Motor funktioniert.“ Same here. 🔧
📚 Quiz-Lösungen
Hier sind die Antworten zum Quiz von oben:
Frage 1: Wie viele Instanzen eines Servlets erstellt der Webcontainer?
Antwort:
Genau eine Instanz pro Servlet-Klasse!
Das ist super wichtig zu verstehen:
- Der Webcontainer erstellt beim Start eine Instanz
- Diese Instanz wird von allen Threads/Requests geteilt
- Deshalb sind Instance-Variablen gefährlich (Race Conditions!)
Beispiel:
@WebServlet("/counter")
public class CounterServlet extends HttpServlet {
private int counter = 0; // ❌ Shared State!
// Alle 1000 User teilen sich diese Variable!
}
Frage 2: Welche Methode wird nur einmal beim Start aufgerufen?
Antwort:
init() – Die Init-Methode!
Der Lifecycle:
- Constructor (1x beim Laden der Klasse)
- init() (1x nach dem Constructor)
- doGet()/doPost() (bei jedem Request, parallel!)
- destroy() (1x beim Shutdown)
Nutze init() für:
- DB-Connection Pool aufbauen
- Config laden
- Cache initialisieren
Nutze destroy() für:
- Connections schließen
- Resources freigeben
Frage 3: Was ist der Unterschied zwischen sendRedirect() und forward()?
Antwort:
sendRedirect() – Client-Side:
- Browser macht neuen Request
- URL im Browser ändert sich
- Kann zu externen URLs gehen
- Status-Code 302 (Found)
- Request-Attribute gehen verloren
response.sendRedirect("/success"); // Neuer Request
forward() – Server-Side:
- Request wird server-intern weitergeleitet
- URL im Browser bleibt gleich
- Nur interne URLs möglich
- Kein neuer Request
- Request-Attribute bleiben erhalten
request.getRequestDispatcher("/success").forward(request, response);
Wann was?
- Nach POST → sendRedirect (PRG-Pattern!)
- MVC: Controller → View → forward()
- Externe URLs → sendRedirect()
- Request-Attribute weitergeben → forward()
Frage 4: Warum sind Instance-Variablen in Servlets problematisch?
Antwort:
Weil alle Threads die gleiche Servlet-Instanz teilen!
Problem:
@WebServlet("/user")
public class UserServlet extends HttpServlet {
private String username; // ❌ GEFÄHRLICH!
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response) {
username = request.getParameter("username");
// Thread 1 setzt "Anna"
// Thread 2 setzt "Bob" → überschreibt "Anna"!
// Thread 1 liest plötzlich "Bob" statt "Anna"!
}
}
Race Condition garantiert!
Lösungen:
- Lokale Variablen (thread-safe)
- Request-Attribute (pro Request)
- Session-Attribute (pro User)
- AtomicInteger/AtomicLong (thread-safe)
- Synchronization (langsam!)
Frage 5: Was ist das PRG-Pattern und warum ist es wichtig?
Antwort:
PRG = Post-Redirect-Get Pattern
Problem ohne PRG:
1. User füllt Formular aus 2. Klick auf "Submit" → POST /register 3. Server verarbeitet → 200 OK mit HTML 4. User drückt F5 (Reload) 5. Browser fragt: "Form nochmal senden?" 6. User klickt "Ja" 7. Daten werden DOPPELT gespeichert! ❌
Lösung mit PRG:
1. User füllt Formular aus
2. Klick auf "Submit" → POST /register
3. Server verarbeitet Daten
4. Server macht sendRedirect("/success") → 302
5. Browser macht GET /success
6. Success-Seite wird angezeigt
7. User drückt F5 (Reload)
8. Nur GET /success wird wiederholt → kein Problem! ✅
Implementation:
@WebServlet("/register")
public class RegisterServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response) {
// 1. Daten verarbeiten
String username = request.getParameter("username");
// ... in DB speichern ...
// 2. Flash-Message in Session
HttpSession session = request.getSession();
session.setAttribute("message", "Registration successful!");
// 3. REDIRECT zu GET-Seite
response.sendRedirect("/success");
}
}
@WebServlet("/success")
public class SuccessServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response) {
// Flash-Message anzeigen
HttpSession session = request.getSession();
String message = (String) session.getAttribute("message");
session.removeAttribute("message"); // Nur einmal anzeigen!
// HTML-Output...
}
}
Vorteile:
- ✅ Kein doppeltes Submit bei F5
- ✅ Saubere URL nach POST
- ✅ Browser-History freundlich
- ✅ Bessere UX
Always use PRG after POST! 🎯
🎉 Tag 3 geschafft!
Wow, das war heftig! 💪
Du hast heute richtig was gelernt:
- ✅ Servlet-Lifecycle verstanden
- ✅ Request/Response-Objekte gemeistert
- ✅ Thread-Safety kennengelernt
- ✅ Filter und Listener eingesetzt
- ✅ Production-Ready Code geschrieben
Real talk: Servlets sind die Basis von ALLEM in Java-Web. Selbst Spring Boot nutzt unter der Haube die Servlet-API. Du hast heute das Fundament gelegt!
Slay! Du bist jetzt ein Servlet-Pro! 🎯
🚀 Wie geht’s weiter?
Morgen (Tag 4): Deployment Descriptor & MVC vs Model 2
Was dich erwartet:
web.xmlim Detail verstehen- MVC-Pattern vs. Model 2 Pattern
- Servlet-Mapping ohne Annotations
- Context-Parameter und Init-Parameter
- Security-Constraints konfigurieren
- Das wird dein Game-Changer für Enterprise-Projekte! 🔥
Besonderheit: Morgen lernst du, wie große Enterprise-Projekte konfiguriert werden – ohne Annotations! Das ist kritisch für Legacy-Code und große Projekte.
Brauchst du eine Pause?
Mach sie! Servlets sind komplex. Lass das heute sacken.
Tipp für heute Abend:
Spiel mit der Mini-Challenge! Bau das Login-System. Teste verschiedene Szenarien:
- Was passiert bei falschem Passwort?
- Was passiert, wenn du
/protecteddirekt aufrufst? - Was passiert nach Logout?
Learning by breaking things! 🔧
🔧 Troubleshooting
Problem 1: „Cannot find HttpServlet“
Lösung: Maven-Dependency fehlt:
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
<scope>provided</scope>
</dependency>
Problem 2: „404 – Servlet not found“
Mögliche Ursachen:
- Falsche URL (z.B.
/hellostatt/HelloPayara/hello) @WebServletAnnotation fehlt- Servlet nicht deployed
- Context-Root falsch
Lösung: Überprüfe:
http://localhost:8080/[CONTEXT-ROOT]/[SERVLET-PATH]
↑ ↑
pom.xml @WebServlet
Problem 3: „IllegalStateException: Response already committed“
Grund: Du versuchst, Response-Header zu setzen, nachdem du schon Output geschrieben hast.
Lösung: Setze IMMER zuerst Headers, dann Content:
// ✅ RICHTIG:
response.setContentType("text/html"); // 1. Content-Type
response.addCookie(cookie); // 2. Cookies
PrintWriter out = response.getWriter(); // 3. Output
out.println("<html>...</html>"); // 4. Content
Problem 4: Umlaute sind kaputt
Lösung: Character-Encoding setzen:
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
Am besten in einem Filter für alle Requests!
Problem 5: Session ist immer null
Grund: Du rufst request.getSession(false) auf, ohne dass eine Session existiert.
Lösung:
// Session erstellen, falls keine existiert:
HttpSession session = request.getSession(); // ← OHNE false
// Oder: Erst prüfen
HttpSession session = request.getSession(false);
if (session == null) {
// Keine Session vorhanden
}
📚 Resources & Links
Offizielle Dokumentation:
Payara Server:
Best Practices:
Tools:
- Postman – API-Testing
- Browser DevTools – Network-Tab
Unsere GitHub-Repos:
💬 Feedback
Wie war Tag 3 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!
👋 Bis morgen!
Das war Tag 3 – einer der härtesten, aber auch wichtigsten Tage!
Servlets sind die Basis von allem. Du hast heute das Fundament gelegt, auf dem du morgen aufbauen wirst.
Bis morgen! 👋
Elyndra
Java Web Basic – Tag 3 von 10
Teil der Java Fleet Learning-Serie
© 2025 Java Fleet Systems Consulting
Website: java-developer.online

