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


🗺️ Deine Position im Kurs

TagThemaStatus
1Java EE Überblick & HTTP ✅ Abgeschlossen
2HTTP-Protokoll Vertiefung & Zustandslosigkeit✅ Abgeschlossen
3Servlets & Servlet API✅ Abgeschlossen
4Deployment Descriptor & MVC vs Model 2✅ Abgeschlossen
→ 5JSP & Expression Languages 👉 DU BIST HIER!
6Java Beans, Actions, Scopes & Direktiven🔜 Kommt als nächstes
7Include-Action vs Include-Direktive🔒 Noch nicht freigeschaltet
8JSTL – Java Standard Tag Libraries🔒 Noch nicht freigeschaltet
9Java Web und Datenbanken – Datasource🔒 Noch nicht freigeschaltet
10Connection 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: JSPs meistern und Expression Language verstehen


📋 Voraussetzungen für diesen Tag

Du brauchst:

  • ✅ JDK 21 LTS installiert
  • ✅ Apache NetBeans 22 (oder neuer)
  • ✅ Payara Server 6.x konfiguriert
  • ✅ Tag 1-4 abgeschlossen
  • ✅ MVC/Model 2 verstanden
  • ✅ Servlet-Grundlagen sitzen

Tag verpasst?
Spring zurück zu Tag 4, um MVC zu verstehen. JSPs sind die View in MVC!

Setup-Probleme?
Schreib uns: support@java-developer.online


⚡ Das Wichtigste in 30 Sekunden

Heute lernst du:

  • ✅ Was JSPs sind und wie sie funktionieren
  • ✅ JSP-Lifecycle verstehen (Translation → Compilation → Execution)
  • ✅ Expression Language (EL) Syntax
  • ✅ Implizite Objekte in JSPs
  • ✅ JSP-Direktiven (<%@ page %><%@ taglib %><%@ include %>)
  • ✅ Scriptlets vs. Expression Language (und warum EL besser ist!)

Am Ende des Tages kannst du:

  • JSPs schreiben und verstehen
  • Expression Language verwenden
  • Implizite Objekte nutzen
  • Direktiven richtig einsetzen
  • Scriptlets vermeiden (Best Practice!)

Zeit-Investment: ~8 Stunden
Schwierigkeitsgrad: Mittel (neue Syntax!)


👋 Willkommen zu Tag 5!

Hi! 👋

Elyndra hier. Heute wird’s spannend – wir lernen JavaServer Pages !

Kurzwiederholung: Challenge von Tag 4

Gestern solltest du eine einfache Blog-Anwendung mit Model 2 Architektur erstellen. Falls du es noch nicht gemacht hast, hier die Musterlösung:

Model: BlogPost.java

package com.blog.model;

import java.time.LocalDate;

public class BlogPost {
    private int id;
    private String title;
    private String content;
    private String author;
    private LocalDate date;
    
    public BlogPost(int id, String title, String content, 
                   String author, LocalDate date) {
        this.id = id;
        this.title = title;
        this.content = content;
        this.author = author;
        this.date = date;
    }
    
    // Getters & Setters
    public int getId() { return id; }
    public void setId(int id) { this.id = id; }
    
    public String getTitle() { return title; }
    public void setTitle(String title) { this.title = title; }
    
    public String getContent() { return content; }
    public void setContent(String content) { this.content = content; }
    
    public String getAuthor() { return author; }
    public void setAuthor(String author) { this.author = author; }
    
    public LocalDate getDate() { return date; }
    public void setDate(LocalDate date) { this.date = date; }
}

Model: BlogPostDAO.java

package com.blog.model;

import java.time.LocalDate;
import java.util.*;

public class BlogPostDAO {
    // In-Memory-Daten (normalerweise aus Database)
    private static List<BlogPost> posts = Arrays.asList(
        new BlogPost(1, "Java Web Basics", 
                    "Learn Servlets and JSP...", 
                    "Elyndra", LocalDate.of(2025, 10, 1)),
        new BlogPost(2, "MVC Pattern", 
                    "Understanding Model 2...", 
                    "Elyndra", LocalDate.of(2025, 10, 15)),
        new BlogPost(3, "Deployment Descriptor", 
                    "web.xml explained...", 
                    "Elyndra", LocalDate.of(2025, 10, 20))
    );
    
    public List<BlogPost> getAll() {
        return posts;
    }
    
    public BlogPost getById(int id) {
        return posts.stream()
                   .filter(post -> post.getId() == id)
                   .findFirst()
                   .orElse(null);
    }
}

Controller: BlogServlet.java

package com.blog.controller;

import com.blog.model.BlogPost;
import com.blog.model.BlogPostDAO;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.*;
import java.io.IOException;
import java.util.List;

@WebServlet("/blog")
public class BlogServlet extends HttpServlet {
    
    private BlogPostDAO blogDAO = new BlogPostDAO();
    
    @Override
    protected void doGet(HttpServletRequest request, 
                        HttpServletResponse response) 
                        throws ServletException, IOException {
        
        String idParam = request.getParameter("id");
        
        if (idParam == null) {
            // Alle Posts anzeigen
            List<BlogPost> posts = blogDAO.getAll();
            request.setAttribute("posts", posts);
            request.getRequestDispatcher("/WEB-INF/views/blog-list.jsp")
                   .forward(request, response);
        } else {
            // Ein Post anzeigen
            try {
                int id = Integer.parseInt(idParam);
                BlogPost post = blogDAO.getById(id);
                
                if (post == null) {
                    response.sendError(HttpServletResponse.SC_NOT_FOUND, 
                                      "Blog post not found");
                    return;
                }
                
                request.setAttribute("post", post);
                request.getRequestDispatcher("/WEB-INF/views/blog-post.jsp")
                       .forward(request, response);
                
            } catch (NumberFormatException e) {
                response.sendError(HttpServletResponse.SC_BAD_REQUEST, 
                                  "Invalid post ID");
            }
        }
    }
}

View: WEB-INF/views/blog-list.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="jakarta.tags.core" %>
<!DOCTYPE html>
<html>
<head>
    <title>Blog Posts</title>
</head>
<body>
    <h1>All Blog Posts</h1>
    
    <ul>
        <c:forEach var="post" items="${posts}">
            <li>
                <h2>
                    <a href="blog?id=${post.id}">${post.title}</a>
                </h2>
                <p>By ${post.author} on ${post.date}</p>
            </li>
        </c:forEach>
    </ul>
</body>
</html>

View: WEB-INF/views/blog-post.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
    <title>${post.title}</title>
</head>
<body>
    <h1>${post.title}</h1>
    <p><em>By ${post.author} on ${post.date}</em></p>
    
    <div>
        ${post.content}
    </div>
    
    <p><a href="blog">← Back to all posts</a></p>
</body>
</html>

Super gemacht! 🎉 Das ist saubere Model 2 Architektur!


Erinnerst du dich an Tag 4?

Wir haben gelernt, dass in Model 2:

  • Controller (Servlet) empfängt Requests und steuert
  • Model (Java) enthält Business-Logik und Daten
  • View (JSP) zeigt Daten an

Heute fokussieren wir uns auf die View – JSPs!

Was dich heute erwartet:

In den letzten Tagen hast du Servlets geschrieben, die HTML erzeugen:

out.println("<html>");
out.println("<body>");
out.println("<h1>Hello, " + name + "</h1>");
out.println("</body>");
out.println("</html>");

Das funktioniert – ist aber mühsam und fehleranfällig!

Mit JSPs kannst du HTML schreiben wie gewohnt, und Java-Code dort einbetten, wo du ihn brauchst:

<html>
<body>
    <h1>Hello, ${name}!</h1>
</body>
</html>

Viel einfacher, oder?

Keine Sorge:

JSPs sehen am Anfang wie „Magie“ aus. Aber ich erkläre dir genau, was unter der Haube passiert. Nach heute wirst du verstehen, warum JSPs so mächtig sind – und wie du sie richtig einsetzt.

Los geht’s! 🎨


🟢 GRUNDLAGEN: Was sind JSPs?

Definition

JSP (JavaServer Pages) ist eine Technologie, die es erlaubt, dynamische Webseiten zu erstellen, indem man Java-Code in HTML einbettet.

Wichtig zu verstehen:

JSPs sind KEINE eigene Sprache! JSPs sind eine Templating-Technologie für Java Web.

Unter der Haube werden JSPs zu Servlets kompiliert!


Das große Bild: Von HTML zu JSP

Evolution der Webseiten-Erzeugung in Java:

Stufe 1: Statisches HTML

<!-- Datei: hello.html -->
<html>
<body>
    <h1>Hello, World!</h1>
</body>
</html>

Problem: Keine Dynamik! Immer gleicher Content.


Stufe 2: Servlet mit HTML-Output

@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, 
                        HttpServletResponse response) 
                        throws ServletException, IOException {
        
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        
        String name = request.getParameter("name");
        
        out.println("<html>");
        out.println("<head><title>Hello</title></head>");
        out.println("<body>");
        out.println("<h1>Hello, " + name + "!</h1>");
        out.println("<form>");
        out.println("<input type='text' name='name'>");
        out.println("<button>Submit</button>");
        out.println("</form>");
        out.println("</body>");
        out.println("</html>");
    }
}

Vorteile: ✅ Dynamisch – Name kommt aus Request

