Java Sprache & Runtime im Enterprise-Kontext

Java-Version, Records, Virtual Threads, Exceptions, Collections und Modularität praxisnah verstehen.

Enterprise JavaBeispieleArchitekturOffline HTML

Java-Version bewusst wählen

In Enterprise-Umgebungen ist die Java-Version eine Architekturentscheidung. Sie betrifft Laufzeit, Security-Patches, Framework-Kompatibilität, Build-Reproduzierbarkeit und Container-Images. Java 21 ist weiterhin eine sehr verbreitete moderne Basis, während Java 25 als aktuelle LTS-Version im Oracle-Ökosystem relevant ist. Für viele Frameworks bleibt Java 17 das Minimum, aber Java 21 ist häufig die sinnvolle produktive Basis.

EntscheidungEmpfehlungBegründung
Neues Enterprise-ProjektJava 21 oder Java 25 prüfenLTS, moderne Sprache, bessere Runtime-Features, lange Wartbarkeit
Migration von Java 8/11Zuerst Java 17/21-Kompatibilität herstellenAbhängigkeiten, Reflection, javax/jakarta, alte App-Server prüfen
Build-Konfigurationmaven.compiler.release setzenReproduzierbares Bytecode-Ziel statt zufälliger lokaler JDK-Effekt
ContainerJRE/JDK-Image bewusst wählenPatch-Strategie, Größe, Security und Supportmodell klären
Maven: Java Release bewusst festlegen
<properties>
    <!-- Enterprise-Regel: eine Java-Version bewusst festlegen, nicht implizit vom lokalen JDK erben. -->
    <maven.compiler.release>21</maven.compiler.release>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.14.1</version>
        </plugin>
    </plugins>
</build>

Records, Value Objects und DTOs

Records sind ideal für unveränderliche Datencontainer: API-Requests, Responses, Commands, Events und einfache Value Objects. Nicht jede Entity sollte ein Record sein, weil JPA/Hibernate bei Entities besondere Anforderungen an Konstruktoren, Proxies und Lifecycle hat. Für fachliche Wertobjekte und Transportobjekte sind Records jedoch sehr lesbar.

Record als Command mit Invarianten
public record PlaceOrderCommand(CustomerId customerId, List<Line> lines) {
    public PlaceOrderCommand {
        Objects.requireNonNull(customerId, "customerId");
        lines = List.copyOf(lines);
        if (lines.isEmpty()) throw new IllegalArgumentException("lines must not be empty");
    }

    public record Line(String sku, int quantity) {}
}
Merke: Records machen Datenflüsse klarer. Für veränderliche Aggregate, Lazy Loading oder komplexe Entity-Lebenszyklen sind normale Klassen oft passender.

Virtual Threads richtig einordnen

Virtual Threads reduzieren den Aufwand blockierender I/O-Operationen, ersetzen aber keine Architekturarbeit. Sie lösen nicht automatisch Datenbank-Locks, schlechte SQLs, fehlende Timeouts oder unkontrollierte Nebenläufigkeit. Sie sind besonders interessant für viele gleichzeitige blockierende Requests, solange Bibliotheken und Frameworks korrekt mitspielen.

Gut geeignetVorsicht
Viele blockierende HTTP-/DB-Aufrufe mit sauberem TimeoutCPU-lastige Arbeit profitiert kaum
Thread-per-request Modell mit moderner RuntimeThreadLocal-Nutzung, Connection-Pool-Größen und Pinning prüfen
Migration ohne komplette Reactive-UmschreibungBackpressure und Ressourcenlimits bleiben erforderlich

Exception-Strategie

Enterprise-Code braucht eine klare Fehler-Taxonomie. Nicht jede Exception ist gleich: Validierungsfehler, Fachfehler, technische Recoverable-Fehler und technische Non-Recoverable-Fehler müssen unterschiedlich behandelt werden.

Fehler bewusst typisieren
public sealed interface OrderFailure permits ValidationFailure, BusinessFailure, TechnicalFailure {}

public record ValidationFailure(String field, String message) implements OrderFailure {}
public record BusinessFailure(String code, String message) implements OrderFailure {}
public record TechnicalFailure(String system, String message, boolean retryable) implements OrderFailure {}

// Pattern: Result Object - erwartbare Fehler werden explizit modelliert statt als Zufalls-Exception geworfen.
public record Result<T>(T value, OrderFailure failure) {
    public static <T> Result<T> ok(T value) { return new Result<>(value, null); }
    public static <T> Result<T> fail(OrderFailure failure) { return new Result<>(null, failure); }
    public boolean isOk() { return failure == null; }
}
⌂ Cockpit