Java Sprache & Runtime im Enterprise-Kontext
Java-Version, Records, Virtual Threads, Exceptions, Collections und Modularität praxisnah verstehen.
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.
| Entscheidung | Empfehlung | Begründung |
|---|---|---|
| Neues Enterprise-Projekt | Java 21 oder Java 25 prüfen | LTS, moderne Sprache, bessere Runtime-Features, lange Wartbarkeit |
| Migration von Java 8/11 | Zuerst Java 17/21-Kompatibilität herstellen | Abhängigkeiten, Reflection, javax/jakarta, alte App-Server prüfen |
| Build-Konfiguration | maven.compiler.release setzen | Reproduzierbares Bytecode-Ziel statt zufälliger lokaler JDK-Effekt |
| Container | JRE/JDK-Image bewusst wählen | Patch-Strategie, Größe, Security und Supportmodell klären |
<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.
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) {}
}
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 geeignet | Vorsicht |
|---|---|
| Viele blockierende HTTP-/DB-Aufrufe mit sauberem Timeout | CPU-lastige Arbeit profitiert kaum |
| Thread-per-request Modell mit moderner Runtime | ThreadLocal-Nutzung, Connection-Pool-Größen und Pinning prüfen |
| Migration ohne komplette Reactive-Umschreibung | Backpressure 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.
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; }
}