Nachteile: ❌ HTML in Java-Strings (unlesbar!)
❌ Kein Syntax-Highlighting für HTML
❌ Designer können nicht mitarbeiten
❌ Jede Änderung = Recompile
❌ Fehleranfällig (vergessene Quotes, Tags)


Stufe 3: JSP (Die richtige Lösung)

<!-- Datei: hello.jsp -->
<html>
<head><title>Hello</title></head>
<body>
    <h1>Hello, ${param.name}!</h1>
    
    <form>
        <input type="text" name="name">
        <button>Submit</button>
    </form>
</body>
</html>

Vorteile: ✅ Dynamisch
✅ HTML ist HTML (lesbar!)
✅ Syntax-Highlighting funktioniert
✅ Designer können mitarbeiten
✅ Änderungen = kein Recompile (in DEV)
✅ Sauber und wartbar

Das ist der Weg!


Wie JSPs funktionieren – Der Lifecycle

Wichtig zu verstehen: JSPs sind NICHT interpretiert! Sie werden zu Servlets kompiliert.

Der JSP-Lifecycle in 3 Schritten:

1. TRANSLATION     2. COMPILATION      3. EXECUTION
   (JSP → Java)      (Java → .class)    (Servlet läuft)

hello.jsp  →  hello_jsp.java  →  hello_jsp.class  →  Response

Schritt 1: Translation (JSP → Java)

Deine JSP:

<!-- hello.jsp -->
<%@ page contentType="text/html;charset=UTF-8" %>
<html>
<body>
    <h1>Hello, ${param.name}!</h1>
</body>
</html>

Wird übersetzt zu (vereinfacht):

// hello_jsp.java (automatisch generiert!)
public class hello_jsp extends HttpServlet {
    
    @Override
    public void _jspService(HttpServletRequest request, 
                           HttpServletResponse response) 
                           throws ServletException, IOException {
        
        response.setContentType("text/html;charset=UTF-8");
        JspWriter out = response.getWriter();
        
        out.write("<html>\n");
        out.write("<body>\n");
        out.write("    <h1>Hello, ");
        out.write(request.getParameter("name"));
        out.write("!</h1>\n");
        out.write("</body>\n");
        out.write("</html>\n");
    }
}

Was ist passiert?

  • JSP wurde zu einer Servlet-Klasse konvertiert
  • HTML wurde zu out.write() Statements
  • ${param.name} wurde zu request.getParameter("name")

Wo liegt diese generierte Datei?

In Payara:

C:\payara6\glassfish\domains\domain1\generated\jsp\

Jede JSP hat dort eine .java Datei!


Schritt 2: Compilation (Java → Bytecode)

Die generierte .java Datei wird zu .class kompiliert – wie jede normale Java-Datei.

Ergebnis:

hello_jsp.class

Diese Klasse ist ein vollwertiges Servlet!


Schritt 3: Execution (Servlet läuft)

Wenn ein Request kommt:

  1. Webcontainer lädt hello_jsp.class
  2. Ruft _jspService() Methode auf
  3. HTML wird generiert und zurückgeschickt

Genau wie bei einem normalen Servlet!


Wann passiert Translation & Compilation?

Zwei Modi:

1. On-Demand (Standard in DEV)

  • JSP wird beim ersten Zugriff übersetzt und kompiliert
  • Vorteil: Änderungen an JSP sind sofort sichtbar (Reload genügt)
  • Nachteil: Erster Request ist langsam

2. Pre-Compilation (PROD)

  • JSPs werden beim Deployment übersetzt und kompiliert
  • Vorteil: Schneller, keine Verzögerung
  • Nachteil: Jede Änderung = Neues Deployment

In Entwicklung:

Du änderst die JSP, drückst F5 im Browser – der Container merkt die Änderung, recompiled automatisch, und du siehst die neue Version!

In Production:

JSPs werden pre-compiled. Keine automatische Recompilation!


Implizite Objekte in JSPs

Was sind implizite Objekte?

In JSPs stehen dir automatisch bestimmte Objekte zur Verfügung – ohne dass du sie deklarieren musst.

Die wichtigsten impliziten Objekte:

ObjektTypBeschreibung
requestHttpServletRequestDer aktuelle Request
responseHttpServletResponseDie aktuelle Response
sessionHttpSessionDie aktuelle Session
applicationServletContextDer Application-Kontext
outJspWriterOutput-Stream
pageContextPageContextZugriff auf alle Scopes
pageObjectDie JSP selbst (this)
configServletConfigServlet-Konfiguration
exceptionThrowableException (nur in Error-Pages)

Beispiel (Scriptlet – bald lernen wir die bessere Alternative!):

<%
    String name = request.getParameter("name");
    session.setAttribute("lastVisit", new Date());
    out.println("Hello, " + name);
%>

Du musst NICHT schreiben:

HttpServletRequest request = ...;  // NICHT nötig!

Es ist automatisch da!


🟡 PROFESSIONALS: JSP-Syntax im Detail

Die verschiedenen JSP-Elemente

JSPs haben verschiedene Syntax-Elemente:

  1. Direktiven – Konfiguration
  2. Declarations – Variablen & Methoden deklarieren (VERALTET!)
  3. Scriptlets – Java-Code (VERALTET!)
  4. Expressions – Werte ausgeben (VERALTET!)
  5. Expression Language (EL) – Moderne Alternative (DAS IST DER WEG! ✅)
  6. Actions – Spezielle Tags

Schauen wir uns jedes Element an:


1. Direktiven

Syntax: <%@ direktive attribut="wert" %>

Direktiven sind Anweisungen an den JSP-Container.

Page-Direktive

Die wichtigste Direktive!

<%@ page contentType="text/html;charset=UTF-8" 
         language="java"
         session="true"
         errorPage="error.jsp"
         isErrorPage="false"
         import="java.util.*, java.time.*" %>

Attribute im Detail:

contentType – MIME-Type der Response

<%@ page contentType="text/html;charset=UTF-8" %>

Setzt den Content-Type Header:

Content-Type: text/html;charset=UTF-8

Andere Content-Types:

<%@ page contentType="application/json" %>
<%@ page contentType="text/plain" %>
<%@ page contentType="application/xml" %>

language – Programmiersprache (immer java)

<%@ page language="java" %>

Theoretisch könnte man andere Sprachen unterstützen – praktisch gibt’s nur Java.


session – Session automatisch erstellen?

<%@ page session="true" %>   <!-- Default -->
<%@ page session="false" %>  <!-- Keine Session! -->

session="true" (Default):

  • JSP erstellt automatisch eine Session
  • session Objekt ist verfügbar

session="false":

  • Keine Session-Objekt
  • Verwendet für statische Pages oder APIs

errorPage – Wohin bei Exception?

<%@ page errorPage="/error.jsp" %>

Wenn eine Exception auftritt, wird der User automatisch zu error.jsp weitergeleitet.

error.jsp:

<%@ page isErrorPage="true" %>
<html>
<body>
    <h1>Error occurred!</h1>
    <p>${exception.message}</p>
</body>
</html>

isErrorPage="true" macht exception Objekt verfügbar!


import – Java-Klassen importieren

<%@ page import="java.util.List" %>
<%@ page import="java.util.ArrayList" %>
<%@ page import="com.shop.model.Product" %>

Oder alles zusammen:

<%@ page import="java.util.*, java.time.*, com.shop.model.*" %>

buffer – Output-Buffer-Größe

<%@ page buffer="8kb" %>   <!-- 8 Kilobyte -->
<%@ page buffer="none" %>   <!-- Kein Buffer -->

Buffer bedeutet: Output wird gesammelt, bevor er gesendet wird.

Warum wichtig?

Solange Buffer nicht voll ist, kannst du noch Redirects machen oder Headers setzen!


Include-Direktive

<%@ include file="header.jsp" %>

<h1>Main Content</h1>

<%@ include file="footer.jsp" %>

Was passiert?

Die Dateien werden zur Translation-Zeit eingefügt – wie Copy/Paste!

Unterschied zu <jsp:include>:

  • <%@ include %> – Compile-Time (statisch, schneller)
  • <jsp:include> – Runtime (dynamisch, flexibler)

Mehr dazu an Tag 7!


Taglib-Direktive

<%@ taglib prefix="c" uri="jakarta.tags.core" %>
<%@ taglib prefix="fmt" uri="jakarta.tags.fmt" %>

Importiert Tag-Libraries (JSTL).

Mehr dazu an Tag 8!


2. Declarations (VERALTET – nicht verwenden!)

Syntax: <%! ... %>

Deklariert Variablen und Methoden auf Klassen-Ebene.

<%!
    private int counter = 0;
    
    public int getNextNumber() {
        return ++counter;
    }
%>

<p>Request number: <%= getNextNumber() %></p>

Problem:

counter ist eine Instanzvariable des Servlets. Wird von ALLEN Requests geteilt!

Race-Condition!

Zwei gleichzeitige Requests → counter wird durcheinander gebracht!

Lösung:

Verwende NIE Declarations! Nutze Servlets für solche Logik.


3. Scriptlets (VERALTET – nicht verwenden!)

Syntax: <% ... %>

Java-Code direkt in der JSP.

<%
    String name = request.getParameter("name");
    if (name == null || name.isEmpty()) {
        name = "Guest";
    }
%>

<h1>Hello, <%= name %>!</h1>

<%
    for (int i = 0; i < 5; i++) {
%>
    <p>Item <%= i %></p>
<%
    }
