Warum Java 17?

Java 17 ist ein Long-Term-Support-Release, das erste nach Java 11. Für Enterprise-Anwendungen, die auf LTS-Versionen setzen, bedeutet das: Der Sprung von Java 11 auf Java 17 bringt sechs Jahre an Sprachentwicklung auf einmal. Das ist signifikant. Sealed Classes, Records, Pattern Matching, Text Blocks und zahlreiche kleinere Verbesserungen machen Java 17 zu dem Release, das die Sprache spürbar modernisiert.

Die Frage ist nicht, ob man migrieren sollte, sondern wann. Java 11 erhält zwar weiterhin Updates, aber die neuen Sprachfeatures verbessern die Lesbarkeit und Ausdruckskraft des Codes so deutlich, dass ein früherer Umstieg sich lohnt.

Records: Value Objects ohne Boilerplate

Records sind das Feature, das im Alltag den größten Impact hat. In Domain-Driven Design arbeitet man intensiv mit Value Objects, Objekte, die durch ihre Werte definiert sind, nicht durch ihre Identität. Bisher bedeutete das in Java: Klasse schreiben, Felder deklarieren, Konstruktor, Getter, equals(), hashCode() und toString() implementieren. Oder Lombok verwenden und damit eine Compile-Time-Abhängigkeit einführen.

// Vorher: 30+ Zeilen für ein einfaches Value Object
public final class Adresse {
    private final String strasse;
    private final String hausnummer;
    private final String plz;
    private final String ort;

    public Adresse(String strasse, String hausnummer, 
                   String plz, String ort) {
        this.strasse = strasse;
        this.hausnummer = hausnummer;
        this.plz = plz;
        this.ort = ort;
    }

    // Getter, equals, hashCode, toString...
}

// Nachher: Eine Zeile
public record Adresse(String strasse, String hausnummer, 
                      String plz, String ort) {}

Records sind immutable, haben automatisch generierte Accessor-Methoden, equals(), hashCode() und toString(). Sie können Validierung im kompakten Konstruktor enthalten:

public record Postleitzahl(String wert) {
    public Postleitzahl {
        if (!wert.matches("\\d{5}")) {
            throw new IllegalArgumentException(
                "Ungültige Postleitzahl: " + wert);
        }
    }
}

Für Domain Modeling ist das ein Quantensprung. Die Hemmschwelle, ein dediziertes Value Object zu erstellen, sinkt drastisch. Statt einer primitiven String-PLZ gibt es jetzt eine typsichere Postleitzahl, ohne den bisherigen Boilerplate-Overhead.

Sealed Classes: Geschlossene Typhierarchien

Sealed Classes definieren explizit, welche Klassen eine Basisklasse erweitern dürfen. Das ist besonders wertvoll für die Modellierung von fachlichen Zuständen:

public sealed interface AntragStatus 
    permits Eingegangen, InPruefung, Genehmigt, Abgelehnt {
}

public record Eingegangen(Instant zeitpunkt) 
    implements AntragStatus {}
public record InPruefung(Instant zeitpunkt, String pruefer) 
    implements AntragStatus {}
public record Genehmigt(Instant zeitpunkt, String genehmiger, 
                        BigDecimal betrag) 
    implements AntragStatus {}
public record Abgelehnt(Instant zeitpunkt, String grund) 
    implements AntragStatus {}

Der Compiler weiß, dass es nur diese vier Zustände gibt. In Kombination mit Pattern Matching (noch als Preview in Java 17, finalisiert in 21) ermöglicht das exhaustive Switches, der Compiler warnt, wenn ein Zustand nicht behandelt wird.

In der fachlichen Domäne ist das Gold wert. Ein Antrag hat eine endliche Menge von Zuständen. Mit Sealed Classes wird diese Einschränkung im Typsystem abgebildet, nicht nur in der Dokumentation.

Pattern Matching für instanceof

Pattern Matching, ein kleines Feature mit großer Wirkung:

// Vorher
if (event instanceof AntragEingegangen) {
    AntragEingegangen e = (AntragEingegangen) event;
    verarbeite(e.getAntragId());
}

// Nachher
if (event instanceof AntragEingegangen e) {
    verarbeite(e.antragId());
}

Die explizite Typumwandlung entfällt. Das reduziert Rauschen im Code und eliminiert eine potenzielle Fehlerquelle. In Event-Handling-Code, wo häufig auf verschiedene Event-Typen geprüft wird, summiert sich dieser Vorteil.

Text Blocks: Mehrzeilige Strings

Text Blocks machen SQL-Queries, JSON-Templates und XML-Fragmente im Code deutlich lesbarer:

String query = """
    SELECT a.antrag_id, a.status, a.erstellt_am
    FROM antraege a
    JOIN kunden k ON a.kunden_id = k.id
    WHERE k.region = :region
      AND a.status IN (:statusListe)
    ORDER BY a.erstellt_am DESC
    """;

Kein String-Concatenation mehr, keine escaped Newlines. Der Code zeigt direkt, wie die Query aussieht. Für Repositories und Data-Access-Schichten, in denen native Queries unvermeidlich sind, verbessert das die Wartbarkeit erheblich.

Migration von Java 11: Praktische Hinweise

Die Migration von Java 11 auf Java 17 ist in den meisten Fällen weniger aufwändig als befürchtet. Die kritischen Punkte:

  • Entfernte Module: Nashorn JavaScript Engine, RMI Activation und Applet API sind entfernt. In Enterprise-Anwendungen ist das selten ein Problem.
  • Interne APIs: Der Zugriff auf interne JDK-APIs (sun., com.sun.) wird strikter eingeschränkt. Mit --illegal-access=warn lassen sich Zugriffe identifizieren. In der Praxis sind es meist Bibliotheken, die interne APIs nutzen, ein Update auf aktuelle Versionen behebt das Problem.
  • Module System (JPMS): Optional. Bestehender Code läuft auf dem Classpath wie bisher. Eine erzwungene Migration bestehender Anwendungen auf JPMS ist selten gerechtfertigt.

Auswirkungen auf DDD und Domain Modeling

Die Kombination aus Records, Sealed Classes und Pattern Matching verändert die Art, wie Domain Models in Java geschrieben werden:

  • Value Objects werden durch Records trivial, keine Ausrede mehr für primitive Obsession
  • Zustandsautomaten werden durch Sealed Interfaces und Records ausdrucksstark modellierbar
  • Event Handling wird durch Pattern Matching lesbarer
  • DTOs und Commands profitieren von der Record-Syntax

Java 17 schließt damit eine Lücke zu Sprachen wie Kotlin und Scala, die diese Ausdrucksmittel schon länger bieten. Für Teams, die Domain-Driven Design praktizieren, ist das ein willkommener Fortschritt.

Fazit

Java 17 ist kein inkrementelles Update, sondern ein substantieller Sprung in der Ausdruckskraft der Sprache. Records, Sealed Classes und Pattern Matching verbessern die Modellierung fachlicher Konzepte spürbar. Die Migration von Java 11 ist in den meisten Fällen gut beherrschbar. Für Enterprise-Anwendungen, die auf LTS-Versionen setzen, gibt es keinen Grund, den Umstieg aufzuschieben. Die Produktivitätsgewinne und die verbesserte Codequalität rechtfertigen den Migrationsaufwand.