%>

Warum ist das schlecht?

❌ Java-Code und HTML gemischt (unlesbar!)
❌ Schwer zu warten
❌ Schwer zu testen
❌ Designer können nicht damit arbeiten
❌ Kein automatisches HTML-Escaping (XSS-Gefahr!)

Das ist Spaghetti-Code – wie Model 1!


4. Expressions (VERALTET – nicht verwenden!)

Syntax: <%= ... %>

Gibt den Wert eines Java-Ausdrucks aus.

<p>Name: <%= request.getParameter("name") %></p>
<p>Session ID: <%= session.getId() %></p>
<p>Current Time: <%= new java.util.Date() %></p>

Wird übersetzt zu:

out.print(request.getParameter("name"));
out.print(session.getId());
out.print(new java.util.Date());

Problem:

Wie Scriptlets – Java-Code in HTML!


5. Expression Language (EL) – Die moderne Lösung! ✅

Syntax: ${ ... }

Die richtige Art, Werte in JSPs auszugeben!

<p>Name: ${param.name}</p>
<p>Session ID: ${pageContext.session.id}</p>
<p>Product Name: ${product.name}</p>

Warum ist EL besser?

✅ Kürzer und lesbarer
✅ Automatisches HTML-Escaping (XSS-Schutz!)
✅ Null-Safe (kein NullPointerException!)
✅ Designer-freundlich
✅ Kein Java-Code sichtbar

EL ist der Standard ab JSP 2.0! Nutze es!


🟢 GRUNDLAGEN: Expression Language (EL)

EL-Syntax Grundlagen

Zugriff auf Attribute:

${attributeName}

Sucht in dieser Reihenfolge:

  1. Page-Scope
  2. Request-Scope
  3. Session-Scope
  4. Application-Scope

Beispiel:

// Im Servlet:
request.setAttribute("user", userObject);
<!-- In JSP: -->
<p>Welcome, ${user.name}!</p>

EL ist null-safe!

Wenn user null ist, gibt EL einfach einen leeren String zurück – KEIN NullPointerException!


Implizite EL-Objekte

EL hat eigene implizite Objekte:

EL-ObjektBeschreibungBeispiel
paramRequest-Parameter (einzelner Wert)${param.name}
paramValuesRequest-Parameter (mehrere Werte)${paramValues.hobby[0]}
headerHTTP-Header${header['User-Agent']}
headerValuesHTTP-Header (mehrere Werte)${headerValues.Accept}
cookieCookies${cookie.sessionId.value}
initParamContext-Parameter${initParam['db.url']}
pageContextPageContext-Objekt${pageContext.request.contextPath}
pageScopePage-Scope Attributes${pageScope.localVar}
requestScopeRequest-Scope Attributes${requestScope.product}
sessionScopeSession-Scope Attributes${sessionScope.user}
applicationScopeApplication-Scope Attributes${applicationScope.config}

Beispiele:

<!-- Request-Parameter -->
URL: /page?name=Anna&age=25

<p>Name: ${param.name}</p>           <!-- Anna -->
<p>Age: ${param.age}</p>             <!-- 25 -->

<!-- Context-Path (wichtig für Links!) -->
<a href="${pageContext.request.contextPath}/products">Products</a>

<!-- Session-Attribute -->
<p>Logged in as: ${sessionScope.user.name}</p>

<!-- Cookie-Werte -->
<p>Session: ${cookie.JSESSIONID.value}</p>

<!-- Header -->
<p>Your Browser: ${header['User-Agent']}</p>

EL-Operatoren

Arithmetische Operatoren:

${5 + 3}        <!-- 8 -->
${5 - 3}        <!-- 2 -->
${5 * 3}        <!-- 15 -->
${5 / 3}        <!-- 1.6666... -->
${5 div 3}      <!-- 1.6666... (gleich wie /) -->
${5 % 3}        <!-- 2 -->
${5 mod 3}      <!-- 2 (gleich wie %) -->

Vergleichs-Operatoren:

${5 == 3}       <!-- false -->
${5 eq 3}       <!-- false (gleich wie ==) -->
${5 != 3}       <!-- true -->
${5 ne 3}       <!-- true (gleich wie !=) -->
${5 < 3}        <!-- false -->
${5 lt 3}       <!-- false (gleich wie <) -->
${5 > 3}        <!-- true -->
${5 gt 3}       <!-- true (gleich wie >) -->
${5 <= 3}       <!-- false -->
${5 le 3}       <!-- false (gleich wie <=) -->
${5 >= 3}       <!-- true -->
${5 ge 3}       <!-- true (gleich wie >=) -->

Warum eqltgt, etc.?

Weil < und > in XML/HTML Sonderzeichen sind! lt und gt sind „less than“ und „greater than“.


Logische Operatoren:

${true && false}     <!-- false -->
${true and false}    <!-- false (gleich wie &&) -->
${true || false}     <!-- true -->
${true or false}     <!-- true (gleich wie ||) -->
${!true}             <!-- false -->
${not true}          <!-- false (gleich wie !) -->

Conditional (Ternary) Operator:

${user != null ? user.name : 'Guest'}

${product.stock > 0 ? 'In Stock' : 'Out of Stock'}

${age >= 18 ? 'Adult' : 'Minor'}

Syntax: ${condition ? ifTrue : ifFalse}


Empty Operator:

${empty param.name}          <!-- true wenn name null oder "" -->
${not empty user}            <!-- true wenn user existiert -->
${empty cart.items}          <!-- true wenn Liste leer -->

empty prüft:

  • Strings: null oder ""
  • Collections: null oder leer
  • Arrays: null oder length 0

Property-Zugriff in EL

Dot-Notation:

${user.name}
${product.price}
${order.customer.address.street}

Entspricht:

user.getName()
product.getPrice()
order.getCustomer().getAddress().getStreet()

EL ruft automatisch die Getter-Methoden auf!


Bracket-Notation:

${user['name']}
${product['price']}

Gleich wie Dot-Notation – aber nützlich bei:

  1. Sonderzeichen in Namen:
${header['User-Agent']}      <!-- "User-Agent" hat "-" -->
  1. Dynamische Property-Namen:
${user[propertyName]}        <!-- propertyName ist Variable -->

Listen und Arrays in EL

Listen:

// Im Servlet:
List<Product> products = productDAO.getAll();
request.setAttribute("products", products);
<!-- In JSP: -->
${products[0].name}          <!-- Erstes Produkt -->
${products[1].price}         <!-- Zweites Produkt -->
${products.size()}           <!-- Anzahl Produkte -->

Arrays:

// Im Servlet:
String[] colors = {"Red", "Green", "Blue"};
request.setAttribute("colors", colors);
<!-- In JSP: -->
${colors[0]}                 <!-- Red -->
${colors[1]}                 <!-- Green -->
${colors.length}             <!-- 3 -->

🟡 PROFESSIONALS: EL Advanced Features

Null-Safety in EL

Das Beste an EL: Null-Safety!

<!-- Angenommen, user ist null -->

<!-- Scriptlet (SCHLECHT - crasht!): -->
<%= user.getName() %>        <!-- NullPointerException! -->

<!-- EL (GUT - sicher!): -->
${user.name}                 <!-- Zeigt nichts (leerer String) -->

EL gibt bei null einfach einen leeren String zurück!

Besser lesbar als:

<% if (user != null && user.getName() != null) { %>
    <%= user.getName() %>
<% } %>

vs.

${user.name}

EL-Funktionen

Statische Methoden aufrufen:

<%@ taglib prefix="fn" uri="jakarta.tags.functions" %>

<!-- String-Länge -->
${fn:length(user.name)}

<!-- String uppercase -->
${fn:toUpperCase(user.name)}

<!-- Substring -->
${fn:substring(user.name, 0, 5)}

<!-- Enthält? -->
${fn:contains(user.email, '@gmail.com')}

Mehr dazu an Tag 8 (JSTL)!


Collection-Operationen

Mit JSTL forEach:

<%@ taglib prefix="c" uri="jakarta.tags.core" %>

<ul>
    <c:forEach var="product" items="${products}">
        <li>${product.name} - $${product.price}</li>
    </c:forEach>
</ul>

Mit EL und JSTL if:

<c:if test="${product.stock > 0}">
    <p>In Stock!</p>
</c:if>

<c:if test="${product.stock == 0}">
    <p>Out of Stock!</p>
</c:if>

Best Practices für EL

✅ DO:

<!-- Verwende EL für alle Daten-Ausgaben -->
<h1>${product.name}</h1>
<p>${product.description}</p>

<!-- Verwende conditional für einfache Bedingungen -->
<p class="${product.stock > 0 ? 'in-stock' : 'out-of-stock'}">
    ${product.stock > 0 ? 'Available' : 'Sold Out'}
</p>

<!-- Verwende pageContext für Context-Path -->
<a href="${pageContext.request.contextPath}/products">Products</a>

❌ DON’T:

<!-- NICHT: Scriptlets verwenden -->
<% Product product = (Product) request.getAttribute("product"); %>
<h1><%= product.getName() %></h1>

<!-- NICHT: Komplexe Logik in EL -->
${product.stock > 10 && product.price < 100 && product.category == 'Electronics' 
  && product.rating >= 4.0 ? 'Hot Deal!' : product.stock > 0 ? 'Available' : 'Out of Stock'}

<!-- NICHT: Business-Logik in View -->
${productService.calculateDiscountedPrice(product, user.membershipLevel)}

Regel:

  • Einfache Darstellung? → EL
  • Komplexe Logik? → Controller/Service!

🔵 BONUS: Fortgeschrittene JSP-Techniken

Custom EL-Funktionen erstellen

Eigene Funktionen definieren:

1. Java-Klasse mit statischer Methode:

package com.shop.utils;

public class StringUtils {
    public static String capitalize(String text) {
        if (text == null || text.isEmpty()) {
            return text;
        }
        return text.substring(0, 1).toUpperCase() + text.substring(1);
    }
}

2. TLD-Datei (Tag Library Descriptor):

<!-- WEB-INF/functions.tld -->
<?xml version="1.0" encoding="UTF-8" ?>
<taglib 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-jsptaglibrary_3_0.xsd"
        version="3.0">
    
    <tlib-version>1.0</tlib-version>
    <short-name>util</short-name>
    <uri>http://shop.com/functions</uri>
    
    <function>
        <name>capitalize</name>
        <function-class>com.shop.utils.StringUtils</function-class>
        <function-signature>
            java.lang.String capitalize(java.lang.String)
        </function-signature>
    </function>
    
</taglib>

3. In JSP verwenden:

<%@ taglib prefix="util" uri="http://shop.com/functions" %>

<p>${util:capitalize(product.name)}</p>

JSP-Fragments

Wiederverwendbare Snippets:

header.jspf (Fragment):

<%-- header.jspf --%>
<header>
    <h1>My Shop</h1>
    <nav>
        <a href="${pageContext.request.contextPath}/">Home</a>
        <a href="${pageContext.request.contextPath}/products">Products</a>
        <a href="${pageContext.request.contextPath}/cart">Cart</a>
    </nav>
</header>

Einbinden:

<%@ include file="/WEB-INF/fragments/header.jspf" %>

<main>
    <!-- Page Content -->
</main>

<%@ include file="/WEB-INF/fragments/footer.jspf" %>

Dateiendung .jspf = JSP Fragment (Convention!)


💬 Real Talk: Scriptlets vs. EL im echten Leben

Java Fleet Büro, 16:00 Uhr. Nova starrt frustriert auf ihren Bildschirm, vor ihr eine JSP voller Scriptlets.


Nova: „Elyndra, ich verstehe nicht, warum Scriptlets so schlimm sein sollen. Ich kann damit doch alles machen! Ist viel flexibler als diese EL-Syntax…“

Elyndra (kommt mit Kaffeetasse): „Zeig mal her. Oh wow… das ist… impressive in der falschen Richtung.“

Nova: „Was? Es funktioniert doch!“

Cassian (schaut über Novas Schulter): „Hmm. 200 Zeilen JSP mit… ist das ein for-Loop in einem if-Statement in einem try-catch-Block… in einer JSP?“

Nova (defensiv): „Ja, und? Ich brauche die Logik dort!“

Elyndra: „Nova, real talk: Kannst du mir erklären, was Zeile 87 macht, ohne die ganze Datei zu lesen?“

Nova (scrollt, sucht): „Ähm… moment… das ist… warte…“

Cassian: „Genau das ist das Problem. Du hast das vor zwei Stunden geschrieben und findest dich schon nicht mehr zurecht.“

Kofi (kommt vorbei): „Dude, ich hab letzte Woche ein Legacy-Projekt übernommen. VOLLER Scriptlets. Ich hab drei Tage gebraucht, nur um zu verstehen, wo die Business-Logik ist. Spoiler: überall!“

Nova: „Okay okay, aber EL kann doch nicht ALLES, was Scriptlets können…“

Elyndra: „Das ist der Punkt! EL SOLL nicht alles können. Views sollen nur ZEIGEN, nicht BERECHNEN.“

Cassian: „Stell dir vor, du bist Designer. Du kannst HTML und CSS. Jetzt kommst du in eine JSP und siehst:“

<%
    if (user != null && user.getRole().equals("admin")) {
        List<Product> products = productDAO.getAll();
        for (Product p : products) {
            if (p.getStock() > 0) {
%>
    <div><%= p.getName() %></div>
<%
            }
        }
    }
%>

Cassian: „Wie soll ein Designer damit arbeiten?“

Nova: „Gar nicht, schätze ich…“

Elyndra: „Exactly. Jetzt schau dir das an:“

<c:if test="${not empty products}">
    <c:forEach var="product" items="${products}">
        <c:if test="${product.stock > 0}">
            <div>${product.name}</div>
        </c:if>
    </c:forEach>
</c:if>

Elyndra: „Siehst du den Unterschied? Ein Designer kann das lesen. Es SIEHT aus wie HTML mit ein paar Extra-Tags.“

Nova: „Aber woher kommt products? Ich sehe keinen Java-Code!“

Cassian: „DAS ist der Punkt! Die Logik ist im Servlet – wo sie hingehört:“

List<Product> products = productDAO.getAll();
request.setAttribute("products", products);
request.getRequestDispatcher("/view.jsp").forward(request, response);

Kofi: „Separation of Concerns, yo. Servlet macht die Arbeit, JSP zeigt das Ergebnis. Clean!“

Nova: „Okay, ich geb’s zu… meine JSP sieht aus wie ein Unfall. Aber wie soll ich jetzt damit umgehen? Alles neu schreiben?“

Elyndra: „Nicht alles auf einmal. Regel: Neuer Code = nur EL. Alter Code = bei Gelegenheit refactoren. Baby steps!“

Cassian: „Und honestly: In einem Monat wirst du deine alten Scriptlet-JSPs angucken und dich fragen, warum du je gedacht hast, das sei eine gute Idee.“

Nova (seufzt): „Fine. Ich probier’s. Aber wenn ich stuck bin, hilfst du mir, Elyndra?“

Elyndra: „Always! Das ist ein Team hier. Und Nova? Welcome to the EL side. Wir haben keine Scriptlets.“

Nova (grinst): „Und hoffentlich auch keine NullPointerExceptions mehr…“

Kofi: „Amen to that!“


✅ Checkpoint: Hast du es verstanden?

Zeit für einen Reality-Check! Beantworte diese Fragen, um zu prüfen, ob du heute alles verstanden hast.

Quiz:

Frage 1: Erkläre den JSP-Lifecycle. Was sind die drei Schritte?

Frage 2: Was ist der Unterschied zwischen On-Demand-Compilation und Pre-Compilation?

Frage 3: Nenne 5 implizite Objekte in JSPs und erkläre, wofür sie verwendet werden.

Frage 4: Was ist der Unterschied zwischen <%@ include %> und <jsp:include>?

Frage 5: Warum solltest du Scriptlets vermeiden? Nenne mindestens 3 Gründe.

Frage 6: Was macht Expression Language (EL) null-safe? Warum ist das wichtig?

Frage 7: Erkläre den Unterschied zwischen ${param.name} und ${paramValues.hobby}.

Frage 8: Was ist der empty Operator in EL und wann würdest du ihn verwenden?


🎯 Mini-Challenge

Aufgabe: Erstelle eine JSP für eine Produkt-Detailseite mit Expression Language.

Requirements:

  1. Daten vom Controller:Product product = productDAO.getById(id); request.setAttribute("product", product);
  2. JSP soll anzeigen:
    • Produkt-Name
    • Beschreibung
    • Preis (formatiert mit $)
    • Lagerbestand
    • „In Stock“ wenn > 0, sonst „Out of Stock“
    • „Add to Cart“ Button (nur wenn auf Lager)
  3. Verwende:
    • EL (kein Scriptlet!)
    • Conditional (ternary) Operator
    • JSTL <c:if> für Button
  4. Bonus:
    • Context-Path verwenden für Links
    • CSS-Klasse dynamisch setzen (in-stock / out-of-stock)

Hinweise:

  • Nutze ${product.name}${product.price}, etc.
  • Verwende ${product.stock > 0 ? 'In Stock' : 'Out of Stock'}
  • JSTL Taglib einbinden: <%@ taglib prefix="c" uri="jakarta.tags.core" %>

Lösung:
Die Lösung zu dieser Challenge findest du am Anfang von Tag 6 als Kurzwiederholung! 🚀

Alternativ kannst du die Musterlösung im GitHub-Projekt checken: https://github.com/java-fleet/java-web-basic-examples


Geschafft? 🎉
Dann bist du bereit für die FAQ-Sektion!


❓ Häufig gestellte Fragen

Frage 1: Werden JSPs wirklich zu Servlets kompiliert? Wo kann ich das sehen?

Ja, absolut! JSPs sind nur eine angenehmere Syntax für Servlets.

Wo findest du die generierten Dateien?

Payara Server:

C:\payara6\glassfish\domains\domain1\generated\jsp\

Tomcat:

C:\tomcat\work\Catalina\localhost\[app-name]\org\apache\jsp\

Schau mal rein! Öffne eine .java Datei dort. Du wirst sehen:

  • Deine JSP wurde zu einer Servlet-Klasse
  • HTML wurde zu out.write() Statements
  • EL wurde zu Java-Code übersetzt

Warum ist das wichtig zu wissen?

Wenn du Fehler in deiner JSP hast, zeigt der Stack-Trace oft die GENERIERTE Java-Datei an, nicht deine originale JSP. Wenn du verstehst, dass JSPs kompiliert werden, kannst du diese Fehler besser debuggen!


Frage 2: EL wird nicht ausgewertet – ich sehe ${param.name} wörtlich im Browser. Was ist falsch?

Häufigste Ursache: web.xml ist zu alt!

Lösung 1: web.xml Version prüfen

<!-- FALSCH - zu alt! -->
<web-app version="2.3" ...>

<!-- RICHTIG - mindestens 2.4+ -->
<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">

EL wurde in Servlet 2.4 (JSP 2.0) eingeführt. Ältere Versionen kennen EL nicht!

Lösung 2: Explizit aktivieren

<%@ page isELIgnored="false" %>

Oder in web.xml:

<jsp-config>
    <jsp-property-group>
        <url-pattern>*.jsp</url-pattern>
        <el-ignored>false</el-ignored>
    </jsp-property-group>
</jsp-config>

Frage 3: Was ist der Unterschied zwischen ${product.name} und ${product['name']}?

Funktional: Kein Unterschied! Beide rufen product.getName() auf.

Wann welche Notation?

Dot-Notation (${product.name}):

  • ✅ Einfacher zu lesen
  • ✅ Standard-Syntax
  • ❌ Funktioniert nicht bei Sonderzeichen im Property-Namen

Bracket-Notation (${product['name']}):

  • ✅ Funktioniert immer
  • ✅ Nötig bei Sonderzeichen (z.B. ${header['User-Agent']})
  • ✅ Ermöglicht dynamische Property-Namen (${product[propertyName]})

Best Practice: Verwende Dot-Notation, außer du hast einen Grund für Brackets!


Frage 4: Kann ich mit EL Methoden aufrufen?

Ja, aber nur Getter!

<!-- ✅ GUT: -->
${product.name}              <!-- ruft getName() -->
${product.price}             <!-- ruft getPrice() -->
${products.size()}           <!-- ruft size() -->
${user.admin}                <!-- ruft isAdmin() oder getAdmin() -->

<!-- ❌ SCHLECHT: -->
${productService.calculateDiscount(product)}  <!-- Geht nicht direkt! -->

Für komplexere Methoden:

Option 1: Verwende JSTL Functions (Tag 8!)

<%@ taglib prefix="fn" uri="jakarta.tags.functions" %>

${fn:length(product.name)}
${fn:toUpperCase(product.name)}

Option 2: Bereite Daten im Servlet vor!

// Servlet:
double discount = productService.calculateDiscount(product);
request.setAttribute("discount", discount);
<!-- JSP: -->
<p>Discount: ${discount}%</p>

Best Practice: Business-Logik gehört NICHT in die View!


Frage 5: Wie escapet EL HTML? Ist es wirklich XSS-sicher?

Ja, EL escapet automatisch HTML-Sonderzeichen!

Beispiel:

// User gibt ein:
String malicious = "<script>alert('XSS')</script>";
request.setAttribute("userInput", malicious);

Mit Scriptlet (GEFÄHRLICH!):

<%= request.getAttribute("userInput") %>

Ergebnis im HTML:

<script>alert('XSS')</script>

→ Script wird ausgeführt! XSS-Angriff!


Mit EL (SICHER!):

${userInput}

Ergebnis im HTML:

&lt;script&gt;alert('XSS')&lt;/script&gt;

→ Script wird als Text angezeigt! Sicher!

Wichtig: Das gilt für normale EL. Wenn du JSTL <c:out> verwendest mit escapeXml="false", wird NICHT escaped – also Vorsicht!


Frage 6: Was passiert, wenn ich in EL durch Null teile?

EL wirft keine ArithmeticException!

${10 / 0}        <!-- Ergebnis: Infinity -->
${0 / 0}         <!-- Ergebnis: NaN (Not a Number) -->
${10 % 0}        <!-- Ergebnis: NaN -->

Das ist anders als in Java:

int result = 10 / 0;  // ArithmeticException!

EL ist fehlertoleranter! Ob das gut oder schlecht ist… kommt drauf an. 😄


Frage 7: Bernd meinte mal, „JSPs sind legacy, echte Devs nutzen Thymeleaf oder React“. Hat er recht?

Lowkey… sowohl ja als auch nein! Real talk:

Bernd hat teilweise recht:

  • Moderne Frameworks wie React, Vue, Angular sind standard für neue Web-Apps
  • Thymeleaf ist die modernere Template-Engine für Java (auch in Spring Boot Standard)
  • JSPs werden in NEW Projects seltener eingesetzt

ABER:

  • Millionen von Enterprise-Anwendungen laufen auf JSPs
  • Banks, Versicherungen, große Konzerne – alle haben JSP-Code
  • Du WIRST in deiner Karriere JSPs maintainen müssen
  • JSPs sind nicht „tot“, nur nicht mehr cutting-edge

Meine Take:

  • Für NEW Projects: Ja, moderne Alternativen sind besser (React + REST API, oder Thymeleaf)
  • Für BESTEHENDE Projekte: JSPs funktionieren gut und sind battle-tested
  • Für dein LERNEN: JSPs verstehen = Fundament für alle Template-Engines

Bernd arbeitet mit modernem Stack (Spring Boot + React). Aber selbst er musste mal Legacy-JSP-Code anfassen – und war froh, dass er’s gelernt hatte!

Bottom line: Lerne JSPs. Nutze moderne Tools für neue Projekte. Aber sei bereit, beide Welten zu beherrschen! 💪


📚 Quiz-Lösungen

Hier sind die Antworten zum Quiz von oben:


Frage 1: Erkläre den JSP-Lifecycle. Was sind die drei Schritte?

Antwort:

Der JSP-Lifecycle beschreibt, wie eine JSP-Datei zu einer lauffähigen Servlet-Klasse wird:

1. Translation (JSP → Java):

  • Die JSP-Datei (hello.jsp) wird in eine Java-Servlet-Klasse übersetzt (hello_jsp.java)
  • HTML-Code wird zu out.write() Statements
  • EL (${...}) wird zu Java-Code übersetzt
  • Direktiven, Scriptlets, etc. werden in Java-Code konvertiert

Wo liegt die generierte Datei?

  • Payara: C:\payara6\glassfish\domains\domain1\generated\jsp\

2. Compilation (Java → Bytecode):

  • Die generierte Java-Datei wird zu Bytecode kompiliert
  • Ergebnis: hello_jsp.class
  • Diese Klasse erweitert HttpServlet und implementiert _jspService() Methode

3. Execution (Servlet läuft):

  • Wenn ein Request kommt, lädt der Container die kompilierte Klasse
  • Ruft _jspService() Methode auf (wie doGet()/doPost() bei normalen Servlets)
  • Generiert HTML-Response und schickt sie zurück

Wichtig zu verstehen:

  • JSPs SIND Servlets! Nur mit angenehmerer Syntax
  • Die drei Schritte passieren nur einmal (beim ersten Zugriff oder beim Deployment)
  • Danach läuft die kompilierte Version (schnell!)

Frage 2: Was ist der Unterschied zwischen On-Demand-Compilation und Pre-Compilation?

Antwort:

On-Demand-Compilation (Standard in DEV):

Wie es funktioniert:

  • JSP wird beim ersten Zugriff übersetzt und kompiliert
  • Container merkt Änderungen an JSP-Dateien automatisch
  • Recompiled JSP wenn Datei geändert wurde

Vorteile:

  • ✅ Entwickler-freundlich: Änderung → Reload → Sofort sichtbar
  • ✅ Kein Deployment nötig bei JSP-Änderungen
  • ✅ Schneller Entwicklungs-Zyklus

Nachteile:

  • ❌ Erster Request ist langsam (Translation + Compilation)
  • ❌ User könnte Verzögerung bemerken
  • ❌ Compilation-Fehler erst zur Laufzeit sichtbar

Pre-Compilation (Standard in PROD):

Wie es funktioniert:

  • JSPs werden beim Deployment/Build übersetzt und kompiliert
  • Fertig kompilierte .class Dateien werden ausgeliefert
  • Keine Recompilation zur Laufzeit

Vorteile:

  • ✅ Schnell: Keine Verzögerung beim ersten Request
  • ✅ Compilation-Fehler werden beim Build erkannt (nicht erst in Production!)
  • ✅ Sicherer: Kein Source-Code (JSPs) auf Production-Server nötig

Nachteile:

  • ❌ JSP-Änderungen = Neues Deployment nötig
  • ❌ Langsamer Entwicklungs-Zyklus

Wann was verwenden?

UmgebungModusGrund
DEV/TESTOn-DemandSchnelle Iteration, sofortiges Feedback
STAGINGPre-CompileProduction-ähnlich testen
PRODUCTIONPre-CompilePerformance, Fehler früh erkennen

Wie aktiviert man Pre-Compilation?

Maven Plugin:

<plugin>
    <groupId>org.apache.sling</groupId>
    <artifactId>jspc-maven-plugin</artifactId>
    <executions>
        <execution>
            <phase>compile</phase>
            <goals>
                <goal>jspc</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Frage 3: Nenne 5 implizite Objekte in JSPs und erkläre, wofür sie verwendet werden.

Antwort:

Die 9 impliziten JSP-Objekte:

1. request (HttpServletRequest):

  • Typ: jakarta.servlet.http.HttpServletRequest
  • Verwendung: Zugriff auf Request-Parameter, Headers, Attribute
  • Beispiel:<% String name = request.getParameter("name"); String userAgent = request.getHeader("User-Agent"); %>
  • In EL: ${param.name}${header['User-Agent']}

2. response (HttpServletResponse):

  • Typ: jakarta.servlet.http.HttpServletResponse
  • Verwendung: Response-Header setzen, Redirects, Cookies
  • Beispiel:<% response.setHeader("Cache-Control", "no-cache"); response.addCookie(new Cookie("theme", "dark")); %>
  • Achtung: In modernen JSPs selten direkt verwendet (Controller macht das!)

3. session (HttpSession):

  • Typ: jakarta.servlet.http.HttpSession
  • Verwendung: Session-Daten speichern/lesen (Login-Status, Warenkorb, etc.)
  • Beispiel:<% User user = (User) session.getAttribute("currentUser"); session.setAttribute("lastPage", request.getRequestURI()); %>
  • In EL: ${sessionScope.currentUser}

4. application (ServletContext):

  • Typ: jakarta.servlet.ServletContext
  • Verwendung: Application-weite Daten, Context-Parameter
  • Beispiel:<% String dbUrl = application.getInitParameter("database.url"); int visitCount = (Integer) application.getAttribute("visitCount"); %>
  • In EL: ${initParam['database.url']}${applicationScope.visitCount}

5. out (JspWriter):

  • Typ: jakarta.servlet.jsp.JspWriter
  • Verwendung: Direkt HTML ausgeben (wie PrintWriter, aber gepuffert)
  • Beispiel:<% out.println("<p>Hello, World!</p>"); out.print("Current time: " + new Date()); %>
  • Achtung: Mit EL brauchst du out fast nie!

Weitere implizite Objekte:

6. pageContext (PageContext):

  • Zugriff auf alle Scopes, Request, Response, Session, etc.
  • Beispiel: ${pageContext.request.contextPath}

7. page (Object):

  • Die JSP selbst (this)
  • Selten verwendet

8. config (ServletConfig):

  • Servlet-Konfiguration
  • Beispiel: config.getServletName()

9. exception (Throwable):

  • Nur verfügbar in Error-Pages (isErrorPage="true")
  • Beispiel: ${exception.message}

Frage 4: Was ist der Unterschied zwischen <%@ include %> und <jsp:include>?

Antwort:

Zwei Arten, andere Dateien einzubinden:

<%@ include file="..." %> (Include-Direktive):

Wann passiert es?

  • Compile-Time (Translation-Phase)
  • Vor der Kompilierung

Wie funktioniert es?

  • Dateiinhalt wird direkt eingefügt (wie Copy/Paste)
  • Beide Dateien werden zu EINER Servlet-Klasse kompiliert

Beispiel:

main.jsp:

<%@ include file="header.jsp" %>
<h1>Main Content</h1>
<%@ include file="footer.jsp" %>

Wird kompiliert als wäre es:

<!-- header.jsp Inhalt -->
<h1>Main Content</h1>
<!-- footer.jsp Inhalt -->

Vorteile:

  • ✅ Schneller (nur eine Servlet-Klasse)
  • ✅ Gut für statische Inhalte (Header, Footer)

Nachteile:

  • ❌ Änderungen an header.jsp → main.jsp muss recompiled werden!
  • ❌ Nicht dynamisch (kann keine Parameter übergeben)

<jsp:include page="..." /> (Include-Action):

Wann passiert es?

  • Runtime (Request-Zeit)
  • Während der Ausführung

Wie funktioniert es?

  • Separate Servlet-Klasse wird aufgerufen
  • Output wird eingefügt

Beispiel:

<jsp:include page="header.jsp" />
<h1>Main Content</h1>
<jsp:include page="footer.jsp" />

Mit Parametern:

<jsp:include page="userInfo.jsp">
    <jsp:param name="userId" value="${user.id}" />
</jsp:include>

Vorteile:

  • ✅ Dynamisch (kann zur Laufzeit entscheiden, was included wird)
  • ✅ Kann Parameter übergeben
  • ✅ Änderungen sofort sichtbar (keine Recompilation nötig)

Nachteile:

  • ❌ Etwas langsamer (separater Request)

Vergleich:

Feature<%@ include %><jsp:include>
ZeitpunktCompile-TimeRuntime
GeschwindigkeitSchnellerEtwas langsamer
FlexibilitätStatischDynamisch
ParameterNeinJa
RecompilationJa, bei ÄnderungNein
Use-CaseHeader/Footer (statisch)Dynamische Komponenten

Wann was verwenden?

<%@ include %>:

  • Statische Header/Footer
  • CSS/JS die sich nie ändern
  • Wiederverwendbare Code-Snippets

<jsp:include>:

  • Dynamische Komponenten (User-Info, Widgets)
  • Wenn Parameter übergeben werden müssen
  • Wenn Inhalt zur Laufzeit ausgewählt wird

Best Practice: In modernen Apps meist <jsp:include> verwenden (flexibler!)


Frage 5: Warum solltest du Scriptlets vermeiden? Nenne mindestens 3 Gründe.

Antwort:

Scriptlets = <% ... %> und <%= ... %>

Warum sind sie problematisch?

1. Spaghetti-Code (Unlesbar):

Java-Code und HTML gemischt:

<!-- SCHLECHT: -->
<html>
<body>
<%
    List<Product> products = (List<Product>) request.getAttribute("products");
    if (products != null) {
        for (Product p : products) {
            if (p.getStock() > 0) {
%>
    <div class="product">
        <h2><%= p.getName() %></h2>
        <p>$<%= p.getPrice() %></p>
    </div>
<%
            }
        }
    }
%>
</body>
</html>

Probleme:

  • Java und HTML vermischt → schwer zu lesen
  • Designer können nicht damit arbeiten
  • Syntax-Highlighting funktioniert nicht richtig

2. Keine Trennung von Concerns (Model 1 Problem):

Business-Logik in View:

<!-- SCHLECHT: Business-Logik in JSP! -->
<%
    ProductDAO dao = new ProductDAO();
    List<Product> products = dao.getAll();
    
    // Discount berechnen (Business-Logik!)
    for (Product p : products) {
        if (p.getPrice() > 100) {
            double discount = p.getPrice() * 0.1;
            p.setPrice(p.getPrice() - discount);
        }
    }
%>

Warum schlecht?

  • View sollte NUR zeigen, nicht berechnen!
  • Business-Logik gehört in Service/Controller
  • Nicht testbar (wie testet man JSPs?)
  • Nicht wiederverwendbar

3. Sicherheitsprobleme (XSS):

Kein automatisches Escaping:

<!-- GEFÄHRLICH: -->
<p>Welcome, <%= request.getParameter("name") %></p>

Wenn name = <script>alert('XSS')</script> ist → Script wird ausgeführt!

Mit EL (sicher):

<p>Welcome, ${param.name}</p>  <!-- Automatisch escaped! -->

4. NullPointerException-Gefahr:

<!-- CRASHT wenn user null ist: -->
<p>Welcome, <%= user.getName() %></p>  <!-- NullPointerException! -->

Mit EL (null-safe):

<p>Welcome, ${user.name}</p>  <!-- Zeigt nichts wenn null -->

5. Schwer zu warten:

Nach 3 Monaten zurückkommen:

<%
    // Was macht dieser Code?? 
    if (request.getAttribute("x") != null) {
        Object o = request.getAttribute("x");
        if (o instanceof List) {
            List l = (List) o;
            if (!l.isEmpty()) {
                // ... 50 Zeilen später ...
            }
        }
    }
%>

Viel Glück beim Verstehen! 😰


6. Kein Syntax-Checking vor Runtime:

<%
    // Tippfehler - wird erst zur Laufzeit entdeckt!
    String name = request.getParamter("name");  // ParamTer statt ParameTer
%>

Compiler sieht das nicht → erst beim Request crasht es!


7. Performance (minimal, aber vorhanden):

Scriptlets müssen bei jedem Request ausgeführt werden. EL-Expressions werden zur Compile-Time optimiert.


Die bessere Alternative: Expression Language (EL)

<!-- BESSER: -->
<html>
<body>
    <c:forEach var="product" items="${products}">
        <c:if test="${product.stock > 0}">
            <div class="product">
                <h2>${product.name}</h2>
                <p>$${product.price}</p>
            </div>
        </c:if>
    </c:forEach>
</body>
</html>

Vorteile:

  • ✅ Lesbar (sieht aus wie HTML mit extra Tags)
  • ✅ Null-safe
  • ✅ Automatisches HTML-Escaping
  • ✅ Designer-freundlich
  • ✅ Testbar (Business-Logik im Controller!)
  • ✅ Keine NullPointerExceptions
  • ✅ Separation of Concerns

Fazit: Scriptlets = Legacy! Verwende EL + JSTL für moderne, saubere JSPs!


Frage 6: Was macht Expression Language (EL) null-safe? Warum ist das wichtig?

Antwort:

EL-Null-Safety bedeutet:

Wenn ein Ausdruck in EL null ergibt, crasht die Seite NICHT – stattdessen wird ein leerer String zurückgegeben!

Beispiel:

// Im Servlet: user ist null!
request.setAttribute("user", null);

Mit Scriptlet (CRASHT!):

<%= user.getName() %>  
<!-- NullPointerException! → 500 Error → Seite kaputt! -->

Mit EL (SICHER!):

${user.name}
<!-- Ergebnis: "" (leerer String) → Seite funktioniert! -->

Wie funktioniert EL Null-Safety?

Null-Checks auf JEDEM Level der Property-Chain:

${user.address.street.name}

EL macht intern:

Object user = pageContext.findAttribute("user");
if (user == null) return "";

Object address = getProperty(user, "address");
if (address == null) return "";

Object street = getProperty(address, "street");
if (street == null) return "";

Object name = getProperty(street, "name");
if (name == null) return "";

return name.toString();

Jeder Schritt wird geprüft!


Warum ist das wichtig?

1. Robuste Anwendungen:

Ohne Null-Safety:

<p>Welcome, <%= user.getName() %></p>

→ Wenn User nicht eingeloggt (user = null) → 500 Error → Ganze Seite kaputt!

Mit Null-Safety:

<p>Welcome, ${user.name}</p>

→ Wenn User nicht eingeloggt → Zeigt „Welcome, “ → Seite funktioniert weiter!


2. Weniger defensive Programmierung nötig:

Ohne EL:

<%
    User user = (User) request.getAttribute("user");
    String name = "Guest";
    if (user != null && user.getName() != null) {
        name = user.getName();
    }
%>
<p>Welcome, <%= name %></p>

Mit EL:

<p>Welcome, ${not empty user.name ? user.name : 'Guest'}</p>

Viel kürzer und lesbarer!


3. Partielle Daten okay:

Manchmal hast du optionale Felder:

<!-- Product mit optionalem Bild -->
<div class="product">
    <h2>${product.name}</h2>
    <p>${product.description}</p>
    
    <!-- Wenn kein Bild → zeigt nichts (kein Crash!) -->
    <img src="${product.imageUrl}" alt="${product.name}">
</div>

Ohne Null-Safety müsstest du für JEDES optionale Feld einen Check machen!


4. Fehlertoleranz während Entwicklung:

Beim Entwickeln vergisst man manchmal, ein Attribute zu setzen:

// Servlet: Vergessen, product zu setzen!
// request.setAttribute("product", product);  // Oops!
request.getRequestDispatcher("/product.jsp").forward(request, response);

Ohne Null-Safety: 500 Error, Seite kaputt, Debugging-Horror!

Mit Null-Safety: Seite zeigt leere Werte, aber crasht nicht → leichter zu debuggen!


Wichtige Ausnahme:

EL-Null-Safety gilt nur für Property-Access (${user.name}), NICHT für:

Arithmetische Operationen:

${null + 5}      <!-- Ergebnis: 5 (null wird zu 0) -->
${null * 5}      <!-- Ergebnis: 0 -->
${null / 5}      <!-- Ergebnis: 0 -->

Vergleiche:

${null == null}  <!-- Ergebnis: true -->
${null == 5}     <!-- Ergebnis: false -->

Best Practice mit EL Null-Safety:

✅ Vertraue auf Null-Safety für Property-Access:

${user.name}
${product.description}
${order.customer.address.street}

✅ Nutze empty für explizite Null-Checks:

<c:if test="${not empty user}">
    <p>Welcome, ${user.name}!</p>
</c:if>

<c:if test="${empty cart.items}">
    <p>Your cart is empty.</p>
</c:if>

✅ Nutze Conditional Operator für Fallbacks:

${not empty user.name ? user.name : 'Guest'}
${not empty product.imageUrl ? product.imageUrl : '/images/default.png'}

❌ Verlasse dich NICHT auf Null-Safety für Business-Logik:

<!-- SCHLECHT: -->
${user.address.street}  <!-- Wenn null, zeigt nichts - User weiß nicht warum! -->

<!-- BESSER: -->
<c:choose>
    <c:when test="${not empty user.address}">
        ${user.address.street}
    </c:when>
    <c:otherwise>
        <p>No address on file. <a href="/profile">Add address</a></p>
    </c:otherwise>
</c:choose>

Fazit: EL-Null-Safety macht JSPs robust und fehlertoleranter! Nutze es, aber sei explizit wenn es um wichtige Daten geht!


Frage 7: Erkläre den Unterschied zwischen ${param.name} und ${paramValues.hobby}.

Antwort:

Beide greifen auf Request-Parameter zu – aber anders!

${param.name} – Einzelner Wert:

Verwendung: Wenn Parameter NUR EINMAL in der URL/Form vorkommt

Beispiel:

URL:

http://localhost:8080/app?name=Anna&age=25

Formular:

<form>
    <input type="text" name="name" value="Anna">
    <input type="number" name="age" value="25">
</form>

In JSP:

<p>Name: ${param.name}</p>     <!-- Anna -->
<p>Age: ${param.age}</p>       <!-- 25 -->

Was macht param?

  • Entspricht: request.getParameter("name")
  • Gibt einen String zurück (oder leeren String wenn nicht vorhanden)

${paramValues.hobby} – Mehrere Werte (Array):

Verwendung: Wenn Parameter MEHRMALS vorkommt (z.B. Checkboxes, Multi-Select)

Beispiel:

URL:

http://localhost:8080/app?hobby=Reading&hobby=Gaming&hobby=Coding

Formular:

<form>
    <input type="checkbox" name="hobby" value="Reading"> Reading
    <input type="checkbox" name="hobby" value="Gaming"> Gaming
    <input type="checkbox" name="hobby" value="Coding"> Coding
</form>

In JSP:

<!-- FALSCH - zeigt nur ersten Wert! -->
<p>Hobby: ${param.hobby}</p>  <!-- Nur "Reading" -->

<!-- RICHTIG - zeigt alle Werte! -->
<c:forEach var="h" items="${paramValues.hobby}">
    <p>Hobby: ${h}</p>
</c:forEach>
<!-- Ergebnis:
    Hobby: Reading
    Hobby: Gaming
    Hobby: Coding
-->

<!-- Oder einzeln zugreifen: -->
<p>First hobby: ${paramValues.hobby[0]}</p>   <!-- Reading -->
<p>Second hobby: ${paramValues.hobby[1]}</p>  <!-- Gaming -->
<p>Third hobby: ${paramValues.hobby[2]}</p>   <!-- Coding -->

Was macht paramValues?

  • Entspricht: request.getParameterValues("hobby")
  • Gibt ein String-Array zurück

Vergleich:

Feature${param.name}${paramValues.hobby}
Rückgabe-TypStringString[] (Array)
Entsprichtrequest.getParameter()request.getParameterValues()
VerwendungSingle-Value ParameterMulti-Value Parameter
BeispielInput-Felder, Select (single)Checkboxes, Multi-Select

Wann welches verwenden?

param verwenden für:

  • Text-Inputs (<input type="text">)
  • Single-Select Dropdowns (<select>)
  • Radio-Buttons (nur einer ausgewählt)
  • Hidden-Fields
  • Einzelne URL-Parameter

paramValues verwenden für:

  • Checkboxes (mehrere ausgewählt)
  • Multi-Select Dropdowns (<select multiple>)
  • Mehrere URL-Parameter mit gleichem Namen

Häufiger Fehler:

<!-- USER HAT MEHRERE HOBBIES AUSGEWÄHLT: -->

<!-- FALSCH - zeigt nur ersten Wert! -->
<p>Your hobbies: ${param.hobby}</p>  <!-- Nur "Reading" -->

<!-- RICHTIG - zeigt alle Werte! -->
<p>Your hobbies:
    <c:forEach var="h" items="${paramValues.hobby}" varStatus="status">
        ${h}<c:if test="${!status.last}">, </c:if>
    </c:forEach>
</p>
<!-- Ergebnis: "Reading, Gaming, Coding" -->

Beispiel: Vollständiges Formular

HTML-Form:

<form action="submit" method="post">
    <!-- Single-Value: -->
    <input type="text" name="username" placeholder="Username">
    
    <!-- Single-Value: -->
    <select name="country">
        <option value="DE">Germany</option>
        <option value="US">USA</option>
    </select>
    
    <!-- Multi-Value: -->
    <p>Interests:</p>
    <input type="checkbox" name="interest" value="Tech"> Tech
    <input type="checkbox" name="interest" value="Sports"> Sports
    <input type="checkbox" name="interest" value="Music"> Music
    
    <button type="submit">Submit</button>
</form>

JSP (Anzeige):

<h2>Your Submission:</h2>

<!-- Single-Value Parameters: -->
<p>Username: ${param.username}</p>
<p>Country: ${param.country}</p>

<!-- Multi-Value Parameter: -->
<p>Interests:</p>
<ul>
    <c:forEach var="interest" items="${paramValues.interest}">
        <li>${interest}</li>
    </c:forEach>
</ul>

Fazit:

  • Ein Wert? → ${param.xxx}
  • Mehrere Werte? → ${paramValues.xxx} + forEach

Frage 8: Was ist der empty Operator in EL und wann würdest du ihn verwenden?

Antwort:

empty Operator prüft, ob etwas „leer“ ist.

Syntax:

${empty variable}       <!-- true wenn leer -->
${not empty variable}   <!-- true wenn NICHT leer -->

Was bedeutet „leer“?

Der empty Operator gibt true zurück wenn:

  1. Variable ist null
  2. String ist "" (leerer String)
  3. Collection/List ist leer (size = 0)
  4. Array ist leer (length = 0)
  5. Map ist leer (size = 0)

Beispiele für verschiedene Datentypen:

Strings:

// Servlet:
request.setAttribute("name", "Anna");        // NICHT leer
request.setAttribute("emptyString", "");     // Leer
request.setAttribute("nullString", null);    // Leer
<!-- JSP: -->
${empty name}           <!-- false -->
${empty emptyString}    <!-- true -->
${empty nullString}     <!-- true -->

${not empty name}       <!-- true -->
${not empty emptyString}<!-- false -->

Collections (Listen):

// Servlet:
List<Product> products = Arrays.asList(
    new Product("Laptop"),
    new Product("Mouse")
);
List<Product> emptyList = new ArrayList<>();
List<Product> nullList = null;

request.setAttribute("products", products);
request.setAttribute("emptyList", emptyList);
request.setAttribute("nullList", nullList);
<!-- JSP: -->
${empty products}       <!-- false (2 items) -->
${empty emptyList}      <!-- true (0 items) -->
${empty nullList}       <!-- true (null) -->

${not empty products}   <!-- true -->

Arrays:

// Servlet:
String[] colors = {"Red", "Green", "Blue"};
String[] emptyArray = {};
String[] nullArray = null;

request.setAttribute("colors", colors);
request.setAttribute("emptyArray", emptyArray);
request.setAttribute("nullArray", nullArray);
<!-- JSP: -->
${empty colors}         <!-- false -->
${empty emptyArray}     <!-- true -->
${empty nullArray}      <!-- true -->

Maps:

// Servlet:
Map<String, String> config = new HashMap<>();
config.put("theme", "dark");

Map<String, String> emptyMap = new HashMap<>();
Map<String, String> nullMap = null;

request.setAttribute("config", config);
request.setAttribute("emptyMap", emptyMap);
request.setAttribute("nullMap", nullMap);
<!-- JSP: -->
${empty config}         <!-- false (1 entry) -->
${empty emptyMap}       <!-- true (0 entries) -->
${empty nullMap}        <!-- true (null) -->

Wann empty verwenden?

Use-Case 1: Optionale Form-Felder anzeigen

<c:if test="${not empty param.name}">
    <p>Hello, ${param.name}!</p>
</c:if>

<c:if test="${empty param.name}">
    <p>Please enter your name.</p>
</c:if>

Use-Case 2: Listen auf Inhalt prüfen

<c:choose>
    <c:when test="${not empty products}">
        <ul>
            <c:forEach var="product" items="${products}">
                <li>${product.name}</li>
            </c:forEach>
        </ul>
    </c:when>
    <c:otherwise>
        <p>No products found.</p>
    </c:otherwise>
</c:choose>

Use-Case 3: Optionale Daten anzeigen

<!-- Nur anzeigen wenn User ein Profilbild hat -->
<c:if test="${not empty user.profileImageUrl}">
    <img src="${user.profileImageUrl}" alt="Profile">
</c:if>

<!-- Nur anzeigen wenn User einen Bio-Text hat -->
<c:if test="${not empty user.bio}">
    <p>${user.bio}</p>
</c:if>

Use-Case 4: Fallback-Werte

<!-- Conditional Operator mit empty -->
<p>Theme: ${not empty user.theme ? user.theme : 'default'}</p>

<p>Language: ${not empty user.language ? user.language : 'en'}</p>

Use-Case 5: Validierungs-Feedback

<form>
    <input type="text" name="email" value="${param.email}">
    
    <c:if test="${empty param.email && not empty param.submit}">
        <p class="error">Email is required!</p>
    </c:if>
</form>

Use-Case 6: Warenkorb-Check

<c:choose>
    <c:when test="${not empty cart.items}">
        <h2>Your Cart (${cart.items.size()} items)</h2>
        <c:forEach var="item" items="${cart.items}">
            <div>${item.productName} - ${item.quantity}x</div>
        </c:forEach>
        <button>Checkout</button>
    </c:when>
    <c:otherwise>
        <p>Your cart is empty. <a href="/products">Start shopping</a></p>
    </c:otherwise>
</c:choose>

Vergleich: empty vs. == null

empty ist besser als == null:

<!-- UMSTÄNDLICH: -->
<c:if test="${param.name != null && param.name != ''}">
    <p>${param.name}</p>
</c:if>

<!-- BESSER: -->
<c:if test="${not empty param.name}">
    <p>${param.name}</p>
</c:if>

Häufige Patterns mit empty:

1. „Wenn vorhanden, dann zeige“:

<c:if test="${not empty user}">
    <p>Welcome, ${user.name}!</p>
</c:if>

2. „Wenn leer, dann Fallback“:

<p>${not empty products ? 'Products available' : 'No products yet'}</p>

3. „Zeige Liste ODER Nachricht“:

<c:choose>
    <c:when test="${not empty items}">
        <c:forEach var="item" items="${items}">
            <!-- Zeige Items -->
        </c:forEach>
    </c:when>
    <c:otherwise>
        <p>Nothing to display.</p>
    </c:otherwise>
</c:choose>

Fazit: empty ist DER Operator für „existiert/hat Inhalt?“-Checks in EL! Nutze ihn statt umständlichen != null && != "" Checks!


🎉 Tag 5 geschafft!

Du rockst! Main Character Energy! ✨

Real talk: JSPs und Expression Language sind ein riesiges Thema. Wenn dir der Kopf schwirrt – das ist völlig okay!

Das hast du heute gelernt:

  • ✅ Was JSPs sind und wie sie zu Servlets kompiliert werden
  • ✅ Der komplette JSP-Lifecycle (Translation → Compilation → Execution)
  • ✅ Implizite Objekte in JSPs
  • ✅ JSP-Direktiven (<%@ page %><%@ include %><%@ taglib %>)
  • ✅ Warum Scriptlets schlecht sind und du sie vermeiden solltest
  • ✅ Expression Language (EL) – die moderne Art
  • ✅ EL-Syntax und alle impliziten EL-Objekte
  • ✅ EL-Operatoren (arithmetisch, logisch, conditional, empty)
  • ✅ Null-Safety in EL
  • ✅ Best Practices für JSPs

Du kannst jetzt:

  • Saubere JSPs ohne Java-Code schreiben
  • Expression Language sicher verwenden
  • Daten vom Controller in der View anzeigen
  • Implizite Objekte nutzen
  • MVC korrekt umsetzen (JSP = View!)

Honestly? Das ist HUGE! JSPs sind das Herzstück vieler Enterprise-Anwendungen. Du verstehst jetzt, wie die „View“ in MVC funktioniert.

Und das Beste: Du weißt, warum Scriptlets böse sind – und wie man es besser macht! 💪


🔮 Wie geht’s weiter?

Morgen (Tag 6): Java Beans, Actions, Scopes & Direktiven

Was dich erwartet:

  • Was JavaBeans sind
  • JSP-Actions (<jsp:useBean><jsp:setProperty><jsp:getProperty>)
  • Forward vs. Include Actions
  • Scopes im Detail (Page, Request, Session, Application)
  • Wann du welchen Scope verwendest
  • Scope-Management wird dein Game-Changer für State-Handling! 🔥

Brauchst du eine Pause?
Mach sie! EL-Syntax braucht Zeit zum Verinnerlichen.

Tipp für heute Abend:
Konvertiere ein altes Scriptlet-JSP zu EL. Das ist die beste Übung!

Learning by doing! 🎨


🔧 Troubleshooting

Problem: EL wird nicht ausgewertet – ${param.name} steht wörtlich im HTML

Lösung:

<!-- Prüfe web.xml Version -->
<web-app version="6.0" ...>  <!-- Muss >= 2.4 sein -->

<!-- Oder explizit aktivieren: -->
<%@ page isELIgnored="false" %>

Problem: NullPointerException in JSP

Lösung:

<!-- FALSCH - kann crashen: -->
<%= request.getAttribute("user").getName() %>

<!-- RICHTIG - null-safe: -->
${user.name}

<!-- Oder mit null-check: -->
${not empty user ? user.name : 'Guest'}

Problem: JSP findet Attribute nicht

Lösung:

// Servlet - prüfe Scope:
request.setAttribute("product", product);  // Request-Scope

// Forward (NICHT Redirect!)
request.getRequestDispatcher("/WEB-INF/views/product.jsp")
       .forward(request, response);

Problem: Änderungen an JSP werden nicht angezeigt

Lösung:

  1. Hard-Refresh im Browser (Ctrl+F5)
  2. Prüfe ob Pre-Compilation aktiv ist
  3. Lösche generated-Ordner manuell:C:\payara6\glassfish\domains\domain1\generated\jsp\
  4. Server neu starten

Problem: JSTL-Tags werden nicht erkannt

Lösung:

<!-- Prüfe Taglib-Import: -->
<%@ taglib prefix="c" uri="jakarta.tags.core" %>

<!-- Jakarta EE 10+: jakarta.tags.* -->
<!-- Alte Versionen: http://java.sun.com/jsp/jstl/core -->

Prüfe auch, ob JSTL-JAR im Classpath ist!


JSP & EL:

Best Practices:

JSTL (kommt Tag 8):


💬 Feedback?

War Tag 5 zu viel Syntax? Zu wenig Beispiele? Mehr Übungen gewünscht?

Schreib uns: feedback@java-developer.online

Wir wollen, dass du erfolgreich lernst!


Bis morgen! 👋

Elyndra

elyndra@java-developer.online
Senior Developer bei Java Fleet Systems Consulting


Java Web Basic – Tag 5 von 10
Teil der Java Fleet Learning-Serie
© 2025 Java Fleet Systems Consulting