Senior Java Interview Masterbook – Interview-Cockpit

Kompakte Lernfassung mit Kapitelsteuerung, heller einklappbarer Sidebar, Fokusmodus für die letzten 24 Stunden und zusammengeführtem thematischem Glossar.

Interview-Cockpit

Der Fokusmodus zeigt 192 priorisierte Karten: Grundlagen, Senior-Fallen, Best Practices und zusammenfassende Q&A-Karten. Die normale Suche bleibt zusätzlich aktiv.

B1 – Band 1: Java Language Professional

B1-K01 – Kapitel 1: Java Core 20 Karten

B1-K01-Q001 – Immutability und Value ObjectsImmutable Objekte machen Zustand explizit und Seiteneffekte selten. 24h
Frage:

„Warum sind immutable Objekte in Enterprise-Java-Anwendungen wichtig, und welche Nachteile können sie haben?“

Kurz erklärt:

Immutability bedeutet, dass ein Objekt nach seiner Erzeugung seinen sichtbaren Zustand nicht mehr verändert. Änderungen erzeugen neue Instanzen.

Musterantwort:

Immutable Objekte reduzieren Seiteneffekte, sind threadsicher ohne Synchronisation und eignen sich sehr gut für Value Objects, DTOs, Events und Domain-Modelle. In einer Banking- oder Microservice-Architektur ist das besonders wertvoll, weil Daten durch viele Schichten laufen: Controller, Service, Messaging, Persistenz und Audit.

Der Nachteil ist, dass bei jeder Änderung neue Objekte entstehen. Das kann mehr Allokationen erzeugen. In der Praxis ist das aber oft akzeptabel, weil moderne JVMs kurzlebige Objekte sehr effizient im Young Generation Heap behandeln und Escape Analysis manche Allokationen sogar eliminieren kann.

Wichtig ist: Immutability ist kein Selbstzweck. Große Objektgraphen, häufige Massendatenverarbeitung oder Performance-Hotspots müssen anders betrachtet werden.

Senior-Antwort:

„Ich verwende Immutability bevorzugt für Value Objects, Commands und Events. Dadurch werden Nebenläufigkeit, Testbarkeit und API-Verträge deutlich robuster. Ich achte aber darauf, keine riesigen Objektgraphen unnötig zu kopieren und bei Frameworks wie JPA die Grenzen von Immutability zu kennen.“

Häufige Fehler:
  • final Felder mit Immutability gleichsetzen.
  • Mutable Listen direkt zurückgeben.
  • Date oder Arrays ohne Kopie speichern.
  • Immutability in JPA-Entities erzwingen, obwohl das Persistenzmodell Mutability erwartet.
Best Practices:
  • Value Objects immutable modellieren.
  • Konstruktorvalidierung konsequent durchführen.
  • Keine internen mutable Referenzen veröffentlichen.
  • Records für einfache Datenaggregate verwenden.
  • Für große Aggregates bewusst zwischen Kopie und Mutation abwägen.
Stolperfalle:

Ein Objekt mit final Feldern ist nicht automatisch immutable, wenn ein Feld auf eine mutable Liste oder ein Array zeigt.

Merksatz:

Immutable Objekte machen Zustand explizit und Seiteneffekte selten.

B1-K01-Q002 – Records als DatenmodellEin Record ist ein klares Statement: Dieses Objekt ist ein Datenwert.
Frage:

„Wann würden Sie in Java 21 einen Record verwenden und wann bewusst nicht?“

Kurz erklärt:

Records sind spezielle Klassen für transparente Datenaggregate. Sie erzeugen automatisch Konstruktor, Accessors, equals , hashCode und toString . Oracle beschreibt Records als spezielle Klassen, die einfache Datenaggregate mit weniger Boilerplate modellieren.

Musterantwort:

Ich verwende Records für fachliche Datenaggregate, DTOs, Commands, Query-Ergebnisse und immutable API-Modelle. Sie sind ideal, wenn die Identität eines Objekts vollständig aus seinen Komponenten besteht.

Ich würde Records nicht für JPA-Entities mit Lifecycle, lazy loading und mutierbarem Persistenzzustand verwenden. Auch bei komplexer Vererbung oder stark verhaltensorientierten Modellen ist eine normale Klasse besser.

Ein Record ist nicht nur Boilerplate-Reduktion. Er kommuniziert eine Designentscheidung: Dieses Objekt ist ein transparenter, wertbasierter Datenträger.

Senior-Antwort:

„Records nutze ich, wenn ein Typ hauptsächlich seine Daten repräsentiert und Wertgleichheit sinnvoll ist. Für Entities mit Identität, Lifecycle und Persistenzverhalten verwende ich weiterhin Klassen.“

Häufige Fehler:
  • Records als Ersatz für jede Klasse verwenden.
  • Mutable Komponenten wie List ohne Kopie speichern.
  • Geschäftslogik in Records überladen.
  • Records mit JavaBeans gleichsetzen.
Best Practices:
  • Records für DTOs, Commands, Events und Value Objects nutzen.
  • Im kompakten Konstruktor validieren.
  • Mutable Komponenten defensiv kopieren.
  • Keine Framework-Magie voraussetzen, sondern Mapping testen.
Stolperfalle:

Ein Record verhindert nicht automatisch, dass seine Komponenten mutable Objekte enthalten.

Merksatz:

Ein Record ist ein klares Statement: Dieses Objekt ist ein Datenwert.

B1-K01-Q003 – equals und hashCodeGleichheit ist kein Implementierungsdetail, sondern Teil des Domänenmodells.
Frage:

„Was ist der Vertrag zwischen equals und hashCode , und welche Fehler sehen Sie in echten Projekten häufig?“

Kurz erklärt:

equals definiert logische Gleichheit. hashCode muss dazu konsistent sein, damit Objekte korrekt in hashbasierten Strukturen funktionieren.

Musterantwort:

Wenn zwei Objekte laut equals gleich sind, müssen sie denselben hashCode liefern. Umgekehrt müssen gleiche Hashcodes nicht bedeuten, dass die Objekte gleich sind.

Der Vertrag ist kritisch für HashMap, HashSet, Caches, Identity-Modelle und ORM. In Enterprise-Systemen ist der häufigste Fehler, mutable Felder in equals oder hashCode einzubeziehen. Wenn sich der Hashcode ändert, während das Objekt in einer HashMap liegt, ist es praktisch nicht mehr zuverlässig auffindbar.

Bei Entities unterscheide ich außerdem zwischen technischer Identität, fachlicher Identität und Datenwerten. Value Objects dürfen vollständig über ihre Werte vergleichen. Entities sollten sehr bewusst über stabile Identität vergleichen.

Senior-Antwort:

„Ich betrachte equals und hashCode als API-Vertrag. Besonders bei HashMaps, Caches und JPA-Entities kann eine falsche Implementierung schwer reproduzierbare Fehler erzeugen.“

Häufige Fehler:
  • equals überschreiben, aber hashCode vergessen.
  • Mutable Felder als HashMap-Key nutzen.
  • Lombok blind auf JPA-Entities anwenden.
  • Floating-Point-Werte unreflektiert vergleichen.
Best Practices:
  • Value Objects immutable machen.
  • Für Records automatische Implementierung nutzen.
  • Entity-Gleichheit bewusst modellieren.
  • Keine großen Objektgraphen in equals .
  • Tests für Gleichheitsvertrag schreiben.
Stolperfalle:

Ein Objekt als HashMap-Key zu verwenden und danach ein Feld zu ändern, das in hashCode eingeht.

Merksatz:

Gleichheit ist kein Implementierungsdetail, sondern Teil des Domänenmodells.

B1-K01-Q004 – Sealed Classes und kontrollierte VererbungSealed Classes machen fachliche Varianten explizit und begrenzen Erweiterbarkeit bewusst.
Frage:

„Welchen Vorteil bieten Sealed Classes gegenüber klassischer Vererbung oder Enums?“

Kurz erklärt:

Sealed Classes begrenzen, welche Typen eine Klasse oder ein Interface erweitern dürfen. Oracle beschreibt sie als Mechanismus, mit dem eine Klasse festlegen kann, welche Klassen sie erweitern dürfen.

Musterantwort:

Sealed Classes modellieren geschlossene Typhierarchien. Das ist ideal, wenn ein fachliches Ergebnis nur aus bekannten Varianten bestehen darf, jede Variante aber eigene Daten tragen kann.

Ein Enum ist gut für einfache Konstanten. Sobald Varianten unterschiedliche Daten oder Verhalten benötigen, sind sealed Interfaces mit Records oft ausdrucksstärker. Gegenüber offener Vererbung verhindern Sealed Classes unerwartete Subtypen und ermöglichen exhaustive Pattern Matching.

Senior-Antwort:

„Sealed Classes nutze ich, wenn die Domäne eine geschlossene Menge von Varianten hat. Das macht ungültige Zustände schwerer ausdrückbar und verbessert die Compiler-Unterstützung.“

Häufige Fehler:
  • Sealed Classes als Ersatz für jede Vererbung verwenden.
  • non-sealed ohne architektonischen Grund erlauben.
  • Zu große Hierarchien bauen.
  • Framework-Serialisierung nicht testen.
Best Practices:
  • Für geschlossene fachliche Varianten verwenden.
  • Mit Records kombinieren.
  • Varianten klein und aussagekräftig halten.
  • Exhaustive switch -Ausdrücke bevorzugen.
  • Keine unnötige Vererbung modellieren.
Stolperfalle:

Ein Enum kann keine typ-spezifischen Daten pro Variante so sauber modellieren wie ein sealed Interface mit Records.

Merksatz:

Sealed Classes machen fachliche Varianten explizit und begrenzen Erweiterbarkeit bewusst.

B1-K01-Q005 – Pattern Matching for switchPattern Matching macht Typlogik sichtbar und vom Compiler überprüfbar.
Frage:

„Wie verändert Pattern Matching for switch den Stil moderner Java-Anwendungen?“

Kurz erklärt:

Pattern Matching for switch erlaubt, Typprüfung und Binding direkt im switch auszudrücken. Ein switch kann dadurch prüfen, ob ein Selector zu einem Pattern passt, statt nur Konstanten zu vergleichen.

Musterantwort:

Es reduziert Boilerplate aus instanceof , Casts und verschachtelten if -Blöcken. Besonders mit sealed Interfaces entsteht ein sehr robustes Modell: Der Compiler kann prüfen, ob alle fachlichen Varianten behandelt wurden.

Das ist nicht nur syntaktischer Zucker. Es verbessert Wartbarkeit, weil neue Varianten an zentralen Stellen Compile-Fehler erzeugen können, statt unentdeckt in Default-Zweigen zu verschwinden.

Senior-Antwort:

„Pattern Matching for switch macht polymorphe Fallunterscheidungen explizit, typsicher und wartbar. In Kombination mit sealed Interfaces ersetzt es viele fehleranfällige instanceof -Ketten.“

Häufige Fehler:
  • Weiterhin große if instanceof -Ketten schreiben.
  • Immer default verwenden und dadurch Compiler-Prüfung verlieren.
  • Pattern Matching für simple Konstanten übertreiben.
  • Fachliche Varianten nicht sealed modellieren.
Best Practices:
  • Mit sealed Hierarchien kombinieren.
  • default vermeiden, wenn Exhaustiveness gewünscht ist.
  • Kleine, fachliche Varianten modellieren.
  • Switch Expressions bevorzugen, wenn ein Wert berechnet wird.
Stolperfalle:

Ein pauschaler default -Zweig kann verhindern, dass der Compiler bei neuen Varianten warnt.

Merksatz:

Pattern Matching macht Typlogik sichtbar und vom Compiler überprüfbar.

B1-K01-Q006 – Optional richtig verwendenOptional ist ein Rückgabetyp, kein Feldtyp und kein Fehlerkonzept.
Frage:

„Wann ist Optional sinnvoll, und wo würden Sie es in Java-Code vermeiden?“

Kurz erklärt:

Optional modelliert einen möglicherweise fehlenden Rückgabewert. Es ist kein allgemeiner Ersatz für null .

Musterantwort:

Ich verwende Optional vor allem als Rückgabetyp, wenn ein Ergebnis legitim fehlen kann. Dadurch zwingt die API den Aufrufer, den Abwesenheitsfall sichtbar zu behandeln.

Ich vermeide Optional als Feld, in DTOs, in JPA-Entities und meist auch als Methodenparameter. Dort erzeugt es oft mehr Komplexität als Nutzen. Für Pflichtwerte nutze ich Validierung. Für fachliche Fehler nutze ich spezifische Exceptions oder Ergebnisobjekte.

Senior-Antwort:

„Optional ist ein API-Signal: Dieser Wert kann fehlen. Ich verwende es bewusst als Rückgabetyp, aber nicht als generellen Null-Ersatz.“

Häufige Fehler:
  • Optional.get() ohne Prüfung.
  • Optional in Entity-Feldern.
  • Optional als Parameter.
  • null in ein Optional hineinreichen.
Best Practices:
  • Als Rückgabetyp verwenden.
  • orElseThrow für Pflichtfälle nutzen.
  • orElseGet bei teuren Default-Werten verwenden.
  • Nicht zur Fehlerbehandlung missbrauchen.
Stolperfalle:

orElse(expensiveCall()) führt den Default-Ausdruck sofort aus. orElseGet ist lazy.

Merksatz:

Optional ist ein Rückgabetyp, kein Feldtyp und kein Fehlerkonzept.

B1-K01-Q007 – String, StringBuilder und Text BlocksString-Code sollte zuerst korrekt und lesbar sein, dann gemessen optimiert werden.
Frage:

„Wann verwenden Sie String-Konkatenation, wann StringBuilder und wann Text Blocks?“

Kurz erklärt:

String ist immutable. StringBuilder ist für wiederholte lokale String-Konstruktion geeignet. Text Blocks verbessern mehrzeilige Literale.

Musterantwort:

Für einfache Ausdrücke ist String-Konkatenation absolut in Ordnung. Der Compiler und die JVM optimieren viele Fälle gut. Für Schleifen oder dynamische große Texte verwende ich StringBuilder , idealerweise mit geschätzter Kapazität.

Text Blocks nutze ich für mehrzeilige SQL-, JSON- oder Template-Fragmente. In Enterprise-Code sollte man aber nicht anfangen, komplexes JSON per String zu bauen. Dafür sind Serializer besser.

Senior-Antwort:

„Ich optimiere Strings dort, wo viele dynamische Operationen stattfinden. Für normale Business-Strings priorisiere ich Lesbarkeit. Text Blocks nutze ich für mehrzeilige Literale, aber nicht als Ersatz für Serializer.“

Häufige Fehler:
  • JSON manuell per String zusammensetzen.
  • StringBuffer ohne Synchronisationsbedarf verwenden.
  • intern() als Standardoptimierung verwenden.
  • In Schleifen unbewusst sehr viele Strings erzeugen.
Best Practices:
  • Einfache Konkatenation lesbar lassen.
  • In Schleifen StringBuilder nutzen.
  • Text Blocks für Lesbarkeit einsetzen.
  • Strukturierte Daten mit Libraries serialisieren.
Stolperfalle:

Text Blocks lösen kein Escaping- oder Injection-Problem.

Merksatz:

String-Code sollte zuerst korrekt und lesbar sein, dann gemessen optimiert werden.

B1-K01-Q008 – BigDecimal und GeldbeträgeGeld ist kein double , sondern ein fachlicher Wert mit Währung und Rundungsregel.
Frage:

„Warum verwenden wir für Geldbeträge nicht double , und worauf achten Sie bei BigDecimal ?“

Kurz erklärt:

Für Geldbeträge ist BigDecimal meist die richtige Wahl, weil binäre Floating-Point-Typen Dezimalwerte nicht exakt darstellen.

Musterantwort:

double ist ein binärer Gleitkommatyp und kann viele Dezimalwerte nicht exakt darstellen. Für Geldbeträge ist das fachlich gefährlich. BigDecimal erlaubt dezimale Genauigkeit, aber man muss Scale, Rundung und Konstruktion korrekt behandeln.

Ich erstelle BigDecimal nicht mit new BigDecimal(0.1) , sondern über Strings oder BigDecimal.valueOf . Außerdem definiere ich Rundungsregeln explizit. In Bankenanwendungen ist Rundung Fachlogik, keine technische Nebensache.

Senior-Antwort:

„Für Geld verwende ich keine Floating-Point-Typen. Ich modelliere Betrag und Währung zusammen und mache Rundung explizit, weil Rundung Teil der Fachdomäne ist.“

Häufige Fehler:
  • new BigDecimal(0.1) verwenden.
  • Währung getrennt vom Betrag behandeln.
  • Keine explizite Rundung angeben.
  • equals für numerischen Vergleich verwenden.
Best Practices:
  • Geld als Money Value Object modellieren.
  • Währung immer mitführen.
  • Rundungsregeln fachlich definieren.
  • Precision und Scale in Datenbanken explizit setzen.
  • Für Hotpaths Minor Units prüfen.
Stolperfalle:

BigDecimal("1.0").equals(BigDecimal("1.00")) ist false, obwohl compareTo 0 liefert.

Merksatz:

Geld ist kein double , sondern ein fachlicher Wert mit Währung und Rundungsregel.

B1-K01-Q009 – Enums mit VerhaltenEin Enum ist gut für stabile Varianten, schlecht für dynamische Regeln.
Frage:

„Wann ist ein Enum mehr als nur eine Liste von Konstanten?“

Kurz erklärt:

Enums sind typsichere Konstanten. Sie können Felder, Konstruktoren und Verhalten besitzen.

Musterantwort:

Ein Enum eignet sich, wenn eine fachliche Menge geschlossen und stabil ist. Wenn jede Variante eigenes Verhalten hat, kann ein Enum eine klare Alternative zu switch sein.

Ich nutze das aber nur bei kleinen, stabilen Regelmengen. Wenn Regeln konfigurierbar, mandantenspezifisch oder häufig änderbar sind, gehört die Logik eher in Strategien, Datenbankkonfiguration oder Rule Engines.

Senior-Antwort:

„Enums sind stark, wenn die Menge geschlossen ist. Sobald die Fachlichkeit dynamisch wird, ziehe ich die Regel aus dem Enum heraus.“

Häufige Fehler:
  • Dynamische Fachregeln hart in Enums kodieren.
  • Enum-Namen direkt in Datenbankverträge schreiben.
  • ordinal() persistieren.
  • Riesige Enums als Ersatz für Domänenmodell verwenden.
Best Practices:
  • Für stabile, kleine Mengen verwenden.
  • Nie ordinal() persistieren.
  • Verhalten nur bei wirklich stabiler Logik einbauen.
  • Bei variablen Regeln Strategy oder Konfiguration nutzen.
Stolperfalle:

ordinal() ist kein stabiler Persistenzwert.

Merksatz:

Ein Enum ist gut für stabile Varianten, schlecht für dynamische Regeln.

B1-K01-Q010 – Interfaces, Default Methods und API-EvolutionEin Interface beschreibt einen Vertrag; Default Methods sollten diesen Vertrag evolvierbar m… 24h
Frage:

„Warum wurden Default Methods eingeführt, und welche Risiken haben sie?“

Kurz erklärt:

Interfaces können abstrakte, default, static und private Methoden enthalten. Default Methods unterstützen API-Evolution.

Musterantwort:

Default Methods ermöglichen es, Interfaces zu erweitern, ohne alle Implementierungen sofort brechen zu lassen. Das war für die Weiterentwicklung großer APIs wichtig.

Das Risiko ist, dass Interfaces zu Implementierungscontainern werden. Wenn zu viel Logik im Interface landet, verschwimmt die Grenze zwischen Vertrag und Verhalten. Außerdem können Konflikte entstehen, wenn mehrere Interfaces dieselbe Default Method definieren.

Senior-Antwort:

„Default Methods sind ein Werkzeug für kontrollierte API-Evolution, aber kein Freifahrtschein, Interfaces mit Logik zu überladen.“

Häufige Fehler:
  • Business-Logik in Interfaces verstecken.
  • Default Methods für alles verwenden.
  • Mehrfachvererbungskonflikte ignorieren.
  • Interfaces zu groß machen.
Best Practices:
  • Interfaces klein und fachlich klar halten.
  • Default Methods sparsam für API-Evolution nutzen.
  • Static Factory Methods nur bei hoher Kohäsion verwenden.
  • Komplexes Verhalten in Klassen oder Services auslagern.
Stolperfalle:

Mehrere Interfaces mit gleicher Default Method führen zu einem Konflikt, den die implementierende Klasse explizit lösen muss.

Merksatz:

Ein Interface beschreibt einen Vertrag; Default Methods sollten diesen Vertrag evolvierbar machen, nicht verstecken.

B1-K01-Q011 – Lokale Typinferenz mit varvar spart Schreibarbeit, aber darf keine Bedeutung verstecken.
Frage:

„Ist var in Java dynamische Typisierung? Wann ist es sinnvoll?“

Kurz erklärt:

var erlaubt lokale Typinferenz. Der Typ bleibt statisch und wird zur Compile-Zeit bestimmt.

Musterantwort:

Nein, var ist keine dynamische Typisierung. Der Compiler leitet den konkreten statischen Typ ab. Zur Laufzeit gibt es keinen Unterschied.

Ich nutze var , wenn der Typ aus der rechten Seite offensichtlich ist oder der konkrete Typ unnötige Leselast erzeugt. Ich vermeide var , wenn der Typ fachliche Bedeutung trägt oder die Lesbarkeit verschlechtert.

Senior-Antwort:

„ var ist statische Typinferenz, keine dynamische Typisierung. Ich setze es dort ein, wo es Lesbarkeit verbessert, nicht um Typen zu verstecken.“

Häufige Fehler:
  • var mit JavaScript- var vergleichen.
  • var überall einsetzen.
  • Fachliche Typinformation verstecken.
  • Unklare Factory-Methoden mit var kombinieren.
Best Practices:
  • Verwenden, wenn der Typ offensichtlich ist.
  • Explizit bleiben, wenn der Typ fachlich wichtig ist.
  • Keine Style-Kriege führen; Team-Konventionen definieren.
  • Lesbarkeit über Kürze stellen.
Stolperfalle:

var value = null; funktioniert nicht, weil der Compiler keinen Typ ableiten kann.

Merksatz:

var spart Schreibarbeit, aber darf keine Bedeutung verstecken.

B1-K01-Q012 – Exception DesignExceptions sind Teil des Architekturvertrags, nicht nur Fehlermechanik.
Frage:

„Wann verwenden Sie checked Exceptions, wann unchecked Exceptions?“

Kurz erklärt:

Exceptions modellieren technische und fachliche Ausnahmesituationen. Gutes Exception-Design trennt erwartbare Fachfehler von unerwarteten technischen Fehlern.

Musterantwort:

Checked Exceptions eignen sich, wenn der Aufrufer sinnvoll und zwingend reagieren soll, etwa bei bestimmten IO-Szenarien. In Business-Code verwende ich meist unchecked fachliche Exceptions oder explizite Ergebnisobjekte, weil checked Exceptions schnell API-Signaturen verschmutzen.

Wichtig ist nicht checked versus unchecked als Dogma, sondern ob die API eine sinnvolle Behandlung erzwingt. Technische Details sollten nicht unkontrolliert durch alle Schichten leaken.

Senior-Antwort:

„Ich verwende Exceptions für außergewöhnliche Situationen, nicht als normalen Kontrollfluss. Fachliche Fehler werden klar typisiert und technische Details werden an Schichtgrenzen übersetzt.“

Häufige Fehler:
  • catch (Exception) ohne sinnvolle Behandlung.
  • Exceptions verschlucken.
  • Technische Exceptions bis zur API leaken.
  • Exceptions für normale Suchergebnisse verwenden.
Best Practices:
  • Fachliche und technische Fehler trennen.
  • Exceptions mit Kontext versehen.
  • Cause erhalten.
  • Keine sensiblen Daten in Messages loggen.
  • Für erwartbare Validierungsfehler strukturierte Fehlerantworten verwenden.
Stolperfalle:

Eine Exception ohne Cause neu zu werfen zerstört wichtige Diagnoseinformationen.

Merksatz:

Exceptions sind Teil des Architekturvertrags, nicht nur Fehlermechanik.

B1-K01-Q013 – try-with-resources und RessourcenmanagementWer Ressourcen öffnet, muss ihre Lebensdauer klar begrenzen.
Frage:

„Warum ist try-with-resources besser als ein manuelles finally ?“

Kurz erklärt:

try-with-resources schließt Ressourcen automatisch, wenn sie AutoCloseable implementieren.

Musterantwort:

Es ist sicherer, kürzer und behandelt auch Fehler beim Schließen korrekt. Bei manuellem finally werden Fehler oft verschluckt oder überschreiben die ursprüngliche Exception. try-with-resources erhält die primäre Exception und hängt Close-Fehler als suppressed Exceptions an.

Senior-Antwort:

„try-with-resources ist Standard für alles, was geschlossen werden muss. Es macht Ressourcenlebensdauer explizit und bewahrt Exception-Informationen korrekt.“

Häufige Fehler:
  • Ressourcen außerhalb des try öffnen.
  • Streams nicht schließen.
  • Exceptions im finally überschreiben.
  • close() manuell zusätzlich aufrufen.
Best Practices:
  • Ressourcen immer im try-Kopf deklarieren.
  • Kleine Ressourcen-Scope wählen.
  • Suppressed Exceptions bei Diagnose beachten.
  • Keine komplexe Businesslogik in close() verstecken.
Stolperfalle:

Eine Exception aus close() kann als suppressed Exception vorliegen und wird bei oberflächlichem Logging übersehen.

Merksatz:

Wer Ressourcen öffnet, muss ihre Lebensdauer klar begrenzen.

B1-K01-Q014 – Null-Sicherheit und defensive API-DesignsNull-Sicherheit beginnt an der API-Grenze, nicht beim Stacktrace.
Frage:

„Wie gehen Sie in Java-Projekten systematisch mit null um?“

Kurz erklärt:

Null-Sicherheit ist in Java kein einzelnes Feature, sondern eine Kombination aus API-Design, Validierung, Konventionen und Tooling.

Musterantwort:

Ich definiere klare API-Verträge: Welche Parameter dürfen nie null sein, welche Rückgaben können fehlen und wie wird Abwesenheit modelliert? Für Pflichtparameter nutze ich Objects.requireNonNull , Bean Validation oder Konstruktorvalidierung. Für optionale Rückgaben nutze ich gezielt Optional .

Wichtig ist Konsistenz. Ein Team braucht Regeln, sonst entsteht ein Flickenteppich aus null , Optional , leeren Strings und Exceptions.

Senior-Antwort:

„Ich behandle Null-Sicherheit als API-Design-Thema. Pflichtwerte validiere ich früh, optionale Ergebnisse modellieren ich explizit, und interne Domänenobjekte sollen möglichst immer gültig sein.“

Häufige Fehler:
  • null als mehrere Bedeutungen verwenden.
  • Leere Strings und null vermischen.
  • Optional inkonsistent einsetzen.
  • Validierung erst tief in der Businesslogik durchführen.
Best Practices:
  • Nullability-Regeln im Team definieren.
  • Eingaben an Systemgrenzen validieren.
  • Domain-Objekte möglichst immer gültig erzeugen.
  • Fehlende Rückgaben explizit modellieren.
  • Keine „halb-validen“ Objekte zulassen.
Stolperfalle:

Ein null kann „nicht vorhanden“, „noch nicht geladen“, „ungültig“ oder „unbekannt“ bedeuten. Diese Mehrdeutigkeit ist gefährlich.

Merksatz:

Null-Sicherheit beginnt an der API-Grenze, nicht beim Stacktrace.

B1-K01-Q015 – java.time APIEin Zeitpunkt braucht eine globale Referenz; ein Kalenderdatum braucht Kontext.
Frage:

„Wann verwenden Sie Instant , LocalDate , LocalDateTime und ZonedDateTime ?“

Kurz erklärt:

java.time trennt maschinenbezogene Zeitpunkte ( Instant ) von lokalen Kalenderdaten ( LocalDate ) und Zeitzonen ( ZoneId ).

Musterantwort:

Instant verwende ich für technische Zeitpunkte, Persistenz, Events und Audit. LocalDate ist für reine Kalenderdaten wie Geburtsdatum oder Fälligkeitsdatum sinnvoll. LocalDateTime hat keine Zeitzone und ist deshalb gefährlich, wenn ein tatsächlicher Zeitpunkt gemeint ist. ZonedDateTime nutze ich, wenn Zeitzonenregeln fachlich relevant sind, etwa bei Terminplanung.

Für testbaren Code injiziere ich Clock , statt direkt Instant.now() aufzurufen.

Senior-Antwort:

„Ich unterscheide strikt zwischen Zeitpunkt, lokalem Datum und zeitzonenabhängiger Darstellung. Zeitfehler entstehen fast immer durch implizite Annahmen.“

Häufige Fehler:
  • LocalDateTime als universellen Zeitpunkt speichern.
  • Server-Zeitzone implizit verwenden.
  • new Date() in modernem Code nutzen.
  • Zeitabhängige Tests ohne Clock .
Best Practices:
  • Technische Zeitpunkte als Instant .
  • Fachliche Kalenderdaten als LocalDate .
  • Zeitzonen explizit behandeln.
  • Clock injizieren.
  • ISO-Formate in APIs bevorzugen.
Stolperfalle:

LocalDateTime sieht wie ein Zeitpunkt aus, ist aber keiner, weil die Zeitzone fehlt.

Merksatz:

Ein Zeitpunkt braucht eine globale Referenz; ein Kalenderdatum braucht Kontext.

B1-K01-Q016 – Objektkopien und defensive copyingImmutability endet dort, wo mutable Referenzen entkommen.
Frage:

„Warum reicht es bei immutable Klassen nicht, Felder einfach final zu machen?“

Kurz erklärt:

Defensive Copying verhindert, dass interne Objektzustände über externe Referenzen verändert werden.

Musterantwort:

final verhindert nur, dass die Referenz neu gesetzt wird. Es verhindert nicht, dass das referenzierte Objekt verändert wird. Wenn ich eine mutable Liste speichere und direkt zurückgebe, kann der Aufrufer den internen Zustand verändern.

Deshalb kopiere ich mutable Eingaben im Konstruktor und gebe keine veränderbaren internen Strukturen zurück. In modernem Java ist List.copyOf dafür oft passend.

Senior-Antwort:

„Final schützt die Referenz, nicht das Objekt. Wenn ich Immutability verspreche, muss ich mutable Komponenten kopieren oder vermeiden.“

Häufige Fehler:
  • Mutable Listen direkt speichern.
  • Arrays direkt zurückgeben.
  • Unmodifiable View mit echter Kopie verwechseln.
  • Defensive Copying bei Records vergessen.
Best Practices:
  • Mutable Eingaben kopieren.
  • Immutable Collections zurückgeben.
  • Arrays defensiv klonen.
  • Ownership klar dokumentieren.
  • Bei großen Datenmengen Performance messen.
Stolperfalle:

Collections.unmodifiableList(original) schützt nicht vor Änderungen an original .

Merksatz:

Immutability endet dort, wo mutable Referenzen entkommen.

B1-K01-Q017 – Annotations und ReflectionAnnotations deklarieren Absicht; Reflection macht sie zur Laufzeit nutzbar.
Frage:

„Welche Rolle spielen Annotations und Reflection in modernen Java-Frameworks, und welche Nachteile haben sie?“

Kurz erklärt:

Annotations liefern Metadaten. Reflection erlaubt Laufzeitzugriff auf Klassen, Methoden, Felder und Annotationen.

Musterantwort:

Frameworks wie Spring, Jakarta EE, Hibernate und Jackson nutzen Annotations, um Konfiguration deklarativ am Code auszudrücken. Reflection ermöglicht dann zur Laufzeit Instanziierung, Mapping, Dependency Injection oder Serialisierung.

Der Nachteil ist, dass Fehler oft später sichtbar werden: zur Laufzeit statt zur Compile-Zeit. Außerdem kann Reflection Performance kosten, Kapselung umgehen und mit JPMS oder Native Images zusätzliche Konfiguration benötigen.

Senior-Antwort:

„Annotations sind deklarative Metadaten; Reflection ist ein mächtiger, aber teurer und weniger typsicherer Mechanismus. Frameworks nutzen ihn kontrolliert, cachen stark und verstecken Komplexität.“

Häufige Fehler:
  • Runtime-Reflection erwarten, obwohl Annotation nur SOURCE ist.
  • Reflection in Hotloops verwenden.
  • Exceptions aus Reflection schlecht behandeln.
  • Kapselungsgrenzen ignorieren.
Best Practices:
  • Reflection-Ergebnisse cachen.
  • Annotationen klar und sparsam designen.
  • Compile-Time-Alternativen prüfen.
  • Framework-Konventionen verstehen.
  • In Performance-kritischem Code messen.
Stolperfalle:

Eine Annotation ohne RetentionPolicy.RUNTIME ist per Reflection nicht sichtbar.

Merksatz:

Annotations deklarieren Absicht; Reflection macht sie zur Laufzeit nutzbar.

B1-K01-Q018 – Generische API-Grenzen im Java-Core-KontextGenerics sollen APIs sicherer machen, nicht unlesbarer.
Frage:

„Was bedeutet PECS, und warum ist es für API-Design wichtig?“

Kurz erklärt:

Generics ermöglichen typsichere APIs. Wildcards drücken Varianz aus: Producer Extends, Consumer Super.

Musterantwort:

PECS steht für Producer Extends, Consumer Super. Wenn eine Struktur Werte produziert, nutze ich ? extends T . Wenn sie Werte konsumiert, nutze ich ? super T .

Das ist wichtig, weil es APIs flexibler macht, ohne Typsicherheit aufzugeben. Senior-relevant ist nicht nur die Syntax, sondern die Frage, ob eine API unnötig restriktiv ist.

Senior-Antwort:

„Generics sind API-Design. Ich nutze Wildcards dort, wo sie Flexibilität schaffen, aber vermeide Signaturen, die niemand mehr versteht.“

Häufige Fehler:
  • Raw Types verwenden.
  • Wildcards überall einsetzen.
  • extends und super verwechseln.
  • Generics zur Laufzeit erwarten.
Best Practices:
  • PECS anwenden.
  • Öffentliche APIs möglichst klar halten.
  • Keine unnötig komplexen Generic-Signaturen.
  • Raw Types vermeiden.
  • Bei Runtime-Typbedarf Class<T> oder Type Tokens verwenden.
Stolperfalle:

List<? extends Number> erlaubt Lesen als Number , aber kein Hinzufügen beliebiger Number -Instanzen.

Merksatz:

Generics sollen APIs sicherer machen, nicht unlesbarer.

B1-K01-Q019 – Module System und starke KapselungEin Modul ist ein Architekturvertrag, nicht nur eine Build-Datei.
Frage:

„Welchen Nutzen hat das Java Module System in Enterprise-Anwendungen, und warum wird es trotzdem nicht überall verwendet?“

Kurz erklärt:

Das Java Platform Module System erlaubt explizite Abhängigkeiten und starke Kapselung auf Modulebene.

Musterantwort:

JPMS macht Abhängigkeiten explizit und kapselt interne Packages stärker als normale JAR-Konventionen. Das ist für Plattformen, Libraries und große modulare Systeme wertvoll.

In vielen Spring-Boot-Enterprise-Anwendungen wird es aber nicht konsequent genutzt, weil Classpath-Ökosysteme, Reflection, Legacy-Abhängigkeiten und Build-Komplexität den Nutzen übersteigen können. Trotzdem ist das Prinzip wichtig: öffentliche API und interne Implementierung sollten klar getrennt sein.

Senior-Antwort:

„JPMS ist stark für explizite Abhängigkeiten und Kapselung. In klassischen Spring-Boot-Apps muss man Aufwand und Nutzen abwägen, aber die Architekturidee bleibt wertvoll.“

Häufige Fehler:
  • JPMS ohne klare Modulgrenzen einführen.
  • Alles exportieren.
  • Reflection-Probleme unterschätzen.
  • Build- und Testsetup nicht anpassen.
Best Practices:
  • Erst fachliche Modulgrenzen klären.
  • Nur stabile APIs exportieren.
  • Interna konsequent verstecken.
  • JPMS besonders für Libraries und Plattformen prüfen.
  • Framework-Kompatibilität testen.
Stolperfalle:

exports macht ein Package für Compile-Zugriff sichtbar; opens erlaubt reflektiven Zugriff.

Merksatz:

Ein Modul ist ein Architekturvertrag, nicht nur eine Build-Datei.

B1-K01-Q020 – API-Design mit Records, Sealed Types und ExceptionsRobuste APIs modellieren Fachlichkeit als Typen, nicht als Kommentare. 24h
Frage:

„Wie würden Sie eine robuste Java-21-API für fachliche Entscheidungen modellieren?“

Kurz erklärt:

Modernes Java-Core-Design kombiniert Records für Daten, Sealed Types für Varianten und Exceptions oder Result-Typen für Fehlersemantik.

Musterantwort:

Ich würde Eingaben als validierte Records modellieren, weil sie immutable und klar sind. Das Ergebnis würde ich als sealed Interface mit fachlichen Varianten ausdrücken. So kann der Compiler sicherstellen, dass alle Fälle behandelt werden.

Exceptions verwende ich für ungültige Aufrufe oder technische Fehler, nicht für normale fachliche Ergebnisse wie „abgelehnt“ oder „manuelle Prüfung“. Diese sind Teil der Domäne und sollten als Rückgabewerte modelliert werden.

Senior-Antwort:

„Ich modelliere normale fachliche Ergebnisse als Typen, nicht als Exceptions oder Strings. Java 21 gibt uns mit Records, sealed Interfaces und Pattern Matching sehr gute Werkzeuge dafür.“

Häufige Fehler:
  • Fachliche Zustände als Strings modellieren.
  • boolean für komplexe Entscheidungen verwenden.
  • Exceptions für normale Business-Ergebnisse werfen.
  • Default-Zweige verwenden und neue Varianten übersehen.
Best Practices:
  • Commands als Records.
  • Ergebnisse als sealed Typen.
  • Fachliche Varianten explizit machen.
  • Exceptions für ungültige Nutzung oder technische Fehler.
  • Pattern Matching für zentrale Fallunterscheidung.
Stolperfalle:

Ein boolean ist oft zu arm, um eine echte fachliche Entscheidung zu repräsentieren.

Merksatz:

Robuste APIs modellieren Fachlichkeit als Typen, nicht als Kommentare.

B1-K02 – Kapitel 2: Collections Framework 10 Karten

B1-K02-Q001 – Auswahl der richtigen CollectionDie Collection verrät die Absicht des Codes. 24h
Frage:

„Wie entscheiden Sie in Java, ob Sie List , Set , Map , Queue oder Deque verwenden?“

Kurz erklärt:

Die Wahl der Collection ist eine Designentscheidung. Sie bestimmt Semantik, Performance, Speicherverbrauch und API-Verständlichkeit.

Musterantwort:

Ich starte nicht mit der Implementierung, sondern mit der Semantik. Wenn Reihenfolge und Duplikate wichtig sind, ist List passend. Wenn Eindeutigkeit im Vordergrund steht, verwende ich Set . Wenn ich über Schlüssel zugreifen muss, ist Map richtig. Für Verarbeitungspipelines, Warteschlangen oder Scheduling nutze ich Queue oder Deque .

Erst danach entscheide ich über die konkrete Implementierung: ArrayList , HashSet , LinkedHashMap , TreeMap , ArrayDeque und so weiter. Auf Senior-Level ist wichtig, dass man nicht aus Gewohnheit überall ArrayList verwendet, sondern die fachliche Absicht ausdrückt.

Senior-Antwort:

„Ich wähle Collections zuerst nach fachlicher Semantik und erst danach nach Implementierung. Eine List sagt etwas anderes aus als ein Set , und eine Map ist kein Ersatz für ein sauberes Domänenmodell.“

Häufige Fehler:
  • Immer ArrayList verwenden.
  • Set verwenden, obwohl Reihenfolge fachlich relevant ist.
  • HashMap verwenden, obwohl deterministische Iteration benötigt wird.
  • Konkrete Implementierungen in öffentlichen APIs erzwingen.
Best Practices:
  • Gegen Interfaces programmieren.
  • Implementierung nach Zugriffsmuster wählen.
  • Fachliche Semantik sichtbar machen.
  • Rückgaben defensiv kopieren oder unveränderbar machen.
  • Performance nicht raten, sondern bei Hotspots messen.
Stolperfalle:

Eine Collection-Implementierung aus Performancegründen zu wählen, ohne vorher die fachliche Semantik zu klären.

Merksatz:

Die Collection verrät die Absicht des Codes.

B1-K02-Q002 – ArrayList, LinkedList und ArrayDequeNicht jede verkettete Liste ist schneller, nur weil sie verkettet ist.
Frage:

„Warum ist LinkedList in Java seltener sinnvoll, als viele Entwickler glauben?“

Kurz erklärt:

ArrayList , LinkedList und ArrayDeque haben unterschiedliche Speicherstrukturen und Zugriffsmuster. Die falsche Wahl kann unnötig CPU, Speicher und Lesbarkeit kosten.

Musterantwort:

Viele glauben, LinkedList sei bei Einfügen und Löschen grundsätzlich schneller. Das stimmt nur sehr eingeschränkt. Wenn ich erst eine Position suchen muss, kostet das Traversieren O(n). Zusätzlich hat jede Node Objekt-Overhead und schlechte CPU-Cache-Lokalität.

Für indexbasierten Zugriff und Iteration ist ArrayList meist besser. Für Queue- oder Stack-Verhalten ist ArrayDeque in der Praxis oft die bessere Wahl als LinkedList . LinkedList ist nur sinnvoll, wenn ich wirklich häufig über vorhandene Iteratorpositionen einfüge oder lösche und die Nachteile akzeptiere.

Senior-Antwort:

„Ich verwende LinkedList nur, wenn das Zugriffsmuster wirklich dafür spricht. In den meisten Enterprise-Fällen sind ArrayList oder ArrayDeque schneller, einfacher und speichereffizienter.“

Häufige Fehler:
  • LinkedList wegen „schnellem Einfügen“ pauschal wählen.
  • Stack statt ArrayDeque verwenden.
  • Indexbasierte Schleifen über unbekannte List -Implementierungen schreiben.
  • Speicherverbrauch ignorieren.
Best Practices:
  • Standardmäßig ArrayList für Listen.
  • ArrayDeque für Stack- und Queue-Verhalten.
  • LinkedList nur bewusst und selten.
  • Bei großen Datenmengen Zugriffsmuster messen.
  • APIs nicht unnötig auf konkrete Implementierungen festlegen.
Stolperfalle:

O(1)-Einfügen bei LinkedList gilt nicht, wenn man die Einfügeposition vorher mit O(n) suchen muss.

Merksatz:

Nicht jede verkettete Liste ist schneller, nur weil sie verkettet ist.

B1-K02-Q003 – HashMap InternaEine HashMap ist nur so zuverlässig wie ihre Keys.
Frage:

„Wie funktioniert eine HashMap intern, und worauf achten Sie bei Keys?“

Kurz erklärt:

HashMap ist eine der wichtigsten Collections in Java. Senior-Kandidaten müssen Hashing, Buckets, Load Factor, Resize und Key-Verträge verstehen.

Musterantwort:

Eine HashMap speichert Einträge in Buckets. Der Hashcode des Keys wird verwendet, um einen Bucket zu bestimmen. Bei Kollisionen liegen mehrere Einträge im selben Bucket. Deshalb müssen equals und hashCode korrekt und stabil sein.

Wichtig sind Initial Capacity und Load Factor. Wenn viele Einträge erwartet werden, setze ich eine passende Anfangskapazität, um Rehashing zu reduzieren. Keys sollten immutable sein, weil ein veränderter Hashcode dazu führen kann, dass ein Eintrag nicht mehr auffindbar ist.

Senior-Antwort:

„Eine HashMap ist schnell, solange der Key-Vertrag stimmt und die Map sinnvoll dimensioniert ist. In produktiven Systemen achte ich besonders auf immutable Keys, Hash-Verteilung und unerwartete Resizes.“

Häufige Fehler:
  • Mutable Objekte als Keys verwenden.
  • equals überschreiben, aber hashCode vergessen.
  • HashMap für deterministische Reihenfolge verwenden.
  • Große Maps ohne erwartete Kapazität erzeugen.
  • HashMap ohne Synchronisation nebenläufig ändern.
Best Practices:
  • Immutable Keys verwenden.
  • Initial Capacity bei großen Maps setzen.
  • LinkedHashMap für stabile Iteration verwenden.
  • ConcurrentHashMap bei echter Nebenläufigkeit nutzen.
  • Keine fachliche Logik auf zufällige Iterationsreihenfolge stützen.
Stolperfalle:

Ein Key wird nach dem Einfügen verändert. Danach liegt der Eintrag im alten Bucket, wird aber mit dem neuen Hashcode gesucht.

Merksatz:

Eine HashMap ist nur so zuverlässig wie ihre Keys.

B1-K02-Q004 – Unmodifiable und Immutable CollectionsUnmodifiable ist ein Zugriffsschutz, Immutability ist ein Objektvertrag.
Frage:

„Was ist der Unterschied zwischen Collections.unmodifiableList , List.copyOf und List.of ?“

Kurz erklärt:

Unmodifiable Collections verhindern Änderungen über diese Collection-Referenz. Das bedeutet aber nicht automatisch tiefe Immutability des gesamten Objektgraphen.

Musterantwort:

Collections.unmodifiableList erzeugt eine unveränderbare View auf eine bestehende Liste. Wenn die Originalliste verändert wird, sieht die View diese Änderungen. List.copyOf erzeugt eine unveränderbare Kopie. List.of erzeugt direkt eine kleine unveränderbare Liste aus angegebenen Elementen.

In Enterprise-Code verwende ich für Konstruktoren oft List.copyOf , weil ich Ownership übernehmen will. Für Konstanten ist List.of sehr gut. Views sind nur sinnvoll, wenn bewusst eine Sicht auf eine veränderbare Quelle gewünscht ist.

Senior-Antwort:

„Unmodifiable heißt: Über diese Referenz nicht änderbar. Für echte Immutability muss ich Ownership, innere Collections und mutable Elemente berücksichtigen.“

Häufige Fehler:
  • Collections.unmodifiableList als echte Kopie missverstehen.
  • Innere Collections nicht kopieren.
  • Mutable Elemente in einer unmodifiable Collection übersehen.
  • Konkrete Implementierungsklassen prüfen.
Best Practices:
  • Ownership im Konstruktor durch copyOf übernehmen.
  • Für Konstanten List.of , Set.of , Map.of verwenden.
  • Verschachtelte Collections ebenfalls kopieren.
  • Keine internen mutable Collections zurückgeben.
  • Bei großen Datenmengen Copy-Kosten bewusst bewerten.
Stolperfalle:

Eine unmodifiable View schützt nicht vor Änderungen an der ursprünglichen Collection.

Merksatz:

Unmodifiable ist ein Zugriffsschutz, Immutability ist ein Objektvertrag.

B1-K02-Q005 – Sequenced Collections in Java 21Sequenced Collections machen Ordnung zum expliziten API-Vertrag.
Frage:

„Was bringen Sequenced Collections in Java 21, und welches Problem lösen sie?“

Kurz erklärt:

Sequenced Collections vereinheitlichen den Zugriff auf geordnete Collections von vorne und hinten sowie auf umgekehrte Views.

Musterantwort:

Vor Java 21 war der Zugriff auf erstes und letztes Element uneinheitlich. Eine List hatte andere Methoden als eine Deque , und bei LinkedHashMap musste man oft umständlich über Iteration arbeiten. Sequenced Collections geben geordneten Collections ein gemeinsames API-Modell.

Das ist vor allem für APIs wertvoll, die Encounter Order fachlich ausdrücken wollen. Mit SequencedCollection , SequencedSet und SequencedMap kann ich klar sagen: Diese Datenstruktur hat eine definierte Reihenfolge, und ich kann beide Enden adressieren.

Senior-Antwort:

„Sequenced Collections schließen eine langjährige API-Lücke: geordnete Collections können jetzt einheitlich erstes, letztes und reversed View ausdrücken. Das verbessert Lesbarkeit und API-Verträge.“

Häufige Fehler:
  • Sequenced mit sortiert verwechseln.
  • reversed() als unabhängige Kopie behandeln.
  • Weiterhin umständliche Iterator-Workarounds schreiben.
  • Java-21-APIs in Libraries verwenden, die noch Java 17 unterstützen müssen.
Best Practices:
  • Sequenced Interfaces verwenden, wenn Reihenfolge Teil des Vertrags ist.
  • Nicht mit Sortierung verwechseln.
  • API-Kompatibilität zur Ziel-Java-Version prüfen.
  • Bei Views Mutability-Verhalten dokumentieren.
  • Für LRU-nahe Strukturen LinkedHashMap prüfen.
Stolperfalle:

Sequenced bedeutet definierte Begegnungsreihenfolge, nicht automatisch natürliche Sortierung.

Merksatz:

Sequenced Collections machen Ordnung zum expliziten API-Vertrag.

B1-K02-Q006 – Sortierung mit Comparable und ComparatorSortierung ist ein Vertrag, kein kosmetisches Detail.
Frage:

„Was bedeutet es, dass ein Comparator konsistent mit equals sein sollte?“

Kurz erklärt:

Sortierung ist nicht nur eine UI-Frage. Sie beeinflusst fachliche Verarbeitung, Datenstrukturen wie TreeSet und TreeMap sowie Gleichheitsannahmen.

Musterantwort:

Ein Comparator ist konsistent mit equals , wenn compare(a, b) == 0 genau dann gilt, wenn a.equals(b) true ist. Das ist besonders wichtig für TreeSet und TreeMap , weil diese Strukturen Gleichheit über den Comparator bestimmen.

Wenn ein Comparator nur nach Nachname sortiert, können zwei verschiedene Kunden aus Sicht eines TreeSet gleich erscheinen. Dann wird einer möglicherweise nicht eingefügt. Für reine Sortierlisten ist das oft okay, für Sets und Maps aber gefährlich.

Senior-Antwort:

„Comparatoren sind Verträge. Für Listen darf eine reine Sortierordnung manchmal von equals abweichen, aber bei TreeSet und TreeMap muss ich extrem vorsichtig sein.“

Häufige Fehler:
  • Comparator ohne stabilen Tie-Breaker.
  • Subtraktion für Integer-Vergleiche verwenden.
  • Comparator mit equals -Vertrag verwechseln.
  • Teure Logik im Comparator ausführen.
Best Practices:
  • Comparator.comparing und thenComparing verwenden.
  • Tie-Breaker für deterministische Ordnung ergänzen.
  • Für TreeSet und TreeMap Konsistenz mit equals prüfen.
  • Keine Seiteneffekte im Comparator.
  • Sortierschlüssel vorab berechnen, wenn teuer.
Stolperfalle:

Ein TreeSet nutzt nicht equals , um Duplikate zu erkennen, sondern seine Vergleichsordnung.

Merksatz:

Sortierung ist ein Vertrag, kein kosmetisches Detail.

B1-K02-Q007 – Queue, Deque und PriorityQueueEine PriorityQueue ist für das nächste Element optimiert, nicht für sortierte Ausgabe.
Frage:

„Wann verwenden Sie Queue , Deque oder PriorityQueue , und welche Missverständnisse gibt es?“

Kurz erklärt:

Queues modellieren Verarbeitung vor Zugriff. Deque unterstützt beide Enden. PriorityQueue verarbeitet nicht nach Einfügereihenfolge, sondern nach Priorität.

Musterantwort:

Queue nutze ich für FIFO-nahe Verarbeitung. Deque verwende ich, wenn beide Enden relevant sind oder Stack-Verhalten benötigt wird. PriorityQueue ist sinnvoll, wenn das nächste Element nach Priorität bestimmt wird, zum Beispiel nächster Retry-Zeitpunkt.

Ein häufiges Missverständnis ist, dass Iteration über eine PriorityQueue sortierte Reihenfolge liefert. Das tut sie nicht. Nur peek und poll respektieren die Prioritätsordnung.

Senior-Antwort:

„Eine Queue modelliert Verarbeitung. Eine PriorityQueue ist keine sortierte Liste, sondern eine Struktur, die mir effizient das nächste Element nach Priorität gibt.“

Häufige Fehler:
  • Iteration einer PriorityQueue als sortiert erwarten.
  • LinkedList als Queue-Standard verwenden.
  • Thread-Safety ignorieren.
  • Comparator ohne Tie-Breaker verwenden.
Best Practices:
  • ArrayDeque für einfache Stack-/Queue-Fälle.
  • PriorityQueue für Prioritätsplanung.
  • BlockingQueue für Producer-Consumer.
  • Comparator stabil und günstig halten.
  • Bei Nebenläufigkeit keine normalen java.util Queues verwenden.
Stolperfalle:

for (var item : priorityQueue) liefert keine sortierte Reihenfolge.

Merksatz:

Eine PriorityQueue ist für das nächste Element optimiert, nicht für sortierte Ausgabe.

B1-K02-Q008 – Fail-Fast Iteratoren und ConcurrentModificationExceptionWährend der Iteration ändert man über den Iterator oder gar nicht.
Frage:

„Warum entsteht eine ConcurrentModificationException , und wie entfernt man Elemente korrekt während der Iteration?“

Kurz erklärt:

Fail-fast Iteratoren erkennen strukturelle Änderungen während der Iteration und werfen häufig ConcurrentModificationException .

Musterantwort:

Sie entsteht, wenn eine Collection strukturell verändert wird, während ein Iterator aktiv ist, ohne den Iterator selbst dafür zu verwenden. Der Iterator erkennt, dass sich der erwartete Änderungszähler geändert hat.

Korrekt ist entweder Iterator.remove() , removeIf , das Arbeiten auf einer Kopie oder eine passende concurrent Collection. Wichtig ist: ConcurrentModificationException ist kein Thread-Safety-Mechanismus, sondern ein Fail-fast-Signal für fehlerhafte Iteration.

Senior-Antwort:

„Fail-fast Iteratoren helfen, fehlerhafte Mutation früh zu erkennen. Sie machen normale Collections aber nicht thread-safe.“

Häufige Fehler:
  • In Enhanced-for direkt entfernen.
  • ConcurrentModificationException als Synchronisationsmechanismus verstehen.
  • Nebenläufige Mutation normaler Collections erlauben.
  • Während Streams die Quelle verändern.
Best Practices:
  • Für einfache Filterung removeIf verwenden.
  • Bei manueller Iteration Iterator.remove() .
  • Für Nebenläufigkeit passende concurrent Collections.
  • Snapshots nutzen, wenn Leser stabilen Stand brauchen.
  • Mutationen während Streams vermeiden.
Stolperfalle:

ConcurrentModificationException kann auch in Single-Thread-Code auftreten.

Merksatz:

Während der Iteration ändert man über den Iterator oder gar nicht.

B1-K02-Q009 – Concurrent CollectionsThread-safe Collection bedeutet nicht thread-safe Geschäftsprozess.
Frage:

„Wann reicht ConcurrentHashMap , und wann brauchen Sie zusätzliche Synchronisation oder ein anderes Design?“

Kurz erklärt:

Concurrent Collections sind für kontrollierten nebenläufigen Zugriff gebaut. Sie ersetzen aber kein vollständiges Nebenläufigkeitsdesign.

Musterantwort:

ConcurrentHashMap reicht, wenn einzelne Map-Operationen oder atomare Compute-Operationen den Konsistenzbedarf abdecken. computeIfAbsent , compute und merge sind wichtige Bausteine.

Sie reicht nicht automatisch, wenn mehrere Datenstrukturen gemeinsam konsistent geändert werden müssen oder wenn eine fachliche Transaktion über mehrere Schritte geht. Dann brauche ich Locking, Transaktionen, Actor-Modelle, Datenbankkonsistenz oder ein anderes Architekturdesign.

Senior-Antwort:

„Concurrent Collections lösen lokale Datenstruktur-Concurrency. Sie lösen nicht automatisch fachliche Konsistenz, verteilte Koordination oder Transaktionsgrenzen.“

Häufige Fehler:
  • containsKey plus put als atomar betrachten.
  • Blockierende IO-Aufrufe in compute ausführen.
  • Concurrent Collection mit fachlicher Transaktion verwechseln.
  • Mutable Values ohne eigene Thread-Safety speichern.
Best Practices:
  • Atomare Map-Methoden nutzen.
  • Values möglichst immutable halten.
  • Mapping-Funktionen kurz halten.
  • Multi-Step-Invarianten separat absichern.
  • Nebenläufigkeit im Design modellieren, nicht nur in der Collection.
Stolperfalle:

ConcurrentHashMap schützt die Map-Struktur, aber nicht automatisch den mutierbaren Zustand der gespeicherten Value-Objekte.

Merksatz:

Thread-safe Collection bedeutet nicht thread-safe Geschäftsprozess.

B1-K02-Q010 – Streams, Collections und MutabilityCollections halten Daten; Streams beschreiben Verarbeitung. 24h
Frage:

„Wann verwenden Sie Streams, wann klassische Schleifen, und welche Fehler sehen Sie bei Stream-Code häufig?“

Kurz erklärt:

Collections speichern Daten. Streams beschreiben Verarbeitung. Gute Senior-Antworten trennen Datenstruktur, Pipeline, Mutability und Performance.

Musterantwort:

Streams verwende ich, wenn eine Transformation, Filterung oder Aggregation dadurch klarer wird. Klassische Schleifen verwende ich, wenn komplexe Kontrollflüsse, frühe Abbrüche, mehrere Seiteneffekte oder Performance-Hotspots besser lesbar sind.

Streams sollten möglichst seiteneffektarm bleiben. Eine Pipeline, die externe Listen verändert, Logs mischt, Remote Calls ausführt und nebenbei aggregiert, ist schlechter Code. Außerdem muss man verstehen, ob das Ergebnis mutable oder unmodifiable ist.

Senior-Antwort:

„Streams sind ein Ausdrucksmittel für Datenverarbeitung, kein Ersatz für gutes Design. Ich verwende sie, wenn sie die Absicht klarer machen, und vermeide Seiteneffekte in Pipelines.“

Häufige Fehler:
  • Externe mutable Listen in forEach befüllen.
  • Parallel Streams mit blockierendem IO verwenden.
  • Zu komplexe Pipelines schreiben.
  • Stream wiederverwenden.
  • Sortierung und Limitierung ohne Performancebewusstsein kombinieren.
Best Practices:
  • Streams für klare Transformationen.
  • Schleifen für komplexe imperative Logik.
  • Keine Nebenwirkungen in map oder filter .
  • Ergebnisse bewusst immutable oder mutable wählen.
  • Parallelisierung nur nach Messung einsetzen.
Stolperfalle:

Ein Stream ist einmalig verbrauchbar. Nach einer Terminal-Operation kann er nicht wiederverwendet werden.

Merksatz:

Collections halten Daten; Streams beschreiben Verarbeitung.

B1-K03 – Kapitel 3: Generics 10 Karten

B1-K03-Q001 – Type ErasureGenerics schützen zur Compile-Zeit, nicht als vollwertiges Runtime-Typmodell. 24h
Frage:

„Wie funktionieren Generics in Java intern, und was bedeutet Type Erasure in der Praxis?“

Kurz erklärt:

Type Erasure bedeutet, dass generische Typinformationen zur Laufzeit weitgehend nicht mehr als konkrete Typparameter verfügbar sind.

Musterantwort:

Java-Generics sind primär ein Compile-Time-Typsicherheitsmechanismus. Der Compiler prüft, ob ich zum Beispiel eine List<Payment> korrekt verwende. Im Bytecode werden Typparameter aber weitgehend entfernt. Ein T extends LedgerEntry wird intern ungefähr wie LedgerEntry behandelt, ein unbeschränktes T wie Object .

Das erklärt, warum ich zur Laufzeit nicht einfach T.class schreiben kann und warum List<String> und List<Integer> zur Laufzeit dieselbe rohe Klasse List haben. Generics verbessern also Typsicherheit und API-Ausdruckskraft, erzeugen aber keine spezialisierten Runtime-Typen wie in manchen anderen Sprachen.

Senior-Antwort:

„Generics sind in Java ein starkes Compile-Time-Feature, aber wegen Type Erasure kein Runtime-Typmodell. Wenn ich Runtime-Typinformationen brauche, übergebe ich sie explizit.“

Häufige Fehler:
  • Generics für Runtime-Typentscheidung verwenden.
  • instanceof List<String> erwarten.
  • Type Erasure mit fehlender Typsicherheit verwechseln.
  • Unchecked Casts ohne API-Grenze verwenden.
Best Practices:
  • Generics für Compile-Time-Typsicherheit nutzen.
  • Runtime-Typen explizit über Class<T> oder Type Tokens übergeben.
  • Unchecked Casts kapseln und dokumentieren.
  • Keine Raw Types in neuem Code verwenden.
  • Bei Framework-APIs Runtime-Typinformationen bewusst modellieren.
Stolperfalle:

List<String> und List<Integer> sind zur Laufzeit beide nur List .

Merksatz:

Generics schützen zur Compile-Zeit, nicht als vollwertiges Runtime-Typmodell.

B1-K03-Q002 – Generische Klassen und MethodenGenerics sollen Typverträge präziser machen, nicht APIs verschleiern.
Frage:

„Wann verwenden Sie einen Typparameter auf Klassenebene und wann auf Methodenebene?“

Kurz erklärt:

Generische Klassen parametrisieren den Zustand eines Typs. Generische Methoden parametrisieren einzelne Operationen unabhängig vom Klassentyp.

Musterantwort:

Ein Typparameter gehört auf Klassenebene, wenn der Typ Teil des Objektzustands oder der Lebensdauer der Instanz ist. Ein Repository<Customer, CustomerId> oder EntityCache<ID, E> speichert typisierte Werte und sollte deshalb auf Klassenebene generisch sein.

Ein Typparameter gehört auf Methodenebene, wenn nur eine einzelne Operation generisch ist. Das ist oft besser, weil es den Scope des Typparameters klein hält. Senior-Code vermeidet unnötig breite Typparameter, die das ganze Objekt komplizierter machen, obwohl nur eine Methode Flexibilität braucht.

Senior-Antwort:

„Ich setze Generics dort ein, wo sie einen echten Typvertrag ausdrücken. Typparameter gehören nur so weit nach außen, wie ihr Gültigkeitsbereich wirklich reicht.“

Häufige Fehler:
  • Typparameter auf Klassenebene definieren, obwohl nur eine Methode sie braucht.
  • Nichtssagende Typparameter in öffentlichen APIs verwenden.
  • Zu komplexe Signaturen ohne fachlichen Nutzen bauen.
  • Generics statt klarer Domänentypen verwenden.
Best Practices:
  • Typparameter so lokal wie möglich halten.
  • Konventionelle Namen verwenden.
  • Öffentliche Signaturen lesbar halten.
  • Generics für Typsicherheit, nicht für Rätsel einsetzen.
  • Bei komplexen Typbeziehungen lieber Domain-Typen einführen.
Stolperfalle:

Ein Typparameter auf Klassenebene erhöht die Komplexität aller Instanzmethoden, auch wenn nur eine Methode ihn benötigt.

Merksatz:

Generics sollen Typverträge präziser machen, nicht APIs verschleiern.

B1-K03-Q003 – Wildcards und PECSExtends liest, Super schreibt.
Frage:

„Erklären Sie PECS anhand einer realistischen API. Warum ist List<? extends Payment> nicht dasselbe wie List<Payment> ?“

Kurz erklärt:

PECS steht für „Producer Extends, Consumer Super“. Es ist eine praktische Regel für Wildcards in generischen APIs.

Musterantwort:

List<? extends Payment> bedeutet: Die Liste produziert Payment-Objekte oder Subtypen davon. Ich kann sicher daraus lesen, aber nicht beliebige Payment -Objekte hinzufügen, weil die konkrete Liste vielleicht eine List<CardPayment> ist.

List<? super ExportRecord> bedeutet: Die Liste kann ExportRecord konsumieren. Sie könnte eine List<ExportRecord> oder eine List<Object> sein. Ich kann ExportRecord hinzufügen, aber beim Lesen bekomme ich nur sicher Object .

PECS macht APIs flexibler, ohne Typsicherheit zu verlieren.

Senior-Antwort:

„PECS ist keine Eselsbrücke für Prüfungen, sondern ein API-Design-Prinzip: Wer produziert, bekommt extends ; wer konsumiert, bekommt super .“

Häufige Fehler:
  • extends und super mechanisch verwechseln.
  • Wildcards überall einsetzen.
  • Rückgabetypen unnötig mit Wildcards versehen.
  • List<SubType> als List<BaseType> erwarten.
Best Practices:
  • Für Producer ? extends T .
  • Für Consumer ? super T .
  • Rückgabetypen möglichst konkret und einfach halten.
  • Wildcards an API-Grenzen nutzen, nicht intern übertreiben.
  • Bei Lesbarkeitsverlust Hilfstypen einführen.
Stolperfalle:

List<? extends Payment> ist gut zum Lesen, aber nicht zum Hinzufügen beliebiger Payments.

Merksatz:

Extends liest, Super schreibt.

B1-K03-Q004 – Bounded Type ParametersBounds geben Typparametern Fähigkeiten.
Frage:

„Was bringen bounded type parameters, und wann verwenden Sie mehrere Bounds?“

Kurz erklärt:

Bounds beschränken Typparameter. Dadurch kann generischer Code bestimmte Methoden sicher verwenden.

Musterantwort:

Bounds geben einem Typparameter Fähigkeiten. Ohne Bound kann ich mit T fast nur Object -Methoden verwenden. Mit T extends RiskScored darf ich riskScore() aufrufen. Mehrere Bounds sind sinnvoll, wenn ein Typ mehrere Verträge erfüllen muss, etwa fachlicher Score plus Auditierbarkeit.

Ich setze Bounds ein, wenn eine generische API echte Fähigkeiten braucht. Wenn die Bound-Liste lang wird, ist das oft ein Zeichen, dass ein explizites Interface oder ein besseres Domänenmodell fehlt.

Senior-Antwort:

„Bounds verwende ich, wenn generischer Code bestimmte Fähigkeiten braucht. Sobald die Bounds die Signatur dominieren, abstrahiere ich fachlich besser.“

Häufige Fehler:
  • Bounds verwenden, obwohl ein normales Interface klarer wäre.
  • Zu viele Bounds in öffentlichen APIs.
  • Bounds mit Vererbungshierarchien verwechseln.
  • Generics einsetzen, obwohl Polymorphie ausreichen würde.
Best Practices:
  • Bounds für echte Fähigkeiten nutzen.
  • Komplexe Bounds hinter sprechenden Interfaces verstecken.
  • Öffentliche APIs lesbar halten.
  • Keine generische Akrobatik statt Domänenmodell.
  • Tests mit mehreren Implementierungen schreiben.
Stolperfalle:

Mehrere Bounds sind mächtig, können aber schnell unlesbare APIs erzeugen.

Merksatz:

Bounds geben Typparametern Fähigkeiten.

B1-K03-Q005 – Invarianz, Kovarianz und KontravarianzSubtyp des Elements bedeutet nicht Subtyp der Collection.
Frage:

„Warum ist List<EmailNotification> keine List<Notification> , obwohl EmailNotification ein Notification ist?“

Kurz erklärt:

Java-Generics sind invariant. Varianz wird nicht an der Klassendeklaration, sondern über Wildcards an der Verwendungsstelle ausgedrückt.

Musterantwort:

Weil Java-Generics invariant sind. Wenn List<EmailNotification> als List<Notification> behandelt werden dürfte, könnte ich eine SmsNotification hineinlegen. Danach wäre die ursprüngliche List<EmailNotification> beschädigt.

Wildcards lösen das kontrolliert: ? extends Notification erlaubt sicheres Lesen als Notification, aber kein beliebiges Schreiben. ? super EmailNotification erlaubt Schreiben von EmailNotifications, aber beim Lesen bekomme ich nur sicher Object .

Senior-Antwort:

„Java macht generische Typen invariant, weil mutierbare Collections sonst unsicher wären. Varianz drücke ich gezielt mit Wildcards an der API-Verwendungsstelle aus.“

Häufige Fehler:
  • Subtyping von Elementen auf Subtyping von Listen übertragen.
  • Wildcards nur als Compiler-Beruhigung verwenden.
  • List<Object> mit „Liste von allem“ verwechseln.
  • Arrays und Generics gleich behandeln.
Best Practices:
  • Invarianz als Schutz verstehen.
  • Wildcards bewusst an API-Grenzen einsetzen.
  • Für interne Verarbeitung einfache konkrete Typen bevorzugen.
  • Mutability bei Varianz immer mitdenken.
  • Fehlermeldungen des Compilers nicht mit Raw Types umgehen.
Stolperfalle:

List<Object> bedeutet nicht „Liste beliebiger Typen“, sondern eine Liste, in die beliebige Objekte geschrieben werden dürfen.

Merksatz:

Subtyp des Elements bedeutet nicht Subtyp der Collection.

B1-K03-Q006 – Runtime-Typinformationen und Type TokensRuntime-Typen müssen in Java explizit übergeben werden, wenn sie gebraucht werden.
Frage:

„Wie gehen Sie damit um, dass generische Typen zur Laufzeit nicht vollständig verfügbar sind?“

Kurz erklärt:

Weil generische Typparameter durch Erasure zur Laufzeit nicht direkt verfügbar sind, werden Runtime-Typinformationen oft explizit über Class<T> oder Type Tokens übergeben.

Musterantwort:

Wenn ich zur Laufzeit einen Typ brauche, übergebe ich ihn explizit. Für einfache nicht-generische Typen reicht Class<T> . Für verschachtelte Typen wie List<CustomerEvent> brauche ich ein Type-Token-Konzept, zum Beispiel Framework-spezifische Typreferenzen.

Wichtig ist, unchecked Casts zu kapseln. In einem Registry-Design kann intern ein Cast nötig sein, aber die öffentliche API muss typsicher bleiben.

Senior-Antwort:

„Wenn ich Runtime-Typinformationen brauche, verlasse ich mich nicht auf Generics allein. Ich mache den Typ explizit Teil der API und kapsle unvermeidbare Casts an einer kontrollierten Stelle.“

Häufige Fehler:
  • T.class verwenden wollen.
  • Runtime-Typinformationen implizit erwarten.
  • Unchecked Casts überall verteilen.
  • Class<?> ohne Typvertrag weiterreichen.
Best Practices:
  • Runtime-Typen explizit modellieren.
  • Class<T> für einfache Typen verwenden.
  • Type Tokens für parametrisierte Typen einsetzen.
  • Unchecked Casts zentral kapseln.
  • Reflection-Ergebnisse cachen.
Stolperfalle:

Class<T> kann Customer.class ausdrücken, aber nicht vollständig List<Customer> .

Merksatz:

Runtime-Typen müssen in Java explizit übergeben werden, wenn sie gebraucht werden.

B1-K03-Q007 – Raw Types, Heap Pollution und unchecked CastsRaw Types sind ein Loch im generischen Typsystem.
Frage:

„Was ist Heap Pollution, und warum sind Raw Types in modernem Java gefährlich?“

Kurz erklärt:

Raw Types umgehen generische Typsicherheit. Heap Pollution entsteht, wenn eine Variable eines parametrisierten Typs auf ein Objekt zeigt, das nicht zu diesem parametrisierten Typ passt.

Musterantwort:

Heap Pollution bedeutet, dass die statische Typannahme nicht mehr zur tatsächlichen Objektstruktur passt. Zum Beispiel glaubt der Compiler, eine Liste sei eine List<CustomerRecord> , tatsächlich enthält sie aber andere Objekte. Der Fehler tritt dann oft später als ClassCastException auf.

Raw Types sind gefährlich, weil sie die generische Prüfung des Compilers deaktivieren. In Legacy-Integrationen kann man sie manchmal nicht vermeiden, aber dann kapsle ich sie an einer Grenze, validiere Inhalte und verhindere, dass unsichere Typen in den Kern der Anwendung gelangen.

Senior-Antwort:

„Unchecked Casts sind manchmal an Integrationsgrenzen unvermeidbar. Dann gehören sie an eine kleine, getestete Stelle, nicht verteilt in die Anwendung.“

Häufige Fehler:
  • Warnings global unterdrücken.
  • Raw Types in neue APIs übernehmen.
  • Unchecked Casts ohne Validierung verwenden.
  • Fehler erst tief in Businesslogik auftreten lassen.
Best Practices:
  • Raw Types vermeiden.
  • List<?> statt roher List verwenden, wenn der Elementtyp unbekannt ist.
  • Unchecked Casts lokal kapseln.
  • Validierung an Legacy-Grenzen durchführen.
  • Warnings ernst nehmen.
Stolperfalle:

List<?> ist typsicherer als rohe List , weil man nicht beliebige Elemente hineinlegen kann.

Merksatz:

Raw Types sind ein Loch im generischen Typsystem.

B1-K03-Q008 – Generische Varargs und @SafeVarargs@SafeVarargs ist ein Versprechen, kein Schutzmechanismus.
Frage:

„Warum warnt der Compiler bei generischen Varargs, und wann darf man @SafeVarargs verwenden?“

Kurz erklärt:

Generische Varargs können Heap-Pollution-Warnungen erzeugen, weil Varargs intern Arrays verwenden und generische Array-Erzeugung problematisch ist.

Musterantwort:

Varargs werden als Array umgesetzt. Arrays kennen ihren Komponententyp zur Laufzeit, generische Typparameter wegen Erasure aber nicht vollständig. Dadurch kann Heap Pollution entstehen.

@SafeVarargs ist eine Aussage des Entwicklers: Diese Methode führt keine unsicheren Operationen auf dem Varargs-Array aus. Ich verwende die Annotation nur, wenn ich das Array nicht manipuliere, nicht veröffentliche und keine unsicheren Schreiboperationen darauf ausführe.

Senior-Antwort:

„ @SafeVarargs ist kein Pflaster für Warnungen, sondern eine Zusicherung. Ich nutze sie nur, wenn die Methode tatsächlich keine Heap Pollution verursachen kann.“

Häufige Fehler:
  • @SafeVarargs reflexartig auf jede Warnung setzen.
  • Varargs-Array speichern und später verwenden.
  • Unsichere Schreiboperationen in das Varargs-Array durchführen.
  • Warnings ignorieren, statt Ursache zu verstehen.
Best Practices:
  • Generische Varargs sparsam verwenden.
  • @SafeVarargs nur nach Prüfung einsetzen.
  • Varargs-Array nicht veröffentlichen.
  • Für komplexe APIs lieber List<T> akzeptieren.
  • Warnings im Build ernst nehmen.
Stolperfalle:

Varargs sehen wie einzelne Parameter aus, sind aber intern ein Array.

Merksatz:

@SafeVarargs ist ein Versprechen, kein Schutzmechanismus.

B1-K03-Q009 – F-Bounded Generics und Fluent APIsF-Bounds sind stark für Frameworks, aber oft zu schwer für normalen Business-Code.
Frage:

„Was sind F-Bounded Generics, und wann würden Sie sie einsetzen?“

Kurz erklärt:

F-Bounded Generics erlauben, dass ein Typ sich selbst als Bound referenziert. Das wird häufig für typsichere Fluent APIs und Builder-Hierarchien verwendet.

Musterantwort:

F-Bounded Generics sind Typparameter, die durch eine Variante ihrer eigenen generischen Struktur begrenzt sind, zum Beispiel SELF extends Builder<SELF> . Das ist nützlich für Fluent APIs, bei denen Methoden der Basisklasse den konkreten Subtyp zurückgeben sollen.

Ich setze das bewusst ein, wenn eine Library- oder Framework-API dadurch deutlich typfreundlicher wird. Für normalen Business-Code ist es oft zu komplex. Dann ist Komposition oder ein nicht-vererbter Builder meist einfacher.

Senior-Antwort:

„F-Bounded Generics sind ein Werkzeug für typsichere Fluent APIs. Ich verwende sie sparsam, weil sie für Nutzer schnell schwer lesbar werden können.“

Häufige Fehler:
  • F-Bounds in einfachem Business-Code übertreiben.
  • Unsichere Casts auf SELF verwenden.
  • Vererbte Builder zu tief verschachteln.
  • Lesbarkeit zugunsten generischer Eleganz opfern.
Best Practices:
  • Nur bei echtem Fluent-API-Nutzen verwenden.
  • Builder-Hierarchien flach halten.
  • Komposition prüfen.
  • Öffentliche APIs dokumentieren.
  • Generische Komplexität durch Tests absichern.
Stolperfalle:

Ein generisch eleganter Builder ist nicht automatisch eine gute API.

Merksatz:

F-Bounds sind stark für Frameworks, aber oft zu schwer für normalen Business-Code.

B1-K03-Q010 – Generics in Frameworks und ArchitekturgrenzenGenerics sind gut für Infrastruktur; Fachlichkeit braucht Sprache. 24h
Frage:

„Wie verwenden Sie Generics sinnvoll in Repository-, Service- oder Framework-APIs?“

Kurz erklärt:

Generics sind an Architekturgrenzen besonders wertvoll, aber auch besonders riskant. Frameworks benötigen oft mehr Informationen als der generische Compile-Time-Typ liefert.

Musterantwort:

Ich verwende Generics, wenn eine API wiederkehrende Struktur hat und Typsicherheit echten Nutzen bringt. Ein Repository<ID, E> kann sinnvoll sein, weil es ID- und Entity-Typ koppelt. Dadurch verhindere ich, dass eine CustomerId versehentlich für eine Contract -Entity verwendet wird.

Ich vermeide aber generische Überabstraktion. Nicht jede Service-Schicht braucht ein generisches Base-Interface. Außerdem berücksichtige ich Type Erasure: Wenn ein Framework Entity-Typen zur Laufzeit kennen muss, übergebe ich Class<E> oder ein Type Token explizit.

Senior-Antwort:

„Generics sind stark für Infrastrukturverträge. In der Domäne verwende ich sie zurückhaltender, weil Fachsprache oft wichtiger ist als maximale Wiederverwendung.“

Häufige Fehler:
  • Ein generisches Base-Service für alle Fachfälle bauen.
  • Fachliche Unterschiede hinter Generics verstecken.
  • Type Erasure bei Framework-Integration ignorieren.
  • Unchecked Casts in jede Repository-Methode streuen.
  • Generics als Ersatz für gutes Domain Design verwenden.
Best Practices:
  • Generics für Infrastruktur und wiederkehrende Typverträge nutzen.
  • Fachliche APIs sprechend halten.
  • Runtime-Typinformationen explizit übergeben.
  • Unchecked Casts kapseln.
  • Generische Basisklassen sparsam einsetzen.
  • Tests für Typgrenzen schreiben.
Stolperfalle:

Ein generischer Base-Service reduziert Codezeilen, kann aber fachliche Unterschiede unsichtbar machen.

Merksatz:

Generics sind gut für Infrastruktur; Fachlichkeit braucht Sprache.

B1-K04 – Kapitel 4: Stream API 10 Karten

B1-K04-Q001 – Stream Pipeline, Laziness und Terminal OperationsEin Stream speichert keine Daten; er beschreibt Verarbeitung. 24h
Frage:

„Wie funktioniert eine Stream Pipeline intern, und warum ist Laziness wichtig?“

Kurz erklärt:

Eine Stream Pipeline besteht aus Quelle, Intermediate Operations und einer Terminal Operation. Intermediate Operations sind lazy und werden erst durch eine Terminal Operation ausgeführt.

Musterantwort:

Ein Stream beschreibt zunächst nur eine Verarbeitungspipeline. Operationen wie filter und map führen noch keine Verarbeitung aus. Erst eine Terminal Operation wie toList , collect , count , findFirst oder forEach startet die Auswertung.

Laziness ist wichtig, weil dadurch Operationen fusioniert und Elemente einzeln durch die Pipeline geschoben werden können. Außerdem ermöglichen short-circuiting Operationen wie findFirst oder anyMatch , dass nicht alle Elemente verarbeitet werden müssen.

In einem Senior-Interview würde ich betonen: Streams sind gut für deklarative Datenverarbeitung, aber sie sind nicht automatisch performanter als Schleifen. Sie machen Absicht lesbar, wenn die Pipeline klar und seiteneffektarm bleibt.

Senior-Antwort:

„Eine Stream Pipeline ist eine deklarative Verarbeitungsvorschrift. Sie wird erst durch eine Terminal Operation ausgeführt. Ich nutze Streams, wenn die Transformation dadurch klarer wird, nicht als Selbstzweck.“

Häufige Fehler:
  • Streams ohne Terminal Operation erwarten.
  • Einen Stream zweimal verwenden.
  • In map Seiteneffekte verstecken.
  • Sehr komplexe Pipelines bauen, die schlechter lesbar sind als Schleifen.
  • Zu glauben, Streams seien automatisch schneller.
Best Practices:
  • Streams für klare Transformationen nutzen.
  • Pipelines kurz und lesbar halten.
  • Seiteneffekte vermeiden.
  • Für komplexen Kontrollfluss Schleifen verwenden.
  • Performance in Hotspots messen.
Stolperfalle:

Ohne Terminal Operation wird eine Stream Pipeline nicht ausgeführt.

Merksatz:

Ein Stream speichert keine Daten; er beschreibt Verarbeitung.

B1-K04-Q002 – map, flatMap und fachliche TransformationflatMap ist die Stream-Antwort auf 1:n-Beziehungen.
Frage:

„Was ist der Unterschied zwischen map und flatMap , und wann verwenden Sie flatMap in Enterprise-Code?“

Kurz erklärt:

map transformiert ein Element in genau ein anderes Element. flatMap transformiert ein Element in mehrere Elemente und flacht die resultierenden Streams ab.

Musterantwort:

map verwende ich, wenn ein Eingabeelement genau ein Ausgabeelement erzeugt. flatMap verwende ich, wenn ein Eingabeelement eine Collection, ein Optional oder einen Stream von Ergebnissen erzeugt und ich am Ende eine flache Ergebnisstruktur brauche.

Ein typisches Beispiel sind Rechnungen mit Rechnungspositionen. Aus einer Liste von Rechnungen entsteht eine Liste von Revenue-Lines. Ohne flatMap hätte ich eine verschachtelte Struktur wie Stream<Stream<RevenueLine>> oder List<List<RevenueLine>> .

Senior-Antwort:

„ map bleibt strukturell gleich tief, flatMap entfernt eine Verschachtelungsebene. Ich verwende flatMap , wenn die Domäne eine echte 1:n-Beziehung ausdrückt.“

Häufige Fehler:
  • map verwenden und am Ende verschachtelte Listen erhalten.
  • flatMap für einfache 1:1-Transformationen verwenden.
  • Null statt leerer Streams zurückgeben.
  • Zu viel Businesslogik direkt in Lambda-Ausdrücke schreiben.
Best Practices:
  • map für 1:1-Transformation.
  • flatMap für 1:n-Transformation.
  • Leere Streams statt null zurückgeben.
  • Komplexe Mapping-Logik in benannte Methoden auslagern.
  • Ergebnisstruktur bewusst wählen.
Stolperfalle:

Eine flatMap -Funktion darf keinen null -Stream liefern.

Merksatz:

flatMap ist die Stream-Antwort auf 1:n-Beziehungen.

B1-K04-Q003 – Collectors und GruppierungEin Collector ist Aggregationslogik, nicht nur ein Listenbauer.
Frage:

„Wie verwenden Sie Collectors für Aggregationen, und welche Fehler passieren häufig bei toMap und groupingBy ?“

Kurz erklärt:

Collectors definieren, wie Stream-Elemente in Ergebnisstrukturen reduziert werden: Listen, Sets, Maps, Gruppierungen, Summen, Durchschnittswerte oder eigene Aggregationen.

Musterantwort:

Collectors sind die Standardlösung, wenn ich aus einem Stream eine Ergebnisstruktur aufbauen möchte. Bei toMap muss ich immer überlegen, ob Schlüssel eindeutig sind. Wenn nicht, brauche ich eine Merge-Funktion, sonst gibt es bei doppelten Keys eine Exception.

Bei groupingBy ist wichtig, ob die Ergebnisstruktur mutable sein darf, welche Map-Implementierung benötigt wird und ob die Downstream-Collectoren fachlich passen. In Senior-Code wähle ich bewusst zwischen toList , toUnmodifiableList , toMap , toUnmodifiableMap , groupingBy und spezifischen Collectors wie mapping , filtering oder reducing .

Senior-Antwort:

„Collectors sind mächtig, aber ich behandle sie wie Aggregationslogik. Ich prüfe Schlüssel-Eindeutigkeit, Mutability des Ergebnisses und ob die Aggregation überhaupt in den JVM-Speicher gehört.“

Häufige Fehler:
  • toMap ohne Merge-Funktion bei nicht eindeutigen Keys.
  • Mutable Ergebnisse unbewusst veröffentlichen.
  • Zu komplexe verschachtelte Collectors bauen.
  • Parallelfähigkeit eigener Collector falsch einschätzen.
  • Aggregationen im Speicher durchführen, obwohl SQL besser wäre.
Best Practices:
  • Bei toMap Schlüssel-Eindeutigkeit bewusst prüfen.
  • Merge-Funktion explizit definieren.
  • Unmodifiable Ergebnisse für API-Rückgaben bevorzugen.
  • Komplexe Aggregationen lesbar extrahieren.
  • Bei großen Datenmengen Datenbankaggregation prüfen.
Stolperfalle:

Collectors.toMap wirft bei doppelten Keys eine Exception, wenn keine Merge-Funktion angegeben ist.

Merksatz:

Ein Collector ist Aggregationslogik, nicht nur ein Listenbauer.

B1-K04-Q004 – toList, Collectors.toList und MutabilityDie Sammelmethode definiert den Mutability-Vertrag.
Frage:

„Was ist der Unterschied zwischen stream().toList() und collect(Collectors.toList()) ?“

Kurz erklärt:

Stream.toList() und Collectors.toList() sehen ähnlich aus, haben aber unterschiedliche API-Verträge bezüglich Mutability.

Musterantwort:

Stream.toList() liefert eine unmodifiable List. Das ist für viele API-Rückgaben ideal, weil der Aufrufer das Ergebnis nicht verändern soll. Collectors.toList() garantiert dagegen laut API keinen konkreten Listentyp und keine Mutability-Eigenschaften. Wenn ich eine mutable ArrayList brauche, verwende ich explizit Collectors.toCollection(ArrayList::new) .

Senior-relevant ist hier: Ich verlasse mich nicht auf zufällige Implementierungsdetails. Ich wähle das Ergebnis passend zum API-Vertrag.

Senior-Antwort:

„Ich wähle die Sammeloperation nach dem Vertrag. Wenn das Ergebnis nicht verändert werden soll, ist toList() gut. Wenn ich eine mutable Liste brauche, fordere ich sie explizit an.“

Häufige Fehler:
  • stream().toList() verwenden und danach add aufrufen.
  • Auf ArrayList als Ergebnis von Collectors.toList() vertrauen.
  • Mutability des Rückgabetyps nicht dokumentieren.
  • Unmodifiable mit tiefer Immutability verwechseln.
Best Practices:
  • Für API-Rückgaben toList() oder toUnmodifiableList bevorzugen.
  • Für mutable Listen explizit toCollection(ArrayList::new) verwenden.
  • Mutability bewusst als Teil des Vertrags behandeln.
  • Keine Implementierungsklasse voraussetzen.
  • Tests nicht auf konkrete JDK-Implementierung schreiben.
Stolperfalle:

stream().toList() liefert kein Ergebnis, das man anschließend beliebig erweitern sollte.

Merksatz:

Die Sammelmethode definiert den Mutability-Vertrag.

B1-K04-Q005 – Side Effects und peekStreams transformieren Daten; Seiteneffekte gehören an klare Grenzen.
Frage:

„Warum sind Side Effects in Stream Pipelines problematisch, und wann ist peek akzeptabel?“

Kurz erklärt:

Streams sollten möglichst seiteneffektarm sein. peek ist primär für Debugging gedacht und wird häufig missbraucht.

Musterantwort:

Side Effects machen Stream-Code schwerer verständlich, schlechter testbar und gefährlich bei Parallelisierung. Eine Pipeline sollte idealerweise Daten transformieren, nicht nebenbei externe Zustände verändern.

peek kann für Debugging oder sehr einfache Beobachtung hilfreich sein. Ich würde es aber nicht für fachliche Logik verwenden. Wenn ich zählen, gruppieren oder aggregieren will, nutze ich Collectors oder eine explizite Schleife.

Senior-Antwort:

„Ich vermeide Side Effects in Streams. Wenn eine Pipeline externe Zustände verändert, frage ich mich zuerst, ob eine Schleife oder ein expliziter Aggregationsschritt klarer wäre.“

Häufige Fehler:
  • peek für Businesslogik verwenden.
  • Externe Listen in forEach befüllen.
  • Atomics als Pflaster für schlechtes Pipeline-Design nutzen.
  • Logs als Beweis für vollständige Verarbeitung interpretieren.
  • Mutationen in filter oder map verstecken.
Best Practices:
  • Streams als reine Transformationen schreiben.
  • peek höchstens für Debugging oder Observability mit Vorsicht nutzen.
  • Für Aggregation Collectors verwenden.
  • Bei Seiteneffekten lieber Schleifen oder explizite Services.
  • Parallelisierung von Seiteneffekten vermeiden.
Stolperfalle:

peek ist keine zuverlässige Stelle für fachliche Logik, weil es lazy und pipeline-abhängig ausgeführt wird.

Merksatz:

Streams transformieren Daten; Seiteneffekte gehören an klare Grenzen.

B1-K04-Q006 – Optional, findFirst, anyMatch und Short-CircuitingWenn das Ergebnis früh feststeht, sollte die Pipeline nicht weiterarbeiten.
Frage:

„Was bedeutet Short-Circuiting in Streams, und welche Rolle spielen findFirst , findAny , anyMatch und limit ?“

Kurz erklärt:

Short-Circuiting bedeutet, dass eine Terminal Operation die Verarbeitung abbrechen kann, sobald das Ergebnis feststeht.

Musterantwort:

Short-Circuiting bedeutet, dass nicht alle Elemente verarbeitet werden müssen. anyMatch kann abbrechen, sobald ein Treffer gefunden wurde. findFirst liefert das erste Element in Encounter Order, während findAny mehr Freiheit lässt und besonders bei parallelen Streams effizienter sein kann. limit begrenzt die Anzahl der Elemente.

Wichtig ist die Ordnung des Streams. Bei geordneten Streams muss findFirst die Reihenfolge respektieren. Das kann Parallelisierung einschränken. Wenn mir irgendein Treffer reicht, ist findAny semantisch ehrlicher und potenziell performanter.

Senior-Antwort:

„Short-Circuiting ist ein wichtiger Vorteil der Stream API. Ich nutze es, wenn das Ergebnis früh feststehen kann, und wähle findFirst nur, wenn die Reihenfolge fachlich relevant ist.“

Häufige Fehler:
  • Erst sammeln und dann prüfen.
  • findFirst verwenden, obwohl Reihenfolge irrelevant ist.
  • Side Effects erwarten, die wegen Short-Circuiting nicht eintreten.
  • Infinite Streams ohne begrenzende Operation verwenden.
Best Practices:
  • anyMatch , noneMatch , allMatch für boolesche Prüfungen nutzen.
  • findAny verwenden, wenn Reihenfolge egal ist.
  • findFirst nur bei fachlicher Reihenfolge.
  • Short-Circuiting bewusst für Performance nutzen.
  • Infinite Streams immer begrenzen.
Stolperfalle:

Bei Short-Circuiting werden nachfolgende Elemente möglicherweise nie verarbeitet; darauf darf keine fachliche Nebenwirkung beruhen.

Merksatz:

Wenn das Ergebnis früh feststeht, sollte die Pipeline nicht weiterarbeiten.

B1-K04-Q007 – Primitive Streams und BoxingFür Zahlenströme sind primitive Streams die bessere Default-Wahl.
Frage:

„Warum gibt es IntStream , LongStream und DoubleStream , und wann sind sie wichtig?“

Kurz erklärt:

Primitive Streams wie IntStream , LongStream und DoubleStream vermeiden Boxing und bieten spezialisierte numerische Operationen.

Musterantwort:

Normale Streams arbeiten mit Referenztypen. Ein Stream<Integer> benötigt Boxing und Unboxing, wenn ich mit primitiven int -Werten arbeite. Primitive Streams vermeiden diese Kosten und bieten passende Operationen wie sum , average , summaryStatistics oder mapToLong .

In Business-Code ist das nicht immer kritisch. Bei großen Datenmengen, Metriken, Latenzanalysen oder Hotpaths ist es aber wichtig, unnötiges Boxing zu vermeiden.

Senior-Antwort:

„Primitive Streams sind nicht nur syntaktischer Komfort. Sie vermeiden Boxing und sind bei großen numerischen Datenmengen deutlich sinnvoller.“

Häufige Fehler:
  • Stream<Integer> für große numerische Datenmengen.
  • reduce verwenden, obwohl sum oder summaryStatistics existiert.
  • Primitive Werte unnötig in DTOs zwischenspeichern.
  • Performance ohne Messung diskutieren.
Best Practices:
  • Für numerische Aggregationen primitive Streams nutzen.
  • summaryStatistics für Standardmetriken verwenden.
  • Boxing in Hotpaths vermeiden.
  • Bei maximaler Performance Schleifen benchmarken.
  • Keine Floating-Point-Summen für Geld verwenden.
Stolperfalle:

Stream<Integer> sieht harmlos aus, kann aber viele Wrapper-Objekte erzeugen.

Merksatz:

Für Zahlenströme sind primitive Streams die bessere Default-Wahl.

B1-K04-Q008 – Parallel StreamsParallel Stream ist ein Parallelitätsmodell, kein Performance-Schalter.
Frage:

„Wann sind Parallel Streams sinnvoll, und wann würden Sie sie vermeiden?“

Kurz erklärt:

Parallel Streams verteilen Stream-Verarbeitung auf mehrere Threads. Sie sind kein allgemeiner Performance-Schalter.

Musterantwort:

Parallel Streams sind sinnvoll bei CPU-lastigen, unabhängigen Operationen auf ausreichend großen Datenmengen, wenn die Quelle gut aufteilbar ist und keine relevanten Side Effects entstehen. Beispiel: Hashes über viele Dokumente berechnen.

Ich vermeide sie in Webanwendungen für blockierendes IO, Datenbankzugriffe, REST-Calls oder Transaktionen. Dort konkurriert der Parallel Stream oft mit anderen Workloads um den gemeinsamen Pool, erzeugt unkontrollierte Parallelität und verschlechtert Latenzen.

Senior-Antwort:

„Parallel Streams sind sinnvoll für große, CPU-lastige und unabhängige Datenverarbeitung. In serverseitigem IO-Code sind sie oft gefährlich, weil sie Parallelität verstecken und Ressourcen unkontrolliert nutzen.“

Häufige Fehler:
  • .parallelStream() als magischen Performance-Fix verwenden.
  • Datenbank- oder HTTP-Calls parallelisieren.
  • Seiteneffekte in parallelen Pipelines.
  • Gemeinsamen ForkJoinPool überlasten.
  • Keine Benchmarks durchführen.
Best Practices:
  • Nur bei CPU-lastigen unabhängigen Aufgaben einsetzen.
  • Datenmenge und Operationkosten prüfen.
  • Keine blockierenden IO-Operationen.
  • Side Effects vermeiden.
  • Mit realistischen Daten messen.
  • Für kontrollierte Parallelität oft eigene Executor-Architektur bevorzugen.
Stolperfalle:

Ein Parallel Stream verwendet nicht automatisch einen fachlich isolierten Thread Pool.

Merksatz:

Parallel Stream ist ein Parallelitätsmodell, kein Performance-Schalter.

B1-K04-Q009 – Spliterator und Stream-QuellenParallelisierung beginnt bei der Teilbarkeit der Quelle.
Frage:

„Was ist ein Spliterator, und warum ist er für Parallel Streams wichtig?“

Kurz erklärt:

Ein Spliterator ist die Brücke zwischen Datenquelle und Stream-Verarbeitung. Er beschreibt Traversierung, mögliche Aufteilung und Eigenschaften der Quelle.

Musterantwort:

Ein Spliterator kann Elemente einer Quelle traversieren und optional aufteilen. Genau diese Aufteilbarkeit ist entscheidend für Parallel Streams. Eine ArrayList lässt sich gut in Teilbereiche splitten. Ein sequentieller Datei-Reader lässt sich meist schlecht oder gar nicht sinnvoll splitten.

Ich würde einen eigenen Spliterator nur schreiben, wenn ich eine spezielle Datenquelle sauber in die Stream API integrieren muss. Dabei muss ich Eigenschaften wie ORDERED , NONNULL , SIZED oder IMMUTABLE korrekt angeben, weil falsche Characteristics zu falschen Annahmen in der Pipeline führen können.

Senior-Antwort:

„Ein Spliterator beschreibt, wie eine Quelle traversiert und geteilt werden kann. Für parallele Streams ist nicht die Stream-Syntax entscheidend, sondern ob die Quelle effizient teilbar ist.“

Häufige Fehler:
  • Falsche Characteristics setzen.
  • IO-Quelle parallelisieren wollen, obwohl sie sequentiell ist.
  • Ressourcen nicht über onClose schließen.
  • trySplit naiv implementieren.
  • Infinite Streams mit falscher Größe versehen.
Best Practices:
  • Eigene Spliteratoren selten und sorgfältig schreiben.
  • Characteristics konservativ setzen.
  • Ressourcenlebensdauer klar regeln.
  • Parallelisierung nur bei sinnvoller Aufteilbarkeit.
  • Bei IO-Quellen Backpressure und Fehlerbehandlung beachten.
Stolperfalle:

Ein Stream über eine IO-Quelle muss Ressourcen sauber schließen; die Stream API übernimmt das nicht magisch für externe Ressourcen.

Merksatz:

Parallelisierung beginnt bei der Teilbarkeit der Quelle.

B1-K04-Q010 – Ressourcenmanagement mit StreamsEin Stream über IO ist eine Ressource, keine harmlose Collection-Sicht. 24h
Frage:

„Wann muss ein Stream geschlossen werden, und warum ist Files.lines ein typischer Sonderfall?“

Kurz erklärt:

Nicht jeder Stream ist ressourcenfrei. Streams über Dateien, IO-Kanäle oder externe Quellen müssen geschlossen werden.

Musterantwort:

Streams über Collections müssen normalerweise nicht geschlossen werden, weil sie keine externe Ressource halten. Streams über Dateien, IO-Kanäle oder andere externe Quellen müssen geschlossen werden. Files.lines ist ein klassisches Beispiel, weil dahinter eine Datei geöffnet wird.

Deshalb verwende ich try-with-resources . Ich würde keinen solchen Stream aus einer Methode zurückgeben, wenn der Aufrufer nicht klar für das Schließen verantwortlich ist. Ressourcenlebensdauer muss an API-Grenzen eindeutig sein.

Senior-Antwort:

„Collection-Streams sind normalerweise ressourcenfrei. Streams über IO-Quellen behandle ich wie jede andere Ressource und schließe sie mit try-with-resources.“

Häufige Fehler:
  • Files.lines(path) ohne try-with-resources verwenden.
  • IO-Streams aus Methoden zurückgeben, ohne Ownership zu klären.
  • Stream-Verarbeitung nach Schließen des Streams erwarten.
  • Große Dateien mit readAllLines unnötig komplett laden.
Best Practices:
  • IO-basierte Streams immer in try-with-resources.
  • Collection-Streams nicht unnötig schließen.
  • Ressourcen-Ownership klar dokumentieren.
  • Große Dateien lazy verarbeiten.
  • Fehlerkontext bei Parsing ergänzen.
Stolperfalle:

Ein Stream sieht immer gleich aus, aber seine Quelle entscheidet, ob Ressourcen geschlossen werden müssen.

Merksatz:

Ein Stream über IO ist eine Ressource, keine harmlose Collection-Sicht.

B1-K05 – Kapitel 5: Functional Programming 10 Karten

B1-K05-Q001 – Functional Interfaces und LambdasEin Lambda ist Verhalten als Wert, aber kein Ersatz für fachliche Modellierung. 24h
Frage:

„Was ist ein Functional Interface, und warum sind Lambdas für modernes Java so wichtig?“

Kurz erklärt:

Ein Functional Interface besitzt genau eine abstrakte Methode. Lambdas sind kompakte Implementierungen solcher Interfaces.

Musterantwort:

Ein Functional Interface ist ein Interface mit genau einer abstrakten Methode. Dadurch kann es durch ein Lambda oder eine Method Reference implementiert werden. Beispiele sind Predicate , Function , Supplier , Consumer , Comparator oder Runnable .

Lambdas sind wichtig, weil sie Verhalten als Wert übergeben können. Dadurch werden APIs flexibler: Filterregeln, Mapping-Logik, Sortierstrategien oder Retry-Entscheidungen können als Parameter modelliert werden, ohne für jeden Fall eine eigene Klasse zu schreiben.

Auf Senior-Level ist aber wichtig: Lambdas ersetzen kein gutes Design. Wenn die Logik fachlich wichtig oder komplex ist, sollte sie benannt, getestet und nicht als anonymer Block versteckt werden.

Senior-Antwort:

„Lambdas sind ein Werkzeug, um Verhalten explizit zu übergeben. Ich nutze sie für kleine, klare Funktionen. Sobald die Logik fachlich wichtig wird, bekommt sie einen Namen, Tests und eine klare API.“

Häufige Fehler:
  • Große Businesslogik in Lambdas verstecken.
  • Functional Interfaces ohne fachlichen Namen verwenden, obwohl ein Domänenbegriff existiert.
  • Lambdas mit Seiteneffekten überladen.
  • Capturing von mutablem Zustand unterschätzen.
  • @FunctionalInterface weglassen bei öffentlichen APIs.
Best Practices:
  • Standardinterfaces wie Predicate , Function , Supplier verwenden, wenn sie semantisch passen.
  • Eigene Functional Interfaces definieren, wenn die Fachlichkeit einen Namen braucht.
  • Lambdas kurz halten.
  • Komplexe Logik in Methoden auslagern.
  • Functional Interfaces mit @FunctionalInterface annotieren.
Stolperfalle:

Ein Lambda kann nur dort stehen, wo der Compiler einen Functional-Interface-Zieltyp kennt.

Merksatz:

Ein Lambda ist Verhalten als Wert, aber kein Ersatz für fachliche Modellierung.

B1-K05-Q002 – Standard Functional InterfacesWähle Functional Interfaces nach Bedeutung, nicht nur nach Signatur.
Frage:

„Welche Standard Functional Interfaces kennen Sie, und wann verwenden Sie welches?“

Kurz erklärt:

Java stellt viele Standard-Functional-Interfaces bereit. Die wichtigsten sind Function , Predicate , Supplier , Consumer , UnaryOperator und BinaryOperator .

Musterantwort:

Function<T, R> transformiert einen Wert von T nach R . Predicate<T> prüft eine Bedingung und liefert boolean. Supplier<T> erzeugt oder liefert einen Wert ohne Eingabe. Consumer<T> verarbeitet einen Wert ohne Rückgabe. UnaryOperator<T> ist eine Function<T, T> , also Eingabe und Ausgabe gleicher Typ. BinaryOperator<T> kombiniert zwei Werte gleichen Typs.

Ich wähle das Interface nach Semantik, nicht nur nach Signatur. Wenn ein Begriff fachlich wichtig ist, zum Beispiel FraudRule oder RetryPolicy , definiere ich lieber ein eigenes Functional Interface.

Senior-Antwort:

„Ich verwende Standardinterfaces, wenn ihre Semantik klar passt. Sobald der Fachbegriff wichtiger ist als die technische Signatur, bekommt er ein eigenes Interface.“

Häufige Fehler:
  • Function<T, Boolean> statt Predicate<T> verwenden.
  • Consumer für Businesslogik mit versteckten Seiteneffekten missbrauchen.
  • Standardinterfaces verwenden, obwohl ein fachlicher Name wichtiger wäre.
  • Boxing-Kosten bei primitiven Massenoperationen ignorieren.
Best Practices:
  • Semantisch passende Standardinterfaces wählen.
  • Primitive Spezialinterfaces in Hotpaths verwenden.
  • Eigene Interfaces für fachliche Konzepte definieren.
  • Lambdas kurz und testbar halten.
  • Seiteneffekte in Consumer klar begrenzen.
Stolperfalle:

Function<T, Boolean> ist fast immer schlechter lesbar als Predicate<T> .

Merksatz:

Wähle Functional Interfaces nach Bedeutung, nicht nur nach Signatur.

B1-K05-Q003 – Function CompositionKomponiere kleine Transformationen, nicht ganze Geschäftsprozesse.
Frage:

„Wie funktioniert Function Composition in Java, und wann ist sie sinnvoll?“

Kurz erklärt:

Function Composition bedeutet, mehrere Funktionen zu einer Verarbeitungskette zu kombinieren.

Musterantwort:

Function bietet andThen und compose . andThen führt zuerst die aktuelle Funktion und danach die nächste aus. compose führt zuerst die übergebene Funktion aus und dann die aktuelle.

Sinnvoll ist das bei klaren, kleinen, unabhängigen Transformationen, zum Beispiel Normalisierung, Validierungsvorbereitung oder Mapping-Pipelines. Ich würde es nicht verwenden, wenn die Schritte komplexe Seiteneffekte, Transaktionen oder stark verzweigte Fachlogik enthalten. Dann ist ein expliziter Service oder Workflow lesbarer.

Senior-Antwort:

„Function Composition ist stark, wenn ich kleine, reine Transformationen verketten will. Sobald Transaktionen, IO oder komplexe Fachentscheidungen dazukommen, bevorzuge ich explizite Workflows.“

Häufige Fehler:
  • Lange unbenannte Kompositionsketten bauen.
  • Seiteneffekte in Funktionen verstecken.
  • Reihenfolge unterschätzen.
  • Exceptions ohne Kontext durchreichen.
  • Fachliche Workflows als technische Function-Ketten verschleiern.
Best Practices:
  • Kleine benannte Funktionen komponieren.
  • Reihenfolge explizit testen.
  • Nur seiteneffektarme Transformationen komponieren.
  • Komplexe Workflows als eigene Services modellieren.
  • Komposition nicht gegen Lesbarkeit einsetzen.
Stolperfalle:

compose und andThen unterscheiden sich nur in der Reihenfolge, aber genau diese Reihenfolge ist fachlich oft kritisch.

Merksatz:

Komponiere kleine Transformationen, nicht ganze Geschäftsprozesse.

B1-K05-Q004 – Pure Functions und Side EffectsPure Functions machen Fachlogik berechenbar statt überraschend.
Frage:

„Was ist eine pure Function, und warum ist das in Java Enterprise-Code nützlich?“

Kurz erklärt:

Eine pure Function liefert für dieselbe Eingabe immer dieselbe Ausgabe und verändert keinen externen Zustand.

Musterantwort:

Eine pure Function ist deterministisch und frei von Seiteneffekten. Bei gleicher Eingabe liefert sie immer dieselbe Ausgabe. Sie schreibt nicht in Datenbanken, verändert keine globalen Variablen, ruft keine externe API auf und hängt nicht von verstecktem Zustand wie Systemzeit ab.

Das ist in Enterprise-Code nützlich, weil solche Funktionen einfach zu testen, zu parallelisieren und zu refactoren sind. Fachliche Berechnungen wie Gebühren, Steuern, Rabatte oder Risiko-Scores sollten möglichst pure sein. IO, Persistenz und Messaging gehören an klare Ränder.

Senior-Antwort:

„Ich versuche fachliche Berechnungen pure zu halten. Das macht sie testbar, deterministisch und einfacher wiederzuverwenden. Seiteneffekte gehören an kontrollierte Architekturgrenzen.“

Häufige Fehler:
  • Fachliche Berechnung mit Datenbankzugriff mischen.
  • Systemzeit direkt in Berechnungen verwenden.
  • Globale Konfiguration versteckt lesen.
  • Mutable Eingabeobjekte verändern.
  • Pure Function mit statischer Methode verwechseln.
Best Practices:
  • Fachliche Berechnung möglichst pure halten.
  • Zeit, Zufall und Konfiguration als Parameter übergeben.
  • Immutable Eingaben und Ausgaben bevorzugen.
  • IO an Architekturgrenzen kapseln.
  • Pure Logik separat testen.
Stolperfalle:

Eine Methode ist nicht pure, wenn sie intern Instant.now() verwendet, auch wenn sie keine Datenbank schreibt.

Merksatz:

Pure Functions machen Fachlogik berechenbar statt überraschend.

B1-K05-Q005 – Closures und effectively finalEin Lambda sollte Werte erfassen, nicht versteckten Zustand verwalten.
Frage:

„Was bedeutet effectively final bei Lambdas, und warum gibt es diese Einschränkung?“

Kurz erklärt:

Ein Lambda kann lokale Variablen aus dem umgebenden Scope erfassen. Diese Variablen müssen final oder effectively final sein.

Musterantwort:

Eine lokale Variable ist effectively final, wenn sie nach ihrer Initialisierung nicht mehr verändert wird. Lambdas dürfen solche Variablen erfassen. Wenn ich die Variable später ändere, lehnt der Compiler das ab.

Diese Einschränkung verhindert viele Probleme mit Lebensdauer und Nebenläufigkeit. Lokale Variablen liegen konzeptionell auf dem Stack, Lambdas können aber länger leben als der Methodenaufruf. Java erfasst deshalb Werte kontrolliert und erlaubt keine veränderlichen lokalen Variablen im Lambda-Capture.

Senior-Antwort:

„Effectively final schützt vor unklarem mutablem Zustand im Lambda-Capture. Wenn ich aggregieren will, verwende ich passende Collector-Mechanismen statt lokale Variablen zu mutieren.“

Häufige Fehler:
  • Lokale Zähler in Lambdas mutieren wollen.
  • Arrays oder AtomicInteger als Workaround missbrauchen.
  • Mutable Instanzfelder in parallelen Streams verändern.
  • Capturing und Thread-Safety verwechseln.
Best Practices:
  • Erfasste Werte immutable halten.
  • Keine mutable Workarounds für lokale Variablen bauen.
  • Für Aggregation Collectors verwenden.
  • Mutable Instanzfelder in Lambdas vermeiden.
  • Capturing bewusst und sparsam einsetzen.
Stolperfalle:

Ein AtomicInteger im Lambda macht den Code nicht automatisch sauber; er versteckt oft nur einen Seiteneffekt.

Merksatz:

Ein Lambda sollte Werte erfassen, nicht versteckten Zustand verwalten.

B1-K05-Q006 – Method ReferencesMethod References sind gut, wenn sie Absicht sichtbar machen.
Frage:

„Wann verwenden Sie Method References statt Lambdas?“

Kurz erklärt:

Method References sind eine kompakte Schreibweise für Lambdas, die lediglich eine vorhandene Methode aufrufen.

Musterantwort:

Ich verwende Method References, wenn sie die Absicht klarer machen. CustomerRisk::customerId ist lesbarer als risk -> risk.customerId() . Sie sind besonders gut für Getter, Konstruktorreferenzen, statische Hilfsmethoden und Comparatoren.

Wenn die Logik aber mehr ausdrückt als einen Methodenaufruf oder Parameter umgeordnet werden müssen, ist ein Lambda oft klarer. Lesbarkeit entscheidet, nicht Kürze.

Senior-Antwort:

„Ich verwende Method References, wenn sie die Absicht klarer machen. Sie sind kein Qualitätsmerkmal an sich; manchmal ist ein Lambda expliziter und besser.“

Häufige Fehler:
  • Method References verwenden, obwohl ein Lambda verständlicher wäre.
  • Zu viel Logik in referenzierte Methoden verstecken.
  • Überladene Methodenreferenzen unklar machen.
  • Kürze über Lesbarkeit stellen.
Best Practices:
  • Method References für einfache Delegation verwenden.
  • Lambdas für explizite Logik verwenden.
  • Referenzierte Methoden sprechend benennen.
  • Bei Mehrdeutigkeit Lambda bevorzugen.
  • In Comparatoren Method References nutzen, wenn klar.
Stolperfalle:

Kürzerer Code ist nicht automatisch verständlicherer Code.

Merksatz:

Method References sind gut, wenn sie Absicht sichtbar machen.

B1-K05-Q007 – Funktionale FehlerbehandlungFunktionaler Stil braucht explizite Fehlersemantik.
Frage:

„Wie gehen Sie mit Exceptions in Lambdas und Stream-Pipelines um?“

Kurz erklärt:

Java-Standard-Functional-Interfaces deklarieren keine checked Exceptions. Fehlerbehandlung in funktionalem Stil muss daher bewusst modelliert werden.

Musterantwort:

Ich vermeide es, checked Exceptions einfach in RuntimeExceptions zu verstecken, ohne Kontext. Wenn ein Fehler fachlich erwartbar ist, zum Beispiel eine ungültige CSV-Zeile, modelliere ich ihn lieber als Ergebnis, etwa ParseResult . Wenn ein technischer Fehler unerwartet ist, kann Wrapping sinnvoll sein, aber dann mit Cause und Kontext.

Für kleine technische Adapter kann ein eigenes ThrowingFunction helfen. Ich würde aber keine generische Exception-Magie über die ganze Anwendung legen. Fehlersemantik muss fachlich klar bleiben.

Senior-Antwort:

„In funktionalem Java muss Fehlersemantik explizit sein. Erwartbare fachliche Fehler modelliere ich als Ergebnis, technische Fehler wrappe ich mit Kontext und Cause.“

Häufige Fehler:
  • Checked Exceptions blind als RuntimeException wrappen.
  • Fehlerkontext verlieren.
  • Exceptions für erwartbare Validierungsfehler massenhaft werfen.
  • Stream-Pipelines mit try-catch-Blöcken unlesbar machen.
  • Importfehler nicht akkumulieren.
Best Practices:
  • Erwartbare Fehler als Ergebnis modellieren.
  • Technische Fehler mit Cause wrappen.
  • Kontext ergänzen.
  • Throwing-Adapter sparsam einsetzen.
  • Bei Massenverarbeitung nicht Exceptions als normalen Kontrollfluss verwenden.
Stolperfalle:

Ein generischer try/catch im Lambda macht eine Pipeline schnell unlesbar und versteckt Fehlersemantik.

Merksatz:

Funktionaler Stil braucht explizite Fehlersemantik.

B1-K05-Q008 – Strategy Pattern mit Functional InterfacesLambdas können Strategies implementieren, aber Fachlichkeit braucht Namen.
Frage:

„Wann würden Sie das Strategy Pattern mit Lambdas umsetzen und wann mit eigenen Klassen?“

Kurz erklärt:

Functional Interfaces können klassische Strategy-Objekte ersetzen, wenn eine Strategie im Kern eine einzelne Operation ist.

Musterantwort:

Wenn eine Strategie wirklich nur eine einzelne, kleine Operation ist, kann ein Functional Interface mit Lambdas sehr elegant sein. Zum Beispiel eine Gebührenberechnung oder eine Routing-Entscheidung.

Wenn die Strategie eigenen Zustand, Konfiguration, mehrere Methoden, Lifecycle, Metriken, Dependencies oder komplexe Tests benötigt, verwende ich eigene Klassen. In Spring-Anwendungen sind Strategies oft Beans, weil sie konfiguriert, injiziert und beobachtet werden müssen.

Die Entscheidung ist nicht Lambda versus Pattern. Lambdas sind eine mögliche Implementierung des Strategy Patterns.

Senior-Antwort:

„Lambdas können Strategies sehr elegant implementieren, wenn die Strategie klein ist. Sobald Konfiguration, Dependencies oder Observability wichtig werden, nutze ich benannte Klassen oder Spring Beans.“

Häufige Fehler:
  • Komplexe Strategien anonym als Lambda verstecken.
  • Keine fachlichen Namen für wichtige Regeln vergeben.
  • Lambdas verwenden, obwohl Dependency Injection nötig ist.
  • Strategien ohne Default- oder Fehlerfall registrieren.
  • Konfiguration und Code vermischen.
Best Practices:
  • Kleine Strategien als Functional Interfaces modellieren.
  • Komplexe Strategien als Klassen.
  • Fachliche Namen verwenden.
  • Strategies in Maps statt großer switch -Blöcke prüfen.
  • In Spring benannte Beans bevorzugen, wenn Observability relevant ist.
Stolperfalle:

Ein Lambda macht aus komplexer Businesslogik keine einfache Businesslogik.

Merksatz:

Lambdas können Strategies implementieren, aber Fachlichkeit braucht Namen.

B1-K05-Q009 – Lazy Evaluation mit SupplierLazy bedeutet später, nicht automatisch einmalig.
Frage:

„Wann verwenden Sie Supplier , und was bedeutet Lazy Evaluation in Java?“

Kurz erklärt:

Supplier<T> erlaubt lazy Evaluation: Ein Wert wird erst berechnet, wenn get() aufgerufen wird.

Musterantwort:

Ein Supplier<T> repräsentiert eine wertliefernde Funktion ohne Eingabe. Ich verwende ihn, wenn die Erzeugung teuer ist, optional benötigt wird oder verzögert erfolgen soll. Ein typisches Beispiel ist ein Default-Wert, ein Lazy Context oder eine teure Diagnoseinformation.

Lazy Evaluation bedeutet, dass die Berechnung nicht beim Erzeugen des Suppliers passiert, sondern erst bei get() . Man muss aber wissen: Ein normaler Supplier cached nicht automatisch. Jeder get() -Aufruf kann neu berechnen.

Senior-Antwort:

„Supplier ist ein gutes Werkzeug für verzögerte Berechnung. Ich behandle ihn aber nicht als Cache. Wenn ein Wert nur einmal berechnet werden darf, modelliere ich Memoization explizit.“

Häufige Fehler:
  • Annehmen, dass Supplier automatisch cached.
  • Supplier mehrfach aufrufen und teure Berechnung wiederholen.
  • Große Objekte im Lambda-Capture festhalten.
  • Lazy Evaluation verwenden, obwohl Eager Evaluation klarer wäre.
  • Exceptions im Supplier zu spät und ohne Kontext erhalten.
Best Practices:
  • Supplier für optionale oder teure Berechnungen verwenden.
  • Mehrfachaufrufe bewusst behandeln.
  • Memoization explizit implementieren, wenn nötig.
  • Capturing großer Kontexte vermeiden.
  • Fehlerkontext beim Ausführen erhalten.
Stolperfalle:

Supplier.get() kann bei jedem Aufruf erneut rechnen.

Merksatz:

Lazy bedeutet später, nicht automatisch einmalig.

B1-K05-Q010 – Funktionaler Stil in Enterprise-ArchitekturFunktionaler Stil ist ein Werkzeug für Klarheit, nicht ein Dogma. 24h
Frage:

„Wie setzen Sie funktionale Programmierung pragmatisch in einer Java-Enterprise-Architektur ein?“

Kurz erklärt:

Funktionaler Stil in Enterprise-Java bedeutet, Verhalten und Transformationen gezielt zu kapseln, ohne objektorientierte Architektur aufzugeben.

Musterantwort:

Ich nutze funktionale Konzepte dort, wo sie Klarheit bringen: kleine Regeln als Predicate , Transformationen als Function , Lazy-Erzeugung als Supplier , Strategien als Functional Interfaces und seiteneffektarme Pipelines für Datenverarbeitung.

Ich versuche aber nicht, Java rein funktional zu schreiben. Transaktionen, Persistenz, Messaging, Security und Observability brauchen explizite Architekturgrenzen. Funktionaler Stil ist besonders stark im Inneren der Fachlogik, wenn Berechnungen deterministisch und testbar bleiben.

Senior-Antwort:

„Ich nutze funktionalen Stil in Java pragmatisch: für kleine Regeln, Transformationen und pure Berechnungen. Architekturgrenzen wie Transaktionen, Messaging und Observability halte ich explizit und objektorientiert modellierbar.“

Häufige Fehler:
  • Java zwanghaft wie eine rein funktionale Sprache schreiben.
  • Komplexe Geschäftsprozesse in Lambda-Ketten verstecken.
  • Transaktionen und IO in funktionale Pipelines mischen.
  • Functional Interfaces ohne fachliche Namen überverwenden.
  • Lesbarkeit für vermeintliche Eleganz opfern.
Best Practices:
  • Funktionale Konzepte pragmatisch einsetzen.
  • Pure Fachlogik bevorzugen.
  • Seiteneffekte an Architekturgrenzen halten.
  • Fachliche Regeln benennen.
  • Spring Beans für komplexe Strategien verwenden.
  • Records und sealed Types für Ergebnis- und Datenmodelle nutzen.
  • Performance-Hotspots messen.
Stolperfalle:

Funktionaler Stil verbessert Code nur, wenn er Klarheit schafft. Eine undurchsichtige Lambda-Kette ist schlechter als eine gute Klasse.

Merksatz:

Funktionaler Stil ist ein Werkzeug für Klarheit, nicht ein Dogma.

B1-K06 – Kapitel 6: Exception Handling 10 Karten

B1-K06-Q001 – Checked vs. Unchecked ExceptionsExceptions sind API-Verträge, nicht nur Fehlerobjekte. 24h
Frage:

„Wann verwenden Sie checked Exceptions und wann unchecked Exceptions?“

Kurz erklärt:

Checked Exceptions erzwingen Behandlung oder Deklaration. Unchecked Exceptions müssen nicht deklariert werden. Die Entscheidung ist eine API-Design-Frage, nicht nur Geschmack.

Musterantwort:

Ich verwende checked Exceptions, wenn der Aufrufer realistisch und sinnvoll reagieren soll und diese Reaktion Teil des API-Vertrags ist. Ein klassisches Beispiel ist IO, bei dem Lesen, Schreiben oder Netzwerke fehlschlagen können.

In Business-Services verwende ich häufiger unchecked Exceptions oder explizite Result-Typen. Fachliche Fehler wie „Zahlung ungültig“ sind oft Teil der Domäne und sollten entweder als fachliche Runtime Exception oder als Ergebnis modelliert werden. Wichtig ist, technische Details nicht unkontrolliert durch alle Schichten zu leaken.

Ich entscheide also nicht dogmatisch, sondern frage: Soll der Aufrufer zur Behandlung gezwungen werden? Ist der Fehler erwartbar? Ist er fachlich oder technisch? Gehört er in die Methodensignatur?

Senior-Antwort:

„Checked versus unchecked ist für mich eine API-Vertragsfrage. Wenn der Aufrufer sinnvoll reagieren muss, kann checked passen. In Business-Schichten bevorzuge ich klare fachliche Runtime Exceptions oder explizite Result-Typen.“

Häufige Fehler:
  • Checked Exceptions blind durch alle Schichten propagieren.
  • Jede fachliche Ablehnung als Exception modellieren.
  • throws Exception in öffentlichen APIs verwenden.
  • Runtime Exceptions ohne Kontext werfen.
  • Checked Exceptions in Runtime Exceptions wrappen und die Cause verlieren.
Best Practices:
  • Fehlersemantik bewusst modellieren.
  • Checked Exceptions nur verwenden, wenn Aufrufer sinnvoll reagieren sollen.
  • Technische Exceptions an Architekturgrenzen übersetzen.
  • Cause immer erhalten.
  • Fachliche Standardfälle nicht als technische Fehler behandeln.
Stolperfalle:

Checked Exceptions erzwingen Behandlung, aber nicht sinnvolle Behandlung. Ein leeres catch oder pauschales Wrapping verbessert nichts.

Merksatz:

Exceptions sind API-Verträge, nicht nur Fehlerobjekte.

B1-K06-Q002 – Fachliche Fehler vs. technische FehlerNicht jeder negative Ausgang ist ein Fehler.
Frage:

„Wann modellieren Sie einen fachlichen Fehler als Exception und wann als Rückgabewert?“

Kurz erklärt:

Nicht jeder negative Ausgang ist eine Exception. Viele fachliche Ergebnisse sind normale Domänenzustände.

Musterantwort:

Wenn ein Ergebnis fachlich erwartbar ist, modelliere ich es meistens als Rückgabewert. Eine Kreditablehnung, eine manuelle Prüfung oder ein nicht erfülltes Limit sind keine technischen Fehler, sondern normale Domänenresultate.

Eine Exception verwende ich, wenn ein Vertrag verletzt wurde oder eine außergewöhnliche Situation auftritt: ungültiger Methodenaufruf, inkonsistenter Zustand, nicht erreichbarer externer Service oder nicht lesbare Datei.

Auf Senior-Level ist die entscheidende Frage: Ist das ein Fehler im Ablauf oder eine Variante des fachlichen Ergebnisses?

Senior-Antwort:

„Eine fachliche Ablehnung ist nicht automatisch eine Exception. Wenn sie Teil des normalen Geschäftsprozesses ist, modelliere ich sie als Ergebnisvariante.“

Häufige Fehler:
  • Jede Ablehnung als Exception werfen.
  • Fachliche Ergebnisse als Strings zurückgeben.
  • boolean success ohne Gründe verwenden.
  • Technische und fachliche Fehler vermischen.
  • Framework-Rollback durch normale Fachentscheidungen auslösen.
Best Practices:
  • Erwartbare Fachzustände als Typen modellieren.
  • Exceptions für Vertragsverletzungen und technische Fehler verwenden.
  • Sealed Results für geschlossene Varianten nutzen.
  • Fehlergründe maschinenlesbar machen.
  • Audit- und Diagnoseanforderungen berücksichtigen.
Stolperfalle:

Exceptions für normale Fachfälle können Transaktionen, Monitoring und Alerting verfälschen.

Merksatz:

Nicht jeder negative Ausgang ist ein Fehler.

B1-K06-Q003 – Eigene Exception-HierarchienEine Exception-Klasse braucht Semantik, nicht nur einen anderen Namen.
Frage:

„Wie entwerfen Sie eine sinnvolle Custom-Exception-Hierarchie?“

Kurz erklärt:

Eigene Exceptions sind sinnvoll, wenn sie Fehlersemantik, Fehlercodes oder spezifische Behandlung ermöglichen. Zu viele eigene Exceptions verschlechtern jedoch Lesbarkeit.

Musterantwort:

Ich baue eigene Exceptions nur, wenn sie einen echten Nutzen haben: spezifische Behandlung, klare Fehlercodes, API-Mapping, Transaktionsverhalten oder bessere Diagnose. Eine gemeinsame Basisklasse pro fachlichem Modul kann sinnvoll sein, etwa PaymentException .

Ich vermeide eine Exception-Klasse für jeden kleinen Fehlertext. Wenn sich Fehler nur durch Message unterscheiden, reicht oft ein Fehlercode oder ein Result-Objekt. Die Hierarchie sollte fachlich stabil und nicht zu tief sein.

Senior-Antwort:

„Eine Custom Exception braucht einen Zweck. Wenn niemand sie spezifisch behandelt und sie keine zusätzliche Semantik trägt, ist sie oft nur Rauschen.“

Häufige Fehler:
  • Für jede Fehlermeldung eine Klasse bauen.
  • Nur technische Namen verwenden.
  • Keine maschinenlesbaren Fehlercodes bereitstellen.
  • Sensible Daten in Messages schreiben.
  • Basisklassen zu allgemein machen.
Best Practices:
  • Exceptions nach Behandlungsbedarf modellieren.
  • Fehlercodes maschinenlesbar halten.
  • Cause erhalten.
  • Hierarchien flach halten.
  • Keine Passwörter, Tokens oder personenbezogenen Details in Messages.
Stolperfalle:

Viele Exception-Klassen verbessern das Design nicht automatisch. Entscheidend ist, ob sie unterschiedliche Behandlung ermöglichen.

Merksatz:

Eine Exception-Klasse braucht Semantik, nicht nur einen anderen Namen.

B1-K06-Q004 – Exception Wrapping und Cause ChainWrapping übersetzt Fehler; die Cause bewahrt die Wahrheit.
Frage:

„Warum ist Exception Wrapping wichtig, und was kann dabei schiefgehen?“

Kurz erklärt:

Exception Wrapping übersetzt eine niedrigere technische Exception in eine höherwertige fachliche oder architektonische Exception. Die Cause Chain muss erhalten bleiben.

Musterantwort:

Wrapping ist wichtig, weil jede Schicht ihre eigene Sprache sprechen sollte. Ein Service sollte nicht überall SocketTimeoutException oder SQLException exponieren, wenn fachlich „Payment Gateway unavailable“ gemeint ist.

Der häufigste Fehler ist, die Cause zu verlieren. Dann sieht man im Log nur die neue Exception, aber nicht die technische Ursache. Ebenso gefährlich ist schlechtes Wrapping ohne Kontext. Eine gute Exception enthält fachlich relevante Referenzen, aber keine sensiblen Daten.

Senior-Antwort:

„Ich übersetze Exceptions an Schichtgrenzen in die Sprache der Zielschicht. Dabei verliere ich niemals die Cause, sonst zerstöre ich Diagnosefähigkeit.“

Häufige Fehler:
  • throw new X("failed") ohne Cause.
  • Technische Exceptions bis zum Controller leaken.
  • Jede Exception mehrfach wrappen.
  • Messages mit IBAN, Token oder personenbezogenen Daten füllen.
  • Kontext komplett weglassen.
Best Practices:
  • An Architekturgrenzen übersetzen.
  • Cause immer erhalten.
  • Fachliche Referenz ergänzen.
  • Keine sensiblen Daten loggen.
  • Nicht unnötig mehrfach wrappen.
Stolperfalle:

Eine neue Exception ohne Cause ist oft schlimmer als die ursprüngliche technische Exception.

Merksatz:

Wrapping übersetzt Fehler; die Cause bewahrt die Wahrheit.

B1-K06-Q005 – try-with-resources und Suppressed Exceptionstry-with-resources schützt Ressourcen und Diagnoseinformationen.
Frage:

„Wie funktioniert try-with-resources, und was sind suppressed Exceptions?“

Kurz erklärt:

try-with-resources schließt Ressourcen automatisch. Wenn sowohl im try-Block als auch beim Schließen Exceptions auftreten, werden Close-Exceptions als suppressed Exceptions an die primäre Exception gehängt.

Musterantwort:

try-with-resources ist der Standardmechanismus für Ressourcen, die AutoCloseable implementieren. Die Ressource wird am Ende des Blocks automatisch geschlossen, auch bei Exceptions.

Wenn im try-Block eine Exception geworfen wird und zusätzlich beim Schließen eine weitere Exception entsteht, bleibt die ursprüngliche Exception die primäre. Die Close-Exception wird als suppressed Exception gespeichert. Das ist besser als ein manuelles finally , bei dem die ursprüngliche Exception leicht überschrieben werden kann.

Senior-Antwort:

„try-with-resources ist mein Default für alles, was geschlossen werden muss. Es schützt nicht nur vor Leaks, sondern erhält auch die primäre Fehlerursache korrekt.“

Häufige Fehler:
  • Ressourcen manuell in finally schließen und primäre Exception überschreiben.
  • Suppressed Exceptions bei Diagnose ignorieren.
  • Ressourcen außerhalb des try-Kopfs erzeugen.
  • close() mit komplexer Businesslogik überladen.
  • Streams aus Methoden zurückgeben, ohne Ownership zu klären.
Best Practices:
  • Ressourcen immer im try-with-resources-Kopf deklarieren.
  • Suppressed Exceptions bei komplexen IO-Fehlern prüfen.
  • close() einfach und robust halten.
  • Ressourcenlebensdauer klein halten.
  • Keine externen Ressourcen in Streams vergessen.
Stolperfalle:

Die Exception aus close() ist nicht weg; sie kann als suppressed Exception an der primären Exception hängen.

Merksatz:

try-with-resources schützt Ressourcen und Diagnoseinformationen.

B1-K06-Q006 – REST-Fehler-MappingClients brauchen Fehlerverträge, keine Stacktraces.
Frage:

„Wie mappen Sie Exceptions sauber auf REST-Fehlerantworten?“

Kurz erklärt:

APIs sollten technische Exceptions nicht direkt nach außen geben. Fehlerantworten brauchen stabile Codes, verständliche Messages, Korrelation und sichere Details.

Musterantwort:

Ich mappe Exceptions zentral an der API-Grenze. Fachliche Validierungsfehler werden zu 400, Konflikte oft zu 409, fehlende Ressourcen zu 404 und technische Fehler zu 5xx. Die konkrete Zuordnung hängt von der API-Semantik ab.

Wichtig ist: Clients brauchen stabile Fehlercodes, nicht Stacktraces. Interne Details bleiben im Log. Die Response enthält eine Correlation ID, einen maschinenlesbaren Code und sichere Details. Sensible Daten gehören nicht in die Antwort.

Senior-Antwort:

„REST-Fehlerantworten sind Teil des API-Vertrags. Ich mappe intern typisierte Fehler zentral auf stabile externe Codes und vermeide technische Details in der Response.“

Häufige Fehler:
  • Stacktraces an Clients senden.
  • Unterschiedliche Fehlerformate pro Endpoint.
  • Fachliche 4xx-Fehler als 500 zurückgeben.
  • Keine maschinenlesbaren Fehlercodes verwenden.
  • Sensitive Daten in Messages ausgeben.
Best Practices:
  • Zentrales Exception Mapping.
  • Stabile Fehlercodes.
  • Einheitliches Fehlerformat.
  • Correlation ID in Response und Logs.
  • Technische Details nur intern loggen.
  • Client-Vertrag dokumentieren.
Stolperfalle:

Eine Exception Message ist kein stabiler API-Vertrag.

Merksatz:

Clients brauchen Fehlerverträge, keine Stacktraces.

B1-K06-Q007 – Exceptions und TransaktionenRollback ist Architektursemantik, nicht nur Exception-Mechanik.
Frage:

„Wie hängen Exceptions und Transaktionsrollback zusammen?“

Kurz erklärt:

Exceptions beeinflussen Transaktionsverhalten. Fachliche und technische Fehler müssen sauber von Transaktionsgrenzen getrennt werden.

Musterantwort:

Transaktionen reagieren typischerweise auf Exceptions, aber die genauen Regeln hängen vom Framework und der Konfiguration ab. Architektonisch ist wichtig: Ich werfe nicht irgendeine Exception, sondern entscheide bewusst, ob ein Fehler rollbackrelevant ist.

Ein technischer Fehler beim Speichern muss rollbacken. Eine fachliche Ablehnung wie „nicht genug Guthaben“ kann je nach Design entweder gar keine Transaktion verändern oder als fachliche Exception zum Rollback führen. Kritisch wird es, wenn innerhalb einer Transaktion externe Nebenwirkungen wie Events oder HTTP-Calls passieren. Dann braucht man Outbox, After-Commit-Hooks oder klare Kompensationsstrategien.

Senior-Antwort:

„Exceptions sind bei Transaktionen nicht nur Fehlerfluss, sondern Konsistenzsignale. Ich achte darauf, welche Exceptions Rollback auslösen und welche Nebenwirkungen erst nach Commit passieren dürfen.“

Häufige Fehler:
  • Exceptions fangen und dadurch Rollback verhindern.
  • Events vor Commit veröffentlichen.
  • HTTP-Calls innerhalb langer DB-Transaktionen ausführen.
  • Fachliche Ablehnungen unnötig als technische Fehler loggen.
  • Self-Invocation bei proxybasierter Transaktion ignorieren.
Best Practices:
  • Transaktionsgrenzen explizit designen.
  • Technische Fehler rollbacken lassen.
  • Fachliche Entscheidungen vor Mutationen prüfen.
  • Externe Nebenwirkungen nach Commit oder über Outbox.
  • Keine Exceptions verschlucken, die Rollback signalisieren sollen.
Stolperfalle:

Eine gefangene und nicht weitergeworfene Exception kann dazu führen, dass eine Transaktion committed, obwohl fachlich ein Fehler passiert ist.

Merksatz:

Rollback ist Architektursemantik, nicht nur Exception-Mechanik.

B1-K06-Q008 – Logging, Observability und KorrelationEin Fehler ohne Korrelation ist ein halber Fehlerbericht.
Frage:

„Wie loggen Sie Exceptions produktionsreif?“

Kurz erklärt:

Exception Handling ohne Observability ist unvollständig. Fehler müssen mit Kontext, Korrelation und sicheren Details diagnostizierbar sein.

Musterantwort:

Ich logge Exceptions mit Stacktrace, Correlation ID, fachlicher Referenz und technischem Kontext. Gleichzeitig achte ich darauf, keine sensiblen Daten wie Tokens, Passwörter, vollständige Kontonummern oder personenbezogene Details in Logs zu schreiben.

Wichtig ist auch das richtige Log-Level. Eine fachliche Validierungsverletzung ist nicht automatisch ERROR . Ein unerwarteter technischer Ausfall dagegen schon. Außerdem sollte eine Exception nicht auf jeder Schicht erneut als Error geloggt werden, sonst entstehen doppelte Logs und falsche Alerts.

Senior-Antwort:

„Ich logge Exceptions so, dass ein Incident analysierbar ist: mit Korrelation, Cause Chain und sicherem Kontext. Ich vermeide doppelte Error-Logs und sensible Daten.“

Häufige Fehler:
  • Exception mehrfach loggen.
  • Nur Message ohne Stacktrace loggen.
  • Sensitive Daten in Logs schreiben.
  • Alle fachlichen Fehler als ERROR loggen.
  • Keine Correlation ID verwenden.
Best Practices:
  • Einmal sinnvoll loggen.
  • Stacktrace bei technischen Fehlern.
  • Correlation ID überall durchreichen.
  • Fehlercodes strukturiert erfassen.
  • Sensitive Daten maskieren.
  • Log-Level nach Semantik wählen.
Stolperfalle:

Mehr Logging bedeutet nicht bessere Diagnose. Mehrfach geloggte Exceptions erzeugen Rauschen und falsche Alarmierung.

Merksatz:

Ein Fehler ohne Korrelation ist ein halber Fehlerbericht.

B1-K06-Q009 – Exceptions in Lambdas und StreamsFehlersemantik ist wichtiger als Stream-Eleganz.
Frage:

„Wie behandeln Sie Exceptions in Stream-Pipelines, ohne den Code unlesbar zu machen?“

Kurz erklärt:

Standard-Functional-Interfaces wie Function deklarieren keine checked Exceptions. In Stream-Pipelines muss Fehlerbehandlung deshalb bewusst modelliert werden.

Musterantwort:

Ich unterscheide erwartbare Fehler und technische Fehler. Bei erwartbaren Parse- oder Validierungsfehlern modelliere ich ein Result pro Element, damit die Pipeline nicht beim ersten Fehler abbricht. Bei unerwarteten technischen Fehlern kann Wrapping sinnvoll sein, aber mit Cause und Kontext.

Ich vermeide große try-catch-Blöcke direkt im Lambda. Wenn nötig, kapsle ich die Fehlerbehandlung in einer kleinen Adaptermethode oder schreibe bewusst eine Schleife, wenn sie lesbarer ist.

Senior-Antwort:

„In Streams vermeide ich anonyme try-catch-Blöcke. Erwartbare Fehler werden Ergebniswerte, unerwartete Fehler werden mit Kontext gewrappt oder bewusst außerhalb der Pipeline behandelt.“

Häufige Fehler:
  • RuntimeException ohne Kontext wrappen.
  • try-catch direkt in jedes Lambda schreiben.
  • Bei erwartbaren Parse-Fehlern die gesamte Pipeline abbrechen.
  • Fehlerzeile nicht im Ergebnis erfassen.
  • Exceptions als normalen Massenkontrollfluss verwenden.
Best Practices:
  • Erwartbare Fehler als Result pro Element modellieren.
  • Technische Fehler mit Cause wrappen.
  • Fehlerbehandlung extrahieren.
  • Bei komplexem Fehlerfluss Schleifen verwenden.
  • Rohdaten und Zeilennummer für Diagnose erfassen.
Stolperfalle:

Ein Stream macht Fehlerbehandlung nicht automatisch eleganter. Manchmal ist eine gute Schleife klarer als eine erzwungene Pipeline.

Merksatz:

Fehlersemantik ist wichtiger als Stream-Eleganz.

B1-K06-Q010 – Performance, Stacktraces und Anti-PatternsExceptions sind für Ausnahmefälle, nicht für den Standardpfad. 24h
Frage:

„Warum sollte man Exceptions nicht für normalen Kontrollfluss verwenden, und welche Exception-Anti-Patterns kennen Sie?“

Kurz erklärt:

Exceptions sind teuer genug, dass sie nicht als normaler Kontrollfluss missbraucht werden sollten. Gleichzeitig darf man Fehlerfälle nicht aus Performanceangst verschleiern.

Musterantwort:

Exceptions erzeugen Stacktraces und sind für außergewöhnliche Situationen gedacht. Wenn ich in einer heißen Schleife ständig Exceptions werfe, zahle ich unnötig CPU- und Speicher-Kosten und mache den Code schwerer lesbar.

Typische Anti-Patterns sind: Exceptions verschlucken, catch (Exception) ohne Behandlung, Wrapping ohne Cause, Stacktraces an Clients senden, Exceptions für erwartbare Suchergebnisse, Fehler nur loggen und dann weitermachen sowie zu breite Exception-Hierarchien.

Ich nutze Optional für legitime Abwesenheit, Result-Typen für fachliche Varianten und Exceptions für echte Vertragsverletzungen oder technische Fehler.

Senior-Antwort:

„Exceptions sind diagnostisch wertvoll, aber kein Ersatz für normale Kontrollstrukturen. Ich mache Abwesenheit, fachliche Varianten und echte Fehler jeweils explizit sichtbar.“

Häufige Fehler:
  • Exceptions für normale Suchlogik verwenden.
  • catch (Exception ignored) schreiben.
  • Stacktrace verlieren.
  • Zu breit catchen.
  • Error-Logs für erwartbare 4xx-Fälle erzeugen.
  • Exception Messages als API-Vertrag verwenden.
Best Practices:
  • Optional für legitime Abwesenheit.
  • Result-Typen für fachliche Varianten.
  • Exceptions für echte Fehler und Vertragsverletzungen.
  • Keine leeren catch-Blöcke.
  • Nicht pauschal Throwable fangen.
  • Performance-Hotspots messen.
Stolperfalle:

Eine Exception ist nicht deshalb richtig, weil sie technisch bequem ist.

Merksatz:

Exceptions sind für Ausnahmefälle, nicht für den Standardpfad.

B2 – Band 2: JVM & Performance

B2-K01 – Kapitel 1: JVM 10 Karten

B2-K01-Q001 – JVM-Architektur und AusführungsmodellDie JVM optimiert zur Laufzeit, aber erst nachdem sie gelernt hat. 24h
Frage:

„Was passiert grob, wenn eine Java-Anwendung auf der JVM startet und Code ausführt?“

Kurz erklärt:

Die JVM führt nicht Java-Quellcode aus, sondern Bytecode. Dieser wird geladen, verifiziert, interpretiert und bei häufig genutzten Codepfaden durch den JIT-Compiler optimiert.

Musterantwort:

Zuerst startet der java -Launcher eine JVM, lädt die Startklasse und ruft deren main -Methode auf. Die JVM lädt benötigte Klassen, verifiziert Bytecode, linkt Klassen und initialisiert sie bei Bedarf. Methoden werden zunächst interpretiert. Wenn Code häufig genug ausgeführt wird, sammelt die JVM Profilinformationen und kompiliert Hotspots mit dem JIT-Compiler zu optimiertem Maschinencode.

Wichtig ist: Die JVM ist keine einfache „Bytecode-Schleife“. Sie ist eine adaptive Laufzeitumgebung. Performance entsteht durch Zusammenspiel aus Bytecode, Class Loading, Heap, Stack, JIT, Garbage Collection, Safepoints und Profiling.

Senior-Antwort:

„Die JVM ist eine adaptive Laufzeitumgebung. Java-Code wird nicht einfach direkt ausgeführt, sondern als Bytecode geladen, verifiziert, interpretiert und später anhand realer Laufzeitprofile optimiert.“

Häufige Fehler:
  • JVM als reine Interpreter-Laufzeit verstehen.
  • JIT-Warm-up bei Benchmarks ignorieren.
  • Startzeit und Laufzeitperformance vermischen.
  • Framework-Startkosten der Sprache Java zuschreiben.
  • Performance ohne JVM-Diagnosedaten bewerten.
Best Practices:
  • JVM-Warm-up in Performance-Tests berücksichtigen.
  • Startzeit und steady-state Performance getrennt messen.
  • JIT, GC und Class Loading mit geeigneten Tools beobachten.
  • Framework-Bootstrapping bei Microservice-Startzeit berücksichtigen.
  • Keine Mikrobenchmarks ohne JMH-Methodik verwenden.
Stolperfalle:

Ein einmaliger lokaler Test direkt nach JVM-Start misst häufig Start- und Warm-up-Kosten, nicht die stabile Laufzeitperformance.

Merksatz:

Die JVM optimiert zur Laufzeit, aber erst nachdem sie gelernt hat.

B2-K01-Q002 – Bytecode und Class-File-FormatBytecode ist der Vertrag zwischen Java-Compiler und JVM.
Frage:

„Was ist Java-Bytecode, und warum ist er für Senior-Entwickler relevant?“

Kurz erklärt:

Java-Quellcode wird durch javac in .class -Dateien kompiliert. Diese enthalten Bytecode, Constant Pool, Methoden, Felder, Attribute und Metadaten.

Musterantwort:

Bytecode ist die plattformunabhängige Zwischendarstellung, die die JVM ausführt. Er ist relevant, weil viele Java-Eigenschaften erst auf Bytecode-Ebene klar werden: Generics durch Type Erasure, Lambdas über invokedynamic , switch -Übersetzung, Bridge Methods, Synthetic Methods, Records, Proxies und Framework-Code-Generierung.

Ich muss nicht jeden Opcode auswendig kennen, aber ich sollte Bytecode lesen können, wenn ich unerwartetes Verhalten, Performance-Fragen oder Framework-Magie analysiere.

Senior-Antwort:

„Bytecode ist die Sprache, die die JVM versteht. Ich muss nicht jeden Opcode kennen, aber ich sollte wissen, dass viele Java-Features und Framework-Mechanismen erst auf Bytecode-Ebene wirklich sichtbar werden.“

Häufige Fehler:
  • Bytecode mit Maschinencode verwechseln.
  • Annehmen, dass jedes Sprachfeature 1:1 im Bytecode sichtbar bleibt.
  • Generics als Runtime-Typmodell verstehen.
  • Bytecode-Analyse als Alltagsoptimierung missbrauchen.
  • Framework-Proxies ohne Bytecode-Verständnis debuggen wollen.
Best Practices:
  • Bytecode bei unerwartetem Compiler- oder Framework-Verhalten prüfen.
  • javap -c -v für Lern- und Diagnosezwecke nutzen.
  • Source-Lesbarkeit nicht wegen vermeintlicher Bytecode-Optimierung opfern.
  • Generierten Code bei Framework-Problemen berücksichtigen.
  • Bytecode-Tools vorsichtig in Build- und Security-Kontexten einsetzen.
Stolperfalle:

.class bedeutet nicht automatisch physische Datei. Klassen können auch dynamisch erzeugt oder von ClassLoadern aus anderen Quellen geladen werden.

Merksatz:

Bytecode ist der Vertrag zwischen Java-Compiler und JVM.

B2-K01-Q003 – Class Loading, Linking und InitializationKlassenidentität besteht aus Klassenname plus ClassLoader.
Frage:

„Was ist der Unterschied zwischen Class Loading, Linking und Initialization?“

Kurz erklärt:

Klassen werden nicht alle beim JVM-Start geladen und initialisiert. Loading, Linking und Initialization passieren dynamisch nach Bedarf.

Musterantwort:

Class Loading bringt eine Klassenrepräsentation in die JVM. Linking besteht grob aus Verification, Preparation und Resolution. Dabei wird Bytecode geprüft, statische Felder werden vorbereitet und symbolische Referenzen können aufgelöst werden. Initialization führt dann statische Initialisierer und Initialisierungen statischer Felder aus.

Senior-relevant ist, dass diese Phasen Auswirkungen auf Startzeit, Fehlerzeitpunkt, ClassLoader-Probleme, statische Initialisierung und Framework-Bootstrapping haben. Ein Fehler in einem statischen Initializer kann eine Klasse unbrauchbar machen und schwer zu diagnostizieren sein.

Senior-Antwort:

„Class Loading lädt eine Klasse, Linking macht sie JVM-seitig verwendbar, Initialization führt statische Initialisierung aus. Besonders bei Frameworks, Plugins und Startzeitproblemen ist diese Unterscheidung wichtig.“

Häufige Fehler:
  • Teure Arbeit in static Initializern ausführen.
  • ClassLoader-Probleme als „Java-Bug“ missverstehen.
  • Framework-Startzeit nur dem Code der Anwendung zuschreiben.
  • Klassenidentität ohne ClassLoader berücksichtigen.
  • Statische Zustände in Tests nicht isolieren.
Best Practices:
  • Static Initializer klein halten.
  • Teure Initialisierung explizit und beobachtbar machen.
  • Classpath und Abhängigkeiten sauber halten.
  • ClassLoader-Kontexte bei Plugins und Application Servern verstehen.
  • Startzeit mit Class-Loading-Logs oder JFR analysieren.
Stolperfalle:

Eine Klasse mit gleichem vollqualifizierten Namen ist nicht derselbe Typ, wenn sie von unterschiedlichen ClassLoadern geladen wurde.

Merksatz:

Klassenidentität besteht aus Klassenname plus ClassLoader.

B2-K01-Q004 – Runtime Data Areas: Heap, Stack und Method AreaDer JVM-Prozess besteht aus mehr als nur Heap.
Frage:

„Was liegt auf dem Heap, was auf dem Stack, und warum ist diese Unterscheidung wichtig?“

Kurz erklärt:

Die JVM besitzt mehrere Runtime Data Areas. Besonders wichtig sind Heap, Java Virtual Machine Stacks, Frames, Program Counter und Method Area.

Musterantwort:

Objekte und Arrays liegen auf dem Heap. Lokale Variablen, Operand Stack und Rücksprunginformationen einer Methode liegen in Frames auf dem Thread-Stack. Eine lokale Variable kann eine Referenz auf ein Heap-Objekt enthalten. Die Referenz liegt dann im Frame, das Objekt selbst auf dem Heap.

Wichtig ist diese Unterscheidung für Memory-Fehler, StackOverflowError, OutOfMemoryError, Escape Analysis, GC-Verhalten und Thread-Speicherbedarf. Auf Senior-Level sollte man nicht sagen: „Primitive liegen immer auf dem Stack und Objekte immer auf dem Heap“ ohne Kontext. JIT-Optimierungen können Allokationen eliminieren oder skalare Werte anders behandeln.

Senior-Antwort:

„Lokale Variablen leben in Stack Frames, Objekte typischerweise auf dem Heap. Aber für Performance ist wichtiger zu verstehen, wie lange Objekte leben, ob sie escapen und welcher Speicherbereich tatsächlich wächst.“

Häufige Fehler:
  • Referenz und Objekt verwechseln.
  • Heap und Stack zu simpel erklären.
  • Metaspace ignorieren.
  • Native Memory außerhalb des Java-Heaps vergessen.
  • GC-Probleme nur über -Xmx betrachten.
Best Practices:
  • Heap, Stack, Metaspace und Native Memory getrennt beobachten.
  • Thread-Anzahl bei Plattform-Threads ernst nehmen.
  • ClassLoader-Leaks bei Metaspace-Wachstum prüfen.
  • Objektlebensdauer statt nur Objektanzahl analysieren.
  • JFR und Native Memory Tracking für Diagnose nutzen.
Stolperfalle:

-Xmx begrenzt den Java-Heap, aber nicht den gesamten Speicherverbrauch des JVM-Prozesses.

Merksatz:

Der JVM-Prozess besteht aus mehr als nur Heap.

B2-K01-Q005 – Object Allocation und Escape AnalysisNicht jede new -Anweisung ist am Ende eine teure Heap-Allokation.
Frage:

„Was ist Escape Analysis, und warum ist sie für immutable Objekte relevant?“

Kurz erklärt:

Nicht jede im Quellcode sichtbare Objektanlage führt zwingend zu einer teuren Heap-Allokation. Die JVM kann durch Escape Analysis Allokationen optimieren oder eliminieren.

Musterantwort:

Escape Analysis prüft, ob ein Objekt den aktuellen Methoden- oder Thread-Kontext verlässt. Wenn ein Objekt nicht „escaped“, kann die JVM es möglicherweise scalar replacen oder die Allokation ganz eliminieren. Das ist ein Grund, warum kleine immutable Objekte oft günstiger sind, als Entwickler erwarten.

Das bedeutet aber nicht, dass Objektallokationen irrelevant sind. Wenn Objekte in Collections landen, zurückgegeben, in Feldern gespeichert oder an andere Threads übergeben werden, escapen sie meist. Dann entstehen echte Heap-Objekte und damit potenziell GC-Druck.

Senior-Antwort:

„Objektallokation ist auf der JVM oft günstiger als erwartet, aber nicht kostenlos. Escape Analysis kann lokale Objekte eliminieren, aber nur Messdaten zeigen, ob das im konkreten Hotpath passiert.“

Häufige Fehler:
  • Jede Objektanlage als teuer ansehen.
  • Escape Analysis als garantiert betrachten.
  • Naive Benchmarks ohne Warm-up verwenden.
  • Framework-Grenzen und Reflection ignorieren.
  • Immutability wegen Allokationsangst vermeiden.
Best Practices:
  • Kleine immutable Objekte nicht vorschnell vermeiden.
  • Allokationsrate mit JFR oder Profiler messen.
  • Hotpaths mit JMH benchmarken.
  • Große Objektgraphen und Collections bewusst gestalten.
  • Code lesbar halten, bis Messdaten Optimierung verlangen.
Stolperfalle:

Aus dem Quellcode allein kann man nicht sicher ableiten, welche Allokationen nach JIT-Optimierung tatsächlich übrig bleiben.

Merksatz:

Nicht jede new -Anweisung ist am Ende eine teure Heap-Allokation.

B2-K01-Q006 – Interpreter, JIT und Tiered CompilationDer JIT optimiert nicht den Code, den Sie geschrieben haben, sondern den Codepfad, den die A…
Frage:

„Wie funktioniert JIT-Kompilierung, und warum ist sie wichtig für Java-Performance?“

Kurz erklärt:

Die JVM startet Code häufig im Interpreter und kompiliert heiße Methoden später mit JIT-Compilern. Tiered Compilation kombiniert schnelle Startoptimierung und starke spätere Optimierung.

Musterantwort:

Die JVM führt Methoden zunächst interpretiert aus und sammelt Profilinformationen: Welche Methoden sind heiß? Welche Branches werden häufig genommen? Welche konkreten Typen treten an polymorphen Aufrufstellen auf? Wenn Code heiß genug ist, kompiliert der JIT ihn zu Maschinencode.

Dadurch kann die JVM Optimierungen durchführen, die ein statischer Compiler schwer treffen kann: Inlining anhand realer Typprofile, Devirtualisierung, Escape Analysis, Lock Elimination und Branch-Profilierung. Der Preis ist Warm-up-Zeit und gelegentliche Deoptimization, wenn Annahmen nicht mehr stimmen.

Senior-Antwort:

„JIT-Kompilierung ist der Grund, warum Java zur Laufzeit sehr schnell sein kann. Die JVM optimiert anhand realer Profile, aber genau deshalb muss man Warm-up und Messmethodik ernst nehmen.“

Häufige Fehler:
  • Warm-up ignorieren.
  • JIT mit javac verwechseln.
  • Einmalige Messungen ernst nehmen.
  • Performance im Debugger bewerten.
  • Code künstlich „JIT-freundlich“ machen, ohne Messdaten.
Best Practices:
  • JMH für Mikrobenchmarks verwenden.
  • Realistische Workloads messen.
  • Warm-up und steady state trennen.
  • JFR für JIT- und CPU-Analyse nutzen.
  • Lesbarkeit nicht ohne Messdaten opfern.
Stolperfalle:

javac kompiliert zu Bytecode; der JIT kompiliert zur Laufzeit zu Maschinencode.

Merksatz:

Der JIT optimiert nicht den Code, den Sie geschrieben haben, sondern den Codepfad, den die Anwendung tatsächlich nimmt.

B2-K01-Q007 – Garbage Collection aus JVM-PerspektiveGarbage Collection verwaltet Speicher, aber sie versteht nicht Ihre fachliche Lebensdauer.
Frage:

„Was muss ein Senior-Entwickler über Garbage Collection wissen, auch wenn ein eigenes GC-Kapitel später folgt?“

Kurz erklärt:

Garbage Collection verwaltet Heap-Speicher automatisch. Sie befreit Entwickler von explizitem free , aber nicht von Verantwortung für Objektlebensdauer, Allokationsrate und Speicherlecks.

Musterantwort:

Ich muss verstehen, dass GC nur Objekte freigeben kann, die nicht mehr erreichbar sind. Wenn eine Collection, ein Cache, ein ThreadLocal oder ein Listener noch Referenzen hält, bleibt das Objekt live. Ein Memory Leak in Java ist also meist kein vergessener free , sondern eine unbeabsichtigte Referenz.

Außerdem ist Allokationsrate wichtig. Viele kurzlebige Objekte sind oft okay, aber dauerhaft wachsende Objektgraphen oder große Buffers können GC-Druck und Pausen erzeugen. GC-Probleme analysiere ich mit Logs, JFR, Heap Dumps und Metriken, nicht durch Vermutungen.

Senior-Antwort:

„GC entfernt nur nicht mehr erreichbare Objekte. Java-Leaks entstehen meist durch unbeabsichtigte Referenzen, nicht durch fehlendes free .“

Häufige Fehler:
  • GC als Lösung für alle Speicherprobleme sehen.
  • Referenzen in Caches unbegrenzt halten.
  • ThreadLocals nicht entfernen.
  • Heap Dumps erst nach OOM einplanen.
  • Nur maximale Heap-Größe erhöhen, ohne Ursache zu analysieren.
Best Practices:
  • Caches begrenzen.
  • Objektlebensdauer bewusst gestalten.
  • ThreadLocal-Nutzung kontrollieren.
  • GC-Logs und JFR aktivieren.
  • Heap Dumps bei OOM automatisiert sichern.
  • Live-Set statt nur Allocation Rate betrachten.
Stolperfalle:

Ein Objekt ist nicht leakfrei, nur weil Java Garbage Collection hat.

Merksatz:

Garbage Collection verwaltet Speicher, aber sie versteht nicht Ihre fachliche Lebensdauer.

B2-K01-Q008 – JVM Diagnostics: jcmd, JFR, Thread Dumps und Heap DumpsOhne Diagnosedaten ist JVM-Performance nur Spekulation.
Frage:

„Welche JVM-Diagnosetools verwenden Sie in Produktion, und wofür?“

Kurz erklärt:

JVM-Diagnosewerkzeuge helfen, Produktionsprobleme datenbasiert zu analysieren: CPU, GC, Threads, Locks, Allocation, Class Loading, Exceptions und IO.

Musterantwort:

Ich nutze jcmd , JFR, Thread Dumps, Heap Dumps, GC-Logs und Metriken. jcmd ist ein zentrales Werkzeug, um Diagnosebefehle an eine laufende JVM zu senden. JFR ist besonders wertvoll, weil es viele JVM- und Anwendungsereignisse mit niedrigem Overhead erfassen kann.

Thread Dumps nutze ich für Deadlocks, Blockierungen und Thread-Pool-Probleme. Heap Dumps nutze ich für Memory-Leaks und Retained-Size-Analyse. GC-Logs nutze ich für Pause Times, Allocation Rate und Heap-Verhalten. Wichtig ist: Nicht erst im Incident überlegen, wie man Daten sammelt.

Senior-Antwort:

„Ich analysiere JVM-Probleme datenbasiert. JFR, jcmd, Thread Dumps, Heap Dumps und GC-Logs sind Standardwerkzeuge, die vor dem Incident einsatzbereit sein müssen.“

Häufige Fehler:
  • Diagnosedaten erst nach dem Incident konfigurieren.
  • Heap Dump ohne Datenschutzbetrachtung speichern.
  • Thread Dumps ohne Zeitserie interpretieren.
  • GC-Logs nicht aktivieren.
  • Nur Applikationslogs betrachten und JVM-Signale ignorieren.
Best Practices:
  • Diagnose-Runbooks vorbereiten.
  • JFR für reproduzierbare Performanceprobleme nutzen.
  • Thread Dumps mehrfach im Abstand aufnehmen.
  • Heap Dumps sicher speichern und löschen.
  • GC-Logs strukturiert erfassen.
  • Eigene JFR-Events für kritische Fachpfade prüfen.
Stolperfalle:

Ein einzelner Thread Dump ist oft nur ein Schnappschuss. Für Blockierungsanalyse braucht man häufig mehrere Dumps über Zeit.

Merksatz:

Ohne Diagnosedaten ist JVM-Performance nur Spekulation.

B2-K01-Q009 – JVM-Optionen, Container und SpeichergrenzenDer Heap ist nur ein Teil des JVM-Prozesses.
Frage:

„Wie konfigurieren Sie JVM-Speicher in Kubernetes oder Docker?“

Kurz erklärt:

JVM-Tuning in Containern bedeutet nicht nur -Xmx setzen. Der gesamte JVM-Prozess muss in CPU- und Memory-Limits passen.

Musterantwort:

Ich betrachte zuerst das Container-Memory-Limit und dann den gesamten JVM-Prozess: Heap, Metaspace, Code Cache, Thread Stacks, Direct Buffers, GC-Strukturen, native Libraries und Agenten. -Xmx begrenzt nur den Heap. Wenn der Container 1 GiB hat und ich -Xmx1g setze, bleibt zu wenig Platz für alles außerhalb des Heaps.

In modernen JVMs kann man mit MaxRAMPercentage arbeiten oder explizit -Xms / -Xmx setzen. Entscheidend ist, die Einstellungen mit realen Metriken zu validieren. Bei Kubernetes-OOMKills muss man zwischen Java- OutOfMemoryError und Container-OOM unterscheiden.

Senior-Antwort:

„Im Container dimensioniere ich nicht nur den Heap, sondern den gesamten JVM-Prozess. -Xmx ist wichtig, aber Metaspace, Threads, Direct Memory und Agenten entscheiden oft über OOMKills.“

Häufige Fehler:
  • -Xmx gleich Container-Limit setzen.
  • Metaspace und Direct Memory ignorieren.
  • Container-OOM mit Java-OOM verwechseln.
  • Keine GC- und Memory-Metriken erfassen.
  • CPU-Limits ohne Latenztests setzen.
Best Practices:
  • Heap bewusst unter Container-Limit halten.
  • Native Memory mit einplanen.
  • Metriken für Heap, Non-Heap, Threads und GC erfassen.
  • OOMKill und Java-OOM getrennt analysieren.
  • MaxRAMPercentage oder explizite Xmx -Strategie standardisieren.
  • Lasttests mit realistischen Limits fahren.
Stolperfalle:

Ein Prozess kann vom Container gekillt werden, ohne dass die JVM vorher einen Java- OutOfMemoryError werfen kann.

Merksatz:

Der Heap ist nur ein Teil des JVM-Prozesses.

B2-K01-Q010 – JVM Logging und produktionsreife DiagnosekonfigurationWas die JVM nicht protokolliert, können Sie im Incident nur erraten. 24h
Frage:

„Welche JVM-Logs würden Sie in Produktion aktivieren, und worauf achten Sie dabei?“

Kurz erklärt:

JVM-Logging ist ein eigener Diagnosekanal neben Applikationslogs. Unified Logging mit -Xlog kann GC, Class Loading, Safepoints, JIT und viele weitere JVM-Komponenten sichtbar machen.

Musterantwort:

Mindestens GC-Logging sollte in produktionsnahen Umgebungen verfügbar sein. Bei Startzeit- oder ClassLoader-Problemen aktiviere ich Class-Loading-Logs. Bei Latenzspitzen können Safepoint-Logs, JFR und GC-Logs helfen. Wichtig ist, Logs nicht pauschal auf maximaler Detailtiefe laufen zu lassen, sondern zielgerichtet, rotierend und mit Retention-Konzept.

Außerdem dokumentiere ich JVM-Startparameter. In Incidents ist oft unklar, mit welchen Optionen ein Service wirklich gestartet wurde. Ein Diagnose-Endpunkt oder Startup-Log mit JVM-Argumenten ist sehr hilfreich.

Senior-Antwort:

„JVM-Logs sind ein eigener Observability-Kanal. Ich aktiviere sie zielgerichtet, rotiert und standardisiert, damit GC, Safepoints und Class Loading im Incident nicht unsichtbar bleiben.“

Häufige Fehler:
  • JVM-Logs gar nicht erfassen.
  • Alte GC-Flags ungeprüft in Java 21 übernehmen.
  • Zu verbose Logging dauerhaft aktivieren.
  • Logs ohne Rotation in Container schreiben.
  • JVM-Argumente im Incident nicht kennen.
Best Practices:
  • GC-Logging standardisieren.
  • Log-Rotation konfigurieren.
  • JFR für tiefe Analyse vorbereiten.
  • JVM-Startargumente dokumentieren.
  • -Xlog:help zur verfügbaren Tag-Analyse nutzen.
  • Diagnosekonfiguration pro Umgebung definieren.
Stolperfalle:

Applikationslogs zeigen nicht, warum die JVM durch GC, Safepoints oder Code-Cache-Probleme pausiert.

Merksatz:

Was die JVM nicht protokolliert, können Sie im Incident nur erraten.

B2-K02 – Kapitel 2: Memory Model 10 Karten

B2-K02-Q001 – Happens-before und SichtbarkeitHappens-before ist die Währung der Sichtbarkeit. 24h
Frage:

„Was bedeutet happens-before im Java Memory Model, und warum ist es wichtiger als die tatsächliche zeitliche Reihenfolge?“

Kurz erklärt:

happens-before ist die zentrale Ordnung im Java Memory Model. Wenn Aktion A happens-before Aktion B ist, sind die Effekte von A für B sichtbar, sofern die betroffenen Daten korrekt über diese Beziehung veröffentlicht werden.

Musterantwort:

happens-before bedeutet nicht einfach „passiert zeitlich vorher“. Es ist eine formale Sichtbarkeits- und Ordnungsbeziehung. Wenn ein Write happens-before einem Read ist, darf der Read die Effekte dieses Writes sehen. Ohne solche Beziehung können andere Threads veraltete, teilweise sichtbare oder überraschende Werte beobachten.

Typische happens-before-Beziehungen entstehen durch synchronized , volatile , Thread.start , Thread.join , Executor-Submission, Future.get , Locks, Latches und Concurrent Collections. Deshalb verlasse ich mich nicht auf Timing, sleep oder zufällige CPU-Reihenfolge, sondern auf definierte Synchronisationsmechanismen.

Senior-Antwort:

„Ich denke bei nebenläufigem Java zuerst in happens-before-Beziehungen. Wenn ich keine definierte Beziehung zwischen Write und Read habe, habe ich keine verlässliche Sichtbarkeit.“

Häufige Fehler:
  • Auf zufällige Timing-Effekte vertrauen.
  • sleep als Synchronisation verwenden.
  • Shared State ohne klare Veröffentlichung lesen.
  • Lokale Tests als Beweis für Thread-Safety betrachten.
  • Happens-before nur mit synchronized verbinden.
Best Practices:
  • Sichtbarkeit über definierte Synchronisationsmechanismen herstellen.
  • Immutable Daten über sichere Kanäle veröffentlichen.
  • Executor-, Future- und Concurrent-Collection-Garantien nutzen.
  • Keine Timing-basierten Synchronisationslösungen bauen.
  • Nebenläufigkeit mit Tests und Tools ergänzen, aber nicht nur durch Tests beweisen wollen.
Stolperfalle:

Zeitlich früher heißt nicht automatisch sichtbar früher.

Merksatz:

Happens-before ist die Währung der Sichtbarkeit.

B2-K02-Q002 – Data Races und korrekt synchronisierte ProgrammeData-Race-frei ist die Mindestanforderung für verlässliche Nebenläufigkeit.
Frage:

„Was ist ein Data Race, und warum sind Data-Race-freie Programme im Java Memory Model so wichtig?“

Kurz erklärt:

Ein Data Race entsteht, wenn zwei Threads ohne happens-before-Beziehung auf dieselbe Variable zugreifen und mindestens einer schreibt.

Musterantwort:

Ein Data Race liegt vor, wenn zwei Threads auf dieselbe shared Variable zugreifen, mindestens ein Zugriff ein Write ist und die Zugriffe nicht durch happens-before geordnet sind. Dann darf das Programm Ergebnisse zeigen, die intuitiv falsch wirken.

Das Java Memory Model gibt korrekt synchronisierten Programmen eine starke Garantie: Sie erscheinen sequentiell konsistent. Das bedeutet nicht, dass die JVM intern nicht optimiert oder reorderd. Es bedeutet, dass korrekt synchronisierter Code so beobachtbar ist, als würden die Aktionen in einer sinnvollen Reihenfolge passieren.

Senior-Antwort:

„Ein Data Race ist nicht nur ein Timing-Risiko, sondern ein Bruch des Memory-Model-Vertrags. Ich vermeide es durch klare Synchronisation, Atomics oder immutable Übergaben.“

Häufige Fehler:
  • Data Race und Race Condition synonym verwenden.
  • volatile für zusammengesetzte Operationen wie ++ verwenden.
  • Korrektheit aus lokalen Tests ableiten.
  • Shared Mutable State ohne Ownership-Regeln verwenden.
  • Atomics für mehrstufige Invarianten überschätzen.
Best Practices:
  • Shared Mutable State minimieren.
  • Immutable Daten bevorzugen.
  • Atomics für einfache einzelne Werte verwenden.
  • Locks oder höhere Abstraktionen für zusammengesetzte Invarianten nutzen.
  • Concurrency-Design dokumentieren.
Stolperfalle:

volatile macht einzelne Reads und Writes sichtbar, aber keine zusammengesetzte Operation atomar.

Merksatz:

Data-Race-frei ist die Mindestanforderung für verlässliche Nebenläufigkeit.

B2-K02-Q003 – volatile: Sichtbarkeit ohne gegenseitigen Ausschlussvolatile ist Sichtbarkeit, nicht Exklusivität.
Frage:

„Was garantiert volatile in Java, und was garantiert es ausdrücklich nicht?“

Kurz erklärt:

volatile stellt Sichtbarkeit und Ordnung für einzelne Variable sicher, aber keinen gegenseitigen Ausschluss und keine atomare Zusammensetzung mehrerer Operationen.

Musterantwort:

volatile garantiert, dass Writes auf ein volatile Feld für spätere Reads dieses Feldes sichtbar werden. Außerdem verhindert es bestimmte Reorderings um den volatile Zugriff herum. Es ist gut für Statusflags, sichere Veröffentlichung einfacher Referenzen oder Konfigurationswechsel.

Es garantiert aber keinen gegenseitigen Ausschluss. volatile int count; count++ ist weiterhin nicht atomar, weil Lesen, Erhöhen und Schreiben drei Schritte sind. Für Zähler nutze ich Atomics oder Locks. Für mehrere zusammenhängende Felder brauche ich ebenfalls eine stärkere Synchronisationsstrategie oder ein immutable Snapshot-Objekt.

Senior-Antwort:

„ volatile löst Sichtbarkeit und Ordnung für einzelne Variablen, aber keine atomaren Workflows. Für Statusflags ist es gut, für Invarianten reicht es nicht.“

Häufige Fehler:
  • volatile als Lock-Ersatz verwenden.
  • Mehrere Felder über volatile einzeln koordinieren.
  • Compound Actions mit volatile absichern wollen.
  • Visibility und Atomicity verwechseln.
  • Busy-Waiting ohne Backoff oder Blocking betreiben.
Best Practices:
  • volatile für einfache Statusflags verwenden.
  • Für Zähler Atomics oder LongAdder verwenden.
  • Für Invarianten über mehrere Felder Locks oder immutable Snapshots nutzen.
  • Volatile nicht als universelle Thread-Safety-Lösung verkaufen.
  • In Hotloops CPU-Verbrauch messen.
Stolperfalle:

volatile macht aus ++ keine atomare Operation.

Merksatz:

volatile ist Sichtbarkeit, nicht Exklusivität.

B2-K02-Q004 – synchronized, Monitore und Memory Barrierssynchronized schützt Invarianten nur, wenn alle Beteiligten denselben Monitor respektieren.
Frage:

„Was garantiert synchronized im Java Memory Model?“

Kurz erklärt:

synchronized verbindet Mutual Exclusion mit Sichtbarkeitsgarantien. Beim Verlassen eines synchronisierten Blocks wird der Monitor freigegeben; ein späterer Eintritt in denselben Monitor sieht die vorherigen Effekte.

Musterantwort:

synchronized garantiert zwei Dinge: gegenseitigen Ausschluss und Sichtbarkeit. Nur ein Thread kann denselben Monitor gleichzeitig halten. Außerdem happens-before ein Unlock auf einem Monitor jedem späteren Lock auf demselben Monitor. Dadurch sieht ein Thread, der denselben Lock nimmt, die Änderungen des vorherigen Threads.

Das ist ein wichtiger Unterschied zu volatile : volatile hat Sichtbarkeit, aber keine Exklusivität. synchronized schützt daher auch zusammengesetzte Invarianten, solange alle Zugriffe konsequent denselben Lock verwenden.

Senior-Antwort:

„ synchronized ist nicht veraltet. Es ist ein klarer Mechanismus für Mutual Exclusion und Sichtbarkeit. Entscheidend ist, Lock-Scope und Invarianten sauber zu gestalten.“

Häufige Fehler:
  • Einen Lock für Writes verwenden, aber Reads ohne Lock ausführen.
  • Auf unterschiedlichen Lock-Objekten synchronisieren.
  • Lange IO-Operationen im Lock ausführen.
  • synchronized(this) in öffentlich zugänglichen Objekten unbedacht nutzen.
  • Deadlocks durch inkonsistente Lock-Reihenfolge erzeugen.
Best Practices:
  • Lock-Objekte privat halten.
  • Kritische Abschnitte kurz halten.
  • Alle Invariantenzugriffe über denselben Lock schützen.
  • Keine externen Calls im Lock.
  • Bei komplexerer Nebenläufigkeit höhere Abstraktionen prüfen.
Stolperfalle:

Ein Lock schützt nur Code, der denselben Lock tatsächlich verwendet.

Merksatz:

synchronized schützt Invarianten nur, wenn alle Beteiligten denselben Monitor respektieren.

B2-K02-Q005 – Safe Publication und immutable ObjekteEin Objekt muss nicht nur immutable sein, es muss auch sicher sichtbar werden.
Frage:

„Was ist Safe Publication, und warum reichen immutable Objekte allein nicht immer aus?“

Kurz erklärt:

Safe Publication bedeutet, dass ein Objekt so an andere Threads übergeben wird, dass sie seinen vollständig konstruierten Zustand sehen.

Musterantwort:

Safe Publication bedeutet, dass eine Referenz auf ein Objekt so veröffentlicht wird, dass andere Threads nicht nur die Referenz sehen, sondern auch den korrekt konstruierten Objektzustand. Gute Wege sind final -Felder, Veröffentlichung über volatile , synchronized , Concurrent Collections, Executor-Submission oder statische Initialisierung.

Immutable Objekte helfen stark, besonders mit final -Feldern. Aber wenn this während des Konstruktors entkommt oder mutable Komponenten nicht kopiert werden, ist die Immutability gebrochen. Außerdem muss die Referenz selbst sicher an andere Threads gelangen.

Senior-Antwort:

„Immutable Objekte sind sehr hilfreich, aber sie müssen korrekt konstruiert und sicher veröffentlicht werden. Eine finale Referenz auf mutable Daten ist keine Immutability.“

Häufige Fehler:
  • this im Konstruktor registrieren.
  • Mutable Collections in final Feldern speichern.
  • Referenzen unsicher in statischen Variablen veröffentlichen.
  • Immutability mit finaler Referenz verwechseln.
  • Framework-Reflection und Deserialisierung ignorieren.
Best Practices:
  • Konstruktor vollständig validieren.
  • this nicht im Konstruktor entkommen lassen.
  • Mutable Komponenten kopieren.
  • Immutable Snapshots sicher veröffentlichen.
  • Für dynamische Snapshots volatile oder AtomicReference nutzen.
Stolperfalle:

final List<T> bedeutet nur, dass die Listenreferenz final ist. Die Liste selbst kann mutable sein, wenn sie nicht kopiert wurde.

Merksatz:

Ein Objekt muss nicht nur immutable sein, es muss auch sicher sichtbar werden.

B2-K02-Q006 – Double-Checked Locking und Lazy InitializationDouble-Checked Locking braucht volatile oder es ist kein Double-Checked Safety.
Frage:

„Warum war Double-Checked Locking früher problematisch, und wie implementiert man es heute korrekt?“

Kurz erklärt:

Double-Checked Locking ist nur korrekt, wenn die gemeinsam genutzte Referenz korrekt sichtbar gemacht wird, typischerweise mit volatile .

Musterantwort:

Das Problem war, dass ohne korrekte Sichtbarkeitsgarantien ein Thread eine Referenz auf ein Objekt sehen konnte, dessen Konstruktion aus seiner Sicht noch nicht vollständig sichtbar war. Dann sah der Code instance != null , aber der Objektzustand war nicht sicher publiziert.

Heute kann Double-Checked Locking mit einer volatile Referenz korrekt sein, weil der volatile Write nach Konstruktion und der volatile Read beim Zugriff eine happens-before-Beziehung herstellen. Trotzdem verwende ich das Pattern sparsam. Für statische Singletons ist der Initialization-on-demand Holder oft einfacher. Für Spring Beans überlasse ich Lifecycle und Lazy Initialization häufig dem Container.

Senior-Antwort:

„Double-Checked Locking ist heute mit volatile korrekt implementierbar, aber ich verwende es nur, wenn Lazy Initialization wirklich nötig ist. Einfachere Lifecycle-Mechanismen sind oft besser.“

Häufige Fehler:
  • Double-Checked Locking ohne volatile.
  • Komplexe Initialisierung im Lock mit externen Calls.
  • Exceptions während Initialisierung nicht behandeln.
  • Lazy Initialization verwenden, obwohl Eager einfacher wäre.
  • Mutable Objekte lazy veröffentlichen.
Best Practices:
  • DCL nur bewusst und mit volatile .
  • Lokale Variable nutzen, um wiederholte volatile Reads zu reduzieren.
  • Initialization-on-demand Holder für statische Fälle bevorzugen.
  • Immutable Ergebnisobjekte veröffentlichen.
  • Framework-Lifecycle nutzen, wenn vorhanden.
Stolperfalle:

Ohne volatile kann ein anderer Thread eine Referenz sehen, bevor der Objektzustand für ihn korrekt sichtbar ist.

Merksatz:

Double-Checked Locking braucht volatile oder es ist kein Double-Checked Safety.

B2-K02-Q007 – Atomic-Klassen, CAS und Lock-free DenkenAtomics sind hervorragend für einzelne Zustandsübergänge, nicht für ganze Geschäftsprozesse.
Frage:

„Wie funktionieren Atomic-Klassen, und wann sind sie besser als Locks?“

Kurz erklärt:

Atomic-Klassen bieten atomare Operationen auf einzelnen Variablen. CAS erlaubt bedingte Updates ohne expliziten Lock.

Musterantwort:

Atomic-Klassen kapseln atomare Operationen wie get , set , compareAndSet , incrementAndGet oder updateAndGet . Intern basieren viele Operationen auf Compare-and-Set: Aktualisiere nur, wenn der aktuelle Wert noch dem erwarteten Wert entspricht.

Sie sind gut für einzelne Werte, Counters, Referenz-Snapshots oder einfache State Machines. Für komplexe Invarianten über mehrere Felder sind Locks oft klarer. Lock-free Code ist nicht automatisch einfacher oder schneller; unter hoher Contention können CAS-Loops viel CPU verbrennen.

Senior-Antwort:

„Atomic-Klassen sind stark für einzelne atomare Zustandsübergänge. Sobald mehrere Felder gemeinsam konsistent sein müssen, ist ein Lock oder ein immutable Snapshot oft besser.“

Häufige Fehler:
  • Atomics für komplexe Multi-Field-Invarianten verwenden.
  • CAS-Loops ohne Abbruch- oder Backoff-Überlegung.
  • ABA-Probleme ignorieren.
  • Mutable Objekte in AtomicReference speichern.
  • Lock-free mit wait-free verwechseln.
Best Practices:
  • Atomic-Klassen für einzelne Zustandswerte nutzen.
  • Immutable Werte in AtomicReference speichern.
  • CAS-Loops klein halten.
  • Bei hoher Contention messen.
  • Für komplexe Invarianten Locks oder Transaktionen verwenden.
Stolperfalle:

Lock-free bedeutet nicht automatisch schneller und schon gar nicht einfacher.

Merksatz:

Atomics sind hervorragend für einzelne Zustandsübergänge, nicht für ganze Geschäftsprozesse.

B2-K02-Q008 – Reordering, CPU Caches und falsche IntuitionOhne Synchronisation ist Quellcode-Reihenfolge keine Thread-Kommunikation.
Frage:

„Warum kann unsynchronisierter Code Ergebnisse zeigen, die gegen unsere Intuition verstoßen?“

Kurz erklärt:

Ohne Synchronisation darf ein Thread nicht davon ausgehen, dass Writes eines anderen Threads in Quellcode-Reihenfolge sichtbar werden.

Musterantwort:

Weil Java keine „Quellcode-Reihenfolge über Threads hinweg“ garantiert, wenn keine happens-before-Beziehung existiert. Der Compiler, die JVM, der JIT, die CPU und Cache-Hierarchien dürfen Optimierungen durchführen. Innerhalb eines einzelnen Threads bleibt das Verhalten so, als wäre die Reihenfolge korrekt, aber andere Threads dürfen ohne Synchronisation andere Sichtweisen haben.

Im Beispiel kann ein Leser ready == true sehen, ohne garantiert data == 42 zu sehen, wenn ready nicht volatile ist und keine andere Synchronisation existiert. Die Lösung ist nicht Hoffnung, sondern sichere Veröffentlichung: volatile , Lock, AtomicReference oder immutable Snapshot.

Senior-Antwort:

„Reordering ist keine Kuriosität, sondern Teil des Optimierungsmodells. Wenn mein Code keine happens-before-Beziehung hat, darf ich mich auf Sichtbarkeit und Reihenfolge nicht verlassen.“

Häufige Fehler:
  • „Auf x86 passiert das nicht“ als Java-Argument verwenden.
  • Quellcode-Reihenfolge mit Sichtbarkeitsordnung verwechseln.
  • Unsynchronisierte Flags verwenden.
  • Benchmarks als Korrektheitsbeweis nutzen.
  • CPU-Details über JMM-Verträge stellen.
Best Practices:
  • Gegen das Java Memory Model programmieren, nicht gegen eine konkrete CPU.
  • Shared State sicher publizieren.
  • Immutable Snapshots verwenden.
  • Unsynchronisierte Flags vermeiden.
  • Concurrency-Code möglichst einfach halten.
Stolperfalle:

Dass ein unsynchronisierter Test 10.000-mal funktioniert, beweist keine Korrektheit.

Merksatz:

Ohne Synchronisation ist Quellcode-Reihenfolge keine Thread-Kommunikation.

B2-K02-Q009 – Concurrent Collections und Memory ConsistencyConcurrent Collections transportieren Daten und Sichtbarkeit.
Frage:

„Warum ist eine BlockingQueue eine sichere Art, Daten zwischen Threads zu übergeben?“

Kurz erklärt:

java.util.concurrent -Datenstrukturen sind nicht nur thread-safe im Sinne atomarer Operationen. Viele liefern auch Memory-Consistency-Garantien für die Übergabe von Objekten zwischen Threads.

Musterantwort:

Eine BlockingQueue kapselt Synchronisation, Thread-Safety und Sichtbarkeit. Wenn ein Producer ein Objekt vollständig erstellt und in eine BlockingQueue legt, garantiert die java.util.concurrent -Memory-Consistency-Semantik, dass Aktionen vor dem Einfügen für einen Consumer sichtbar sind, der das Element später aus der Queue entnimmt.

Das ist besser als eine selbst gebaute Liste plus Flag, weil die Queue nicht nur Daten speichert, sondern einen klaren Synchronisationsvertrag liefert.

Senior-Antwort:

„Concurrent Collections sind Synchronisationswerkzeuge. Sie übergeben nicht nur Referenzen, sondern definieren auch Sichtbarkeit zwischen Producer und Consumer.“

Häufige Fehler:
  • Objekte nach dem Enqueue weiter mutieren.
  • Unbounded Queues in produktiven Pipelines verwenden.
  • Normale ArrayList zwischen Threads teilen.
  • Memory Consistency und atomare Business-Transaktion verwechseln.
  • Queue-Größe nicht überwachen.
Best Practices:
  • Immutable Messages über Queues übergeben.
  • Bounded Queues für Backpressure prüfen.
  • Queue-Länge metrisch überwachen.
  • Keine nachträgliche Mutation veröffentlichter Objekte.
  • java.util.concurrent -Abstraktionen statt eigener Low-Level-Lösungen bevorzugen.
Stolperfalle:

Die Queue veröffentlicht die Referenz sicher, schützt aber nicht vor späterer Mutation des Objekts durch den Producer.

Merksatz:

Concurrent Collections transportieren Daten und Sichtbarkeit.

B2-K02-Q010 – VarHandle und feinere SpeicherzugriffsmodiVarHandle ist Präzisionswerkzeug, kein Alltagswerkzeug. 24h
Frage:

„Was ist ein VarHandle, und wann würden Sie ihn verwenden?“

Kurz erklärt:

VarHandle ist ein Low-Level-Mechanismus für stark typisierte Zugriffe auf Variablen mit verschiedenen Speicherzugriffsmodi.

Musterantwort:

Ein VarHandle ist eine typisierte Referenz auf eine Variable, etwa ein Feld, statisches Feld oder Array-Element. Er erlaubt verschiedene Zugriffsmodes: plain, opaque, acquire/release, volatile und atomare Operationen wie compare-and-set.

Ich verwende VarHandles selten im normalen Business-Code. Sie sind eher für Frameworks, Datenstrukturen, Off-Heap- oder hochoptimierte Concurrency-Komponenten relevant. In Anwendungslogik nutze ich meist volatile , Atomic* , Locks oder Concurrent Collections, weil sie lesbarer und sicherer sind.

Senior-Antwort:

„VarHandle ist ein Low-Level-Werkzeug für kontrollierte Speicherzugriffe. Im Business-Code vermeide ich es meistens; in Bibliotheken und hochoptimierten Datenstrukturen kann es sinnvoll sein.“

Häufige Fehler:
  • VarHandles aus Neugier im Business-Code verwenden.
  • Speicherzugriffsmodi nicht exakt verstehen.
  • Atomics ohne Not durch VarHandles ersetzen.
  • Low-Level-Code ohne Stress-Tests und Reviews schreiben.
  • Korrektheit für Mikroperformance opfern.
Best Practices:
  • VarHandles nur in Low-Level-Komponenten nutzen.
  • Höhere Abstraktionen bevorzugen.
  • Speichersemantik dokumentieren.
  • Mit Concurrency-Stress-Tests absichern.
  • Code Reviews durch erfahrene JVM-/Concurrency-Entwickler verlangen.
Stolperfalle:

Mehr Kontrolle über Speicherordnung bedeutet auch mehr Verantwortung für Korrektheit.

Merksatz:

VarHandle ist Präzisionswerkzeug, kein Alltagswerkzeug.

B2-K03 – Kapitel 3: Garbage Collection 10 Karten

B2-K03-Q001 – Reachability, GC Roots und ObjektlebensdauerGarbage Collection befreit Speicher nach Erreichbarkeit, nicht nach Absicht. 24h
Frage:

„Wie entscheidet die JVM, ob ein Objekt durch Garbage Collection freigegeben werden darf?“

Kurz erklärt:

Garbage Collection entfernt nicht „alte“ Objekte, sondern nicht mehr erreichbare Objekte. Solange ein Objekt über GC Roots erreichbar ist, bleibt es live.

Musterantwort:

Die JVM entscheidet über Erreichbarkeit. Ein Objekt kann freigegeben werden, wenn es von keinem GC Root mehr erreichbar ist. GC Roots sind zum Beispiel Referenzen aus Thread-Stacks, statische Felder, JNI-Referenzen und bestimmte JVM-interne Strukturen.

Wichtig ist: Die Garbage Collection kennt keine fachliche Lebensdauer. Wenn ein Objekt in einer Map, einem Cache, einem ThreadLocal oder einem Listener weiter referenziert wird, bleibt es erreichbar, auch wenn es fachlich nicht mehr gebraucht wird. Java verhindert also viele klassische Speicherfehler, aber keine Speicherlecks durch gehaltene Referenzen.

Senior-Antwort:

„Die GC sammelt nicht, was fachlich unnötig ist, sondern was nicht mehr erreichbar ist. Speicherlecks in Java sind meistens unbeabsichtigte Referenzen.“

Häufige Fehler:
  • Annehmen, dass die GC fachlich ungenutzte Objekte erkennt.
  • Unbounded Maps oder Caches verwenden.
  • ThreadLocals nicht entfernen.
  • Listener nicht deregistrieren.
  • Heap-Probleme nur durch mehr Heap lösen wollen.
Best Practices:
  • Objekt-Ownership klar definieren.
  • Caches begrenzen.
  • Sessions und Registries aktiv bereinigen.
  • ThreadLocals in Pools sauber entfernen.
  • Heap Dumps mit Dominator Tree und Retained Size analysieren.
Stolperfalle:

Ein Objekt kann fachlich tot sein und trotzdem technisch live bleiben.

Merksatz:

Garbage Collection befreit Speicher nach Erreichbarkeit, nicht nach Absicht.

B2-K03-Q002 – Generational Hypothesis, Young Generation und PromotionDie GC liebt Objekte, die jung sterben.
Frage:

„Was ist die Generational Hypothesis, und warum ist sie für Garbage Collection wichtig?“

Kurz erklärt:

Viele Garbage Collectors nutzen die Beobachtung, dass die meisten Objekte jung sterben. Deshalb wird der Heap oft logisch in junge und alte Bereiche organisiert.

Musterantwort:

Die Generational Hypothesis besagt vereinfacht: Die meisten Objekte sterben jung, und Objekte, die lange genug überleben, leben oft noch länger. Deshalb konzentrieren generational Collectors ihre Arbeit stark auf junge Objekte.

In Java-Anwendungen entstehen viele temporäre Objekte: DTOs, Parser-Zwischenwerte, Stream-Objekte, Strings, Iteratoren oder Request-spezifische Daten. Wenn diese schnell sterben, kann die GC sie effizient in Young Collections beseitigen. Problematisch wird es, wenn temporäre Objekte versehentlich länger leben und in ältere Bereiche promoted werden.

Senior-Antwort:

„Generational GC ist effizient, wenn temporäre Objekte wirklich temporär bleiben. Ich achte deshalb nicht nur auf Allokationen, sondern vor allem auf Überlebensrate und Live Set.“

Häufige Fehler:
  • Jede Objektanlage als gleich teuer betrachten.
  • Große temporäre Listen unnötig lange halten.
  • Batch-Größe ohne Memory-Profil wählen.
  • Zwischenobjekte in globalen Collections speichern.
  • Allocation Rate und Live Set verwechseln.
Best Practices:
  • Objektlebensdauer analysieren.
  • Große Batches begrenzen oder streamen.
  • Temporäre Daten nicht unnötig speichern.
  • Allocation Rate und Promotion Rate beobachten.
  • Speicherprofile mit realistischen Datenmengen erstellen.
Stolperfalle:

Viele kurzlebige Objekte sind oft weniger schlimm als wenige unkontrolliert langlebige Objektgraphen.

Merksatz:

Die GC liebt Objekte, die jung sterben.

B2-K03-Q003 – G1 Garbage CollectorG1 ist ein starker Default, aber kein Ersatz für Speicherdisziplin.
Frage:

„Wie funktioniert G1 grob, und wann ist er eine gute Wahl?“

Kurz erklärt:

G1 ist der Standard-Collector in den meisten Java-21-HotSpot-Konfigurationen. Er arbeitet regionenbasiert und versucht, Pause-Time-Ziele mit hoher Wahrscheinlichkeit einzuhalten.

Musterantwort:

G1 teilt den Heap in Regionen auf. Diese Regionen können logisch jungen oder alten Generationen zugeordnet sein. G1 sammelt bevorzugt Regionen, in denen viel Speicher zurückgewonnen werden kann. Daher der Name Garbage-First.

G1 ist eine gute Default-Wahl für viele Serveranwendungen, weil er Durchsatz und Pausenzeiten balanciert. Er ist kein Echtzeit-Collector und garantiert keine harte maximale Pause. Wenn ich strengste Latenzanforderungen habe, prüfe ich ZGC. Wenn Durchsatz wichtiger ist und längere Pausen akzeptabel sind, kann Parallel GC interessant sein.

Senior-Antwort:

„G1 ist für viele Server-Workloads ein sehr guter Default. Ich tune ihn nicht blind, sondern schaue zuerst auf GC-Logs, Full GCs, Pause-Verteilung, Allocation Rate und Live Set.“

Häufige Fehler:
  • G1 als Echtzeit-GC verstehen.
  • Zu aggressive Pause-Ziele setzen.
  • Humongous Allocations ignorieren.
  • Full GCs in G1-Logs übersehen.
  • Heap zu klein dimensionieren und dann G1-Tuning erzwingen.
Best Practices:
  • G1 zunächst mit Defaults betreiben.
  • GC-Logs aktivieren.
  • Full GCs ernst nehmen.
  • Humongous Object Fragmentation prüfen.
  • Pause-Ziele realistisch setzen.
  • Heap-Größe und Allocation Rate zusammen betrachten.
Stolperfalle:

MaxGCPauseMillis ist ein Zielwert, kein SLA-Vertrag.

Merksatz:

G1 ist ein starker Default, aber kein Ersatz für Speicherdisziplin.

B2-K03-Q004 – ZGC und Low-Latency-GC in Java 21ZGC reduziert Pausen, aber nicht die Physik der Speicherverwaltung.
Frage:

„Wann würden Sie ZGC in Java 21 prüfen, und welche Trade-offs erwarten Sie?“

Kurz erklärt:

ZGC ist ein Low-Latency-Collector. In Java 21 wurde Generational ZGC eingeführt, das den Heap logisch in junge und alte Generationen trennt.

Musterantwort:

Ich prüfe ZGC, wenn sehr niedrige Pausenzeiten wichtiger sind als maximaler Durchsatz, zum Beispiel bei interaktiven APIs, Trading-Systemen, großen Heaps oder latency-sensitiven Services. ZGC führt teure GC-Arbeit weitgehend concurrent aus und zielt auf sehr kurze Pausen.

Der Trade-off ist, dass mehr Arbeit concurrent mit der Anwendung läuft. Das kann CPU kosten und Durchsatz beeinflussen. Außerdem muss ich beobachten, ob die Anwendung schneller allokiert, als ZGC concurrent Speicher zurückgewinnen kann. Low Pause heißt nicht automatisch keine Speicherprobleme.

Senior-Antwort:

„ZGC ist spannend, wenn Pausenzeiten geschäftskritisch sind. Ich bewerte ihn aber nicht isoliert nach Pause Time, sondern mit CPU, Durchsatz, Allocation Rate und Container-Limits.“

Häufige Fehler:
  • ZGC als universellen besseren Collector betrachten.
  • Nur Pause Time betrachten und Durchsatz ignorieren.
  • CPU-Headroom für concurrent GC vergessen.
  • ZGC ohne realistischen Lasttest aktivieren.
  • Allocation Rate und Live Set nicht messen.
Best Practices:
  • ZGC bei strengen Latenzanforderungen prüfen.
  • CPU-Headroom einplanen.
  • p95/p99/p999-Latenz messen.
  • GC-Logs und JFR vergleichen.
  • Nicht nur Pausen, sondern auch Durchsatz und Kosten bewerten.
Stolperfalle:

Low-Latency-GC bedeutet nicht kostenlose GC.

Merksatz:

ZGC reduziert Pausen, aber nicht die Physik der Speicherverwaltung.

B2-K03-Q005 – Collector-Auswahl: Serial, Parallel, G1 und ZGCGarbage Collector wählt man nach SLO, nicht nach Trend. 24h
Frage:

„Wie wählen Sie einen Garbage Collector für eine Java-21-Anwendung aus?“

Kurz erklärt:

Die Collector-Wahl ist eine Entscheidung zwischen Durchsatz, Latenz, Heap-Größe, CPU-Verfügbarkeit, Workload und Betriebsanforderungen.

Musterantwort:

Ich starte mit dem Default, meistens G1. Dann messe ich mit realistischem Workload. Wenn die Anwendung kleine Datenmengen und sehr einfache Umgebung hat, kann Serial GC passen. Wenn maximaler Durchsatz wichtiger ist und längere Pausen akzeptabel sind, prüfe ich Parallel GC. Wenn niedrige Pausen geschäftskritisch sind, prüfe ich ZGC.

Ich entscheide nicht nach Mode, sondern nach SLO: Durchsatz, p99-Latenz, Pause-Time-Verteilung, CPU-Kosten, Heap-Größe, Container-Limits und Betriebserfahrung.

Senior-Antwort:

„Ich wähle keinen GC nach Bauchgefühl. Ich starte mit einem sinnvollen Default, messe unter realistischen Bedingungen und entscheide anhand von Latenz, Durchsatz, CPU und Speicherprofil.“

Häufige Fehler:
  • Collector-Auswahl ohne Messung.
  • Pausen und Durchsatz nicht getrennt betrachten.
  • ZGC als generellen Ersatz für G1 sehen.
  • Parallel GC in latency-kritischen APIs blind einsetzen.
  • Container-Limits nicht im Test reproduzieren.
Best Practices:
  • Mit Default starten.
  • Realistischen Lasttest durchführen.
  • GC-Logs und JFR erfassen.
  • Durchsatz, Latenz und CPU zusammen bewerten.
  • Collector-Wahl dokumentieren.
  • Produktionsähnliche Container-Limits verwenden.
Stolperfalle:

Der Collector mit den kürzesten Pausen ist nicht automatisch der Collector mit dem besten Gesamtergebnis.

Merksatz:

Garbage Collector wählt man nach SLO, nicht nach Trend.

B2-K03-Q006 – GC-Logs, JFR und DiagnoseGC-Tuning beginnt mit Diagnose, nicht mit Flags.
Frage:

„Welche Informationen brauchen Sie, um ein GC-Problem zu analysieren?“

Kurz erklärt:

GC-Probleme analysiert man mit Daten: GC-Logs, JFR, Metriken, Heap Dumps und Allocation-Profilen.

Musterantwort:

Ich brauche mindestens GC-Logs, Heap- und Non-Heap-Metriken, Allocation Rate, Pause-Time-Verteilung, Live-Set-Entwicklung, Container-Limits, CPU-Auslastung und idealerweise JFR. Bei Memory-Leaks brauche ich Heap Dumps und Retained-Size-Analyse.

Ich schaue nicht nur auf „GC war langsam“, sondern frage: Ist der Heap zu klein? Ist die Allocation Rate zu hoch? Wächst das Live Set? Gibt es Full GCs? Gibt es Humongous Allocations? Ist CPU knapp? Wird der Container gekillt? Gibt es einen Cache oder ThreadLocal-Leak?

Senior-Antwort:

„Ich tune GC nicht blind. Ich analysiere GC-Logs, JFR, Allocation Rate, Live Set, CPU und Containerdaten und entscheide dann, ob die Ursache wirklich der Collector ist.“

Häufige Fehler:
  • Ohne GC-Logs tunen.
  • Durchschnittspause statt Verteilung betrachten.
  • Heap Dump ohne Datenschutzkonzept erzeugen.
  • GC als Ursache annehmen, obwohl CPU oder Locking dominiert.
  • Nur Applikationslogs betrachten.
Best Practices:
  • GC-Logging standardmäßig verfügbar machen.
  • JFR für reproduzierbare Performanceprobleme nutzen.
  • Metriken mit Logs korrelieren.
  • Heap Dumps sicher behandeln.
  • Vor Tuning Ursache identifizieren.
  • p95, p99 und p999 statt nur Durchschnitt betrachten.
Stolperfalle:

Eine GC-Pause im Graph bedeutet nicht automatisch, dass der GC die eigentliche Ursache ist.

Merksatz:

GC-Tuning beginnt mit Diagnose, nicht mit Flags.

B2-K03-Q007 – Java Memory Leaks und Heap-Dump-AnalyseEin Java Memory Leak ist eine zu lange gehaltene Referenz.
Frage:

„Wie analysieren Sie einen Memory Leak in einer Java-Anwendung?“

Kurz erklärt:

Ein Java Memory Leak entsteht, wenn Objekte erreichbar bleiben, obwohl sie fachlich nicht mehr benötigt werden.

Musterantwort:

Zuerst prüfe ich Metriken: wächst der Heap nach Full GC oder nach Old-Generation-GCs weiter? Steigt das Live Set? Dann nehme ich einen Heap Dump, analysiere Dominator Tree, Retained Size und Pfade zu GC Roots. Ich suche nicht nur nach vielen Objekten, sondern nach dem Objekt, das sie hält.

Typische Ursachen sind unbounded Caches, statische Maps, ThreadLocals, Listener, Sessions, Metrics-Labels mit hoher Kardinalität, ClassLoader-Leaks oder nicht geleerte Queues. Die Lösung ist meistens Lifecycle- und Ownership-Design, nicht nur mehr Heap.

Senior-Antwort:

„Bei Memory Leaks suche ich nicht nur nach vielen Objekten, sondern nach dem Referenzhalter. Die zentrale Frage lautet: Wer hält das Objekt noch live?“

Häufige Fehler:
  • Heap nur nach Objektanzahl analysieren.
  • Retained Size ignorieren.
  • Caches ohne Maximalgröße bauen.
  • Heap Dumps erst nach OOM planen.
  • Memory Leak mit hoher Allocation Rate verwechseln.
Best Practices:
  • Caches mit Größen- und Zeitlimit.
  • Heap Dumps bei OOM automatisch konfigurieren.
  • ThreadLocals immer entfernen.
  • Metrics-Labels kontrollieren.
  • Queue-Größen begrenzen und überwachen.
  • Leak-Tests mit realistischen Datenmengen durchführen.
Stolperfalle:

Mehr Heap verschiebt einen Memory Leak oft nur nach hinten.

Merksatz:

Ein Java Memory Leak ist eine zu lange gehaltene Referenz.

B2-K03-Q008 – Allocation Rate, Object Churn und PerformanceAllokationen optimiert man dort, wo sie gemessen dominieren.
Frage:

„Wann ist hohe Allocation Rate ein Problem, und wie optimieren Sie sie sinnvoll?“

Kurz erklärt:

Allocation Rate beschreibt, wie viel Speicher pro Zeit neu allokiert wird. Object Churn bezeichnet schnell erzeugte und schnell wieder verworfene Objekte.

Musterantwort:

Hohe Allocation Rate ist nicht automatisch schlecht, weil junge Objekte oft effizient gesammelt werden. Sie wird problematisch, wenn die GC zu häufig läuft, CPU verbraucht, Pausen erzeugt oder Objekte zu lange überleben.

Ich optimiere nicht reflexartig jede Allokation. Zuerst messe ich mit JFR oder Profiler, welche Codepfade dominieren. Dann prüfe ich, ob Zwischenobjekte unnötig sind, ob Boxing passiert, ob große Strings oder Collections entstehen, ob Batches zu groß sind oder ob Daten besser gestreamt werden sollten.

Senior-Antwort:

„Ich optimiere Allocation Rate datengetrieben. Kurzlebige Objekte sind oft akzeptabel, aber in echten Hotpaths können unnötige Zwischenobjekte GC und CPU stark belasten.“

Häufige Fehler:
  • Objektallokationen ohne Messung optimieren.
  • Object Pools für normale Java-Objekte bauen.
  • Lesbarkeit wegen minimaler Allokationen zerstören.
  • Boxing in Hotpaths ignorieren.
  • Logging von großen Objekten im Hotpath übersehen.
Best Practices:
  • Allocation Hotspots mit JFR messen.
  • Zwischenobjekte in Hotpaths reduzieren.
  • Primitive Streams oder primitive Strukturen prüfen.
  • Große Batches chunkweise verarbeiten.
  • Object Pooling nur für sehr spezielle Ressourcen verwenden.
  • Lesbarkeit gegen messbaren Gewinn abwägen.
Stolperfalle:

Object Pooling ist in modernen JVMs meistens keine Standardoptimierung, sondern oft eine neue Fehlerquelle.

Merksatz:

Allokationen optimiert man dort, wo sie gemessen dominieren.

B2-K03-Q009 – System.gc, Finalization, Cleaner und ReferenzenDie GC ist kein Ersatz für close() .
Frage:

„Warum sollte man sich nicht auf System.gc() oder Finalization verlassen?“

Kurz erklärt:

Garbage Collection ist kein deterministischer Ressourcenmanagement-Mechanismus. Ressourcen wie Dateien, Sockets, Direct Buffers oder native Handles müssen explizit geschlossen werden.

Musterantwort:

System.gc() ist höchstens ein Hinweis an die JVM, keine zuverlässige Steuerung. Finalization ist problematisch, unvorhersehbar und in modernem Java nicht mehr der richtige Ansatz. Für Ressourcenmanagement verwende ich AutoCloseable und try-with-resources .

Cleaner kann als Sicherheitsnetz für native Ressourcen dienen, ersetzt aber kein explizites Schließen. Der Zeitpunkt, wann ein Objekt unreachable wird und wann eine Cleanup-Aktion läuft, ist nicht geeignet für fachlich oder technisch kritische Ressourcenfreigabe.

Senior-Antwort:

„Garbage Collection ist kein deterministisches Ressourcenmanagement. Speicher darf die GC verwalten, aber externe Ressourcen schließe ich explizit.“

Häufige Fehler:
  • System.gc() als Tuning-Maßnahme einbauen.
  • Externe Ressourcen der GC überlassen.
  • Cleaner als normalen Lifecycle verwenden.
  • Direct Memory bei Container-Limits ignorieren.
  • Finalizer in neuem Code verwenden.
Best Practices:
  • Ressourcen explizit mit AutoCloseable schließen.
  • try-with-resources verwenden.
  • Cleaner nur als Backup betrachten.
  • Direct Memory und Native Memory beobachten.
  • System.gc() in Anwendungscode vermeiden.
  • Ressourcen-Ownership klar dokumentieren.
Stolperfalle:

Ein stabiler Heap bedeutet nicht, dass Native Memory oder Direct Buffers stabil sind.

Merksatz:

Die GC ist kein Ersatz für close() .

B2-K03-Q010 – GC-Tuning-Strategie und Anti-PatternsTuning ohne Diagnose ist Glücksspiel. 24h
Frage:

„Wie gehen Sie systematisch an GC-Tuning heran?“

Kurz erklärt:

GC-Tuning ist keine Sammlung magischer Flags. Es ist ein datengetriebener Diagnose- und Entscheidungsprozess.

Musterantwort:

Ich beginne mit Zielen: Durchsatz, p99-Latenz, Speicherlimit, Kosten und Stabilität. Dann erfasse ich Daten: GC-Logs, JFR, Heap-Metriken, Allocation Rate, Live Set, CPU, Container-OOMs, Thread-Zahlen und Applikationsmetriken.

Dann trenne ich Ursachen: Memory Leak, zu kleiner Heap, zu hohe Allocation Rate, große Objektgraphen, falsche Batch-Größe, Collector-Mismatch oder CPU-Knappheit. Erst danach ändere ich Flags. Viele Probleme löst man durch Code, Cache-Design oder Batch-Strategie, nicht durch Collector-Tuning.

Senior-Antwort:

„GC-Tuning ist kein Flag-Raten. Ich definiere Ziele, sammle Daten, klassifiziere die Ursache und ändere dann gezielt Code, Heap, Collector oder Betriebsparameter.“

Häufige Fehler:
  • Flags aus Blogposts kopieren.
  • GC-Tuning ohne GC-Logs.
  • Pause-Ziel zu aggressiv setzen.
  • Heap größer setzen als Container sinnvoll erlaubt.
  • Memory Leak mit Collector-Wechsel behandeln.
  • Nur Durchschnittswerte betrachten.
Best Practices:
  • Ziele und SLOs definieren.
  • Diagnose vor Tuning.
  • Immer nur wenige Änderungen gleichzeitig.
  • Produktionsähnliche Lasttests verwenden.
  • Vorher-nachher-Vergleich dokumentieren.
  • Collector- und Heap-Strategie standardisieren.
  • Code- und Architekturursachen zuerst prüfen.
Stolperfalle:

Ein Collector-Wechsel löst keinen Memory Leak.

Merksatz:

Tuning ohne Diagnose ist Glücksspiel.

B2-K04 – Kapitel 4: ClassLoader 10 Karten

B2-K04-Q001 – Aufgaben eines ClassLoadersEin ClassLoader ist ein Namensraum, nicht nur ein Datei-Lader. 24h
Frage:

„Was ist ein ClassLoader, und welche Aufgaben hat er in der JVM?“

Kurz erklärt:

Ein ClassLoader lädt Klassen nicht nur aus Dateien. Er kann Klassen aus JARs, Modulen, Netzwerken, generiertem Bytecode oder anderen Quellen definieren.

Musterantwort:

Ein ClassLoader ist dafür verantwortlich, Klassen anhand ihres binären Namens zu finden und in der JVM zu definieren. Typisch ist das Laden einer .class -Datei aus dem Classpath oder Module Path, aber Klassen können auch aus anderen Quellen kommen, etwa generiertem Bytecode, Plugins oder Framework-Proxies.

ClassLoader laden außerdem Ressourcen, zum Beispiel Konfigurationsdateien oder Service-Metadaten. Wichtig ist: Eine Klasse ist zur Laufzeit nicht nur durch ihren vollqualifizierten Namen identifiziert, sondern durch Name plus definierenden ClassLoader. Deshalb sind ClassLoader zentral für Isolation, Plugins, Application Server und Frameworks.

Senior-Antwort:

„Ein ClassLoader definiert nicht nur, woher Klassen kommen, sondern auch ihren Laufzeit-Namensraum. Klassenidentität ist in Java immer Klassenname plus definierender ClassLoader.“

Häufige Fehler:
  • ClassLoader nur als „Classpath-Leser“ verstehen.
  • Bootstrap ClassLoader als normales Java-Objekt erwarten.
  • Classpath, Module Path und ClassLoader vermischen.
  • Ressourcenladen und Klassenladen gleichsetzen.
  • Metaspace-Kosten durch viele Klassen ignorieren.
Best Practices:
  • ClassLoader bei Framework- und Plugin-Problemen immer mitdenken.
  • Diagnoseausgaben um ClassLoader-Informationen ergänzen.
  • Dynamisches Laden sparsam und kontrolliert verwenden.
  • Startzeit und Metaspace bei großen Frameworks beobachten.
  • Keine Annahmen über konkrete ClassLoader-Implementierungen treffen.
Stolperfalle:

Zwei Klassen mit demselben vollqualifizierten Namen können zur Laufzeit unterschiedliche Typen sein, wenn sie von unterschiedlichen ClassLoadern definiert wurden.

Merksatz:

Ein ClassLoader ist ein Namensraum, nicht nur ein Datei-Lader.

B2-K04-Q002 – Parent Delegation ModelDelegation schützt Konsistenz, Isolation braucht bewusste Grenzen.
Frage:

„Wie funktioniert Parent Delegation, und warum ist dieses Modell wichtig?“

Kurz erklärt:

Das Parent Delegation Model sorgt dafür, dass ein ClassLoader normalerweise zuerst seinen Parent fragt, bevor er selbst eine Klasse oder Ressource sucht.

Musterantwort:

Beim Laden einer Klasse delegiert ein ClassLoader typischerweise zuerst an seinen Parent. Erst wenn der Parent die Klasse nicht findet, sucht der Child selbst. Dadurch werden zentrale Plattformklassen konsistent von höheren Loadern geladen, und Anwendungen können nicht einfach eigene Versionen von java.lang.String oder anderen Basisklassen einschleusen.

Das Modell schützt Konsistenz und vermeidet doppelte Definitionen zentraler Typen. Es ist aber nicht immer strikt hierarchisch. Application Server, OSGi, Module Layer, Plugin-Systeme oder bestimmte Frameworks können kontrolliert andere Strategien verwenden.

Senior-Antwort:

„Parent Delegation schützt die Konsistenz zentraler Klassen. Ich breche sie nur bewusst, etwa für Plugins oder Container-Isolation, und dann mit klarer API-Grenze.“

Häufige Fehler:
  • Parent Delegation als zwingendes JVM-Gesetz für alle Umgebungen verstehen.
  • Child-first Loading ohne Isolation und Sicherheitskonzept einsetzen.
  • Doppelte Libraries im Classpath ignorieren.
  • Ressourcenreihenfolge unterschätzen.
  • Fehler durch unterschiedliche Loader als normale Cast-Probleme behandeln.
Best Practices:
  • Parent Delegation als Default respektieren.
  • Child-first nur gezielt in Plugin- oder Container-Szenarien einsetzen.
  • Gemeinsame API-Typen in gemeinsamem Parent laden.
  • Classpath-Duplikate vermeiden.
  • ClassLoader-Ketten bei Problemen sichtbar machen.
Stolperfalle:

Child-first Loading kann Library-Konflikte lösen, aber auch neue Klassenidentitätsprobleme erzeugen.

Merksatz:

Delegation schützt Konsistenz, Isolation braucht bewusste Grenzen.

B2-K04-Q003 – Klassenidentität: Name plus ClassLoaderKlassenname plus ClassLoader ergibt den Laufzeittyp.
Frage:

„Warum kann ein ClassCastException auftreten, obwohl zwei Klassen denselben vollqualifizierten Namen haben?“

Kurz erklärt:

Zur Laufzeit ist eine Klasse nicht nur ihr Name. Entscheidend ist auch, welcher ClassLoader sie definiert hat.

Musterantwort:

Weil Klassenidentität in Java aus binärem Namen und definierendem ClassLoader besteht. Wenn zwei verschiedene ClassLoader jeweils com.example.PluginApi definieren, sind das aus JVM-Sicht zwei verschiedene Typen. Ein Objekt der einen Klasse kann nicht zur anderen gecastet werden, obwohl der Name gleich aussieht.

Das passiert oft in Plugin-Systemen, Application Servern, Testumgebungen, DevTools oder wenn Libraries in mehreren ClassLoader-Schichten dupliziert sind. Die Lösung ist, gemeinsame API-Typen in einem gemeinsamen Parent ClassLoader zu laden und Plugin-Implementierungen darunter zu isolieren.

Senior-Antwort:

„Ein vollqualifizierter Name reicht in Java nicht zur Laufzeitidentität. Entscheidend ist: Wer hat die Klasse definiert? Deshalb debugge ich Cast-Probleme immer mit ClassLoader-Information.“

Häufige Fehler:
  • Nur auf Klassennamen in Stacktraces schauen.
  • API-JARs in Host und Plugin duplizieren.
  • Gemeinsame Interfaces im Child-Loader laden.
  • ClassCastException als „unmöglich“ einstufen.
  • Diagnosen ohne ClassLoader-Informationen erstellen.
Best Practices:
  • Gemeinsame API-Typen im Parent ClassLoader halten.
  • Plugin-Implementierungen isoliert laden.
  • ClassLoader-Namen in Diagnoseausgaben aufnehmen.
  • Dependency-Duplikate prüfen.
  • DevTools- und Test-ClassLoader bewusst berücksichtigen.
Stolperfalle:

Zwei gleichnamige Klassen sind nicht kompatibel, wenn sie aus unterschiedlichen ClassLoader-Namensräumen stammen.

Merksatz:

Klassenname plus ClassLoader ergibt den Laufzeittyp.

B2-K04-Q004 – Loading, Linking und InitializationLoading macht eine Klasse bekannt; Initialization führt ihren statischen Code aus.
Frage:

„Was passiert bei Loading, Linking und Initialization einer Klasse?“

Kurz erklärt:

Laden, Linken und Initialisieren sind unterschiedliche Phasen. Eine Klasse kann geladen sein, ohne dass ihre statische Initialisierung bereits ausgeführt wurde.

Musterantwort:

Beim Loading wird die binäre Repräsentation einer Klasse gefunden und daraus eine Class -Repräsentation in der JVM erzeugt. Linking verbindet diese Klasse mit dem Runtime-Zustand der JVM. Dazu gehören Verification, Preparation und Resolution. Initialization führt dann statische Initialisierer und statische Feldinitialisierungen aus.

Senior-relevant ist der Unterschied zwischen „Klasse bekannt“ und „Klasse initialisiert“. Viele Fehler entstehen in statischen Initializern, durch Seiteneffekte beim Laden oder durch falsche Annahmen darüber, wann Initialisierung passiert.

Senior-Antwort:

„Eine geladene Klasse ist nicht automatisch initialisiert. Diese Trennung ist wichtig für Frameworks, Startzeitdiagnose und statische Initialisierungsfehler.“

Häufige Fehler:
  • Loading und Initialization gleichsetzen.
  • Teure Arbeit in static Initializern ausführen.
  • Fehler in static Initializern unterschätzen.
  • Class.forName unbewusst mit Initialisierung verwenden.
  • Framework-Metadatenlesen mit aktiver Klassennutzung verwechseln.
Best Practices:
  • Static Initializer klein und deterministisch halten.
  • Teure Initialisierung explizit modellieren.
  • Fehlerkontext bei Initialisierung klar loggen.
  • Lazy Initialization bewusst und thread-safe gestalten.
  • Framework-Scanning nicht mit Geschäftsinitialisierung vermischen.
Stolperfalle:

Class.forName(String) initialisiert standardmäßig, wenn die Klasse geladen wird.

Merksatz:

Loading macht eine Klasse bekannt; Initialization führt ihren statischen Code aus.

B2-K04-Q005 – Custom ClassLoader und dynamisches LadenDynamisches Laden ist einfach; sauberes Entladen ist schwer. 24h
Frage:

„Wann würden Sie einen eigenen ClassLoader schreiben, und welche Risiken gibt es?“

Kurz erklärt:

Eigene ClassLoader können Klassen aus alternativen Quellen laden oder isolierte Namensräume erzeugen. Sie sind mächtig, aber fehleranfällig.

Musterantwort:

Einen eigenen ClassLoader schreibe ich nur für spezielle Infrastruktur: Plugins, Scripting, isolierte Kundenmodule, dynamische Codeausführung, Testumgebungen oder Frameworks. Im normalen Business-Code ist das fast nie nötig.

Risiken sind Klassenidentitätsprobleme, ClassLoader-Leaks, Security-Risiken, Ressourcenauflösung, native Libraries, Package-Sealing, paralleles Laden, Deadlocks und schweres Debugging. Außerdem muss klar sein, wer den Loader referenziert und wann er wieder freigegeben werden kann.

Senior-Antwort:

„Eigene ClassLoader sind Infrastrukturwerkzeuge. Sie lösen Isolation und dynamisches Laden, bringen aber Klassenidentität, Lifecycle und Leak-Risiken mit.“

Häufige Fehler:
  • Parent Delegation unbeabsichtigt brechen.
  • Loader in statischen Feldern halten.
  • Threads aus Plugin-Code nicht stoppen.
  • API-Klassen im Plugin-Loader duplizieren.
  • Native Libraries und Ressourcen vergessen.
Best Practices:
  • Custom ClassLoader nur für Infrastrukturcode.
  • Gemeinsame APIs im Parent laden.
  • Plugin-Lifecycle explizit modellieren.
  • Threads, Timer und Executor beim Unload stoppen.
  • ClassLoader-Referenzen mit Heap Dump prüfen.
  • Security und Signaturprüfung berücksichtigen.
Stolperfalle:

Ein ClassLoader kann erst freigegeben werden, wenn keine Klassen, Instanzen, Threads, statischen Felder oder Framework-Caches ihn mehr referenzieren.

Merksatz:

Dynamisches Laden ist einfach; sauberes Entladen ist schwer.

B2-K04-Q006 – Thread Context ClassLoaderDer Context ClassLoader ist mächtig, aber in Thread Pools lifecycle-gefährlich.
Frage:

„Was ist der Thread Context ClassLoader, und warum existiert er?“

Kurz erklärt:

Der Thread Context ClassLoader erlaubt Frameworks und Bibliotheken, Klassen oder Services im Kontext des aufrufenden Threads zu laden, statt nur den eigenen definierenden ClassLoader zu verwenden.

Musterantwort:

Der Thread Context ClassLoader ist ein ClassLoader, der an einem Thread hängt und von Frameworks oder Bibliotheken genutzt werden kann, um Klassen im Kontext der Anwendung zu laden. Das ist wichtig, weil eine Bibliothek selbst oft von einem höheren Loader geladen wurde, aber Implementierungen oder Provider in der Anwendung liegen.

Typische Beispiele sind ServiceLoader , JNDI, Application Server, SPI-Mechanismen, JDBC-Treiber oder Framework-Integrationen. Der Context ClassLoader ist eine Art kontrollierter Ausweg aus reinem Parent-Delegation-Denken.

Senior-Antwort:

„Der Thread Context ClassLoader ist wichtig, wenn Framework-Code Anwendungscode finden muss. Ich nutze ihn bewusst und stelle ihn in Thread-Pool-Szenarien immer wieder zurück.“

Häufige Fehler:
  • Context ClassLoader in Thread Pools nicht zurücksetzen.
  • Plugin-ClassLoader über Worker-Thread festhalten.
  • Bibliotheks-ClassLoader statt Anwendungskontext verwenden.
  • Context ClassLoader global als Lösung für alle Ladeprobleme missbrauchen.
  • Tests ohne realistische ClassLoader-Situation schreiben.
Best Practices:
  • Context ClassLoader nur kontrolliert setzen.
  • Alten Loader in finally wiederherstellen.
  • Thread Pools beim Undeploy bereinigen.
  • Service-Lookups cachen, aber lifecycle-bewusst.
  • In Plattformcode ClassLoader explizit als Parameter akzeptieren.
Stolperfalle:

Ein falsch gesetzter Context ClassLoader kann ein altes Deployment über einen langlebigen Thread am Leben halten.

Merksatz:

Der Context ClassLoader ist mächtig, aber in Thread Pools lifecycle-gefährlich.

B2-K04-Q007 – Ressourcenladen und Service ProviderRessourcen haben ebenfalls ClassLoader-Semantik.
Frage:

„Wie funktioniert Ressourcenladen mit ClassLoadern, und welche typischen Fehler gibt es?“

Kurz erklärt:

ClassLoader laden nicht nur Klassen, sondern auch Ressourcen. Viele SPI-Mechanismen bauen darauf auf.

Musterantwort:

Ressourcen werden über abstrakte Pfade geladen, zum Beispiel config/payment-rules.yml oder META-INF/services/... . Der ClassLoader sucht diese Ressourcen gemäß seiner Delegations- und Suchstrategie. Wichtig ist, dass Ressourcen nicht dasselbe sind wie Klassen: Es kann mehrere Ressourcen mit demselben Namen geben, die Reihenfolge kann relevant sein, und Module können Zugriff beeinflussen.

Typische Fehler sind falsche Pfade, führende Slashes bei ClassLoader-Methoden, doppelte Ressourcen in mehreren JARs, nicht geschlossene Streams oder die Nutzung des falschen ClassLoaders.

Senior-Antwort:

„Ressourcenladen ist Teil des ClassLoader-Modells. Ich achte auf den richtigen Loader, Mehrfachvorkommen, Modulgrenzen und das Schließen von Streams.“

Häufige Fehler:
  • Resource-Stream nicht schließen.
  • Falschen ClassLoader verwenden.
  • Nur die erste Ressource laden, obwohl mehrere erwartet werden.
  • Doppelte Konfigurationsdateien im Classpath übersehen.
  • Ressourcenpfade mit Dateisystempfaden verwechseln.
Best Practices:
  • Ressourcenpfade klar dokumentieren.
  • Streams immer schließen.
  • Bei mehreren erwarteten Ressourcen getResources verwenden.
  • ClassLoader explizit wählen.
  • Service-Lookups lifecycle-bewusst cachen.
  • Doppelte Ressourcen im Build prüfen.
Stolperfalle:

ClassLoader.getResource("x") erwartet keinen führenden Slash; Class.getResource hat andere Regeln für relative Pfade.

Merksatz:

Ressourcen haben ebenfalls ClassLoader-Semantik.

B2-K04-Q008 – JPMS, ModuleLayer und ClassLoaderJPMS strukturiert Module; ClassLoader definieren weiterhin Laufzeittypen.
Frage:

„Wie hängen JPMS Module Layer und ClassLoader zusammen?“

Kurz erklärt:

Mit JPMS können Module in Layern definiert werden. Module Layer verbinden Modulkonfiguration, Lesbarkeit und ClassLoader-Strategien.

Musterantwort:

JPMS erweitert das klassische Classpath-Modell um Module, Lesbarkeitsbeziehungen und starke Kapselung. Module werden in Module Layern definiert. Ein Layer kann Module mit einem oder mehreren ClassLoadern definieren. Dadurch lassen sich modulare Plugin- oder Laufzeitstrukturen aufbauen.

Wichtig ist: Module lösen nicht alle ClassLoader-Probleme automatisch. Klassenidentität, Loader-Isolation, Ressourcen, Reflection-Zugriff und Lifecycle bleiben relevant. JPMS gibt aber eine stärkere formale Struktur als ein flacher Classpath.

Senior-Antwort:

„JPMS ergänzt das ClassLoader-Modell um explizite Modulgrenzen. Für modulare Plattformen ist das stark, aber ClassLoader-Lifecycle, Klassenidentität und Reflection bleiben weiterhin Architekturthemen.“

Häufige Fehler:
  • JPMS nur als ClassLoader-Ersatz verstehen.
  • Reflection-Zugriff ohne opens erwarten.
  • Module Layer ohne Lifecycle-Konzept erzeugen.
  • Classpath- und Module-Path-Abhängigkeiten vermischen.
  • Gemeinsame API-Module falsch laden.
Best Practices:
  • Module für klare Grenzen nutzen.
  • Layer-Lifecycle bewusst gestalten.
  • Gemeinsame APIs stabil und zentral laden.
  • Reflection-Zugriffe explizit über opens modellieren.
  • ClassLoader- und Moduldiagnose zusammen betrachten.
Stolperfalle:

Ein Modul ist keine magische Isolationseinheit ohne ClassLoader-Semantik.

Merksatz:

JPMS strukturiert Module; ClassLoader definieren weiterhin Laufzeittypen.

B2-K04-Q009 – ClassLoader-Leaks und Class UnloadingClass Unloading beginnt mit ClassLoader-Freigabe.
Frage:

„Was ist ein ClassLoader-Leak, und wie verhindern Sie ihn?“

Kurz erklärt:

Class Unloading ist nur möglich, wenn der ClassLoader und alle von ihm definierten Klassen nicht mehr erreichbar sind.

Musterantwort:

Ein ClassLoader-Leak entsteht, wenn ein eigentlich nicht mehr benötigter ClassLoader weiterhin erreichbar bleibt. Dadurch bleiben auch alle von ihm definierten Klassen, statischen Felder, Metadaten und oft ganze Objektgraphen live. Das führt zu Metaspace-Wachstum und kann alte Deployments oder Plugins im Speicher halten.

Typische Ursachen sind Threads, ThreadLocals, statische Caches, Logger, Metrik-Registries, JDBC-Treiber, Timer, Executor, Shutdown Hooks, Context ClassLoader oder globale Registries. Verhindern kann man das nur mit sauberem Lifecycle: stoppen, deregistrieren, ThreadLocals entfernen, Context ClassLoader zurücksetzen und Referenzen freigeben.

Senior-Antwort:

„Bei ClassLoader-Leaks frage ich: Wer hält den Loader noch fest? Meist sind es Threads, ThreadLocals, statische Caches oder globale Registries, nicht der ClassLoader selbst.“

Häufige Fehler:
  • Executor aus Plugin-Code nicht stoppen.
  • ThreadLocal-Werte nicht entfernen.
  • Context ClassLoader auf langlebigen Threads falsch lassen.
  • Globale Registries nicht deregistrieren.
  • Alte ClassLoader in statischen Caches halten.
Best Practices:
  • Plugin- und Deployment-Lifecycle explizit implementieren.
  • Alle Threads und Executor stoppen.
  • ThreadLocals entfernen.
  • Metriken, Treiber, Listener und Hooks deregistrieren.
  • Heap Dump auf Pfade zum ClassLoader analysieren.
  • Metaspace und loaded class count überwachen.
Stolperfalle:

Klassen werden nicht einzeln sinnvoll unloadbar, solange ihr definierender ClassLoader noch erreichbar ist.

Merksatz:

Class Unloading beginnt mit ClassLoader-Freigabe.

B2-K04-Q010 – Framework-Proxies, Instrumentierung und DiagnoseWer Proxies debuggt, debuggt auch ClassLoader. 24h
Frage:

„Welche Rolle spielen ClassLoader bei Spring-Proxies, JPA, Mockito, APM-Agenten oder Bytecode-Instrumentierung?“

Kurz erklärt:

Viele Frameworks erzeugen Klassen zur Laufzeit oder instrumentieren vorhandene Klassen. Der richtige ClassLoader entscheidet, ob diese Klassen kompatibel mit Anwendungstypen sind.

Musterantwort:

Frameworks erzeugen oft zusätzliche Klassen: JDK-Proxies, CGLIB- oder Byte Buddy-Klassen, Hibernate-Proxies, Mockito-Mocks oder APM-instrumentierte Klassen. Diese Klassen müssen in einem ClassLoader definiert werden, der die referenzierten Typen sehen kann. Wenn der falsche Loader verwendet wird, entstehen ClassCastException , IllegalAccessError , LinkageError oder fehlende Ressourcen.

Bei Diagnose schaue ich deshalb nicht nur auf die Klasse, sondern auf ClassLoader, Modulgrenzen, Proxy-Typ, Parent Delegation und ob mehrere Versionen einer Library geladen wurden.

Senior-Antwort:

„Framework-Magie ist oft ClassLoader- und Bytecode-Magie. Wenn Proxies oder Instrumentierung scheitern, prüfe ich immer, welcher Loader die erzeugte Klasse definiert und welche Typen er sehen kann.“

Häufige Fehler:
  • Proxy mit falschem Loader erzeugen.
  • Pro Request neue Klassen generieren.
  • Modulzugriff für Reflection ignorieren.
  • Agenten und Instrumentierung bei Diagnose vergessen.
  • Proxy-Klasse statt Zielklasse falsch interpretieren.
Best Practices:
  • Proxy-ClassLoader bewusst wählen.
  • Gemeinsame Interfaces sichtbar für Caller laden.
  • Dynamische Klassenerzeugung cachen.
  • JPMS-Opens/Exports dokumentieren.
  • APM-Agenten und Instrumentierung in Diagnose einbeziehen.
  • ClassLoader-Information bei Proxy-Problemen loggen.
Stolperfalle:

Ein Proxy ist eine eigene Klasse mit eigenem definierendem ClassLoader, nicht einfach „das Originalobjekt“.

Merksatz:

Wer Proxies debuggt, debuggt auch ClassLoader.

B2-K05 – Kapitel 5: JIT 10 Karten

B2-K05-Q001 – Interpreter, Hotspots und adaptive CompilationDie JVM optimiert das, was wirklich heiß wird. 24h
Frage:

„Was ist ein Hotspot, und warum ist adaptive Compilation so wichtig für Java-Performance?“

Kurz erklärt:

Die JVM führt Code nicht dauerhaft nur interpretiert aus. Häufig ausgeführte Methoden werden als Hotspots erkannt und vom JIT-Compiler zu Maschinencode kompiliert.

Musterantwort:

Ein Hotspot ist ein Codebereich, der häufig oder performancekritisch ausgeführt wird. Die JVM startet Code typischerweise interpretiert, sammelt Laufzeitprofile und kompiliert häufig genutzte Methoden später mit dem JIT-Compiler.

Das ist wichtig, weil die JVM echte Laufzeitinformationen hat. Sie sieht, welche konkreten Klassen an virtuellen Aufrufstellen auftreten, welche Branches häufig genommen werden und welche Methoden wirklich heiß sind. Dadurch kann sie optimieren, was ein statischer Compiler oft nur raten könnte.

Auf Senior-Level ist entscheidend: Java-Performance ist phasenabhängig. Cold Start, Warm-up und steady state sind unterschiedliche Zustände.

Senior-Antwort:

„Die JVM optimiert adaptiv. Ich unterscheide deshalb Cold Start, Warm-up und steady state und messe Performance nicht anhand einzelner Aufrufe direkt nach dem Start.“

Häufige Fehler:
  • JIT mit javac verwechseln.
  • Performance direkt nach JVM-Start bewerten.
  • Warm-up bei Benchmarks ignorieren.
  • Einmalige lokale Messungen ernst nehmen.
  • Framework-Overhead ohne Profiling beurteilen.
Best Practices:
  • Cold Start und steady state getrennt messen.
  • Warm-up in Benchmarks berücksichtigen.
  • JMH für Mikrobenchmarks verwenden.
  • JFR oder Profiler für Hotpath-Analyse nutzen.
  • Performance nicht aus Quellcode allein ableiten.
Stolperfalle:

Ein kurzer Test misst oft Interpreter-, Class-Loading- und Warm-up-Kosten, nicht die optimierte Laufzeitperformance.

Merksatz:

Die JVM optimiert das, was wirklich heiß wird.

B2-K05-Q002 – Tiered Compilation: C1, C2 und ProfilingTiered Compilation optimiert den Weg von Startperformance zu Hotpath-Performance.
Frage:

„Was ist Tiered Compilation, und warum verwendet HotSpot mehrere Compiler-Stufen?“

Kurz erklärt:

Tiered Compilation kombiniert schnelle, weniger stark optimierende Kompilierung mit späterer stärker optimierender Kompilierung.

Musterantwort:

Tiered Compilation bedeutet, dass eine Methode nicht sofort mit maximalem Optimierungsaufwand kompiliert werden muss. Die JVM kann zuerst schnelleren Code mit Profiling erzeugen und später besonders heiße Methoden stärker optimieren.

Vereinfacht: C1 kompiliert schneller und kann Profilinformationen sammeln. C2 optimiert stärker, braucht aber mehr Zeit und Ressourcen. Durch Tiered Compilation bekommt die Anwendung früher brauchbare Performance und später bessere Hotpath-Performance.

Das ist besonders wichtig für Serveranwendungen: Sie sollen nicht ewig warm laufen, aber trotzdem nach einiger Zeit sehr effizient sein.

Senior-Antwort:

„Tiered Compilation ist ein Warm-up-Kompromiss: schnell genug starten, Profile sammeln und echte Hotpaths später stärker optimieren.“

Häufige Fehler:
  • Tiered Compilation als unnötige Komplexität sehen.
  • JIT-Kompilierungszeit bei CPU-Limits ignorieren.
  • Warm-up nur als Benchmark-Thema betrachten.
  • Tiered Compilation ohne Diagnose deaktivieren.
  • Compiler-Stufen mit Java-Sprachversionen verwechseln.
Best Practices:
  • Tiered Compilation normalerweise aktiviert lassen.
  • Warm-up-Verhalten in Lasttests beobachten.
  • CPU-Headroom für Compilation einplanen.
  • Nur mit Messdaten an Compiler-Flags drehen.
  • JIT- und Code-Cache-Metriken bei großen Services prüfen.
Stolperfalle:

Mehr Optimierung ist nicht kostenlos; JIT-Compiler verbrauchen während Warm-up CPU.

Merksatz:

Tiered Compilation optimiert den Weg von Startperformance zu Hotpath-Performance.

B2-K05-Q003 – Inlining und Call-Site-OptimierungInlining macht aus vielen kleinen Methoden einen optimierbaren Hotpath.
Frage:

„Warum ist Inlining so wichtig für JVM-Performance?“

Kurz erklärt:

Inlining ist eine der wichtigsten JIT-Optimierungen. Der JIT ersetzt einen Methodenaufruf durch den Methodenrumpf, wenn dies vorteilhaft erscheint.

Musterantwort:

Inlining spart nicht nur den Methodenaufruf. Noch wichtiger ist, dass nach dem Inlining weitere Optimierungen möglich werden: konstante Werte können propagiert werden, Branches verschwinden, Escape Analysis wird einfacher, virtuelle Aufrufe können devirtualisiert werden und kleine Objektallokationen können eventuell eliminiert werden.

Deshalb ist guter objektorientierter Code nicht automatisch langsam. Viele kleine Methoden können vom JIT sehr gut optimiert werden, solange sie inlinbar sind und die Call-Sites nicht zu komplex oder zu megamorph werden.

Senior-Antwort:

„Inlining macht objektorientierten Code oft schnell, weil danach weitere Optimierungen möglich werden. Ich schreibe deshalb nicht absichtlich unlesbaren Code, sondern messe Hotpaths.“

Häufige Fehler:
  • Kleine Methoden aus Performanceangst manuell zusammenkopieren.
  • Inlining als garantiert betrachten.
  • Sehr große Methoden schreiben, weil man „weniger Calls“ will.
  • Proxy- und Reflection-Pfade bei Inlining ignorieren.
  • PrintInlining ohne Kontext interpretieren.
Best Practices:
  • Lesbaren, kleinen Code schreiben.
  • Hotpaths messen, nicht erraten.
  • Megamorphe Dispatch-Pfade in Hotpaths vermeiden.
  • Inlining-Diagnose nur gezielt verwenden.
  • Codegröße und Code Cache im Blick behalten.
Stolperfalle:

Inlining ist keine reine Methodenaufruf-Optimierung, sondern oft der Türöffner für viele Folgeoptimierungen.

Merksatz:

Inlining macht aus vielen kleinen Methoden einen optimierbaren Hotpath.

B2-K05-Q004 – Devirtualisierung, Polymorphie und megamorphe Call-SitesDer JIT optimiert nicht die Hierarchie, sondern die beobachtete Call-Site.
Frage:

„Wie beeinflusst Polymorphie die JIT-Optimierung?“

Kurz erklärt:

Der JIT kann virtuelle Aufrufe optimieren, wenn an einer Call-Site nur wenige konkrete Typen auftreten. Bei vielen Typen wird Optimierung schwieriger.

Musterantwort:

Java ist stark objektorientiert, und virtuelle Aufrufe sind normal. Der JIT kann aber zur Laufzeit beobachten, welche konkreten Typen an einer Call-Site tatsächlich auftreten. Wenn nur ein Typ oder wenige Typen auftreten, kann er devirtualisieren und eventuell inlinen.

Wenn sehr viele unterschiedliche Implementierungen an derselben heißen Call-Site auftreten, spricht man von einer megamorphen Call-Site. Dann wird es schwieriger, den Aufruf zu devirtualisieren und zu inlinen. Das kann in Rule-Engines, Plugin-Systemen oder generischen Dispatch-Schichten relevant werden.

Senior-Antwort:

„Polymorphie ist nicht automatisch langsam. Der JIT optimiert echte Laufzeittypen. Kritisch werden sehr heiße Call-Sites mit vielen unterschiedlichen Implementierungen.“

Häufige Fehler:
  • Interfaces pauschal als langsam bezeichnen.
  • Polymorphie in Hotpaths ohne Messung problematisieren.
  • Sehr generische Plugin-Dispatches in engen Schleifen bauen.
  • Proxies im CPU-Profil ignorieren.
  • Objektorientiertes Design unnötig manuell entpolymorphisieren.
Best Practices:
  • Interfaces normal verwenden.
  • Heiße Dispatch-Pfade messen.
  • Sehr dynamische Rule-Engines profilieren.
  • Typvielfalt an Hot-Call-Sites beobachten.
  • Bei Bedarf Strategien gruppieren, spezialisieren oder vorab kompilieren.
Stolperfalle:

Ein Interface-Aufruf kann nach JIT-Optimierung praktisch verschwinden, wenn die Call-Site stabil ist.

Merksatz:

Der JIT optimiert nicht die Hierarchie, sondern die beobachtete Call-Site.

B2-K05-Q005 – Escape Analysis, Scalar Replacement und Lock EliminationNicht jede sichtbare Allokation überlebt den JIT. 24h
Frage:

„Was ist Escape Analysis, und warum ist sie für moderne Java-Performance wichtig?“

Kurz erklärt:

Escape Analysis prüft, ob ein Objekt den lokalen Kontext verlässt. Wenn nicht, kann der JIT Allokationen und manchmal Synchronisation eliminieren.

Musterantwort:

Escape Analysis analysiert, ob ein Objekt außerhalb der Methode oder des Threads sichtbar wird. Wenn ein Objekt nicht escaped, kann der JIT es eventuell gar nicht als Heap-Objekt anlegen. Stattdessen kann er seine Felder als einzelne skalare Werte behandeln. Das nennt man Scalar Replacement.

Außerdem kann die JVM Synchronisation eliminieren, wenn ein Objekt nur lokal verwendet wird und der Lock nie von anderen Threads gesehen werden kann. Das ist ein Grund, warum kleine immutable Value Objects oft performanter sind, als man aus dem Quellcode vermuten würde.

Senior-Antwort:

„Escape Analysis zeigt, warum Quellcode-Allokationen nicht automatisch echte Heap-Allokationen sind. Ich optimiere deshalb nicht aus Angst, sondern anhand von Messdaten.“

Häufige Fehler:
  • Jede new -Anweisung als tatsächliche Heap-Allokation betrachten.
  • Escape Analysis als garantiert ansehen.
  • Immutability aus Allokationsangst vermeiden.
  • JMH ohne geeignete Methodik verwenden.
  • Framework-Grenzen bei Optimierungen ignorieren.
Best Practices:
  • Value Objects nicht vorschnell vermeiden.
  • Hotpaths mit JMH und JFR messen.
  • Allocation Rate datengetrieben optimieren.
  • Code lesbar halten, bis Messdaten Optimierung verlangen.
  • Große Objektgraphen stärker beachten als lokale kleine Objekte.
Stolperfalle:

Wenn ein Objekt in eine Collection gelegt wird, ist es für Escape Analysis meistens nicht mehr lokal.

Merksatz:

Nicht jede sichtbare Allokation überlebt den JIT.

B2-K05-Q006 – Code Cache und Compiler ThreadsOptimierter Code braucht Platz und Compilerzeit.
Frage:

„Was ist der Code Cache, und warum kann er in großen Anwendungen relevant werden?“

Kurz erklärt:

JIT-kompilierter Maschinencode wird im Code Cache gespeichert. Compiler Threads führen die Kompilierungsarbeit im Hintergrund aus.

Musterantwort:

Der Code Cache ist ein Speicherbereich für JIT-kompilierten Code. Wenn Methoden kompiliert werden, landet der erzeugte Maschinencode dort. Große Anwendungen, viele Framework-Proxies, viele dynamische Klassen oder sehr umfangreiche Hotpaths können den Code Cache stärker belasten.

Wenn der Code Cache voll oder stark fragmentiert ist, kann die JVM weniger oder nicht mehr sinnvoll kompilieren. Dann kann Performance leiden. In solchen Fällen analysiere ich nicht nur Heap und GC, sondern auch Code Cache, Compiler Queue und Compilation Logs.

Senior-Antwort:

„JIT-Code braucht Speicher im Code Cache, und Compilation braucht CPU. Bei großen Anwendungen und Containern ist das Teil der Kapazitätsplanung.“

Häufige Fehler:
  • Code Cache bei JVM-Speicheranalysen ignorieren.
  • Nur Heap betrachten.
  • Compiler-CPU während Warm-up unterschätzen.
  • Compiler Threads in CPU-limitierten Containern ignorieren.
  • Code Cache Flags ohne Messdaten ändern.
Best Practices:
  • Code Cache bei großen Anwendungen im Blick behalten.
  • jcmd Compiler.codecache für Diagnose nutzen.
  • Compiler-Queue und Compilation Time beobachten.
  • CPU-Limits realistisch testen.
  • Code Cache nur datenbasiert tunen.
Stolperfalle:

Ein JVM-Prozess besteht nicht nur aus Heap. JIT-kompilierter Code belegt eigenen Speicher im Code Cache.

Merksatz:

Optimierter Code braucht Platz und Compilerzeit.

B2-K05-Q007 – Deoptimization und spekulative OptimierungDer JIT optimiert mutig, weil er zurückrudern kann.
Frage:

„Was ist Deoptimization, und warum ist sie kein Fehler, sondern Teil des JIT-Modells?“

Kurz erklärt:

Die JVM optimiert spekulativ anhand von Laufzeitannahmen. Wenn Annahmen später nicht mehr gelten, kann sie optimierten Code verwerfen und deoptimieren.

Musterantwort:

Deoptimization bedeutet, dass die JVM aus optimiertem Maschinencode wieder in einen sichereren Zustand zurückfällt, etwa interpretierte Ausführung oder eine weniger spezialisierte Kompilierung. Das passiert, wenn eine spekulative Annahme nicht mehr stimmt.

Beispiel: Der JIT sieht lange nur eine Implementierung eines Interfaces und inlined sie. Später kommt durch ein Plugin eine weitere Implementierung. Die alte Annahme ist nicht mehr sicher, also muss die JVM deoptimieren und möglicherweise neu kompilieren.

Das ist kein Bug, sondern ein Kernmechanismus adaptiver Optimierung.

Senior-Antwort:

„Der JIT optimiert spekulativ. Deoptimization ist der Preis dafür, dass die JVM aggressive Annahmen treffen und bei Bedarf sicher zurückfallen kann.“

Häufige Fehler:
  • Deoptimization als JVM-Fehler verstehen.
  • Laufzeitdynamik in Hotpaths ignorieren.
  • Plugin-Laden während Lastspitzen planen.
  • Performanceprofile nur vor dynamischem Class Loading aufnehmen.
  • Recompilation-Kosten nicht berücksichtigen.
Best Practices:
  • Dynamische Typänderungen in Hotpaths messen.
  • Plugins oder Regeln möglichst vor Traffic-Warm-up laden.
  • Hotpath-Stabilität beobachten.
  • JFR und Compilation Logs bei Latenzspitzen nutzen.
  • Architektur so gestalten, dass Runtime-Dynamik kontrolliert bleibt.
Stolperfalle:

Dynamisches Class Loading während hoher Last kann nicht nur Class-Loading-Kosten, sondern auch JIT-Profiländerungen verursachen.

Merksatz:

Der JIT optimiert mutig, weil er zurückrudern kann.

B2-K05-Q008 – Intrinsics, CPU-spezifische Optimierungen und BibliothekscodeDer beste Low-Level-Code ist oft der, den die JVM bereits kennt.
Frage:

„Was sind JVM Intrinsics, und warum sollte man Standardbibliotheken oft selbstgeschriebenem Code vorziehen?“

Kurz erklärt:

Die JVM kann bestimmte Bibliotheksmethoden durch optimierten, CPU-spezifischen Code ersetzen. Solche Spezialbehandlungen nennt man Intrinsics.

Musterantwort:

Intrinsics sind spezielle Optimierungen, bei denen die JVM bestimmte bekannte Methoden durch besonders effiziente Implementierungen ersetzt oder CPU-Instruktionen nutzt. Das betrifft zum Beispiel Teile von String , System.arraycopy , Kryptografie, Hashing oder Base64, abhängig von Plattform und JVM.

Deshalb ist es oft besser, gut optimierte JDK- oder etablierte Bibliotheksfunktionen zu verwenden, statt eigene Low-Level-Implementierungen zu schreiben. Die JVM kennt diese APIs und kann sie gezielt optimieren.

Senior-Antwort:

„Ich vertraue bei Low-Level-Operationen bevorzugt auf JDK-APIs, weil die JVM sie kennen und plattformspezifisch optimieren kann. Selbstgeschriebener Low-Level-Code ist selten die bessere Wahl.“

Häufige Fehler:
  • Standardbibliotheken durch selbstgeschriebenen Code ersetzen.
  • CPU-spezifische Optimierungen als überall garantiert annehmen.
  • Kryptografie selbst implementieren.
  • Intrinsic-Flags ohne Bedarf ändern.
  • Performance auf anderer Hardware ungeprüft übertragen.
Best Practices:
  • JDK-APIs bevorzugen.
  • Kryptografie nie selbst implementieren.
  • CPU- und JVM-Version in Benchmarks dokumentieren.
  • Intrinsic-Flags normalerweise nicht anfassen.
  • Performance auf Zielhardware messen.
Stolperfalle:

Ein Intrinsic ist eine Optimierung der JVM, kein semantischer Vertrag der Java-Sprache.

Merksatz:

Der beste Low-Level-Code ist oft der, den die JVM bereits kennt.

B2-K05-Q009 – Benchmarking, JMH und typische MessfehlerWer JVM-Performance misst, misst immer auch den JIT.
Frage:

„Warum sind naive Java-Mikrobenchmarks oft falsch, und warum verwendet man JMH?“

Kurz erklärt:

Mikrobenchmarks auf der JVM sind schwierig, weil JIT, Dead-Code-Elimination, Constant Folding, Warm-up, GC, CPU-Skalierung und OS-Effekte das Ergebnis beeinflussen.

Musterantwort:

Ein naiver Benchmark mit System.nanoTime in einer Schleife ist oft falsch, weil der JIT den Code optimieren oder sogar entfernen kann. Wenn das Ergebnis nicht verwendet wird, kann Dead-Code-Elimination greifen. Wenn Werte konstant sind, kann Constant Folding passieren. Außerdem braucht die JVM Warm-up, und einzelne Läufe sind stark von GC, CPU-Frequenz, OS-Scheduling und Profiländerungen beeinflusst.

JMH wurde genau dafür gebaut. Es berücksichtigt Warm-up, Forks, Messphasen, Blackholes, State-Handling und viele typische JVM-Benchmark-Fallen.

Senior-Antwort:

„Auf der JVM ist Benchmarking selbst ein Fachgebiet. Ich traue keinem selbstgebauten Mikrobenchmark, bevor Warm-up, Dead Code, Forks und JIT-Effekte sauber kontrolliert sind.“

Häufige Fehler:
  • System.nanoTime -Schleifen als Beweis verwenden.
  • Kein Warm-up.
  • Ergebnis nicht konsumieren.
  • Nur einen Lauf messen.
  • Mikrobenchmark-Ergebnisse auf Gesamtsysteme übertragen.
  • Debugger oder IDE-Run als Benchmark verwenden.
Best Practices:
  • JMH für Mikrobenchmarks verwenden.
  • Warm-up und Forks konfigurieren.
  • Ergebnisse korrekt konsumieren.
  • Realistische Daten verwenden.
  • Mikrobenchmark und End-to-End-Test trennen.
  • Hardware, JVM-Version und Flags dokumentieren.
Stolperfalle:

Ein Mikrobenchmark kann schneller werden, weil er nichts mehr misst.

Merksatz:

Wer JVM-Performance misst, misst immer auch den JIT.

B2-K05-Q010 – JIT-Diagnose: PrintCompilation, PrintInlining, jcmd und JFRJIT-Diagnose braucht JVM-Daten, nicht Vermutungen. 24h
Frage:

„Wie diagnostizieren Sie JIT-Verhalten in einer laufenden JVM?“

Kurz erklärt:

JIT-Probleme diagnostiziert man nicht durch Raten. Die JVM bietet Flags und Tools, um Compilation, Inlining, Code Cache und Compiler Queue sichtbar zu machen.

Musterantwort:

Ich beginne mit JFR und allgemeinen Profiling-Daten, weil sie CPU, Allocation, Locks, GC, Threads und teilweise Compileraktivität im Zusammenhang zeigen. Für spezifische JIT-Fragen nutze ich -XX:+PrintCompilation , -XX:+PrintInlining , -XX:+LogCompilation oder jcmd -Compiler-Kommandos.

Wichtig ist, Diagnose nicht dauerhaft maximal verbose zu aktivieren. PrintInlining und LogCompilation können sehr viel Output erzeugen. In Produktion nutze ich bevorzugt JFR, Metriken und gezielte jcmd -Abfragen.

Senior-Antwort:

„Ich diagnostiziere JIT nicht über Bauchgefühl. Für Überblick nutze ich JFR, für gezielte Fragen PrintCompilation, PrintInlining, LogCompilation und jcmd.“

Häufige Fehler:
  • JIT-Verhalten ohne Tooling raten.
  • PrintInlining dauerhaft in Produktion aktivieren.
  • Compilation CPU mit Business-CPU verwechseln.
  • Code Cache nicht prüfen.
  • JFR-Daten nicht mit Deployment-Zeitpunkten korrelieren.
Best Practices:
  • JFR als Standarddiagnose vorbereiten.
  • Verbose JIT-Flags gezielt und zeitlich begrenzt nutzen.
  • jcmd für laufende Prozesse kennen.
  • Compilation Time und JVM-Argumente sichtbar machen.
  • Diagnoseergebnisse mit Last, Deployment und Warm-up korrelieren.
Stolperfalle:

Verbose JIT-Logging kann selbst so viel Output erzeugen, dass es Messung und Betrieb stört.

Merksatz:

JIT-Diagnose braucht JVM-Daten, nicht Vermutungen.

B2-K06 – Kapitel 6: Concurrency 10 Karten

B2-K06-Q001 – Thread-Safety und Shared Mutable StateThread-Safety ist Invarianten-Schutz unter gleichzeitiger Ausführung. 24h
Frage:

„Was bedeutet Thread-Safety, und wie erreichen Sie sie in Java?“

Kurz erklärt:

Thread-Safety bedeutet, dass Code bei gleichzeitiger Ausführung durch mehrere Threads korrekt bleibt. Das Hauptproblem ist meist gemeinsam veränderlicher Zustand.

Musterantwort:

Thread-Safety bedeutet, dass ein Objekt oder eine Komponente auch bei gleichzeitigen Zugriffen korrekte Ergebnisse liefert und ihre Invarianten nicht verletzt. Der wichtigste Hebel ist, Shared Mutable State zu vermeiden oder klar zu schützen.

Ich erreiche Thread-Safety durch Immutability, Thread Confinement, sichere Veröffentlichung, synchronized , Locks, Atomics, Concurrent Collections oder höhere Abstraktionen wie Executor-Modelle. Ich wähle die Lösung nach Invariante: Für einzelne Werte können Atomics reichen, für Maps ConcurrentHashMap , für mehrere zusammenhängende Felder oft ein Lock oder ein immutable Snapshot.

Senior-Antwort:

„Thread-Safety beginnt bei der Frage, ob Zustand geteilt und verändert wird. Wenn ich Shared Mutable State vermeiden kann, ist das meist besser als ihn nachträglich mit Locks zu reparieren.“

Häufige Fehler:
  • Thread-Safety nur auf einzelne Methoden beziehen.
  • Mutable Felder in Singleton-Services speichern.
  • ConcurrentHashMap für mehrstufige Geschäftsprozesse überschätzen.
  • Immutable Referenz mit immutable Objekt verwechseln.
  • Tests ohne echte Parallelität als Beweis verwenden.
Best Practices:
  • Shared Mutable State minimieren.
  • Immutable Werte bevorzugen.
  • Thread-Safety im API-Vertrag dokumentieren.
  • Für zusammengesetzte Invarianten klare Locks verwenden.
  • Spring-Beans stateless halten.
  • Concurrent Collections bewusst einsetzen.
Stolperfalle:

Eine thread-safe Collection macht nicht automatisch den gesamten Geschäftsprozess thread-safe.

Merksatz:

Thread-Safety ist Invarianten-Schutz unter gleichzeitiger Ausführung.

B2-K06-Q002 – ExecutorService und Task-basierte NebenläufigkeitExecutor verwalten Parallelität; Threads sind nur das Ausführungsdetail.
Frage:

„Warum sollte man in Enterprise-Java meistens Executor statt manuell gestarteter Threads verwenden?“

Kurz erklärt:

ExecutorService trennt Task-Beschreibung von Thread-Management. Statt Threads direkt zu erzeugen, übergibt man Aufgaben an einen Executor.

Musterantwort:

Ein Executor kapselt Thread-Erzeugung, Scheduling, Queueing, Shutdown und Ergebnisverwaltung. Dadurch kann ich Nebenläufigkeit kontrollieren, statt überall new Thread(...) zu verwenden.

ExecutorService liefert außerdem Future s für Ergebnisse und kontrolliertes Beenden. Wichtig ist aber: Ein Executor ist eine Ressource. Er braucht eine klare Größe, Queue-Strategie, ThreadFactory, Fehlerbehandlung, Monitoring und Shutdown.

Senior-Antwort:

„Ich starte Threads selten direkt. Ich modelliere Arbeit als Tasks und lasse Executor kontrollieren, wie viel Parallelität, Queueing und Shutdown erlaubt sind.“

Häufige Fehler:
  • Pro Request neue Executor erzeugen.
  • Executor nicht herunterfahren.
  • Exceptions in Future s nicht abholen.
  • Unbounded Queues verwenden.
  • CPU-bound und IO-bound Tasks im selben Pool mischen.
  • Common Pool unbewusst belasten.
Best Practices:
  • Executor-Lifecycle klar definieren.
  • Eigene ThreadFactory mit Namen verwenden.
  • Pool-Größe nach Workload wählen.
  • Queue-Größe und Rejection überwachen.
  • shutdown und awaitTermination verwenden.
  • Exceptions aus Future s behandeln.
Stolperfalle:

Ein Executor ohne Shutdown- und Monitoring-Konzept ist ein Ressourcenleck.

Merksatz:

Executor verwalten Parallelität; Threads sind nur das Ausführungsdetail.

B2-K06-Q003 – ThreadPoolExecutor, Queues und BackpressureBackpressure ist kontrolliertes Nein-Sagen.
Frage:

„Warum sind unbounded Queues in Thread Pools gefährlich, und wie gestalten Sie Backpressure?“

Kurz erklärt:

Thread Pools brauchen nicht nur eine Thread-Anzahl. Queue-Größe und Rejection-Policy sind entscheidend für Stabilität und Backpressure.

Musterantwort:

Unbounded Queues verstecken Überlast. Wenn mehr Tasks eingereicht werden, als verarbeitet werden können, wächst die Queue weiter. Die Anwendung wirkt zunächst stabil, aber Latenz und Speicherverbrauch steigen, bis OOM oder massive Timeouts entstehen.

Backpressure bedeutet, dass Überlast sichtbar und begrenzt wird. Das erreiche ich mit bounded Queues, Rejection Policies, Timeouts, Rate Limits, Bulkheads oder externer Queueing-Infrastruktur. Wichtig ist, dass der Aufrufer erfährt, dass das System gesättigt ist, statt beliebig Arbeit anzunehmen.

Senior-Antwort:

„Ein Thread Pool ohne bounded Queue ist oft nur ein verzögerter Ausfall. Ich möchte Überlast kontrolliert sichtbar machen, nicht im Speicher verstecken.“

Häufige Fehler:
  • Executors.newFixedThreadPool ohne Queue-Verständnis verwenden.
  • Unbounded Queues in Hochlastsystemen einsetzen.
  • Rejections als Fehler statt Schutzmechanismus behandeln.
  • Queue-Länge nicht metrisch erfassen.
  • Max Pool Size falsch interpretieren.
  • Tasks ohne Timeout in Pools legen.
Best Practices:
  • Bounded Queues bevorzugen.
  • Rejection-Policy bewusst wählen.
  • Thread-Pool-Metriken erfassen.
  • Queue-Länge alarmieren.
  • Tasks klein und begrenzt halten.
  • Bulkheads pro externer Abhängigkeit nutzen.
Stolperfalle:

Eine wachsende Queue ist keine Skalierung, sondern gespeicherte Latenz.

Merksatz:

Backpressure ist kontrolliertes Nein-Sagen.

B2-K06-Q004 – Locks, ReentrantLock und ConditionsEin Lock schützt eine Invariante, nicht nur einen Codeblock.
Frage:

„Wann würden Sie ReentrantLock statt synchronized verwenden?“

Kurz erklärt:

Locks schützen gemeinsam genutzte Ressourcen. ReentrantLock bietet explizite Lock-/Unlock-Kontrolle und zusätzliche Features gegenüber synchronized .

Musterantwort:

synchronized ist für viele Fälle ausreichend und gut lesbar. ReentrantLock verwende ich, wenn ich zusätzliche Kontrolle brauche: tryLock , interruptibles Locking, Fairness-Optionen, mehrere Condition s oder erweiterte Diagnosemethoden.

Wichtig ist: Ein expliziter Lock muss immer in finally freigegeben werden. Sonst kann eine Exception einen dauerhaften Lock verursachen. Ich verwende Locks nur, wenn die geschützte Invariante klar ist und der kritische Abschnitt klein bleibt.

Senior-Antwort:

„Ich verwende ReentrantLock , wenn ich explizite Lock-Funktionen brauche. Für einfache Exklusivität reicht oft synchronized . Entscheidend ist die geschützte Invariante.“

Häufige Fehler:
  • unlock nicht in finally .
  • Externe Calls im Lock ausführen.
  • Unterschiedliche Locks für dieselbe Invariante verwenden.
  • Lock-Objekte öffentlich zugänglich machen.
  • Fairness blind aktivieren.
Best Practices:
  • Kritische Abschnitte kurz halten.
  • Locks privat halten.
  • Invarianten klar dokumentieren.
  • Externe IO-Aufrufe außerhalb des Locks ausführen.
  • tryLock für zeitkritische Pfade prüfen.
  • Conditions nur bei klarer Wartelogik verwenden.
Stolperfalle:

Ein vergessener unlock ist ein produktiver Deadlock-Kandidat.

Merksatz:

Ein Lock schützt eine Invariante, nicht nur einen Codeblock.

B2-K06-Q005 – Atomics, LongAdder und skalierbare ZählerAtomics schützen einzelne Speicherstellen, nicht ganze Fachprozesse.
Frage:

„Wann verwenden Sie AtomicLong , wann LongAdder , und wann reicht beides nicht?“

Kurz erklärt:

Atomic-Klassen ermöglichen thread-safe Updates einzelner Werte. LongAdder skaliert bei hoher Contention für additive Zähler oft besser als AtomicLong .

Musterantwort:

AtomicLong ist gut, wenn ich einen einzelnen Wert atomar lesen und aktualisieren muss und exakte momentane Werte wichtig sind. LongAdder ist gut für hochfrequente Zähler unter Contention, zum Beispiel Metriken. Er verteilt Updates intern und summiert später.

Beides reicht nicht, wenn mehrere Felder gemeinsam konsistent geändert werden müssen. Dann brauche ich einen Lock, eine Transaktion, immutable Snapshots oder eine andere Koordinationsstrategie.

Senior-Antwort:

„Atomics sind stark für einzelne Zustände. LongAdder ist stark für Metriken unter Contention. Für fachliche Invarianten über mehrere Werte brauche ich ein anderes Modell.“

Häufige Fehler:
  • LongAdder für fachlich exakte Zustände verwenden.
  • Atomics für Multi-Field-Invarianten nutzen.
  • get + put statt computeIfAbsent verwenden.
  • Metrik-Maps ohne Kardinalitätskontrolle bauen.
  • CAS-Loops ohne Contention-Bewertung schreiben.
Best Practices:
  • AtomicLong für einzelne exakte atomare Werte.
  • LongAdder für hochfrequente Metriken.
  • Immutable Objekte in AtomicReference speichern.
  • Multi-Field-Invarianten mit Locks schützen.
  • Metrics-Cardinality begrenzen.
Stolperfalle:

LongAdder ist kein Ersatz für eine fachlich konsistente Kontoführung.

Merksatz:

Atomics schützen einzelne Speicherstellen, nicht ganze Fachprozesse.

B2-K06-Q006 – ConcurrentHashMap und atomare Map-OperationenConcurrentHashMap macht einzelne Map-Updates sicher, nicht ganze Workflows.
Frage:

„Was muss man bei ConcurrentHashMap.compute und computeIfAbsent beachten?“

Kurz erklärt:

ConcurrentHashMap bietet thread-safe Map-Operationen und atomare Compute-Methoden. Sie ersetzt aber keine fachliche Transaktion über mehrere Datenstrukturen.

Musterantwort:

compute , computeIfAbsent und verwandte Methoden erlauben atomare Updates pro Schlüssel. Das ist viel besser als get plus put , weil dazwischen ein anderer Thread eingreifen könnte.

Die Mapping-Funktion muss kurz, deterministisch und frei von blockierenden externen Calls sein. Sie kann während interner Synchronisation laufen. Wenn sie langsam ist oder andere Updates auf derselben Map auslöst, kann sie Contention oder sogar Probleme verursachen.

Senior-Antwort:

„ ConcurrentHashMap gibt mir atomare Operationen pro Schlüssel. Das ist stark, aber es ist keine verteilte Transaktion und kein Ersatz für fachliches Konsistenzdesign.“

Häufige Fehler:
  • get plus put als atomar betrachten.
  • IO in computeIfAbsent ausführen.
  • Compute-Funktionen mit Seiteneffekten schreiben.
  • Mehrere Maps ohne Transaktionsmodell aktualisieren.
  • ConcurrentHashMap als globale Geschäftsprozesssperre missbrauchen.
Best Practices:
  • Atomare Map-Methoden nutzen.
  • Mapping-Funktionen kurz halten.
  • Immutable Values speichern.
  • Hot-Key-Contestion messen.
  • Für mehrstufige Invarianten explizites Design wählen.
  • Cache-Größe und Lifecycle regeln.
Stolperfalle:

Eine langsame computeIfAbsent -Funktion kann genau den Bereich blockieren, der eigentlich schnell und nebenläufig sein sollte.

Merksatz:

ConcurrentHashMap macht einzelne Map-Updates sicher, nicht ganze Workflows.

B2-K06-Q007 – BlockingQueue, Producer-Consumer und BackpressureEine bounded Queue verwandelt Überlast in ein sichtbares Signal.
Frage:

„Warum ist eine bounded BlockingQueue oft besser als eine unbounded Queue?“

Kurz erklärt:

BlockingQueue ist ein klassisches Producer-Consumer-Werkzeug. Bounded Queues ermöglichen lokale Backpressure.

Musterantwort:

Eine bounded Queue hat eine Kapazitätsgrenze. Wenn Producer schneller sind als Consumer, wird Überlast sichtbar: put blockiert oder offer schlägt fehl. Dadurch entsteht Backpressure.

Eine unbounded Queue nimmt weiter Arbeit an und speichert Latenz und Speicherverbrauch. Das kann kurzfristig angenehm wirken, aber langfristig zu OOM, Timeouts oder riesigen Verarbeitungsrückständen führen.

Senior-Antwort:

„Eine bounded Queue ist ein Schutzmechanismus. Sie zeigt mir, wann Producer schneller sind als Consumer, statt Überlast im Heap zu verstecken.“

Häufige Fehler:
  • Unbounded Queues verwenden.
  • Queue-Größe nicht überwachen.
  • Poison-Pill-Pattern unklar einsetzen.
  • InterruptedException ignorieren.
  • Producer und Consumer ohne Shutdown-Protokoll starten.
Best Practices:
  • Bounded Queues bevorzugen.
  • Queue-Länge metrisch erfassen.
  • Shutdown-Protokoll definieren.
  • Interrupts respektieren.
  • Immutable Messages übergeben.
  • Für persistente, verteilte Anforderungen externe Broker nutzen.
Stolperfalle:

Queue-Länge ist Latenz in Objektform.

Merksatz:

Eine bounded Queue verwandelt Überlast in ein sichtbares Signal.

B2-K06-Q008 – Cancellation, Interrupts und TimeoutsCancellation funktioniert nur, wenn Code mitmacht.
Frage:

„Wie funktionieren Interrupts und Task-Cancellation in Java?“

Kurz erklärt:

Cancellation in Java ist meist kooperativ. Interrupts sind ein Signal, kein erzwungener Thread-Kill.

Musterantwort:

Ein Interrupt setzt einen Interrupt-Status oder bricht interruptible Blocking-Operationen wie sleep , wait oder bestimmte Queue-Operationen mit InterruptedException ab. Er tötet den Thread nicht gewaltsam.

Cancellation ist deshalb kooperativ: Code muss Interrupts beachten, Schleifen beenden, Ressourcen freigeben und den Interrupt-Status gegebenenfalls wiederherstellen. Bei Future.cancel(true) wird versucht, den ausführenden Thread zu interrupten, wenn die Task bereits läuft.

Senior-Antwort:

„Interrupt ist ein kooperatives Abbruchsignal. Robuster Code reagiert darauf, räumt auf und versteckt den Interrupt nicht.“

Häufige Fehler:
  • InterruptedException catchen und ignorieren.
  • Endlosschleifen ohne Interrupt-Prüfung.
  • Blocking Calls ohne Timeout.
  • Cancellation als garantierten Abbruch verstehen.
  • Retries ohne Deadline bauen.
Best Practices:
  • Interrupts respektieren.
  • Interrupt-Status nach Catch wiederherstellen, wenn nicht weitergeworfen wird.
  • Timeouts für externe Calls setzen.
  • Deadlines statt unendlicher Retries verwenden.
  • Ressourcen in finally freigeben.
  • Cancellation in APIs dokumentieren.
Stolperfalle:

Future.cancel(true) garantiert nicht, dass die Task sofort beendet ist.

Merksatz:

Cancellation funktioniert nur, wenn Code mitmacht.

B2-K06-Q009 – Deadlocks, Starvation und Lock ContentionDeadlocks verhindert man durch Ordnung, nicht durch Hoffnung.
Frage:

„Wie entstehen Deadlocks, und wie diagnostizieren und verhindern Sie sie?“

Kurz erklärt:

Deadlocks entstehen, wenn Threads zyklisch auf Ressourcen warten. Lock Ordering ist eine klassische Gegenmaßnahme.

Musterantwort:

Ein Deadlock entsteht, wenn mehrere Threads Ressourcen in unterschiedlicher Reihenfolge halten und aufeinander warten. Klassisch: Thread A hält Lock 1 und wartet auf Lock 2, Thread B hält Lock 2 und wartet auf Lock 1.

Verhindern kann man Deadlocks durch konsistente Lock-Reihenfolge, kleine kritische Abschnitte, keine externen Calls unter Lock, tryLock mit Timeout oder höhere Abstraktionen. Diagnostisch nutze ich Thread Dumps, JFR und Lock-Contention-Events.

Senior-Antwort:

„Deadlocks verhindere ich durch Design: klare Lock-Reihenfolge, kleine kritische Abschnitte und keine externen Calls unter Lock. Diagnostisch sind Thread Dumps Pflicht.“

Häufige Fehler:
  • Locks in unterschiedlicher Reihenfolge nehmen.
  • Externe Calls unter Lock ausführen.
  • Öffentliche Objekte als Lock verwenden.
  • Deadlocks nur lokal testen wollen.
  • Starvation und Deadlock verwechseln.
Best Practices:
  • Globale Lock-Reihenfolge definieren.
  • Locks privat halten.
  • Kritische Abschnitte klein halten.
  • Keine IO-Aufrufe unter Lock.
  • Thread Dumps bei Hängern aufnehmen.
  • JFR für Lock Contention nutzen.
Stolperfalle:

Ein Deadlock ist nicht immer hohe CPU. Oft ist das System auffällig ruhig, weil Threads blockiert warten.

Merksatz:

Deadlocks verhindert man durch Ordnung, nicht durch Hoffnung.

B2-K06-Q010 – CompletableFuture und asynchrone KompositionAsynchronität braucht genauso viel Ressourcen-Design wie Synchronität. 24h
Frage:

„Wann verwenden Sie CompletableFuture , und worauf achten Sie bei asynchroner Komposition?“

Kurz erklärt:

CompletableFuture ermöglicht asynchrone Verarbeitung und Komposition von Ergebnissen. Es ersetzt aber kein sauberes Executor-, Timeout- und Fehlerkonzept.

Musterantwort:

Ich verwende CompletableFuture , wenn mehrere unabhängige Operationen asynchron kombiniert werden sollen, zum Beispiel parallele Calls zu Kundenprofil und Risiko-Scoring. Es eignet sich gut für Komposition: thenApply , thenCompose , thenCombine , exceptionally , handle .

Wichtig ist der Executor. Async-Methoden ohne expliziten Executor können den Default-Async-Mechanismus verwenden, häufig den Common Pool. Für blockierendes IO möchte ich meist einen expliziten, passend dimensionierten Executor. Außerdem brauche ich Timeouts, Fehlerbehandlung, Cancellation und Observability.

Senior-Antwort:

„ CompletableFuture ist stark für asynchrone Komposition, aber nur mit kontrolliertem Executor, Timeouts und Fehlerstrategie. Asynchron bedeutet nicht automatisch ressourcenschonend.“

Häufige Fehler:
  • Async-Methoden ohne expliziten Executor verwenden.
  • Blockierende Calls im Common Pool ausführen.
  • join() überall aufrufen und Parallelität zerstören.
  • Fehlerpfade nicht behandeln.
  • Keine Timeouts setzen.
  • Kontext wie MDC oder Security nicht propagieren.
Best Practices:
  • Expliziten Executor für blockierende Tasks verwenden.
  • Timeouts und Deadlines definieren.
  • Fehler mit handle oder exceptionally modellieren.
  • thenCompose für flache async Ketten verwenden.
  • Nicht unnötig blockieren.
  • Context Propagation bewusst lösen.
Stolperfalle:

Ein blockierender Call in CompletableFuture.supplyAsync blockiert trotzdem einen Thread.

Merksatz:

Asynchronität braucht genauso viel Ressourcen-Design wie Synchronität.

B2-K07 – Kapitel 7: Virtual Threads 10 Karten

B2-K07-Q001 – Platform Threads vs. Virtual ThreadsVirtual Threads skalieren Warten, nicht Rechnen. 24h
Frage:

„Was ist der Unterschied zwischen Platform Threads und Virtual Threads?“

Kurz erklärt:

Platform Threads sind Wrapper um Betriebssystem-Threads. Virtual Threads sind leichtgewichtige Threads, die vom JDK verwaltet und auf eine kleinere Anzahl von Carrier-Platform-Threads gemountet werden.

Musterantwort:

Platform Threads sind Java-Threads, die typischerweise direkt mit Betriebssystem-Threads verbunden sind. Sie sind relativ teuer: Stack-Speicher, Scheduling und Kontextwechsel begrenzen, wie viele man sinnvoll parallel betreiben kann.

Virtual Threads sind ebenfalls Thread -Instanzen, aber leichtgewichtig. Die JVM kann sehr viele Virtual Threads verwalten und sie auf wenige Platform Threads schedulen. Wenn ein Virtual Thread bei unterstütztem Blocking blockiert, kann er vom Carrier entkoppelt werden, sodass der Platform Thread andere Virtual Threads ausführen kann.

Der wichtigste Punkt ist: Virtual Threads verbessern die Skalierbarkeit von blockierendem, IO-lastigem Code. Sie machen CPU-bound Code nicht automatisch schneller, weil CPU-Zeit weiterhin begrenzt ist.

Senior-Antwort:

„Virtual Threads machen blockierenden Code skalierbarer, nicht CPU schneller. Ich nutze sie, wenn viele Tasks überwiegend warten, und begrenze weiterhin Zugriffe auf knappe Ressourcen.“

Häufige Fehler:
  • Virtual Threads als CPU-Beschleuniger verstehen.
  • Virtual Threads mit Reactive Programming verwechseln.
  • Weiterhin große IO-Thread-Pools bauen.
  • Unbegrenzte Parallelität gegen externe Systeme erlauben.
  • Platform-Thread-Metriken falsch interpretieren.
Best Practices:
  • Virtual Threads für IO-lastige, blockierende Workloads prüfen.
  • CPU-bound Arbeit weiterhin begrenzen.
  • Externe Systeme mit Semaphoren, Rate Limits oder Bulkheads schützen.
  • Thread-per-task statt Pooling von Virtual Threads nutzen.
  • Produktionsmetriken anpassen: Anzahl Virtual Threads kann sehr groß sein.
Stolperfalle:

Mehr Virtual Threads bedeuten nicht mehr Datenbankverbindungen, mehr CPU oder unbegrenzte externe Kapazität.

Merksatz:

Virtual Threads skalieren Warten, nicht Rechnen.

B2-K07-Q002 – Thread-per-Task statt Thread PoolPoolen Sie knappe Ressourcen, nicht Virtual Threads.
Frage:

„Warum sollte man Virtual Threads normalerweise nicht poolen?“

Kurz erklärt:

Virtual Threads werden typischerweise nicht gepoolt. Man erstellt für jede Aufgabe einen neuen Virtual Thread.

Musterantwort:

Platform Threads poolt man, weil sie teuer sind. Virtual Threads sind leichtgewichtig und dafür gedacht, pro Task neu erstellt zu werden. Ein Virtual-Thread-Pool begrenzt oft nur künstlich die Nebenläufigkeit und nimmt einem das einfache Thread-per-Task-Modell.

Wenn ich Parallelität begrenzen muss, zum Beispiel Datenbankzugriffe auf 50 gleichzeitig, begrenze ich die knappe Ressource mit Semaphore, Connection Pool oder Rate Limiter. Ich pool nicht Virtual Threads selbst.

Senior-Antwort:

„Virtual Threads poolt man normalerweise nicht. Ich begrenze Datenbanken, APIs und andere knappe Ressourcen, nicht die Existenz billiger Virtual Threads.“

Häufige Fehler:
  • Fixed Pool mit Virtual Threads bauen.
  • Parallelität überhaupt nicht begrenzen.
  • Externe Systeme durch zu viele gleichzeitige Calls überlasten.
  • Virtual Threads als Ersatz für Connection Pools verstehen.
  • Keine Timeouts setzen.
Best Practices:
  • newVirtualThreadPerTaskExecutor für Task-basierte IO verwenden.
  • Virtual Threads nicht selbst poolen.
  • Ressourcen mit Semaphoren und Pools begrenzen.
  • Timeouts und Deadlines definieren.
  • Task-Anzahl und Downstream-Saturation beobachten.
Stolperfalle:

Unbounded Virtual Threads ohne Ressourcenbegrenzung können Downstream-Systeme genauso überlasten wie zu viele Platform Threads.

Merksatz:

Poolen Sie knappe Ressourcen, nicht Virtual Threads.

B2-K07-Q003 – Blocking IO und klassische synchrone ProgrammierungVirtual Threads machen Blocking erschwinglicher, nicht irrelevant.
Frage:

„Warum können Virtual Threads synchrone, blockierende Programmierung wieder attraktiver machen?“

Kurz erklärt:

Virtual Threads machen klassischen blockierenden Code wieder sehr attraktiv, weil Blocking nicht mehr zwingend einen teuren Platform Thread dauerhaft blockiert.

Musterantwort:

Klassischer synchroner Code ist oft einfacher zu schreiben, zu lesen und zu debuggen als Callback- oder Reactive-Code. Vor Virtual Threads war Thread-per-request bei sehr hoher Concurrency teuer, weil jeder blockierende Request einen Platform Thread belegte.

Virtual Threads ändern diese Kostenstruktur. Ich kann weiterhin Connection , PreparedStatement , HTTP-Client oder andere blockierende APIs verwenden, ohne für jeden wartenden Request einen OS-Thread zu reservieren. Das reduziert den Druck, rein wegen Thread-Skalierung auf Reactive Programming umzusteigen.

Aber: Blocking auf knappe Ressourcen bleibt Blocking. Datenbankverbindungen, Locks, Rate Limits und externe Systeme müssen weiterhin kontrolliert werden.

Senior-Antwort:

„Virtual Threads erlauben mir, einfachen blockierenden Code zu schreiben, ohne die Thread-Skalierung klassischer Platform Threads zu bezahlen. Sie ersetzen aber keine Ressourcenlimits und keine Backpressure-Strategie.“

Häufige Fehler:
  • Virtual Threads mit Non-blocking IO gleichsetzen.
  • Datenbankpool nicht begrenzen.
  • Blocking Locks ignorieren.
  • CPU-bound Workloads auf Virtual Threads migrieren und Wunder erwarten.
  • Reactive nur aus Mode oder nur aus Angst vor Threads einsetzen.
Best Practices:
  • Synchrone APIs für IO-lastige Use Cases neu bewerten.
  • Connection Pools und Rate Limits beibehalten.
  • Timeouts konsequent setzen.
  • Framework-Kompatibilität prüfen.
  • Performance mit realistischem IO messen.
  • Reactive weiterhin nutzen, wenn Streaming, Backpressure oder Event-Flows zentral sind.
Stolperfalle:

Virtual Threads sind nicht dasselbe wie non-blocking IO; sie machen Blocking günstiger beobachtbar und skalierbarer.

Merksatz:

Virtual Threads machen Blocking erschwinglicher, nicht irrelevant.

B2-K07-Q004 – Pinning und synchronized in Java 21Pinning entsteht, wenn ein Virtual Thread seinen Carrier nicht loslassen kann.
Frage:

„Was ist Pinning bei Virtual Threads, und warum ist es in Java 21 wichtig?“

Kurz erklärt:

In Java 21 kann ein Virtual Thread in bestimmten Situationen gepinnt werden, insbesondere wenn er innerhalb eines synchronized -Blocks oder einer synchronized -Methode blockiert.

Musterantwort:

Pinning bedeutet, dass ein Virtual Thread an seinen Carrier Platform Thread gebunden bleibt und nicht unmounted werden kann. In Java 21 ist das besonders relevant, wenn ein Virtual Thread innerhalb eines synchronized -Blocks oder einer synchronized -Methode blockiert. Dann kann der Carrier nicht für andere Virtual Threads genutzt werden.

Das ist nicht automatisch ein Fehler. Kurze synchronisierte Abschnitte sind meist unproblematisch. Problematisch sind lange oder blockierende Operationen innerhalb von synchronized , etwa IO, Datenbankzugriffe oder Remote Calls. In solchen Fällen sollte man die Synchronisation verkleinern oder java.util.concurrent -Locks prüfen.

Senior-Antwort:

„Pinning ist in Java 21 kein Grund, Virtual Threads abzulehnen, aber ein Grund, blockierende Operationen in synchronized -Bereichen sehr ernst zu nehmen.“

Häufige Fehler:
  • synchronized pauschal verbieten.
  • Blockierende IO innerhalb von synchronized ausführen.
  • Pinning ignorieren, weil lokale Tests funktionieren.
  • Große kritische Abschnitte nicht refaktorieren.
  • Pinning mit Deadlock verwechseln.
Best Practices:
  • Kurze synchronized -Abschnitte sind akzeptabel.
  • Keine blockierenden Remote Calls im Monitor-Lock.
  • Bei langen kritischen Abschnitten ReentrantLock prüfen.
  • Pinning mit JFR oder JVM-Optionen diagnostizieren.
  • Locks und IO klar trennen.
Stolperfalle:

synchronized ist nicht das Problem; lange blockierende Arbeit innerhalb von synchronized ist das Problem.

Merksatz:

Pinning entsteht, wenn ein Virtual Thread seinen Carrier nicht loslassen kann.

B2-K07-Q005 – ThreadLocal, Scoped Values und KontextViele leichte Threads vertragen keinen schweren ThreadLocal-Ballast.
Frage:

„Wie gehen Sie mit ThreadLocal bei Virtual Threads um?“

Kurz erklärt:

Virtual Threads unterstützen ThreadLocals, aber massenhafte oder schwere ThreadLocal-Nutzung kann Speicher- und Designprobleme erzeugen.

Musterantwort:

Virtual Threads unterstützen ThreadLocals wie Platform Threads. Das ist wichtig für bestehende Libraries, Logging-Kontexte oder Security-Kontexte. Trotzdem bin ich vorsichtig: Wenn ich sehr viele Virtual Threads erzeuge und jeder große ThreadLocal-Werte hält, kann das Speicher kosten.

Außerdem sollte Kontext nicht unkontrolliert als globaler Nebeneffekt durchs System laufen. Ich entferne ThreadLocal-Werte immer in finally . Für neue Designs prüfe ich explizite Parameterübergabe oder moderne Alternativen wie Scoped Values, sofern verfügbar und passend.

Senior-Antwort:

„Virtual Threads unterstützen ThreadLocal, aber ich behandle ThreadLocal nicht als kostenlosen globalen Kontext. Viele leichte Threads machen schweren Kontext teuer.“

Häufige Fehler:
  • ThreadLocal-Werte nicht entfernen.
  • Große Objekte in ThreadLocal speichern.
  • Kontext heimlich über viele Layer tragen.
  • ThreadLocal als Ersatz für sauberes API-Design nutzen.
  • Annahmen aus Platform-Thread-Pools unverändert übernehmen.
Best Practices:
  • ThreadLocal sparsam verwenden.
  • Werte in finally entfernen.
  • Nur kleine Kontextwerte speichern.
  • Explizite Parameter für fachlichen Kontext bevorzugen.
  • MDC-/Security-Integration bei Framework-Migration prüfen.
  • Scoped Values für neue Designs beobachten und prüfen.
Stolperfalle:

Virtual Threads lösen ThreadLocal-Leaks nicht automatisch.

Merksatz:

Viele leichte Threads vertragen keinen schweren ThreadLocal-Ballast.

B2-K07-Q006 – Ressourcenbegrenzung: Semaphore, Bulkheads und Connection PoolsVirtual Threads erhöhen mögliche Gleichzeitigkeit; Architektur muss erlaubte Gleichzeitigkei…
Frage:

„Warum brauchen Virtual-Thread-Anwendungen weiterhin Bulkheads, Rate Limits und Connection Pools?“

Kurz erklärt:

Virtual Threads reduzieren Thread-Kosten, aber nicht Ressourcenverbrauch externer Systeme. Parallelität muss weiterhin begrenzt werden.

Musterantwort:

Virtual Threads machen es leicht, sehr viele gleichzeitige Tasks zu starten. Das ist gut für Warteskalierung, aber gefährlich für knappe Ressourcen. Eine Datenbank hat weiterhin begrenzte Connections, ein Downstream-Service begrenzte Kapazität, und ein System hat begrenzte CPU, Netzwerkbandbreite und Speicher.

Deshalb begrenze ich nicht Virtual Threads selbst, sondern die Ressource: Connection Pool für Datenbank, Semaphore für externe Calls, Rate Limiter für APIs, Bulkheads pro Abhängigkeit und Timeouts für alle IO-Pfade.

Senior-Antwort:

„Virtual Threads entfernen nicht die Kapazitätsgrenzen meiner Abhängigkeiten. Ich begrenze Datenbank, HTTP-Clients und externe Systeme explizit.“

Häufige Fehler:
  • Virtual Threads als unbounded Parallelitätsfreigabe verstehen.
  • Datenbankpool zu groß setzen.
  • Downstream-Rate-Limits ignorieren.
  • Semaphore ohne Timeout nutzen.
  • Alle externen Systeme über denselben Bulkhead steuern.
Best Practices:
  • Pro knapper Ressource eigene Begrenzung.
  • Connection Pools nicht unbegrenzt vergrößern.
  • Semaphoren mit Timeouts prüfen.
  • Bulkheads pro Downstream-Service.
  • Rate Limits und Circuit Breaker kombinieren.
  • Saturation-Metriken erfassen.
Stolperfalle:

Wenn der Thread-Flaschenhals verschwindet, wird der nächste Flaschenhals sichtbarer.

Merksatz:

Virtual Threads erhöhen mögliche Gleichzeitigkeit; Architektur muss erlaubte Gleichzeitigkeit definieren.

B2-K07-Q007 – Virtual Threads und CPU-bound WorkloadsVirtual Threads helfen beim Warten, nicht beim Rechnen.
Frage:

„Sollte man CPU-bound Arbeit mit Virtual Threads parallelisieren?“

Kurz erklärt:

Virtual Threads helfen primär bei vielen wartenden Tasks. CPU-bound Workloads bleiben durch verfügbare CPU-Kerne begrenzt.

Musterantwort:

Virtual Threads sind nicht primär für CPU-bound Parallelisierung gedacht. Wenn eine Aufgabe dauerhaft CPU verbraucht und kaum blockiert, kann der Virtual Thread seinen Carrier nicht freigeben. Dann konkurrieren viele Virtual Threads einfach um dieselben CPU-Kerne.

Für CPU-bound Arbeit dimensioniere ich Parallelität nach verfügbaren Prozessoren, etwa mit einem festen Pool, ForkJoin oder spezialisierten Worker-Modellen. Virtual Threads können CPU-bound Code ausführen, aber sie machen ihn nicht schneller.

Senior-Antwort:

„Virtual Threads sind kein CPU-Multiplikator. Für CPU-bound Arbeit begrenze ich Parallelität weiterhin an den verfügbaren Kernen.“

Häufige Fehler:
  • Millionen CPU-bound Virtual Threads starten.
  • CPU-Saturation nicht messen.
  • IO- und CPU-bound Arbeit im selben Modell behandeln.
  • CPU-Limits im Container ignorieren.
  • Parallelität mit Concurrency verwechseln.
Best Practices:
  • CPU-bound Parallelität begrenzen.
  • availableProcessors() und Container-Limits prüfen.
  • CPU- und IO-Workloads trennen.
  • Virtual Threads für wartende Workloads nutzen.
  • Profiling mit realistischen Daten durchführen.
Stolperfalle:

Viele gleichzeitige Tasks sind nicht dasselbe wie mehr Rechenleistung.

Merksatz:

Virtual Threads helfen beim Warten, nicht beim Rechnen.

B2-K07-Q008 – Observability und DebuggingBei Virtual Threads fragt man: Worauf warten sie?
Frage:

„Wie debuggen und beobachten Sie Anwendungen mit Virtual Threads?“

Kurz erklärt:

Virtual Threads sind echte Java-Threads und können in Dumps, JFR und Debugging sichtbar sein. Allerdings ändern sich Thread-Zählung, Metriken und Diagnosemuster.

Musterantwort:

Ich nutze Thread Dumps, JFR, Logs mit Correlation IDs, Executor-/Bulkhead-Metriken, Downstream-Latenzen und Pinning-Diagnose. Wichtig ist, dass klassische Platform-Thread-Zählung nicht mehr dasselbe bedeutet wie Request-Concurrency. Es können sehr viele Virtual Threads existieren.

Ich achte auf: Welche Virtual Threads blockieren? Wo gibt es Pinning? Welche Ressourcen sind saturiert? Gibt es zu viele wartende Tasks auf Datenbankpool, Semaphore oder HTTP-Client? JFR ist besonders wertvoll, weil es Thread-, Blocking-, Pinning- und Scheduling-Effekte im Zusammenhang sichtbar machen kann.

Senior-Antwort:

„Mit Virtual Threads beobachte ich weniger klassische Thread-Pool-Auslastung und mehr Ressourcen-Saturation: Datenbankpool, Semaphoren, Timeouts, Pinning und Downstream-Latenzen.“

Häufige Fehler:
  • Nur Platform-Thread-Count beobachten.
  • Virtual Threads einzeln loggen.
  • Resource Saturation nicht messen.
  • Pinning nicht diagnostizieren.
  • Alte Dashboards unverändert übernehmen.
Best Practices:
  • JFR für Virtual-Thread-Diagnose nutzen.
  • Correlation IDs sauber propagieren.
  • Saturation pro Ressource messen.
  • Pinning gezielt prüfen.
  • Thread-Dump-Strategie an viele Threads anpassen.
  • Dashboards nach Migration aktualisieren.
Stolperfalle:

Wenig Platform-Thread-Auslastung bedeutet nicht, dass das System nicht überlastet ist.

Merksatz:

Bei Virtual Threads fragt man: Worauf warten sie?

B2-K07-Q009 – Migration bestehender AnwendungenVirtual Threads migriert man nach Workload, nicht nach Versionsnummer.
Frage:

„Wie würden Sie eine bestehende Spring-Boot- oder Jakarta-EE-Anwendung auf Virtual Threads migrieren?“

Kurz erklärt:

Virtual-Thread-Migration ist keine reine Flag-Änderung. Man muss Workload, Frameworks, Libraries, Ressourcenlimits, Timeouts und Observability prüfen.

Musterantwort:

Ich beginne mit Workload-Analyse: IO-bound oder CPU-bound? Dann prüfe ich Framework-Support, JDBC-/HTTP-Clients, Timeouts, Connection Pools, synchronisierte Abschnitte, ThreadLocal/MDC/Security Context und Observability.

Danach führe ich Lasttests durch: p95/p99-Latenzen, Durchsatz, CPU, Memory, DB-Pool-Wartezeiten, Downstream-Fehler, Pinning und GC. Ich migriere nicht blind alles, sondern starte mit einem passenden Service oder Endpoint. Wichtig ist, Ressourcenlimits neu zu bewerten, weil Virtual Threads mehr gleichzeitige Arbeit ermöglichen.

Senior-Antwort:

„Ich migriere nicht nach Hype, sondern nach Workload. IO-lastige Services mit sauberem Timeout- und Ressourcenmodell sind gute Kandidaten; CPU-bound Services eher nicht.“

Häufige Fehler:
  • CPU-bound Services migrieren und Durchsatzwunder erwarten.
  • Timeouts erst nach Migration prüfen.
  • Datenbankpool unverändert lassen und mehr Last erzeugen.
  • MDC/Security Context nicht testen.
  • Observability-Dashboards nicht anpassen.
  • Pinning in Legacy-Code ignorieren.
Best Practices:
  • Mit IO-lastigen Services starten.
  • Produktionsähnliche Lasttests durchführen.
  • Timeouts und Bulkheads vor Migration setzen.
  • Pinning analysieren.
  • Context Propagation testen.
  • Rollout schrittweise durchführen.
  • Dashboards und Alerts anpassen.
Stolperfalle:

Eine erfolgreiche Virtual-Thread-Migration kann Downstream-Überlast erzeugen, weil plötzlich mehr gleichzeitige Arbeit möglich ist.

Merksatz:

Virtual Threads migriert man nach Workload, nicht nach Versionsnummer.

B2-K07-Q010 – Virtual Threads, CompletableFuture und Reactive ProgrammingVirtual Threads ersetzen Komplexität, wenn die Komplexität nur wegen Thread-Knappheit entsta… 24h
Frage:

„Ersetzen Virtual Threads CompletableFuture und Reactive Programming?“

Kurz erklärt:

Virtual Threads, CompletableFuture und Reactive Programming lösen überlappende, aber nicht identische Probleme.

Musterantwort:

Virtual Threads ersetzen nicht pauschal CompletableFuture oder Reactive Programming. Sie reduzieren aber den Bedarf, wegen Thread-Skalierung auf komplexe asynchrone oder reaktive Modelle auszuweichen.

CompletableFuture bleibt sinnvoll für explizite asynchrone Komposition, parallele unabhängige Tasks oder APIs, die Futures liefern. Reactive Programming bleibt sinnvoll für Streaming, Backpressure über Datenströme, Push-basierte Event-Flows und Ökosysteme, die darauf aufgebaut sind.

Virtual Threads sind besonders stark, wenn ich klassischen synchronen Code mit vielen blockierenden IO-Operationen schreiben möchte.

Senior-Antwort:

„Virtual Threads reduzieren die Notwendigkeit komplexer Async-Modelle, wenn es nur um viele blockierende Requests geht. Für Streaming, Backpressure und event-driven Flows bleibt Reactive weiterhin relevant.“

Häufige Fehler:
  • Virtual Threads als Ersatz für alle Async-Modelle sehen.
  • Reactive komplett abschreiben.
  • CompletableFuture unnötig verschachteln.
  • Blocking Code in Event Loops ausführen.
  • Backpressure-Anforderungen ignorieren.
Best Practices:
  • Problem zuerst klassifizieren.
  • Virtual Threads für blockierenden Request/Response-Code prüfen.
  • Reactive für Streaming und echte Backpressure nutzen.
  • CompletableFuture für explizite parallele Komposition verwenden.
  • Framework-Modell nicht mischen, ohne Threading zu verstehen.
  • Diagnose- und Fehlerstrategie passend wählen.
Stolperfalle:

Virtual Threads lösen nicht automatisch Backpressure über Datenströme.

Merksatz:

Virtual Threads ersetzen Komplexität, wenn die Komplexität nur wegen Thread-Knappheit entstanden ist.

B2-K08 – Kapitel 8: Structured Concurrency 10 Karten

B2-K08-Q001 – Grundidee und Scope-ModellStructured Concurrency macht parallele Arbeit wieder blockstrukturiert. 24h
Frage:

„Was ist Structured Concurrency, und welches Problem löst sie?“

Kurz erklärt:

Structured Concurrency macht klar: Unteraufgaben gehören zur Lebensdauer ihres übergeordneten Scopes. Der Parent läuft erst weiter, wenn die relevanten Subtasks abgeschlossen oder abgebrochen sind.

Musterantwort:

Structured Concurrency überträgt die Idee strukturierter Programmierung auf Nebenläufigkeit. Wenn eine Operation in mehrere parallele Unteraufgaben aufgeteilt wird, werden diese Unteraufgaben in einem Scope zusammengefasst. Der Scope sorgt dafür, dass der Parent nicht einfach weiterläuft, während Child Tasks irgendwo im Hintergrund weiterarbeiten.

Das löst typische Probleme unstrukturierter Nebenläufigkeit: verlorene Tasks, vergessene Cancellation, unklare Fehlerbehandlung, Thread-Leaks und schlechte Observability. Besonders zusammen mit Virtual Threads kann man parallele IO-Aufgaben klar und synchron lesbar ausdrücken.

Senior-Antwort:

„Structured Concurrency gibt parallelen Subtasks eine klare Lebensdauer. Ich sehe nicht mehr lose Futures, sondern eine gemeinsame Arbeitseinheit mit definierter Fehler- und Cancellation-Semantik.“

Häufige Fehler:
  • Structured Concurrency als reines Performance-Feature sehen.
  • Subtasks außerhalb des Scopes weiterverwenden.
  • Fehlerbehandlung nach join() vergessen.
  • Preview-Status in Java 21 ignorieren.
  • Ressourcenlimits durch Scope-Struktur ersetzen wollen.
Best Practices:
  • Zusammengehörige Subtasks in einem Scope bündeln.
  • Scope-Lebensdauer klein halten.
  • Nach join() Fehler explizit behandeln.
  • Subtask-Ergebnisse erst nach erfolgreichem Join lesen.
  • Ressourcenlimits weiterhin separat modellieren.
  • Preview-API bewusst kapseln.
Stolperfalle:

Ein Scope ersetzt kein Bulkhead. Er strukturiert Tasks, aber er begrenzt nicht automatisch externe Ressourcen.

Merksatz:

Structured Concurrency macht parallele Arbeit wieder blockstrukturiert.

B2-K08-Q002 – ShutdownOnFailure und FehlersemantikWenn ein Teil das Ganze unmöglich macht, sollte der Scope das Ganze abbrechen.
Frage:

„Wann verwenden Sie StructuredTaskScope.ShutdownOnFailure ?“

Kurz erklärt:

ShutdownOnFailure beendet den Scope, wenn ein Subtask fehlschlägt. Andere noch laufende Subtasks werden abgebrochen.

Musterantwort:

Ich verwende ShutdownOnFailure , wenn alle Subtasks für das Gesamtergebnis notwendig sind. Wenn ein Teil fehlschlägt, ist das Gesamtergebnis nicht mehr sinnvoll. Dann sollten andere laufende Subtasks nicht unnötig weiterarbeiten.

Beispiel: Eine Account-Overview braucht Balance, Limits und Transaktionen. Wenn Balance nicht geladen werden kann, soll der Request fehlschlagen und die anderen Tasks sollten abgebrochen werden. Das ist klarer als mehrere Futures manuell zu canceln.

Senior-Antwort:

„ ShutdownOnFailure verwende ich, wenn alle Subtasks notwendig sind. Es macht Fail-fast und Cancellation explizit und verhindert lose weiterlaufende Arbeit.“

Häufige Fehler:
  • throwIfFailed() vergessen.
  • Interrupts in Subtasks ignorieren.
  • Fail-fast verwenden, obwohl Teilresultate erlaubt sind.
  • Downstream-Time-outs nicht setzen.
  • Scope-Fehler ohne Kontext wrappen.
Best Practices:
  • ShutdownOnFailure für „alles oder nichts“-Aggregation nutzen.
  • Subtasks interruptible schreiben.
  • Timeouts pro externer Operation setzen.
  • Fehler mit fachlichem Kontext wrappen.
  • Teilresultat-Semantik separat modellieren.
  • Cancellation in Tests prüfen.
Stolperfalle:

Fail-fast funktioniert nur sauber, wenn Subtasks Cancellation und Interrupts respektieren.

Merksatz:

Wenn ein Teil das Ganze unmöglich macht, sollte der Scope das Ganze abbrechen.

B2-K08-Q003 – ShutdownOnSuccess und Racing RequestsNimm den ersten Erfolg nur, wenn jeder Erfolg gleichwertig genug ist.
Frage:

„Wann verwenden Sie StructuredTaskScope.ShutdownOnSuccess ?“

Kurz erklärt:

ShutdownOnSuccess ist sinnvoll, wenn der erste erfolgreiche Subtask reicht und übrige Subtasks abgebrochen werden sollen.

Musterantwort:

Ich verwende ShutdownOnSuccess , wenn mehrere alternative Wege zum Ergebnis führen und das erste erfolgreiche Ergebnis ausreicht. Zum Beispiel redundante Abfragen mehrerer Regionen, Mirror Services oder alternative Datenquellen.

Sobald ein Subtask erfolgreich ist, werden die anderen Tasks nicht mehr benötigt und können abgebrochen werden. Das reduziert Latenz und Last. Wichtig ist, dass die Ergebnisquellen fachlich äquivalent oder zumindest akzeptabel austauschbar sind.

Senior-Antwort:

„ ShutdownOnSuccess ist gut für alternative Ergebnisquellen. Ich nutze es aber nur, wenn das erste erfolgreiche Ergebnis fachlich akzeptabel ist und die zusätzliche Last kontrolliert bleibt.“

Häufige Fehler:
  • Racing für nicht äquivalente Datenquellen verwenden.
  • Downstream-Last vervielfachen.
  • Teure Subtasks nach Success nicht canceln.
  • Fehlerfall „alle fehlgeschlagen“ nicht modellieren.
  • Erfolgsdefinition nicht fachlich klären.
Best Practices:
  • Nur äquivalente oder akzeptabel austauschbare Quellen racen.
  • Downstream-Limits beachten.
  • Racing mit Timeouts kombinieren.
  • Ergebnisse mit Quelle kennzeichnen.
  • Kosten gegen Latenzgewinn abwägen.
  • Observability für abgebrochene Subtasks einplanen.
Stolperfalle:

Racing reduziert Latenz, kann aber Last vervielfachen.

Merksatz:

Nimm den ersten Erfolg nur, wenn jeder Erfolg gleichwertig genug ist.

B2-K08-Q004 – Cancellation und Interrupts im ScopeStructured Cancellation ist kooperativ, aber endlich sichtbar.
Frage:

„Wie funktioniert Cancellation in Structured Concurrency, und welche Rolle spielen Interrupts?“

Kurz erklärt:

Structured Concurrency erleichtert Cancellation, aber Subtasks müssen weiterhin interrupt-freundlich sein.

Musterantwort:

Wenn ein Scope heruntergefahren wird, zum Beispiel durch Failure oder Success-Policy, werden noch laufende Subtasks abgebrochen. In Java bedeutet das typischerweise, dass ihre Threads interruptet werden.

Das funktioniert nur sauber, wenn der Code Interrupts respektiert: Blocking Calls brauchen Timeouts oder müssen interruptible sein, InterruptedException darf nicht verschluckt werden, und Ressourcen müssen in finally geschlossen werden. Structured Concurrency gibt die Struktur, aber kooperative Cancellation bleibt Aufgabe des Subtask-Codes.

Senior-Antwort:

„Structured Concurrency macht Cancellation sichtbar und zusammenhängend. Aber sie funktioniert nur so gut wie die Interrupt- und Timeout-Disziplin der Subtasks.“

Häufige Fehler:
  • Interrupts verschlucken.
  • Cancellation als garantierten harten Abbruch verstehen.
  • Blocking IO ohne Timeout.
  • Ressourcen nicht in finally schließen.
  • Tasks starten, die nicht zum Scope gehören.
Best Practices:
  • Subtasks interrupt-freundlich schreiben.
  • Timeouts auf IO setzen.
  • InterruptedException weiterwerfen oder Interrupt-Status wiederherstellen.
  • Ressourcen in try-with-resources nutzen.
  • Cancellation-Pfade testen.
  • Langlaufende Tasks regelmäßig auf Interrupt prüfen.
Stolperfalle:

Ein Scope kann Cancellation signalisieren, aber er kann schlecht geschriebenen blockierenden Code nicht zuverlässig sofort stoppen.

Merksatz:

Structured Cancellation ist kooperativ, aber endlich sichtbar.

B2-K08-Q005 – Deadlines und joinUntilEin Scope braucht eine Zeitgrenze, nicht nur gute Absichten. 24h
Frage:

„Wie setzen Sie Timeouts oder Deadlines mit Structured Concurrency um?“

Kurz erklärt:

joinUntil erlaubt, auf Subtasks nur bis zu einer Deadline zu warten. Deadlines sind für produktive Services oft besser als unbounded Waits.

Musterantwort:

Ich setze eine Deadline für den gesamten Scope und verwende joinUntil(deadline) . Dadurch warte ich nicht unbegrenzt auf Subtasks. Wenn die Deadline erreicht wird, muss ich den Fehler fachlich sauber behandeln und sicherstellen, dass die Subtasks abgebrochen werden.

Wichtig ist: Zusätzlich sollten einzelne IO-Calls eigene Timeouts haben. Scope-Deadline und Client-Timeouts ergänzen sich. Eine globale Deadline verhindert, dass der Parent ewig wartet; IO-Timeouts sorgen dafür, dass einzelne blockierende Operationen kontrolliert abbrechen.

Senior-Antwort:

„Ich möchte nicht nur einzelne Calls timeouten, sondern den gesamten Request zeitlich begrenzen. joinUntil gibt dem Scope eine klare Deadline.“

Häufige Fehler:
  • Unbounded join() in Request-Pfaden.
  • Nur äußeren Timeout setzen, aber IO-Calls ohne Timeout lassen.
  • Deadlines nicht an Subtasks propagieren.
  • Timeouts ohne fachliche Fehlerantwort.
  • Timeout-Raten nicht metrisch erfassen.
Best Practices:
  • Request-Deadline definieren.
  • joinUntil für Scope-Begrenzung verwenden.
  • Client-Timeouts zusätzlich setzen.
  • Deadlines an Downstreams propagieren, wenn möglich.
  • Timeout-Fehler fachlich mappen.
  • Timeout-Metriken beobachten.
Stolperfalle:

Eine Scope-Deadline ersetzt keine Timeouts in blockierenden Clients.

Merksatz:

Ein Scope braucht eine Zeitgrenze, nicht nur gute Absichten.

B2-K08-Q006 – Structured Concurrency vs CompletableFutureFutures beschreiben Ergebnisse; Scopes beschreiben Task-Lebensdauer.
Frage:

„Wann würden Sie Structured Concurrency statt CompletableFuture verwenden?“

Kurz erklärt:

CompletableFuture modelliert asynchrone Ergebnis-Komposition. Structured Concurrency modelliert Lebensdauer, Fehler und Cancellation einer Gruppe verwandter Tasks.

Musterantwort:

Ich verwende Structured Concurrency, wenn mehrere Subtasks logisch zu einer Parent-Operation gehören und gemeinsam abgeschlossen, abgebrochen und diagnostiziert werden sollen. Die Struktur im Code entspricht der Struktur der Aufgabe.

CompletableFuture bleibt sinnvoll, wenn ich eine asynchrone API brauche, Ergebnisse an spätere Verarbeitung weiterreiche oder komplexe Completion-Pipelines modelliere. Aber für klassische Request-Aggregation mit parallelen IO-Calls ist ein StructuredTaskScope oft lesbarer und hat klarere Cancellation-Semantik.

Senior-Antwort:

„Structured Concurrency ist für logisch zusammengehörige Subtasks oft klarer als Future-Ketten. CompletableFuture bleibt nützlich, wenn das Ergebnis selbst asynchroner API-Vertrag ist.“

Häufige Fehler:
  • Structured Concurrency als Future-Ersatz für alle Fälle sehen.
  • CompletableFuture ohne Executor und Timeout nutzen.
  • Future-Ketten bauen, obwohl Scope-Struktur klarer wäre.
  • Structured Scopes für langlaufende Hintergrundjobs verwenden.
  • Cancellation-Semantik nicht vergleichen.
Best Practices:
  • StructuredTaskScope für Parent-Child-Task-Gruppen nutzen.
  • CompletableFuture für asynchrone API-Grenzen verwenden.
  • Cancellation bewusst modellieren.
  • Executors und Ressourcenlimits klar definieren.
  • Lesbarkeit und Observability als Entscheidungskriterien nutzen.
Stolperfalle:

CompletableFuture.allOf bricht nicht automatisch alle anderen Tasks sauber ab, nur weil einer fehlschlägt.

Merksatz:

Futures beschreiben Ergebnisse; Scopes beschreiben Task-Lebensdauer.

B2-K08-Q007 – Observability, Thread Dumps und Task-HierarchieStrukturierte Tasks sind leichter zu finden als lose Futures.
Frage:

„Warum verbessert Structured Concurrency Observability und Debugging?“

Kurz erklärt:

Structured Concurrency verbessert Observability, weil Subtasks eine erkennbare Beziehung zur Parent-Operation haben.

Musterantwort:

Unstrukturierte Nebenläufigkeit erzeugt lose Tasks, deren Beziehung zum ursprünglichen Request oft schwer erkennbar ist. Structured Concurrency hält Subtasks in einem Scope zusammen. Dadurch lässt sich besser verstehen: Welche Operation hat welche Subtasks gestartet? Welche Subtasks laufen noch? Welche wurden abgebrochen? Welcher Fehler hat den Scope beendet?

Mit Virtual Threads und JFR/Thread Dumps kann diese Hierarchie deutlich besser sichtbar werden. Voraussetzung ist aber, dass wir sinnvolle Namen, Correlation IDs und Metriken verwenden.

Senior-Antwort:

„Structured Concurrency macht Task-Beziehungen sichtbar. Im Incident will ich nicht nur wissen, dass Threads laufen, sondern zu welchem Request und Scope sie gehören.“

Häufige Fehler:
  • Tasks ohne Namen starten.
  • Correlation ID nicht in Subtasks propagieren.
  • Observability nur auf Parent-Request beschränken.
  • Jeden Virtual Thread einzeln loggen.
  • Scope-Struktur nicht in Metriken abbilden.
Best Practices:
  • Scope-Namen sinnvoll wählen.
  • ThreadFactory mit lesbaren Thread-Namen nutzen.
  • Correlation IDs propagieren.
  • Metriken pro Downstream und Scope erfassen.
  • JFR für Blockierung und Pinning nutzen.
  • Error-Logging am Scope-Rand zentralisieren.
Stolperfalle:

Structured Concurrency verbessert Observability nur, wenn Kontext und Namen tatsächlich gepflegt werden.

Merksatz:

Strukturierte Tasks sind leichter zu finden als lose Futures.

B2-K08-Q008 – Preview-Status, API-Kapselung und MigrationPreview-APIs gehören hinter klare Grenzen.
Frage:

„Würden Sie Structured Concurrency in Java 21 produktiv verwenden, obwohl es Preview ist?“

Kurz erklärt:

In Java 21 ist Structured Concurrency Preview. Produktionsarchitektur sollte Preview-API-Nutzung bewusst kapseln.

Musterantwort:

Ich würde es nur bewusst und kontrolliert verwenden. Preview bedeutet: Die API ist nicht final und kann sich in späteren Java-Versionen ändern. Für produktive Systeme muss man das Risiko abwägen.

Wenn der Nutzen groß ist, etwa für klar begrenzte interne Aggregationslogik, würde ich die Nutzung kapseln, zum Beispiel hinter einer kleinen internen Facade. Ich würde nicht die Preview-API durch die gesamte Codebase leaken lassen. Außerdem muss Build, Runtime und Deployment mit --enable-preview umgehen.

Senior-Antwort:

„Preview heißt für mich nicht automatisch verboten, aber es heißt: bewusst kapseln, Flags standardisieren, Upgrade-Risiko akzeptieren und keine öffentlichen Verträge darauf bauen.“

Häufige Fehler:
  • Preview-API breit in Fachcode streuen.
  • --enable-preview nur lokal setzen.
  • CI/CD-Konfiguration vergessen.
  • Upgrade-Risiko ignorieren.
  • Preview mit Experimental gleichsetzen oder umgekehrt verharmlosen.
Best Practices:
  • Preview-Status dokumentieren.
  • API-Nutzung kapseln.
  • Build und Runtime Flags standardisieren.
  • Upgrade-Pfad einplanen.
  • Tests für Failure, Timeout und Cancellation schreiben.
  • Öffentliche Library-APIs nicht von Preview-Typen abhängig machen.
Stolperfalle:

Preview-Code, der überall in der Codebase verteilt ist, wird beim JDK-Upgrade teuer.

Merksatz:

Preview-APIs gehören hinter klare Grenzen.

B2-K08-Q009 – Ressourcenlimits innerhalb strukturierter ScopesStruktur begrenzt Lebensdauer, nicht Last.
Frage:

„Wie kombinieren Sie Structured Concurrency mit Bulkheads oder Semaphoren?“

Kurz erklärt:

Structured Concurrency strukturiert Task-Lebensdauer. Ressourcenlimits wie Downstream-Kapazität, DB-Connections oder Rate Limits müssen zusätzlich modelliert werden.

Musterantwort:

Ich nutze Structured Concurrency, um zusammengehörige Tasks sauber zu starten, zu joinen und abzubrechen. Für knappe Ressourcen nutze ich zusätzlich Bulkheads, Semaphoren, Connection Pools oder Rate Limiter.

Ein Scope kann 100 Subtasks starten, aber vielleicht darf nur 20-mal gleichzeitig der Customer-Service aufgerufen werden. Diese Grenze gehört nicht in die Thread-Anzahl, sondern an den Downstream-Zugriff. Dadurch bleibt der Code strukturiert, aber die Ressource geschützt.

Senior-Antwort:

„Structured Concurrency gibt mir Lebensdauerstruktur. Bulkheads geben mir Kapazitätskontrolle. Ich brauche beides.“

Häufige Fehler:
  • Alle Subtasks gleichzeitig auf einen Downstream loslassen.
  • Semaphore ohne Timeout verwenden.
  • Bulkhead für mehrere unterschiedliche Downstreams teilen.
  • Saturation als 500 statt kontrollierten Fehler mappen.
  • Limits nicht metrisch erfassen.
Best Practices:
  • Pro Downstream eigenes Limit.
  • Semaphoren mit Timeout prüfen.
  • Saturation-Metriken erfassen.
  • Limits in Konfiguration auslagern.
  • Fail-fast oder Queueing bewusst entscheiden.
  • Scope und Bulkhead getrennt erklären.
Stolperfalle:

Viele billige Subtasks können teure externe Ressourcen sehr schnell überlasten.

Merksatz:

Struktur begrenzt Lebensdauer, nicht Last.

B2-K08-Q010 – Architekturgrenzen und geeignete EinsatzfälleStructured Concurrency ist für Arbeit, die gemeinsam beginnt und gemeinsam endet. 24h
Frage:

„Wann ist Structured Concurrency geeignet, und wann nicht?“

Kurz erklärt:

Structured Concurrency ist besonders geeignet für begrenzte Parent-Child-Task-Strukturen. Nicht jede Nebenläufigkeit ist ein Scope.

Musterantwort:

Structured Concurrency passt sehr gut, wenn eine Parent-Operation mehrere verwandte Subtasks startet und erst weitergehen darf, wenn diese Subtasks erfolgreich, fehlgeschlagen oder abgebrochen sind. Typische Fälle sind Request-Aggregation, parallele Validierungen, redundante Queries oder bounded IO-Fan-out.

Nicht passend ist es für unabhängige Fire-and-Forget-Jobs, langlebige Hintergrundprozesse, Event-Streaming mit Backpressure oder durable Workflows, die einen Request überleben müssen. Dafür brauche ich Job-Infrastruktur, Messaging, Workflow-Engines oder Reactive Streams.

Senior-Antwort:

„Structured Concurrency ist hervorragend für Parent-Child-Arbeitseinheiten. Es ist nicht die Lösung für langlebige, unabhängige oder durable Hintergrundarbeit.“

Häufige Fehler:
  • Alles Parallelisierbare strukturieren, obwohl es keinen Gewinn bringt.
  • Fire-and-forget in einem Scope verstecken.
  • Durable Workflows request-lokal starten.
  • Streaming-Backpressure ignorieren.
  • Parallelisierung ohne Ressourcenbudget.
Best Practices:
  • Nur verwandte Subtasks in einen Scope.
  • Parent muss Kind-Lebensdauer besitzen.
  • Keine langlebigen Jobs im Request-Scope.
  • Sequential code bevorzugen, wenn ausreichend.
  • Für durable Arbeit Messaging oder Job-System nutzen.
  • Für Streaming echte Backpressure-Modelle verwenden.
Stolperfalle:

Fire-and-forget ist bewusst unstrukturiert; wenn es zuverlässig sein soll, braucht es Infrastruktur, nicht nur einen Thread.

Merksatz:

Structured Concurrency ist für Arbeit, die gemeinsam beginnt und gemeinsam endet.

B2-K09 – Kapitel 9: Performance 10 Karten

B2-K09-Q001 – Performance-Ziele: Latenz, Durchsatz, Footprint und KostenPerformance ohne Ziel ist Tuning-Theater. 24h
Frage:

„Wie definieren Sie Performance-Ziele für einen Java-Service?“

Kurz erklärt:

Performance muss an Zielen gemessen werden: Latenz, Durchsatz, Speicherverbrauch, Stabilität, Kosten und Nutzererlebnis. Ohne Ziel gibt es keine sinnvolle Optimierung.

Musterantwort:

Ich beginne mit SLOs und Workload: Welche Operationen sind kritisch? Welche p95- und p99-Latenz ist akzeptabel? Welcher Durchsatz wird erwartet? Wie viele gleichzeitige Nutzer, welche Datenmengen, welche Fehlerrate, welches Speicher- und CPU-Budget?

Ich unterscheide Durchschnitt und Tail Latency. Durchschnittswerte sind oft trügerisch. Für Nutzer und SLAs sind p95, p99 und p999 häufig wichtiger. Außerdem betrachte ich Kosten: Ein Service kann schneller werden, aber doppelt so viel CPU verbrauchen. Das ist nicht automatisch besser.

Senior-Antwort:

„Ich optimiere nicht auf Gefühl, sondern auf Ziele. Ohne p99, Durchsatz, Workload und Ressourcenbudget ist Performancearbeit nicht steuerbar.“

Häufige Fehler:
  • Ohne Ziel optimieren.
  • Durchschnittswerte als Hauptmetrik verwenden.
  • Lokale Entwicklerdaten als Produktionsworkload annehmen.
  • Durchsatz verbessern und p99 ruinieren.
  • CPU- und Cloud-Kosten ignorieren.
Best Practices:
  • SLOs vor Optimierung definieren.
  • p95/p99 statt nur Durchschnitt messen.
  • Workload realistisch modellieren.
  • Kosten und Ressourcenverbrauch berücksichtigen.
  • Performanceziele dokumentieren.
  • Vorher-nachher-Vergleich erzwingen.
Stolperfalle:

Eine bessere Durchschnittslatenz kann schlechtere Nutzererfahrung bedeuten, wenn p99 explodiert.

Merksatz:

Performance ohne Ziel ist Tuning-Theater.

B2-K09-Q002 – Messmethodik: Hypothese, Baseline und ReproduzierbarkeitPerformancearbeit ist Experimentieren mit Belegen.
Frage:

„Wie gehen Sie methodisch vor, wenn ein Java-Service zu langsam ist?“

Kurz erklärt:

Seriöse Performancearbeit braucht eine Baseline, eine Hypothese, reproduzierbare Messung und einen Vergleich unter gleichen Bedingungen.

Musterantwort:

Ich starte nicht mit Codeänderungen. Zuerst definiere ich das Symptom: Welche Operation, welche Metrik, welcher Zeitraum, welche Last? Dann erstelle ich eine Baseline. Danach formuliere ich Hypothesen: CPU, GC, Datenbank, Netzwerk, Locking, Allocation, Serialization, JIT-Warm-up oder Downstream?

Dann sammle ich Daten mit Profilern, JFR, Logs, Metriken, Traces und Lasttests. Ich ändere jeweils möglichst eine Sache, messe erneut und dokumentiere Ergebnis und Umgebung. Performancearbeit ohne Reproduzierbarkeit führt zu Cargo-Cult-Optimierungen.

Senior-Antwort:

„Ich behandle Performance wie ein Experiment: Baseline, Hypothese, Messung, kontrollierte Änderung, erneute Messung. Alles andere ist Bauchgefühl.“

Häufige Fehler:
  • Optimieren ohne Baseline.
  • Mehrere Änderungen gleichzeitig einführen.
  • Lasttestdaten wechseln.
  • JVM-Version oder Flags nicht dokumentieren.
  • Lokalen Laptop als Referenz verwenden.
  • Ergebnisse ohne Streuung betrachten.
Best Practices:
  • Baseline vor jeder Optimierung.
  • Hypothesen explizit formulieren.
  • Umgebung versionieren.
  • JVM-Argumente mitschreiben.
  • Realistische Daten verwenden.
  • Wiederholungen und Streuung betrachten.
  • Ergebnisse im ADR oder Performance-Log dokumentieren.
Stolperfalle:

Eine Optimierung, die nicht reproduzierbar gemessen wurde, ist nur eine Behauptung.

Merksatz:

Performancearbeit ist Experimentieren mit Belegen.

B2-K09-Q003 – Profiling mit JFR statt RatenJFR macht aus Vermutungen Zeitlinien.
Frage:

„Warum ist JFR für Java-Performanceanalyse so wertvoll?“

Kurz erklärt:

Java Flight Recorder ist ein JVM-nahes Diagnose- und Profiling-Werkzeug. Es verbindet CPU, Allocation, GC, Threads, Locks, IO und eigene Events.

Musterantwort:

JFR ist wertvoll, weil es viele JVM- und Anwendungssignale zeitlich korreliert erfasst: CPU, Allocation, GC, Lock Contention, Thread-Zustände, Exceptions, IO, Class Loading und eigene Events. Dadurch sehe ich nicht nur, dass ein Request langsam war, sondern welche JVM- oder Anwendungsvorgänge gleichzeitig passiert sind.

Im Gegensatz zu reinem Logging oder isoliertem CPU-Profiling liefert JFR einen breiten Kontext. Es ist besonders gut für produktionsnahe Diagnose, Warm-up-Analyse, Allocation-Hotspots und sporadische Latenzspitzen.

Senior-Antwort:

„JFR ist mein bevorzugtes JVM-Diagnosewerkzeug, weil es CPU, Allocation, GC, Locks, Threads und fachliche Events zeitlich zusammenführt.“

Häufige Fehler:
  • Ohne Profiling optimieren.
  • Nur CPU betrachten und Allocation/Locks ignorieren.
  • JFR zu spät im Incident aktivieren.
  • Eigene Events mit sensiblen Daten füllen.
  • JFR-Ergebnisse ohne Zeitkorrelation lesen.
Best Practices:
  • JFR als Standarddiagnose vorbereiten.
  • Eigene Events für kritische Use Cases definieren.
  • Sensible Daten vermeiden.
  • Recordings mit Deployment- und Lastzeitpunkten korrelieren.
  • Allocation, Locks, GC und CPU gemeinsam betrachten.
  • Default-Settings vor aggressivem Profiling nutzen.
Stolperfalle:

Ein CPU-Profil allein erklärt nicht alle Latenzspitzen. Oft sind Allocation, Blocking, GC oder Downstreams entscheidend.

Merksatz:

JFR macht aus Vermutungen Zeitlinien.

B2-K09-Q004 – Mikrobenchmarks mit JMHMikrobenchmarks beantworten Mikrofragen.
Frage:

„Warum ist JMH besser als eine selbstgeschriebene System.nanoTime -Schleife?“

Kurz erklärt:

JMH ist das OpenJDK-Harness für zuverlässigere Benchmarks auf der JVM. Es hilft, typische Messfehler durch JIT, Warm-up und Dead-Code-Elimination zu vermeiden.

Musterantwort:

Eine einfache nanoTime -Schleife misst auf der JVM oft nicht das, was man denkt. Der JIT kann Code wegoptimieren, Konstanten falten, Schleifen anders behandeln oder unrealistische Profile sammeln. Außerdem braucht die JVM Warm-up, und einzelne Läufe sind instabil.

JMH adressiert typische JVM-Benchmark-Fallen: Warm-up, Measurement-Phasen, Forks, Blackholes, State-Management und Benchmark-Modi. Trotzdem ersetzt JMH keinen Lasttest. Ein Mikrobenchmark zeigt isoliertes Verhalten, nicht automatisch End-to-End-Performance.

Senior-Antwort:

„JMH ist Pflicht für seriöse JVM-Mikrobenchmarks. Aber ich vergesse nie: Ein Mikrobenchmark beantwortet eine kleine isolierte Frage, nicht die Gesamtperformance eines Services.“

Häufige Fehler:
  • nanoTime -Schleifen als Beweis verwenden.
  • Ergebnisse nicht konsumieren.
  • Zu kleine oder konstante Daten verwenden.
  • Mikrobenchmark auf Architekturentscheidung übertragen.
  • Benchmark im Debugger oder in der IDE bewerten.
Best Practices:
  • JMH für JVM-Mikrobenchmarks nutzen.
  • Realistische Daten und Typen verwenden.
  • Warm-up und Forks konfigurieren.
  • Allocation-Profiler bei Bedarf nutzen.
  • Ergebnisse mit Lasttest validieren.
  • Hardware, JVM und Flags dokumentieren.
Stolperfalle:

Ein perfekter Mikrobenchmark kann für die echte Anwendung irrelevant sein.

Merksatz:

Mikrobenchmarks beantworten Mikrofragen.

B2-K09-Q005 – Algorithmische Komplexität und DatenstrukturenDatenstruktur schlägt JVM-Flag. 24h
Frage:

„Wie wichtig ist algorithmische Komplexität bei Java-Performance im Vergleich zu JVM-Tuning?“

Kurz erklärt:

Die beste JVM-Optimierung hilft wenig gegen einen schlechten Algorithmus. Datenstrukturwahl schlägt Mikrooptimierung fast immer.

Musterantwort:

Algorithmische Komplexität ist oft wichtiger. Wenn Code eine verschachtelte Suche mit O(n*m) macht, hilft JIT-Tuning nur begrenzt. Eine passende Datenstruktur kann aus Minuten Sekunden machen.

JVM-Tuning optimiert die Ausführung eines gegebenen Programms. Algorithmus- und Datenstrukturdesign ändern das Programm selbst. Senior-Performancearbeit beginnt deshalb oft mit Datenfluss und Komplexität: Wie viele Elemente? Welche Operation? Welche Kardinalität? Welche Indizes? Welche Zugriffsmuster?

Senior-Antwort:

„Bevor ich JVM-Flags anfasse, prüfe ich Algorithmus und Datenstruktur. Der schnellste GC rettet keinen O(n²) -Report auf Millionen Daten.“

Häufige Fehler:
  • JVM-Tuning vor Algorithmusanalyse.
  • Listen für Lookups verwenden.
  • Datenbankindizes ignorieren.
  • Alle Daten in Memory joinen.
  • Big-O ohne konstante Faktoren und Datenverteilung betrachten.
Best Practices:
  • Datenmengen und Kardinalitäten kennen.
  • Zugriffsmuster analysieren.
  • Geeignete Datenstrukturen wählen.
  • Datenbankarbeit nicht unnötig in Java nachbauen.
  • Speicherbedarf der Datenstruktur berücksichtigen.
  • Große Datenmengen chunkweise verarbeiten.
Stolperfalle:

Mikrooptimierung eines falschen Algorithmus bleibt ein falscher Algorithmus.

Merksatz:

Datenstruktur schlägt JVM-Flag.

B2-K09-Q006 – Allocation Rate, GC und Object LifetimeNicht jede Allokation ist teuer, aber jede überlebende Allokation zählt.
Frage:

„Wie analysieren Sie Allocation-Probleme in Java?“

Kurz erklärt:

Allocation Rate und Objektlebensdauer beeinflussen GC-Verhalten, CPU und Latenz. Nicht jede Allokation ist schlimm, aber Hotpath-Allokationen können dominieren.

Musterantwort:

Ich messe zuerst Allocation Rate und Allocation Hotspots, typischerweise mit JFR oder Profiler. Dann frage ich: Welche Objekte werden häufig erzeugt? Sterben sie jung? Werden sie promoted? Wächst das Live Set? Sind große temporäre Collections, Strings, BigDecimal-Operationen, Boxing, Mapping oder Serialization beteiligt?

Ich optimiere nicht jede new -Anweisung. Kurzlebige Objekte sind oft günstig, und der JIT kann manche Allokationen eliminieren. Relevant sind gemessene Hotpaths, hohe Allocation Rate, Promotion, GC-Druck und Live-Set-Wachstum.

Senior-Antwort:

„Allocation ist erst dann ein Problem, wenn sie gemessen relevant ist: hohe Rate, Promotion, GC-Druck oder wachsendes Live Set. Ich optimiere Hotspots, nicht jeden Konstruktor.“

Häufige Fehler:
  • Allokationen ohne Messung optimieren.
  • Object Pools für normale Objekte bauen.
  • Live Set und Allocation Rate verwechseln.
  • Große Zwischenlisten ignorieren.
  • GC-Tuning statt Allocation-Hotspot-Fix.
Best Practices:
  • Allocation mit JFR messen.
  • Hotpath-Zwischenobjekte reduzieren.
  • Große Daten streamen oder chunken.
  • Boxing in Hotpaths vermeiden.
  • Object Pooling nur für spezielle teure Ressourcen.
  • Lesbarkeit nur bei messbarem Gewinn opfern.
Stolperfalle:

Object Pooling für normale Java-Objekte verschlechtert oft GC-Verhalten und Codequalität.

Merksatz:

Nicht jede Allokation ist teuer, aber jede überlebende Allokation zählt.

B2-K09-Q007 – Datenbank, Netzwerk und IO als häufigste EngpässeDie langsamste Abhängigkeit bestimmt oft die gefühlte Service-Performance.
Frage:

„Wie erkennen Sie, ob ein Performanceproblem wirklich im Java-Code liegt oder in IO/Datenbank/Downstream?“

Kurz erklärt:

In Enterprise-Anwendungen liegt der Engpass oft nicht in Java-Code, sondern in Datenbank, Netzwerk, Remote Calls, Serialization oder Queueing.

Musterantwort:

Ich korreliere End-to-End-Latenz mit Traces, Datenbankmetriken, HTTP-Client-Metriken, Connection-Pool-Wartezeiten, JFR und Logs. Wenn CPU niedrig ist und Threads warten, liegt das Problem oft bei IO, Datenbank, Locks oder Downstreams.

Bei Datenbanken prüfe ich Query-Pläne, Indizes, Result-Set-Größen, N+1-Queries, Connection-Pool-Saturation und Transaktionsdauer. Bei Netzwerk prüfe ich Timeouts, DNS, TLS, Retries, Payload-Größe und Downstream-p99. Java-Optimierung bringt wenig, wenn der Service 90 Prozent der Zeit auf die Datenbank wartet.

Senior-Antwort:

„Ich frage zuerst, wo Zeit verbracht wird. Wenn Threads warten und CPU niedrig ist, ist Java-Code selten der primäre Engpass.“

Häufige Fehler:
  • Java-Code optimieren, obwohl Datenbank wartet.
  • ORM-Abfragen nicht sichtbar machen.
  • Keine Timeouts setzen.
  • Retries ohne Deadline.
  • Result Sets unbounded laden.
  • Connection Pool einfach immer vergrößern.
Best Practices:
  • Tracing und DB-Metriken nutzen.
  • Query-Pläne prüfen.
  • Pagination und Limits verwenden.
  • N+1 vermeiden.
  • Connection-Pool-Wartezeit messen.
  • Timeouts, Retries und Deadlines definieren.
  • Payload-Größe begrenzen.
Stolperfalle:

Ein schneller Java-Service mit langsamer Datenbank ist ein langsamer Service.

Merksatz:

Die langsamste Abhängigkeit bestimmt oft die gefühlte Service-Performance.

B2-K09-Q008 – Caching, Locality und KonsistenzkostenCaching kauft Geschwindigkeit mit Konsistenz- und Speicherkomplexität.
Frage:

„Wann ist Caching eine gute Performance-Optimierung, und welche Risiken entstehen?“

Kurz erklärt:

Caching kann Performance massiv verbessern, aber es erzeugt Konsistenz-, Speicher-, Invalidierungs- und Betriebsprobleme.

Musterantwort:

Caching ist gut, wenn Daten häufig gelesen, relativ selten geändert und teuer zu berechnen oder zu laden sind. Der Nutzen hängt von Hit Ratio, Datenlokalität, TTL, Invalidierung, Speicherbedarf und Konsistenzanforderungen ab.

Risiken sind stale data, unbounded memory, Cache Stampede, falsche Invalidierung, hohe Kardinalität, verteilte Inkonsistenz und Debugging-Komplexität. Ein Cache braucht Metriken: Hit Ratio, Miss Ratio, Evictions, Größe, Ladezeit, Fehler und Speicherverbrauch.

Senior-Antwort:

„Caching ist keine kostenlose Beschleunigung. Ich brauche Hit Ratio, Begrenzung, Invalidierung, Konsistenzmodell und Speicherbudget.“

Häufige Fehler:
  • Cache ohne Größenlimit.
  • Keine Hit-Ratio-Metrik.
  • Stale data fachlich ignorieren.
  • Cache Stampede nicht behandeln.
  • Hohe Kardinalität unterschätzen.
  • Cache als Ersatz für fehlenden Datenbankindex verwenden.
Best Practices:
  • Cache-Ziel und Konsistenzbedarf definieren.
  • Größenlimit und TTL setzen.
  • Hit/Miss/Eviction messen.
  • Stampede-Schutz prüfen.
  • Keys bewusst designen.
  • Lokalen Heap-Effekt berücksichtigen.
  • Invalidierungsstrategie dokumentieren.
Stolperfalle:

Ein Cache ohne Limit ist ein Memory Leak mit guter Absicht.

Merksatz:

Caching kauft Geschwindigkeit mit Konsistenz- und Speicherkomplexität.

B2-K09-Q009 – Load Testing, Capacity und ScalabilityKapazität misst man unter echter Last, nicht unter Wunschlast.
Frage:

„Wie planen Sie einen sinnvollen Lasttest für einen Java-Service?“

Kurz erklärt:

Lasttests zeigen, wie ein System unter realistischem Workload reagiert. Sie sind nicht dasselbe wie Mikrobenchmarks oder lokale Profiling-Sessions.

Musterantwort:

Ich definiere zuerst das Ziel: Baseline, Kapazitätsgrenze, Regression, Skalierung, p99, Stabilität oder Resilienz. Dann modellieren ich realistische Workloads: Request-Mix, Datenmengen, Auth, Payloads, Downstreams, Think Time, Ramp-up und Dauer.

Während des Tests erfasse ich Applikationsmetriken, JVM-Metriken, GC, JFR, CPU, Speicher, Datenbank, Downstreams, Netzwerk und Fehler. Ich achte besonders auf Saturation: Queue-Längen, Connection-Pool-Wartezeit, Thread-Pools, Semaphoren, Rate Limits und p99.

Senior-Antwort:

„Ein Lasttest muss den echten Workload abbilden. Sonst teste ich nur, dass mein System unter einer erfundenen Last gut aussieht.“

Häufige Fehler:
  • Unrealistischen Request-Mix testen.
  • Zu kurze Testdauer.
  • Downstreams mocken und dann echte Latenz ignorieren.
  • Nur Durchschnittslatenz betrachten.
  • Keine JVM-Daten erfassen.
  • Testdaten zu klein wählen.
Best Practices:
  • Produktionsähnlichen Request-Mix verwenden.
  • Ramp-up und Warm-up trennen.
  • p95/p99/p999 messen.
  • JVM, DB und Downstream-Metriken korrelieren.
  • Soak Tests für Leaks durchführen.
  • Capacity-Ergebnisse dokumentieren.
  • Regressionstests in CI/CD prüfen.
Stolperfalle:

Ein Service kann unter einem synthetischen Einzelendpoint-Test schnell sein und unter realem Request-Mix scheitern.

Merksatz:

Kapazität misst man unter echter Last, nicht unter Wunschlast.

B2-K09-Q010 – Performance-Tuning-Strategie und Anti-PatternsTuning beginnt nicht mit Änderung, sondern mit Beweis. 24h
Frage:

„Welche Performance-Anti-Patterns sehen Sie häufig in Java-Projekten?“

Kurz erklärt:

Performance-Tuning ist datengetriebene Ursachenanalyse. Anti-Patterns entstehen, wenn Teams ohne Messung optimieren oder Symptome mit falschen Mitteln behandeln.

Musterantwort:

Häufige Anti-Patterns sind: JVM-Flags aus Blogposts kopieren, Caches ohne Metriken einbauen, Datenbankprobleme mit Java-Code-Optimierung behandeln, Mikrobenchmarks falsch interpretieren, Object Pooling für normale Objekte, unbounded Queues, fehlende Timeouts, nur Durchschnittslatenz messen und mehrere Änderungen gleichzeitig ausrollen.

Eine gute Strategie ist: Ziel definieren, Daten sammeln, Engpass identifizieren, kleine Änderung machen, erneut messen und dokumentieren. Wenn die Ursache DB-Wartezeit ist, optimiere ich Queries. Wenn Allocation dominiert, prüfe ich Objektgraphen. Wenn p99 wegen Queueing steigt, brauche ich Backpressure.

Senior-Antwort:

„Performance-Tuning ist Ursachenanalyse. Ich ändere nicht blind Flags oder Code, sondern suche den dominanten Engpass und validiere jede Verbesserung.“

Häufige Fehler:
  • Tuning ohne Diagnose.
  • JVM-Flags kopieren.
  • Caching ohne Konsistenzmodell.
  • Object Pooling ohne Beleg.
  • p99 ignorieren.
  • Queueing als Lösung verkaufen.
  • Performancegewinne nicht dokumentieren.
Best Practices:
  • Diagnose vor Optimierung.
  • Engpass systemisch betrachten.
  • Kleine Änderungen einzeln messen.
  • Performance-Entscheidungen dokumentieren.
  • Tests mit produktionsähnlicher Last.
  • Regressionen automatisiert erkennen.
  • Kosten und Wartbarkeit berücksichtigen.
Stolperfalle:

Der lauteste Verdacht ist nicht immer der größte Engpass.

Merksatz:

Tuning beginnt nicht mit Änderung, sondern mit Beweis.

B3 – Band 3: Spring Professional

B3-K01 – Kapitel 1: Spring Core 10 Karten

B3-K01-Q001 – IoC Container und Bean ManagementSpring Core ist kontrolliertes Objektgraph-Management. 24h
Frage:

„Was ist der Spring IoC Container, und warum ist er mehr als eine Factory für Objekte?“

Kurz erklärt:

Der Spring IoC Container erzeugt, konfiguriert und verwaltet Beans. Anwendungscode beschreibt Abhängigkeiten; der Container löst sie auf und stellt die Objektgraphen bereit.

Musterantwort:

Der Spring IoC Container ist die Laufzeitumgebung, die Bean Definitionen liest, Objekte erzeugt, Abhängigkeiten injiziert, Scopes verwaltet, Lifecycle-Callbacks ausführt, Post-Processor anwendet, Proxies erzeugt und Environment-/Profile-Konfiguration berücksichtigt.

Er ist mehr als eine Factory, weil er den gesamten Objektgraphen und dessen Querschnittsverhalten verwaltet. Spring kann Transaktionen, Security, AOP, Events, Validierung oder Konfiguration nur deshalb konsistent bereitstellen, weil Beans kontrolliert durch den Container laufen.

Senior-Antwort:

„Der Spring Container verwaltet nicht nur Objekte, sondern den gesamten Bean-Lifecycle und viele Querschnittsmechanismen. Wenn ich Spring-Features nutzen will, muss der Code durch den Container laufen.“

Häufige Fehler:
  • Spring als reine Annotation-Bibliothek verstehen.
  • Container-Lifecycle ignorieren.
  • Beans manuell mit new erzeugen und Spring-Features erwarten.
  • Zu viel Logik in Bean-Erzeugung verstecken.
  • Startup-Kosten nicht messen.
Best Practices:
  • Abhängigkeiten explizit deklarieren.
  • Beans durch den Container verwalten lassen.
  • Fachlogik von Container-Konfiguration trennen.
  • Schwergewichtige Initialisierung bewusst modellieren.
  • Bean-Lifecycle und Proxy-Grenzen kennen.
  • Startzeit mit Actuator/JFR beobachten.
Stolperfalle:

Ein Objekt, das Sie selbst mit new erzeugen, ist keine Spring Bean und erhält keine automatische Dependency Injection, Transaktion, AOP oder Lifecycle-Behandlung.

Merksatz:

Spring Core ist kontrolliertes Objektgraph-Management.

B3-K01-Q002 – Dependency Injection und Constructor InjectionDependency Injection soll Design sichtbar machen, nicht verstecken.
Frage:

„Warum bevorzugen Sie in Spring meistens Constructor Injection?“

Kurz erklärt:

Dependency Injection bedeutet, dass ein Objekt seine Abhängigkeiten nicht selbst erzeugt, sondern von außen erhält. Constructor Injection macht diese Abhängigkeiten explizit und unveränderlich.

Musterantwort:

Constructor Injection macht Pflichtabhängigkeiten sichtbar, ermöglicht final Felder, erleichtert Tests, verhindert halb initialisierte Objekte und macht zyklische Abhängigkeiten früher sichtbar. Wenn eine Klasse ohne bestimmte Abhängigkeit nicht sinnvoll existieren kann, gehört diese Abhängigkeit in den Konstruktor.

Field Injection versteckt Abhängigkeiten, erschwert Tests ohne Spring-Kontext, verhindert einfache Immutability und kann Designprobleme verschleiern. Setter Injection nutze ich nur für optionale oder nachträglich veränderbare Abhängigkeiten.

Senior-Antwort:

„Constructor Injection macht Abhängigkeiten ehrlich. Wenn ein Service zehn Konstruktorparameter braucht, ist das kein DI-Problem, sondern ein Designsignal.“

Häufige Fehler:
  • Field Injection in Produktivcode verwenden.
  • Zu viele Abhängigkeiten im Konstruktor ignorieren.
  • Service Locator über ApplicationContext.getBean bauen.
  • Optionale Abhängigkeiten als Pflichtabhängigkeiten modellieren.
  • Zyklen mit Lazy Injection kaschieren.
Best Practices:
  • Constructor Injection als Default.
  • Felder final halten.
  • Zu viele Konstruktorparameter als Designsignal betrachten.
  • @Qualifier gezielt verwenden.
  • Fachliche Ports statt konkrete Infrastrukturklassen injizieren.
  • Zirkuläre Abhängigkeiten architektonisch auflösen.
Stolperfalle:

Field Injection ist bequem, aber sie versteckt Abhängigkeiten und macht Klassen schwerer isoliert testbar.

Merksatz:

Dependency Injection soll Design sichtbar machen, nicht verstecken.

B3-K01-Q003 – Bean Scopes und ZustandsmodellScope beschreibt Lebensdauer, nicht automatisch Sicherheit.
Frage:

„Welche Spring Bean Scopes kennen Sie, und warum ist Singleton nicht dasselbe wie Thread-Safety?“

Kurz erklärt:

Bean Scope bestimmt, wie viele Instanzen einer Bean der Container erzeugt und wie lange sie leben. Der Default Scope ist Singleton.

Musterantwort:

Spring unterstützt unter anderem Singleton, Prototype sowie webbezogene Scopes wie Request, Session, Application und WebSocket. Singleton bedeutet im Spring-Kontext: eine Bean-Instanz pro Container. Es bedeutet nicht, dass die Bean thread-safe ist.

Ein Singleton-Service wird in Webanwendungen von vielen Request-Threads gleichzeitig verwendet. Deshalb sollten Services typischerweise stateless sein oder ihren Zustand klar synchronisieren. Prototype erzeugt neue Instanzen bei jeder Anforderung durch den Container, löst aber nicht automatisch Lifecycle- oder Thread-Safety-Probleme.

Senior-Antwort:

„Spring Singleton heißt eine Instanz pro Container, nicht automatisch thread-safe. Deshalb sind meine Services im Default stateless.“

Häufige Fehler:
  • Mutable Felder in Singleton-Services speichern.
  • Prototype als automatische Lösung für Request-Zustand verwenden.
  • Prototype Lifecycle falsch erwarten.
  • Web Scopes in Domain-Logik leaken lassen.
  • Session Scope für große Objektgraphen missbrauchen.
Best Practices:
  • Singleton + Statelessness als Default.
  • Request-Zustand als Methodenparameter übergeben.
  • Mutable State lokal halten.
  • Prototype nur gezielt verwenden.
  • Provider für dynamische Instanzen nutzen.
  • Web Scopes an Web-Rand begrenzen.
Stolperfalle:

Prototype bedeutet nicht automatisch „eine neue Instanz pro Methodenaufruf“, wenn die Prototype Bean einmal in einen Singleton injiziert wurde.

Merksatz:

Scope beschreibt Lebensdauer, nicht automatisch Sicherheit.

B3-K01-Q004 – Bean Lifecycle und Post-ProcessorSpring Features entstehen im Bean Lifecycle.
Frage:

„Wie sieht der Spring Bean Lifecycle aus, und wofür sind BeanPostProcessor wichtig?“

Kurz erklärt:

Spring Beans durchlaufen einen Lifecycle: Instanziierung, Dependency Injection, Awareness-Callbacks, Post-Processor, Initialisierung, Nutzung und Zerstörung.

Musterantwort:

Vereinfacht: Spring liest Bean Definitionen, erzeugt Bean-Instanzen, injiziert Abhängigkeiten, ruft bestimmte Aware-Callbacks auf, wendet BeanPostProcessor vor der Initialisierung an, führt Initialisierung aus, wendet BeanPostProcessor nach der Initialisierung an und verwaltet später Destroy-Callbacks.

BeanPostProcessor sind extrem wichtig, weil viele Spring-Features darüber implementiert werden oder daran hängen: AOP-Proxies, @Autowired , @PostConstruct , Validation, Configuration Properties, Event Listener und weitere Infrastruktur. Deshalb kann der tatsächliche Bean, den man injiziert bekommt, ein Proxy sein und nicht die rohe Zielinstanz.

Senior-Antwort:

„Der Spring Bean Lifecycle ist die Pipeline, in der viele Framework-Features entstehen. Wenn ich Proxies, AOP oder Initialisierungsfehler debugge, denke ich in Lifecycle-Phasen.“

Häufige Fehler:
  • Teure Remote Calls in @PostConstruct ausführen.
  • Lifecycle-Reihenfolge falsch annehmen.
  • Fachklassen unnötig an Spring-Interfaces koppeln.
  • Proxy-Erzeugung durch PostProcessor ignorieren.
  • Destroy-Callbacks bei Prototype Beans erwarten.
Best Practices:
  • Initialisierung klein und deterministisch halten.
  • Externe Systeme nicht unkontrolliert im Bean-Start blockieren.
  • Lifecycle-Logik bewusst testen.
  • @PostConstruct für lokale Validierung nutzen.
  • Ressourcen sauber schließen.
  • Infrastruktur-PostProcessor sparsam und gut dokumentieren.
Stolperfalle:

Das Objekt, das nach der Initialisierung im Context liegt, kann ein Proxy sein, nicht die ursprüngliche Zielinstanz.

Merksatz:

Spring Features entstehen im Bean Lifecycle.

B3-K01-Q005 – Component Scanning und StereotypeComponent Scanning ist automatische Architekturaufnahme. 24h
Frage:

„Wie funktioniert Component Scanning, und was ist der Unterschied zwischen @Component , @Service und @Repository ?“

Kurz erklärt:

Component Scanning findet Klassen, die als Spring-Komponenten markiert sind, und registriert daraus Bean Definitionen.

Musterantwort:

Component Scanning durchsucht definierte Packages nach Kandidaten, die bestimmten Filtern entsprechen, typischerweise Klassen mit @Component oder Meta-Annotationen. Für diese Kandidaten werden Bean Definitionen registriert.

@Component ist der generische Stereotyp. @Service drückt fachliche Service-Rolle aus. @Repository markiert Persistenzkomponenten und kann zusätzliche Infrastruktur wie Exception Translation unterstützen. @Controller ist für Web-MVC-Komponenten. Technisch sind sie eng verwandt, semantisch helfen sie Architektur, Lesbarkeit und tooling.

Senior-Antwort:

„Component Scanning ist bequem, aber es ist auch Architektur. Was ich scanne, wird Teil meines Containers und damit Teil meines Laufzeitmodells.“

Häufige Fehler:
  • Hauptklasse im falschen Package platzieren.
  • Alles global scannen.
  • Stereotype rein dekorativ verwenden.
  • Architekturgrenzen durch Scanning verwischen.
  • Komponenten in Tests zufällig mitladen.
Best Practices:
  • Klare Package-Struktur.
  • Scan-Bereich klein halten.
  • Stereotype semantisch nutzen.
  • Infrastrukturadapter von Fachlogik trennen.
  • Explizite @Bean -Definitionen für externe Libraries nutzen.
  • Test-Slices gezielt konfigurieren.
Stolperfalle:

Wenn die Spring-Boot-Hauptklasse zu tief im Package liegt, werden Geschwisterpackages möglicherweise nicht gescannt.

Merksatz:

Component Scanning ist automatische Architekturaufnahme.

B3-K01-Q006 – Java-basierte Konfiguration und @Bean@Bean ist explizite Objektgraph-Konstruktion.
Frage:

„Wann verwenden Sie @Bean statt @Component ?“

Kurz erklärt:

@Bean -Methoden registrieren Beans explizit. Sie sind ideal für externe Libraries, Infrastruktur, konfigurierte Clients und komplexe Erzeugungslogik.

Musterantwort:

Ich verwende @Component , wenn ich eigene Klassen als managed components markieren möchte. Ich verwende @Bean , wenn ich Instanzen explizit konfiguriere, besonders bei externen Klassen, Clients, SDKs, Factories, Security-Komponenten oder komplexer Erzeugungslogik.

@Bean macht die Konstruktion sichtbar und zentral konfigurierbar. Es ist auch gut, wenn die Klasse nicht unter meiner Kontrolle steht oder ich mehrere unterschiedlich konfigurierte Instanzen desselben Typs brauche.

Senior-Antwort:

„ @Component ist gut für eigene managed classes. @Bean ist gut, wenn ich Konstruktion bewusst kontrollieren will, vor allem bei Infrastruktur und externen Libraries.“

Häufige Fehler:
  • Komplexe Geschäftslogik in Konfigurationsklassen packen.
  • Mehrere Beans gleichen Typs ohne Namen oder Qualifier.
  • @Bean -Methoden mit Seiteneffekten schreiben.
  • proxyBeanMethods = false verwenden, ohne Semantik zu verstehen.
  • Externe Clients ohne Timeouts konfigurieren.
Best Practices:
  • @Bean für Infrastruktur und externe Typen nutzen.
  • Konfigurationsklassen thematisch schneiden.
  • Bean-Namen bewusst wählen.
  • Timeouts und Ressourcenlimits in Clients konfigurieren.
  • @Configuration schlank halten.
  • proxyBeanMethods = false nur bewusst nutzen.
Stolperfalle:

Direkte Aufrufe zwischen @Bean -Methoden verhalten sich je nach @Configuration -Proxy-Modus unterschiedlich.

Merksatz:

@Bean ist explizite Objektgraph-Konstruktion.

B3-K01-Q007 – Bean-Auflösung: @Primary, @Qualifier und CollectionsBean-Auswahl ist Teil des Designs.
Frage:

„Wie löst Spring Abhängigkeiten auf, wenn mehrere Beans desselben Typs existieren?“

Kurz erklärt:

Wenn mehrere Beans gleichen Typs existieren, muss Spring entscheiden, welche Bean injiziert wird. Dafür gibt es unter anderem @Primary , @Qualifier , Bean-Namen und Collection-Injection.

Musterantwort:

Spring sucht zunächst nach Typ. Wenn mehrere Kandidaten existieren, helfen @Primary , @Qualifier , Bean-Name, generische Typinformationen oder Priorisierung. Bei Collection-Injection, also List<T> oder Map<String,T> , injiziert Spring mehrere passende Beans.

Ich verwende @Primary , wenn eine Implementierung der sinnvolle Default ist. @Qualifier nutze ich, wenn die konkrete Variante fachlich relevant ist. Wenn viele Strategien bewusst zusammenarbeiten sollen, injiziere ich eine Liste oder Map und ordne sie bei Bedarf mit @Order .

Senior-Antwort:

„Mehrere Beans gleichen Typs sind kein Problem, solange die Auswahl fachlich modelliert ist. @Qualifier , @Primary und Strategy-Collections sind Designwerkzeuge.“

Häufige Fehler:
  • Mehrere Beans ohne klare Auswahl registrieren.
  • Bean-Namen unbewusst als fachliche Qualifier verwenden.
  • @Primary als globalen Hack nutzen.
  • Container per getBean zur Laufzeit als Strategy Registry missbrauchen.
  • Reihenfolge in Listen ohne @Order voraussetzen.
Best Practices:
  • Default mit @Primary bewusst definieren.
  • Fachliche Varianten mit @Qualifier ausdrücken.
  • Strategy-Listen oder Maps gezielt injizieren.
  • Reihenfolge explizit mit @Order oder Comparator.
  • Mehrdeutigkeit beim Start scheitern lassen.
  • Bean-Namen stabil und lesbar wählen.
Stolperfalle:

@Primary löst technische Mehrdeutigkeit, aber nicht automatisch fachliche Auswahl.

Merksatz:

Bean-Auswahl ist Teil des Designs.

B3-K01-Q008 – AOP, Proxies und Self-InvocationSpring AOP wirkt am Proxy, nicht magisch am Methodenrumpf.
Frage:

„Warum funktioniert @Transactional bei Self-Invocation oft nicht?“

Kurz erklärt:

Viele Spring-Features wie Transaktionen, Security, Caching oder Async werden proxy-basiert umgesetzt. Methodenaufrufe müssen über den Proxy laufen, damit Advice greift.

Musterantwort:

Spring AOP arbeitet im Standard proxy-basiert. Der Container injiziert nicht direkt zwingend die Zielklasse, sondern häufig einen Proxy. Wenn ein externer Aufrufer eine Methode über den Proxy aufruft, kann der Proxy Transaktionen, Security, Caching oder Logging anwenden.

Bei Self-Invocation ruft eine Methode derselben Klasse eine andere Methode direkt über this auf. Dieser interne Aufruf geht nicht durch den Proxy. Deshalb greifen proxy-basierte Advices wie @Transactional , @Cacheable oder @Async dort nicht wie erwartet.

Senior-Antwort:

„Spring AOP ist im Standard proxy-basiert. Wenn ein Aufruf den Proxy nicht passiert, passiert auch kein Advice. Das ist die Erklärung für viele @Transactional -Überraschungen.“

Häufige Fehler:
  • Interne @Transactional -Methoden erwarten.
  • Private Methoden annotieren.
  • Final Klassen oder Methoden mit Proxy-AOP kombinieren.
  • Proxy-Klasse im Debugger falsch interpretieren.
  • AOP als Compile-Time-Magie verstehen.
Best Practices:
  • AOP-Grenzen bewusst über Bean-Grenzen legen.
  • Transactional Boundary auf öffentliche Service-Methoden.
  • Self-Invocation vermeiden.
  • Methoden nicht unnötig final/private annotieren.
  • Proxies in Tests berücksichtigen.
  • Fachliche Transaktionsgrenzen klar modellieren.
Stolperfalle:

Annotation auf einer Methode bedeutet nicht automatisch, dass sie bei jedem Aufruf wirkt. Der Aufrufspfad muss zum AOP-Modell passen.

Merksatz:

Spring AOP wirkt am Proxy, nicht magisch am Methodenrumpf.

B3-K01-Q009 – ApplicationContext EventsSpring Events entkoppeln im Prozess, nicht automatisch im System.
Frage:

„Wann verwenden Sie Spring Application Events, und wo sind ihre Grenzen?“

Kurz erklärt:

Spring Events ermöglichen lose Kopplung innerhalb eines ApplicationContext. Sie sind kein Ersatz für Messaging zwischen Services.

Musterantwort:

Spring Events sind sinnvoll für lose gekoppelte In-Process-Reaktionen innerhalb desselben ApplicationContext: Audit, lokale Cache-Invalidierung, interne Benachrichtigung, fachliche Domain Events innerhalb einer Anwendungsschicht.

Ihre Grenze: Sie sind standardmäßig kein verteiltes Messaging, keine persistente Queue und kein garantiertes Eventing über Prozessgrenzen. Wenn ein Event Service-Grenzen überschreiten, wiederholbar, persistent oder skalierbar verarbeitet werden muss, brauche ich Kafka, RabbitMQ, Outbox Pattern oder eine andere Messaging-Infrastruktur.

Senior-Antwort:

„Spring Events sind gut für lose In-Process-Kopplung. Sobald Persistenz, Delivery-Garantie oder Service-Grenzen ins Spiel kommen, brauche ich Messaging-Architektur.“

Häufige Fehler:
  • Spring Events als verteiltes Messaging verwenden.
  • Listener-Latenz ignorieren.
  • Exceptions in Listenern nicht behandeln.
  • Event-Ketten schwer nachvollziehbar machen.
  • Fachliche Transaktionsgrenzen ignorieren.
Best Practices:
  • Events für lose In-Process-Kopplung nutzen.
  • Keine kritischen externen Integrationen rein synchron im Listener verstecken.
  • Listener kurz und beobachtbar halten.
  • Für verteilte Events Outbox/Messaging verwenden.
  • Transactional Event Listener bei Commit-Semantik prüfen.
  • Event-Namen fachlich klar halten.
Stolperfalle:

Ein Spring Event ist nicht automatisch ein zuverlässiges Integration Event.

Merksatz:

Spring Events entkoppeln im Prozess, nicht automatisch im System.

B3-K01-Q010 – Environment, Profiles und Configuration PropertiesGute Konfiguration ist typisiert, validiert und betriebssicher. 24h
Frage:

„Wie gehen Sie professionell mit Konfiguration in Spring um?“

Kurz erklärt:

Environment und Profiles steuern, welche Konfiguration und Beans aktiv sind. Configuration Properties bündeln externe Konfiguration typisiert und validierbar.

Musterantwort:

Ich trenne Konfiguration von Code. Werte wie URLs, Timeouts, Limits, Feature Flags oder Poolgrößen gehören in externe Konfiguration und werden typisiert über Configuration Properties gebunden. Damit werden sie validierbar, dokumentierbar und testbar.

Profiles nutze ich sparsam für grobe Umgebungsauswahl, nicht für beliebige Geschäftslogik. Kritische Unterschiede zwischen Umgebungen sollten sichtbar und getestet sein. Besonders wichtig sind Timeouts, Limits und Security-relevante Einstellungen.

Senior-Antwort:

„Konfiguration ist Teil der Architektur. Ich will typisierte, validierte und beobachtbare Konfiguration, besonders für Timeouts, Limits und externe Endpunkte.“

Häufige Fehler:
  • URLs und Timeouts hardcoden.
  • Profiles für komplexe Businesslogik verwenden.
  • @Value über viele Klassen verteilen.
  • Konfiguration nicht validieren.
  • Defaults verstecken.
  • Secrets in Git speichern.
Best Practices:
  • @ConfigurationProperties für strukturierte Konfiguration.
  • Konfiguration beim Start validieren.
  • Timeouts und Limits explizit konfigurieren.
  • Profiles sparsam verwenden.
  • Secrets über sichere Secret-Mechanismen.
  • Konfigurationswerte dokumentieren.
  • Prod-nahe Konfiguration testen.
Stolperfalle:

Profiles sind kein Ersatz für sauberes Feature- oder Konfigurationsmodell.

Merksatz:

Gute Konfiguration ist typisiert, validiert und betriebssicher.

B3-K02 – Kapitel 2: Spring Boot 10 Karten

B3-K02-Q001 – Bootstrapping und @SpringBootApplicationSpring Boot vereinfacht den Start, aber der Container bleibt das Laufzeitmodell. 24h
Frage:

„Was passiert beim Start einer Spring-Boot-Anwendung mit SpringApplication.run ?“

Kurz erklärt:

@SpringBootApplication ist eine Convenience-Annotation. Sie kombiniert zentrale Spring-Boot-Startmechanismen wie Komponenten-Scanning, Auto-Configuration und Konfigurationsklasse.

Musterantwort:

SpringApplication.run erstellt und startet einen Spring ApplicationContext. Dabei werden Environment und Property Sources vorbereitet, Profile berücksichtigt, Bean Definitionen geladen, Auto-Configuration ausgewertet, der Context refreshed, Singleton Beans erzeugt, Lifecycle-Callbacks ausgeführt und bei Webanwendungen ein eingebetteter Server gestartet.

@SpringBootApplication bündelt typischerweise @SpringBootConfiguration , @EnableAutoConfiguration und @ComponentScan . Das bedeutet: Die Klasse ist Konfigurationsquelle, Boot versucht Auto-Configuration basierend auf Classpath, Properties und vorhandenen Beans, und Spring scannt Komponenten ab dem Package der Hauptklasse.

Senior-Antwort:

„Spring Boot startet keinen Zauber, sondern einen Spring Context mit vorbereiteter Umgebung, Auto-Configuration, Component Scanning und Lifecycle. Wer den Startprozess versteht, debuggt Boot-Probleme deutlich schneller.“

Häufige Fehler:
  • Hauptklasse im falschen Package platzieren.
  • Boot als „Magie“ statt Container-Startprozess verstehen.
  • Teure externe Calls beim Context-Start ausführen.
  • Auto-Configuration nicht diagnostizieren.
  • Startup-Zeit erst in Produktion bemerken.
Best Practices:
  • Hauptklasse im Root-Package des Services platzieren.
  • Package-Struktur bewusst gestalten.
  • Startup-Initialisierung minimieren.
  • Auto-Configuration Report bei Problemen nutzen.
  • Boot-Startphasen in Logs und Observability sichtbar machen.
  • Externe Systeme über Readiness statt bloß Startup kontrollieren.
Stolperfalle:

@SpringBootApplication scannt standardmäßig ab dem Package der Hauptklasse nach unten, nicht automatisch die ganze Codebase.

Merksatz:

Spring Boot vereinfacht den Start, aber der Container bleibt das Laufzeitmodell.

B3-K02-Q002 – Auto-Configuration und Conditional BeansSpring Boot konfiguriert automatisch, aber immer bedingt.
Frage:

„Wie funktioniert Spring Boot Auto-Configuration, und warum ist @ConditionalOnMissingBean so wichtig?“

Kurz erklärt:

Auto-Configuration versucht, sinnvolle Beans automatisch zu registrieren, wenn bestimmte Bedingungen erfüllt sind. Eigene Beans sollen diese Defaults überschreiben können.

Musterantwort:

Spring Boot Auto-Configuration wertet Konfigurationsklassen aus, die abhängig von Classpath, Properties, vorhandenen Beans, Web Application Type und weiteren Bedingungen aktiviert werden. Dadurch kann Boot zum Beispiel DataSource, WebMVC, Jackson, Actuator oder Security sinnvoll vorkonfigurieren.

@ConditionalOnMissingBean ist wichtig, weil Boot Defaults liefern soll, aber Anwendungscode die Kontrolle behalten muss. Wenn der Entwickler eine eigene Bean definiert, soll Boot normalerweise nicht noch eine konkurrierende Default-Bean erzeugen.

Senior-Antwort:

„Auto-Configuration liefert Defaults, nicht Kontrollverlust. Gute Auto-Configuration ist conditional, überschreibbar und transparent diagnostizierbar.“

Häufige Fehler:
  • Auto-Configuration als unkontrollierbare Magie verstehen.
  • Eigene Beans definieren, aber Conditions falsch setzen.
  • Starter hinzufügen und Seiteneffekte nicht prüfen.
  • @ConditionalOnMissingBean zu breit verwenden.
  • Auto-Configuration in normalen Feature-Code mischen.
Best Practices:
  • Auto-Configuration für Infrastruktur und Starter verwenden.
  • Defaults überschreibbar machen.
  • Conditions eng und verständlich halten.
  • Properties typisiert konfigurieren.
  • Condition Evaluation Report nutzen.
  • Starter-Abhängigkeiten bewusst wählen.
Stolperfalle:

Ein Starter kann durch Auto-Configuration Beans aktivieren, die man nicht erwartet. Deshalb prüfe ich bei neuen Dependencies die Auto-Configuration-Effekte.

Merksatz:

Spring Boot konfiguriert automatisch, aber immer bedingt.

B3-K02-Q003 – Starters und Dependency ManagementStarter sind Classpath-Entscheidungen mit Laufzeitfolgen.
Frage:

„Was ist ein Spring Boot Starter, und warum ist Dependency Management wichtig?“

Kurz erklärt:

Spring Boot Starter bündeln typische Dependencies für einen Anwendungsbereich. Dependency Management sorgt für kompatible Versionskombinationen.

Musterantwort:

Ein Starter ist eine kuratierte Dependency, die typische Bibliotheken für einen Use Case zusammenzieht, etwa Web, Data JPA, Security, Actuator oder Testing. Ein Starter enthält meistens wenig bis keinen Anwendungscode, sondern Abhängigkeiten und damit indirekt Auto-Configuration-Auslöser.

Dependency Management ist wichtig, weil Spring Boot getestete Versionen vieler Bibliotheken zusammenführt. Dadurch muss ich nicht jede Transitive Dependency manuell versionieren. Trotzdem muss ich wissen, was ein Starter auf den Classpath bringt, weil Classpath bei Boot Auto-Configuration auslöst.

Senior-Antwort:

„Starter sind bequem, aber sie verändern den Classpath. Und in Spring Boot verändert der Classpath das Laufzeitverhalten.“

Häufige Fehler:
  • Starter hinzufügen, ohne Classpath-Effekte zu prüfen.
  • Dependency-Versionen wild überschreiben.
  • WebMVC und WebFlux unbewusst mischen.
  • Test-Starter in Production Scope bringen.
  • Security-relevante Dependencies unnötig mitziehen.
Best Practices:
  • Nur notwendige Starter hinzufügen.
  • Boot Dependency Management respektieren.
  • Overrides dokumentieren und testen.
  • Dependency Tree regelmäßig prüfen.
  • Starter-Effekte auf Auto-Configuration verstehen.
  • Security- und CVE-Scanning integrieren.
Stolperfalle:

Ein zusätzlicher Starter kann Auto-Configuration aktivieren, obwohl Sie keine einzige Zeile Code geändert haben.

Merksatz:

Starter sind Classpath-Entscheidungen mit Laufzeitfolgen.

B3-K02-Q004 – Externalized Configuration und Property BindingExternalized Configuration macht Code portabel; validierte Configuration macht Betrieb sicher.
Frage:

„Warum bevorzugen Sie @ConfigurationProperties gegenüber vielen @Value -Feldern?“

Kurz erklärt:

Spring Boot erlaubt Externalized Configuration aus Properties, YAML, Environment Variables, Command-Line Arguments und weiteren Quellen. @ConfigurationProperties bindet diese Werte typisiert.

Musterantwort:

@ConfigurationProperties bündelt zusammengehörige Konfiguration in einem typisierten Objekt. Das ist testbarer, validierbar, dokumentierbarer und sauberer als viele verstreute @Value -Felder. Gerade für externe Clients, Timeouts, Poolgrößen, Security-Optionen oder Feature-Konfiguration möchte ich eine zentrale Struktur.

@Value ist für einfache Einzelwerte okay, aber in größeren Systemen führt es schnell zu verteilter, unvalidierter Konfiguration. Configuration Properties machen Konfiguration zu einem expliziten API-Vertrag.

Senior-Antwort:

„Konfiguration ist ein Vertrag zwischen Betrieb und Anwendung. Ich will sie typisiert, validiert und zentral nachvollziehbar haben.“

Häufige Fehler:
  • Timeouts hardcoden.
  • @Value in vielen Klassen verteilen.
  • Konfiguration nicht validieren.
  • Unsichere Defaults setzen.
  • Secrets in YAML einchecken.
  • Property-Reihenfolge nicht verstehen.
Best Practices:
  • Strukturierte Properties pro Client oder Modul.
  • Validierung mit Jakarta Validation.
  • Keine sensiblen Werte in Git.
  • Defaults bewusst und dokumentiert.
  • Timeouts, Limits und Poolgrößen explizit.
  • Properties in Tests prüfen.
  • Configuration Metadata generieren.
Stolperfalle:

Ein fehlender Timeout ist auch eine Konfiguration, nur eine gefährliche.

Merksatz:

Externalized Configuration macht Code portabel; validierte Configuration macht Betrieb sicher.

B3-K02-Q005 – Profiles, Environments und Feature-SchalterProfiles modellieren Umgebung, nicht Produktstrategie. 24h
Frage:

„Wann verwenden Sie Spring Profiles, und wann sind sie ein Anti-Pattern?“

Kurz erklärt:

Profiles aktivieren oder deaktivieren Beans und Konfigurationsabschnitte abhängig von der Umgebung. Sie sollten sparsam und bewusst verwendet werden.

Musterantwort:

Profiles sind sinnvoll für grobe Umgebungsunterschiede: local, test, prod oder spezielle Infrastrukturvarianten. Sie sind gut, wenn tatsächlich andere Beans oder Konfigurationsabschnitte pro Umgebung benötigt werden.

Sie werden zum Anti-Pattern, wenn sie komplexe Geschäftslogik, Feature-Rollouts oder Kundenvarianten steuern. Dafür sind Feature Flags, Mandantenkonfiguration oder fachliche Strategy-Modelle besser. Zu viele Profile erzeugen eine Kombinatorik, die niemand mehr testet.

Senior-Antwort:

„Profiles sind für Umgebungsvarianten gut, aber nicht für beliebige fachliche Schalter. Wenn ich jedes Feature als Profil modelliere, verliere ich Testbarkeit und Transparenz.“

Häufige Fehler:
  • Zu viele Profiles definieren.
  • Profiles für Business-Varianten verwenden.
  • Prod-Profile lokal nie testen.
  • Default-Profil unbewusst nutzen.
  • Profilkombinationen nicht dokumentieren.
Best Practices:
  • Profiles sparsam verwenden.
  • Umgebungsunterschiede von Feature Flags trennen.
  • Kritische Profile automatisiert testen.
  • Aktive Profiles beim Start loggen.
  • Prod-nahe Konfiguration in Staging testen.
  • Profile nicht als Mandantenmodell missbrauchen.
Stolperfalle:

Ein Codepfad, der nur im Prod-Profil existiert, aber nie in CI getestet wird, ist ein Incident-Kandidat.

Merksatz:

Profiles modellieren Umgebung, nicht Produktstrategie.

B3-K02-Q006 – Actuator, Health Checks und Production ReadinessLiveness fragt: Soll ich neu starten? Readiness fragt: Soll ich Traffic bekommen?
Frage:

„Wie nutzen Sie Spring Boot Actuator professionell in Produktion?“

Kurz erklärt:

Spring Boot Actuator stellt produktionsreife Monitoring- und Managementfunktionen bereit: Health, Metrics, Info, Loggers, Mappings, Env und mehr.

Musterantwort:

Ich nutze Actuator für Health Checks, Readiness/Liveness, Metrics, Info, Prometheus-Integration, Log-Level-Management, Build-Informationen und Diagnoseendpunkte. Wichtig ist Security: Nicht jeder Endpoint darf öffentlich erreichbar sein.

Health Checks müssen sorgfältig gestaltet sein. Liveness soll sagen, ob der Prozess neu gestartet werden muss. Readiness soll sagen, ob der Service Traffic annehmen darf. Externe Abhängigkeiten gehören meist eher in Readiness als in Liveness, sonst kann ein Downstream-Ausfall zu unnötigen Restart-Schleifen führen.

Senior-Antwort:

„Actuator ist Produktionsinfrastruktur. Ich exponiere nur, was gebraucht wird, sichere Endpoints ab und unterscheide sauber zwischen Prozess-Lebendigkeit und Traffic-Bereitschaft.“

Häufige Fehler:
  • Externe Downstreams in Liveness prüfen.
  • Actuator öffentlich ungeschützt exponieren.
  • Health Checks mit langen Timeouts.
  • Sensitive Details in Health ausgeben.
  • Readiness und Business-Verfügbarkeit verwechseln.
Best Practices:
  • Liveness minimal halten.
  • Readiness für Traffic-Fähigkeit nutzen.
  • Health Checks schnell und timeout-begrenzt.
  • Actuator absichern.
  • Nur notwendige Endpoints exponieren.
  • Metrics und Alerts auf SLOs ausrichten.
  • Health Details je Umgebung begrenzen.
Stolperfalle:

Wenn Liveness von externen Downstreams abhängt, kann ein Downstream-Ausfall eine Restart-Lawine verursachen.

Merksatz:

Liveness fragt: Soll ich neu starten? Readiness fragt: Soll ich Traffic bekommen?

B3-K02-Q007 – Logging, Metrics und ObservabilityGute Observability beantwortet Produktionsfragen, ohne neue Produktionsprobleme zu erzeugen.
Frage:

„Wie gestalten Sie Observability in einer Spring-Boot-Anwendung?“

Kurz erklärt:

Spring Boot integriert Logging und Micrometer-basierte Metriken. Observability bedeutet, Logs, Metrics, Traces und Health sinnvoll zusammenzuführen.

Musterantwort:

Ich unterscheide Logs, Metrics und Traces. Logs erklären konkrete Ereignisse. Metrics zeigen aggregiertes Systemverhalten. Traces zeigen Request-Fluss über Komponenten und Services. Actuator und Micrometer liefern viele technische Metriken automatisch, aber fachliche Metriken müssen wir bewusst modellieren.

Wichtig sind stabile Metriknamen, kontrollierte Label-Kardinalität, Correlation IDs, strukturierte Logs, SLO-bezogene Alerts und keine sensiblen Daten. Observability soll Fragen im Incident beantworten: Was ist langsam? Wo ist Saturation? Welche Abhängigkeit fällt aus? Welche Kunden oder Operationen sind betroffen?

Senior-Antwort:

„Observability ist kein Log-Level. Ich brauche Metriken für Trends, Logs für Details und Traces für Flüsse. Und ich kontrolliere Kardinalität und Datenschutz.“

Häufige Fehler:
  • Kundennummern oder IDs als Metrik-Tag verwenden.
  • Alles loggen statt gezielt beobachten.
  • Keine Correlation IDs.
  • Logs als einzige Observability-Quelle.
  • Alerts auf technische Einzelwerte statt SLOs.
  • Sensitive Daten in Logs oder Actuator Details ausgeben.
Best Practices:
  • Strukturierte Logs verwenden.
  • Correlation IDs propagieren.
  • Metrik-Tags kardinalitätsarm halten.
  • Fachliche und technische Metriken kombinieren.
  • SLO-basierte Alerts definieren.
  • Sensitive Daten maskieren.
  • Logs, Metrics und Traces korrelieren.
Stolperfalle:

Eine Payment-ID als Metrik-Tag kann Ihr Monitoring-System ruinieren.

Merksatz:

Gute Observability beantwortet Produktionsfragen, ohne neue Produktionsprobleme zu erzeugen.

B3-K02-Q008 – Testing: @SpringBootTest, Slices und TestcontainersTeste so klein wie möglich und so integriert wie nötig.
Frage:

„Wann verwenden Sie @SpringBootTest , wann Test-Slices und wann reine Unit-Tests?“

Kurz erklärt:

Spring Boot Testing unterstützt vollständige Context-Tests und spezialisierte Test-Slices. Nicht jeder Test braucht einen vollständigen Spring-Boot-Kontext.

Musterantwort:

Reine Unit-Tests nutze ich für Fachlogik ohne Container. Test-Slices nutze ich, wenn ich nur einen Teil des Spring-Kontexts brauche, zum Beispiel MVC, JSON, JPA oder Web Layer. @SpringBootTest nutze ich für echte Integrationsszenarien, bei denen Auto-Configuration, Configuration Properties, Beans und Infrastruktur zusammenspielen sollen.

Der häufige Fehler ist, alles mit @SpringBootTest zu testen. Das macht Tests langsam, fragil und schwer verständlich. Gute Testarchitektur nutzt die kleinste sinnvolle Testebene.

Senior-Antwort:

„Ich starte den Spring Context nur, wenn ich den Spring Context testen will. Fachlogik teste ich ohne Boot, Integration gezielt mit Boot.“

Häufige Fehler:
  • Jeden Test mit @SpringBootTest .
  • Unit-Tests vom Spring-Kontext abhängig machen.
  • Test-Konfiguration pro Klasse unnötig variieren.
  • Zu viele @MockBean s verwenden.
  • H2 verwenden, obwohl Produktion PostgreSQL nutzt.
  • Testcontainers ohne Lifecycle-Strategie einsetzen.
Best Practices:
  • Kleinste sinnvolle Testebene wählen.
  • Fachlogik ohne Spring testen.
  • Slices für Web/JPA/JSON nutzen.
  • @SpringBootTest gezielt einsetzen.
  • Context Reuse beachten.
  • Testcontainers für echte Infrastruktur verwenden.
  • Produktionsnahe Datenbankfeatures testen.
Stolperfalle:

@SpringBootTest ist bequem, aber als Default macht es Test-Suites langsam und unklar.

Merksatz:

Teste so klein wie möglich und so integriert wie nötig.

B3-K02-Q009 – Packaging, Deployment und RuntimeSpring Boot vereinfacht Packaging, nicht Betriebsverantwortung.
Frage:

„Was ist beim Deployment einer Spring-Boot-Anwendung als ausführbares JAR wichtig?“

Kurz erklärt:

Spring Boot-Anwendungen werden häufig als ausführbare JARs gebaut und mit eingebettetem Server betrieben. Betriebskontext, JVM-Flags, Container-Limits und Konfiguration sind Teil des Deployments.

Musterantwort:

Ein ausführbares Boot-JAR enthält Anwendung, Dependencies und Bootstrap-Layout, sodass es mit java -jar gestartet werden kann. Im Deployment muss ich aber mehr betrachten: JVM-Version, Speicherlimits, Container-Erkennung, JVM-Flags, externe Konfiguration, Secrets, Health Probes, Logging, Timezone, Netzwerk, Zertifikate, Shutdown-Verhalten und Observability.

Boot macht Packaging einfach, aber Production Runtime bleibt Architekturarbeit.

Senior-Antwort:

„Das Boot-JAR ist nur das Artefakt. Produktionsreife entsteht durch Runtime-Konfiguration: JVM, Container, Probes, Secrets, Logging, Shutdown und Observability.“

Häufige Fehler:
  • Nur -Xmx betrachten.
  • Actuator-Probes falsch konfigurieren.
  • Secrets ins Image backen.
  • JVM-Version lokal und Produktion unterscheiden.
  • Kein Graceful Shutdown.
  • Image-Größe ignorieren.
Best Practices:
  • Java-Version standardisieren.
  • Container-Limits bewusst setzen.
  • Logs nach stdout/stderr.
  • Externe Konfiguration und Secrets getrennt.
  • Readiness/Liveness korrekt konfigurieren.
  • Graceful Shutdown aktiv testen.
  • Buildpacks oder saubere Dockerfiles nutzen.
  • SBOM/CVE-Scanning integrieren.
Stolperfalle:

Ein Container-Limit begrenzt den Prozess, nicht nur den Java-Heap.

Merksatz:

Spring Boot vereinfacht Packaging, nicht Betriebsverantwortung.

B3-K02-Q010 – Production Troubleshooting und Auto-Configuration-DiagnoseSpring Boot debuggt man über Bedingungen, nicht über Vermutungen. 24h
Frage:

„Wie debuggen Sie eine Spring-Boot-Anwendung, die lokal startet, aber in Produktion falsch konfiguriert ist oder andere Beans erzeugt?“

Kurz erklärt:

Professionelle Boot-Diagnose nutzt Condition Reports, Actuator, Logs, Metrics, Environment, Bean-Inspection, Startup Events und JFR.

Musterantwort:

Ich prüfe zuerst Unterschiede in Environment, aktiven Profiles, Property Sources, Command-Line Arguments, Secrets, Classpath, Starter-Versionen und JVM-Version. Dann nutze ich Condition Evaluation Report, Actuator Endpoints wie health, info, env, configprops, beans und metrics, sofern sicher exponiert. Zusätzlich prüfe ich Logs beim Startup und Dependency Tree.

Bei Auto-Configuration-Fragen will ich wissen: Welche Auto-Configuration hat gematcht, welche nicht, und warum? Bei Bean-Fragen: Welche Bean existiert, aus welcher Konfiguration kommt sie, welche Conditions waren aktiv?

Senior-Antwort:

„Bei Boot-Problemen debugge ich nicht nur Code, sondern den Laufzeitkontext: Classpath, Properties, Profiles, Conditions und Beans. Boot-Verhalten ist kontextabhängig.“

Häufige Fehler:
  • Nur den Code vergleichen, nicht Environment.
  • Actuator ungeschützt öffnen.
  • Debug-Logs dauerhaft aktivieren.
  • Dependency Tree nicht prüfen.
  • Property Overrides durch Environment Variables übersehen.
  • Fehlende Secrets als Codefehler behandeln.
Best Practices:
  • Startup-Diagnose für Name, Version, Profiles und wichtige Properties.
  • Condition Report bei Auto-Configuration-Problemen nutzen.
  • Actuator sicher und selektiv exponieren.
  • Dependency Tree in CI prüfen.
  • Konfiguration validieren.
  • Build- und Git-Informationen exponieren.
  • Produktions- und Staging-Konfiguration vergleichen.
Stolperfalle:

Lokal und Produktion unterscheiden sich oft nicht im Code, sondern in Property Sources, Secrets, Profiles oder Dependencies.

Merksatz:

Spring Boot debuggt man über Bedingungen, nicht über Vermutungen.

B3-K03 – Kapitel 3: Spring MVC 10 Karten

B3-K03-Q001 – DispatcherServlet und Request LifecycleDer DispatcherServlet ist der Dirigent der Spring-MVC-Webschicht. 24h
Frage:

„Was passiert intern, wenn ein HTTP-Request in Spring MVC ankommt?“

Kurz erklärt:

Der DispatcherServlet ist der zentrale Einstiegspunkt für Spring-MVC-Requests. Er empfängt HTTP-Anfragen und koordiniert Handler Mapping, Handler Adapter, Controller-Aufruf, Message Conversion, View Rendering und Exception Handling.

Musterantwort:

Ein HTTP-Request landet zunächst im Servlet Container, zum Beispiel Tomcat. Dort wird er an den DispatcherServlet weitergeleitet. Der DispatcherServlet sucht über HandlerMapping den passenden Handler, ruft ihn über einen HandlerAdapter auf, löst Controller-Methodenparameter auf, bindet Request-Daten, führt Validierung aus, wandelt Request- und Response-Bodies über HttpMessageConverter und behandelt Exceptions über konfigurierte Exception Resolver.

Bei einer REST-API wird das Rückgabeobjekt typischerweise nicht als View gerendert, sondern per Message Converter, zum Beispiel Jackson, in JSON geschrieben. Der DispatcherServlet ist also nicht die Businesslogik, sondern der Orchestrator der Webschicht.

Senior-Antwort:

„Spring MVC ist um den DispatcherServlet organisiert. Er koordiniert Mapping, Binding, Controller-Aufruf, Conversion und Fehlerbehandlung. Meine Controller bleiben dünn, weil die Webschicht nicht die Fachlogik besitzen sollte.“

Häufige Fehler:
  • DispatcherServlet als Businesslogik-Komponente verstehen.
  • Filter und Interceptors verwechseln.
  • Message Conversion ignorieren.
  • Controller zu stark mit Fachlogik füllen.
  • Request-Lifecycle bei Performanceproblemen nicht kennen.
Best Practices:
  • Controller schlank halten.
  • Fachlogik in Use Cases oder Services auslagern.
  • Request/Response-DTOs von Domain-Modellen trennen.
  • Message Conversion bewusst konfigurieren.
  • Webschicht mit @WebMvcTest gezielt testen.
  • Exception Handling zentralisieren.
Stolperfalle:

Ein Request läuft durch mehrere Framework-Schichten, bevor er Ihre Controller-Methode erreicht. Fehler können in Mapping, Binding, Conversion oder Validation entstehen, nicht nur im Controller.

Merksatz:

Der DispatcherServlet ist der Dirigent der Spring-MVC-Webschicht.

B3-K03-Q002 – Annotated Controllers und Request MappingEin Controller-Mapping ist ein Vertrag, kein Implementierungsdetail.
Frage:

„Wie entscheidet Spring MVC, welche Controller-Methode für einen Request zuständig ist?“

Kurz erklärt:

Spring MVC bietet ein annotation-basiertes Programmiermodell. Controller nutzen Annotationen, um Request-Mapping, Eingaben, Rückgaben und Fehlerbehandlung auszudrücken.

Musterantwort:

Spring MVC wertet Request-Mapping-Metadaten aus: Pfad, HTTP-Methode, Request-Parameter, Header, consumes , produces und weitere Bedingungen. @GetMapping , @PostMapping und ähnliche Annotationen sind spezialisierte Varianten von @RequestMapping .

Wenn mehrere Methoden passen könnten, nutzt Spring spezifischere Mapping-Regeln. Ambige Mappings führen typischerweise zu Fehlern beim Start oder bei der Request-Auflösung. Gute APIs vermeiden deshalb unklare Pfade und überladene Mapping-Semantik.

Senior-Antwort:

„Request Mapping ist nicht nur Annotation-Syntax. Es ist die öffentliche HTTP-Vertragsfläche meiner Anwendung. Pfad, Methode, Content-Type und Response-Type müssen fachlich und technisch eindeutig sein.“

Häufige Fehler:
  • Zu viele optionale Pfade in einer Methode.
  • GET für zustandsändernde Operationen verwenden.
  • produces und consumes ignorieren.
  • Fachliche Suche über unklare Pfadvariablen modellieren.
  • Controller-Methoden zu groß werden lassen.
Best Practices:
  • HTTP-Methoden semantisch korrekt verwenden.
  • Pfade ressourcenorientiert und eindeutig gestalten.
  • consumes und produces für APIs bewusst setzen.
  • Pagination und Limits anbieten.
  • Controller-DTOs explizit halten.
  • Mapping-Konventionen teamweit definieren.
Stolperfalle:

Ein Mapping, das lokal „irgendwie funktioniert“, kann bei Erweiterung der API ambig werden oder falsche Semantik ausdrücken.

Merksatz:

Ein Controller-Mapping ist ein Vertrag, kein Implementierungsdetail.

B3-K03-Q003 – Request Body, Response Body und HttpMessageConverterREST-Controller sprechen nicht direkt JSON; sie sprechen über Message Converter.
Frage:

„Wie wandelt Spring MVC JSON in Java-Objekte und Java-Objekte wieder in JSON?“

Kurz erklärt:

@RequestBody und @ResponseBody nutzen HttpMessageConverter , um HTTP-Body und Java-Objekte ineinander umzuwandeln.

Musterantwort:

Spring MVC nutzt HttpMessageConverter . Bei @RequestBody liest ein passender Converter den HTTP-Request-Body und erzeugt ein Java-Objekt. Bei @ResponseBody oder @RestController schreibt ein passender Converter das Java-Rückgabeobjekt in den HTTP-Response-Body.

Welcher Converter verwendet wird, hängt unter anderem von Content-Type, Accept-Header, Java-Typ und verfügbaren Libraries ab. Für JSON ist in Spring-Boot-Anwendungen typischerweise Jackson konfiguriert. Fehler können beim Lesen, Typ-Mapping, unbekannten Feldern, Datumsformaten oder Validation auftreten.

Senior-Antwort:

„ @RequestBody ist nicht einfach Parameter-Magie. Dahinter steht Message Conversion. Ich muss Content-Type, DTOs, Validation, Serialization und Fehlerfälle bewusst behandeln.“

Häufige Fehler:
  • Domain Entities direkt als Request/Response verwenden.
  • Große Payloads ohne Limit akzeptieren.
  • Content-Type nicht testen.
  • Datums- und Zeitzonenformate unklar lassen.
  • Validation und Deserialization-Fehler gleich behandeln.
  • Sensitive Felder versehentlich serialisieren.
Best Practices:
  • Explizite API-DTOs verwenden.
  • Content-Type und Accept testen.
  • Payload-Größen begrenzen.
  • Datumsformate standardisieren.
  • Validation auf Request-DTOs legen.
  • Sensitive Felder kontrollieren.
  • Serialization-Hotpaths profilieren.
Stolperfalle:

Ein erfolgreich deserialisiertes Objekt ist noch kein fachlich gültiger Request.

Merksatz:

REST-Controller sprechen nicht direkt JSON; sie sprechen über Message Converter.

B3-K03-Q004 – Data Binding, Validation und Method ValidationBinding macht Daten lesbar; Validation macht sie vertraglich akzeptabel.
Frage:

„Wie funktionieren Binding und Validation in Spring MVC, und welche Fehlerfälle müssen Sie unterscheiden?“

Kurz erklärt:

Spring MVC bindet Request-Daten an Java-Objekte und kann diese mit Jakarta Validation validieren. Binding-Fehler und Validation-Fehler sind verschiedene Fehlerquellen.

Musterantwort:

Binding bedeutet, dass Spring Request-Daten in Java-Parameter oder Objekte überführt: Pfadvariablen, Query-Parameter, Formdaten oder JSON-Bodies. Validation prüft danach oder währenddessen Constraints wie @NotBlank , @Email , @Positive .

Ich unterscheide Deserialization-Fehler, Type-Conversion-Fehler, Binding-Fehler, Bean-Validation-Fehler und fachliche Validierungsfehler. Ein ungültiges JSON ist etwas anderes als ein syntaktisch korrektes JSON mit leerem Pflichtfeld. Und beides ist etwas anderes als eine fachliche Regel wie „Kunde ist gesperrt“.

Senior-Antwort:

„Validation ist mehrschichtig. Spring prüft API-Verträge, die Fachlogik prüft Geschäftsregeln, und die Datenbank schützt Persistenzinvarianten. Diese Ebenen dürfen nicht vermischt werden.“

Häufige Fehler:
  • Bean Validation mit fachlicher Domain-Validation verwechseln.
  • @Valid auf verschachtelten Objekten vergessen.
  • Custom Validator mit Datenbankzugriff bauen.
  • Alle Validation-Fehler als 500 zurückgeben.
  • API-DTOs ohne Constraint-Dokumentation definieren.
Best Practices:
  • Request-DTOs mit Jakarta Validation annotieren.
  • Fachliche Regeln im Use Case validieren.
  • Verschachtelte DTOs mit @Valid .
  • Einheitliches Error Response Format verwenden.
  • Binding-, Validation- und Business-Fehler trennen.
  • Keine Remote Calls in Bean Validatoren.
Stolperfalle:

@Valid ersetzt keine fachliche Prüfung im Use Case.

Merksatz:

Binding macht Daten lesbar; Validation macht sie vertraglich akzeptabel.

B3-K03-Q005 – Exception Handling mit @ControllerAdviceFehlerantworten sind öffentliche API-Verträge. 24h
Frage:

„Wie gestalten Sie professionelles Exception Handling in Spring MVC REST-APIs?“

Kurz erklärt:

@ControllerAdvice zentralisiert Exception Handling für Controller. @ExceptionHandler ordnet Exceptions HTTP-Antworten zu.

Musterantwort:

Ich zentralisiere API-Fehler über @RestControllerAdvice und @ExceptionHandler . Dabei mappe ich technische und fachliche Exceptions bewusst auf HTTP-Statuscodes und ein konsistentes Fehlerformat, idealerweise RFC-7807-orientiert mit ProblemDetail .

Wichtig ist die Trennung: Validation-Fehler sind typischerweise 400, nicht gefundene Ressourcen 404, Konflikte 409, Authentifizierungsprobleme 401, Autorisierungsprobleme 403 und unerwartete Fehler 500. Ich gebe keine Stacktraces oder internen Details an Clients zurück, logge aber serverseitig ausreichend Kontext.

Senior-Antwort:

„Exception Handling ist Teil des API-Vertrags. Ich mappe fachliche und technische Fehler konsistent auf HTTP-Status und ein stabiles Fehlerformat, ohne interne Details zu leaken.“

Häufige Fehler:
  • Alle Fehler als 500 zurückgeben.
  • Stacktraces an Clients senden.
  • Fachliche Konflikte als 400 behandeln.
  • Validation-Fehler inkonsistent formatieren.
  • Exceptions zur normalen Schleifensteuerung nutzen.
  • Security-Fehler im MVC-Advice falsch behandeln.
Best Practices:
  • Globales Fehlerformat definieren.
  • Fachliche Exceptions bewusst auf HTTP-Status mappen.
  • Validation-Fehler detailliert, aber sicher zurückgeben.
  • Unerwartete Fehler intern loggen, extern generisch halten.
  • Correlation ID in Fehlerantwort oder Header aufnehmen.
  • Fehlerverträge testen.
Stolperfalle:

Nicht jede Exception gehört in denselben globalen Handler. Security, Servlet-Container-Fehler und MVC-Fehler haben unterschiedliche Verarbeitungspfade.

Merksatz:

Fehlerantworten sind öffentliche API-Verträge.

B3-K03-Q006 – ResponseEntity, Statuscodes und HeaderHTTP-Header sind Teil der API, nicht Dekoration.
Frage:

„Wann verwenden Sie ResponseEntity statt einfach ein Objekt zurückzugeben?“

Kurz erklärt:

ResponseEntity erlaubt, Body, Statuscode und Header explizit zu kontrollieren. Das ist besonders für REST-APIs, Caching, Location Header, ETags und Fehlerfälle wichtig.

Musterantwort:

Wenn nur ein Objekt mit Status 200 zurückgegeben wird, reicht ein Rückgabeobjekt oft aus. ResponseEntity verwende ich, wenn ich Statuscode, Header oder Body explizit kontrollieren will: 201 Created mit Location , 204 No Content , 304 Not Modified , ETag, Cache-Control, Download-Header oder bedingte Antworten.

Senior-relevant ist: HTTP ist mehr als JSON-Body. Statuscodes und Header sind Teil des API-Vertrags und beeinflussen Caching, Idempotenz, Clients und Observability.

Senior-Antwort:

„REST-Antworten bestehen aus Status, Headern und Body. ResponseEntity nutze ich, wenn dieser HTTP-Vertrag explizit werden muss.“

Häufige Fehler:
  • Immer 200 zurückgeben.
  • Nach Create keinen Location -Header setzen.
  • Fehler nur im Body statt im Statuscode ausdrücken.
  • Cache-Header ignorieren.
  • ETags falsch formatieren.
  • 204 mit Body senden.
Best Practices:
  • Statuscodes semantisch korrekt nutzen.
  • ResponseEntity für Header- und Statuskontrolle verwenden.
  • Location bei Resource Creation setzen.
  • ETag und Cache-Control bewusst einsetzen.
  • API-Statuscodes dokumentieren.
  • Header-Verhalten testen.
Stolperfalle:

Ein JSON-Feld status: "error" ersetzt keinen korrekten HTTP-Statuscode.

Merksatz:

HTTP-Header sind Teil der API, nicht Dekoration.

B3-K03-Q007 – Filter, HandlerInterceptor und ControllerAdviceDie richtige Webschicht entscheidet, welche Informationen Sie haben und welche Fehler Sie be…
Frage:

„Was ist der Unterschied zwischen Servlet Filter, HandlerInterceptor und ControllerAdvice?“

Kurz erklärt:

Servlet Filters laufen vor dem Spring-MVC-Handler-Mapping auf Servlet-Ebene. HandlerInterceptors laufen innerhalb von Spring MVC rund um Controller-Handler.

Musterantwort:

Ein Servlet Filter gehört zur Servlet-Schicht. Er läuft vor und nach der gesamten Servlet-Verarbeitung, also auch vor Spring MVC. Er eignet sich für Cross-Cutting-Themen wie Correlation ID, Security-Filterketten, Compression, Encoding oder Low-Level Request/Response-Behandlung.

Ein HandlerInterceptor gehört zu Spring MVC. Er läuft nach Handler-Mapping und kann vor dem Controller, nach Handler-Ausführung und nach Abschluss reagieren. Er kennt den ausgewählten Handler. ControllerAdvice ist dagegen für controllerbezogene Querschnittslogik wie Exception Handling, Model Attribute oder Binder-Konfiguration zuständig.

Senior-Antwort:

„Filter gehören zur Servlet-Kette, Interceptors zur Spring-MVC-Kette und ControllerAdvice zur Controller-Infrastruktur. Ich wähle die Ebene nach dem benötigten Kontext.“

Häufige Fehler:
  • Businesslogik in Filter packen.
  • Remote Calls in Interceptors ausführen.
  • MDC nicht aufräumen.
  • Filter-Reihenfolge ignorieren.
  • Security, MVC und Exception Handling vermischen.
  • Response bereits im Filter committen und später Controller erwarten.
Best Practices:
  • Filter für Servlet-weite technische Themen.
  • Interceptors für MVC-nahe Querschnittsthemen.
  • ControllerAdvice für API-Fehler und Binder.
  • MDC immer in finally reinigen.
  • Reihenfolge explizit konfigurieren.
  • Hotpath-Kosten messen.
Stolperfalle:

Ein @ExceptionHandler fängt nicht automatisch alle Fehler aus Servlet Filters, weil diese vor dem MVC-Controller-Lifecycle passieren können.

Merksatz:

Die richtige Webschicht entscheidet, welche Informationen Sie haben und welche Fehler Sie behandeln können.

B3-K03-Q008 – Content Negotiation und API-VersionierungContent Negotiation ist stark, aber nur so gut wie der API-Standard dahinter.
Frage:

„Wie funktioniert Content Negotiation in Spring MVC, und wann würden Sie sie für API-Versionierung nutzen?“

Kurz erklärt:

Content Negotiation entscheidet anhand von Media Types, welche Repräsentation ein Client erhält. Sie kann auch zur API-Versionierung genutzt werden, sollte aber bewusst standardisiert werden.

Musterantwort:

Content Negotiation bedeutet, dass Client und Server über Media Types aushandeln, welche Repräsentation gesendet wird. In Spring MVC beeinflussen Accept , Content-Type , produces , consumes und konfigurierte Message Converter, welche Controller-Methode und welcher Converter genutzt werden.

Für API-Versionierung kann man Versionen über URL, Header oder Media Type modellieren. Media-Type-Versionierung ist sauber im HTTP-Sinn, aber für Clients, Gateways, Caches und Dokumentation komplexer. In vielen Teams ist URL-Versionierung einfacher zu betreiben. Entscheidend ist ein konsistenter Standard.

Senior-Antwort:

„Content Negotiation ist HTTP-Vertragsdesign. Sie ist mächtig, aber Versionierung muss für Clients, Gateways, Tests und Dokumentation beherrschbar bleiben.“

Häufige Fehler:
  • Versionierungsstrategie pro Team anders wählen.
  • Accept -Header in Clients nicht testen.
  • Alte Versionen ohne Deprecation-Plan behalten.
  • DTOs zwischen Versionen vermischen.
  • Content Negotiation mit Business-Feature-Schaltern verwechseln.
Best Practices:
  • API-Versionierungsstandard definieren.
  • DTOs pro Version explizit halten.
  • Deprecation- und Sunset-Strategie definieren.
  • 406 und 415 testen.
  • API-Dokumentation versionieren.
  • Gateway- und Client-Fähigkeiten berücksichtigen.
Stolperfalle:

Wenn Clients keinen korrekten Accept -Header senden, ist Media-Type-Versionierung in der Praxis oft schwieriger als auf dem Papier.

Merksatz:

Content Negotiation ist stark, aber nur so gut wie der API-Standard dahinter.

B3-K03-Q009 – Async MVC, Streaming und Servlet-Thread-ModellStreaming spart Speicher, aber verlängert Ressourcenbindung.
Frage:

„Wann nutzen Sie Async MVC oder Streaming in Spring MVC?“

Kurz erklärt:

Spring MVC basiert auf Servlet-Technologie und ist traditionell thread-per-request. Async MVC kann Servlet-Threads freigeben, löst aber nicht automatisch Backpressure- oder Streaming-Architektur.

Musterantwort:

Ich nutze Async MVC oder Streaming, wenn Requests lange laufen, Daten schrittweise gesendet werden sollen oder Servlet-Threads nicht während der gesamten Verarbeitung blockiert bleiben sollen. Beispiele sind große Exporte, Server-Sent Events, langlaufende Berechnungen oder Response Streaming.

Wichtig ist: Async MVC ist nicht dasselbe wie WebFlux. Es bleibt Servlet-basiert. Man braucht weiterhin Timeouts, Fehlerbehandlung, Executor-Konfiguration, Client-Abbruchbehandlung und Ressourcenlimits. Für echte reaktive Backpressure über Datenströme kann WebFlux oder ein Messaging-/Streaming-Modell besser passen.

Senior-Antwort:

„Async MVC und Streaming sind Werkzeuge für spezielle Web-Workloads. Ich nutze sie, um Speicher oder Servlet-Threads zu entlasten, aber ich ersetze damit nicht automatisch Backpressure- oder Job-Architektur.“

Häufige Fehler:
  • Riesige Exporte vollständig in Memory laden.
  • Async Executor nicht konfigurieren.
  • Fehler nach Response Commit nicht bedenken.
  • Keine Timeout-Strategie.
  • Streaming ohne Backpressure-Verständnis.
  • Langlaufende Jobs über HTTP-Request offen halten.
Best Practices:
  • Große Daten streamen oder als Job auslagern.
  • Async Executor bewusst konfigurieren.
  • Timeouts setzen.
  • Client-Abbrüche behandeln.
  • Speicherverbrauch messen.
  • Für sehr lange Exporte Job + Object Storage prüfen.
  • WebFlux für echte reaktive Streams evaluieren.
Stolperfalle:

Wenn die Response bereits teilweise geschrieben wurde, können Sie Fehler nicht mehr sauber als normales JSON-Fehlerformat zurückgeben.

Merksatz:

Streaming spart Speicher, aber verlängert Ressourcenbindung.

B3-K03-Q010 – REST API Design, Security Boundaries und ProduktionsreifeSpring MVC baut Endpoints; Architektur macht daraus APIs. 24h
Frage:

„Was macht eine Spring-MVC-REST-API produktionsreif?“

Kurz erklärt:

Produktionsreife REST-APIs brauchen mehr als Controller-Methoden: Verträge, Validation, Fehlerformat, Idempotenz, Security, Observability, Pagination, Rate Limits und Versionierung.

Musterantwort:

Eine produktionsreife REST-API hat klare Ressourcenmodelle, korrekte HTTP-Methoden, semantische Statuscodes, stabile DTOs, Validation, einheitliche Fehlerformate, Authentifizierung und Autorisierung, Idempotenz bei kritischen Schreiboperationen, Pagination, Limits, Observability, Dokumentation und Versionierungsstrategie.

Spring MVC liefert die technischen Mechanismen, aber API-Qualität ist Architekturarbeit. Ich muss außerdem Security Boundaries beachten: Controller dürfen nicht blind Client-Felder übernehmen, interne IDs oder Rollen vertrauen, Domain-Entities exponieren oder sensible Daten loggen.

Senior-Antwort:

„Spring MVC gibt mir die Webmechanik. Produktionsreife entsteht durch API-Verträge, Security Boundaries, Limits, Fehlerstandards und Observability.“

Häufige Fehler:
  • Entities direkt exponieren.
  • Client-gelieferte Rollen oder Preise vertrauen.
  • Keine Pagination.
  • Keine Idempotenz bei kritischen POSTs.
  • Fehlerformate inkonsistent.
  • Sensitive Daten loggen.
  • Rate Limits und Payload-Limits ignorieren.
Best Practices:
  • API-DTOs stabil und explizit halten.
  • Domain- und Persistence-Modelle nicht exponieren.
  • Idempotenz für kritische Schreiboperationen.
  • Pagination und Limits erzwingen.
  • Einheitliches Fehlerformat.
  • Security am Rand und im Use Case prüfen.
  • Correlation IDs und Metriken einbauen.
  • API-Verträge testen und dokumentieren.
Stolperfalle:

Ein Controller, der technisch funktioniert, ist noch keine gute API.

Merksatz:

Spring MVC baut Endpoints; Architektur macht daraus APIs.

B3-K04 – Kapitel 4: Spring Data JPA 10 Karten

B3-K04-Q001 – Repositories als PersistenzabstraktionSpring Data JPA reduziert Boilerplate, nicht Verantwortung. 24h
Frage:

„Was ist ein Spring Data JPA Repository, und wo liegen seine Grenzen?“

Kurz erklärt:

Spring Data JPA Repositories stellen eine Abstraktion über JPA-Entity-Zugriff bereit. Sie reduzieren Boilerplate, ersetzen aber nicht das Verständnis von Entity-Modell, Transaktionen und SQL.

Musterantwort:

Ein Spring Data JPA Repository ist ein Interface, über das ich CRUD-Operationen, Query Methods, Pagination und eigene Queries für JPA-Entities definieren kann. Spring erzeugt zur Laufzeit eine Implementierung und integriert sie in den Spring-Container.

Die Grenze ist wichtig: Ein Repository ist keine magische Datenbankoptimierung. Es übersetzt Methodenaufrufe in JPA-Operationen. Fetching, Transaktionen, Persistence Context, Query-Pläne, Indizes, Locking und N+1-Probleme muss ich weiterhin verstehen. In einer sauberen Architektur ist ein Spring Data Repository meist ein Infrastrukturdetail, nicht zwingend das Domain-Port-Interface.

Senior-Antwort:

„Spring Data JPA Repositories sparen Boilerplate, aber sie sind Infrastruktur. Ich nutze sie bewusst hinter Architekturgrenzen und prüfe trotzdem SQL, Transaktionen und Fetching.“

Häufige Fehler:
  • JpaRepository als Domain-Port veröffentlichen.
  • findAll() unlimitiert verwenden.
  • Entities direkt an Controller zurückgeben.
  • Repository-Methoden ohne Transaktionskonzept nutzen.
  • JPA-Repository als Ersatz für SQL-Verständnis sehen.
Best Practices:
  • Repositories an Aggregat- oder Use-Case-Grenzen schneiden.
  • Unlimitierte Abfragen vermeiden.
  • Entities nicht direkt als API-DTOs exponieren.
  • Persistence Adapter für Architekturgrenzen nutzen.
  • Repository-Methoden anhand von Query-Plänen prüfen.
  • Datenbankindizes zum Query-Modell passend designen.
Stolperfalle:

Ein Repository-Methodenname sieht harmlos aus, kann aber eine sehr teure SQL-Abfrage erzeugen.

Merksatz:

Spring Data JPA reduziert Boilerplate, nicht Verantwortung.

B3-K04-Q002 – Query Methods und Derived QueriesQuery Methods sind für einfache Queries, nicht für versteckte Query-Komplexität.
Frage:

„Wann sind Derived Query Methods sinnvoll, und wann werden sie problematisch?“

Kurz erklärt:

Query Methods leiten Queries aus Methodennamen ab. Das ist produktiv, aber nur für überschaubare Abfragen gut lesbar.

Musterantwort:

Derived Query Methods sind sinnvoll für einfache, gut lesbare Abfragen: findByCustomerId , existsByEmail , findTop20ByStatusOrderByCreatedAtAsc . Sie reduzieren Boilerplate und machen Standardfälle sehr schnell umsetzbar.

Problematisch werden sie, wenn Methodennamen sehr lang werden, viele optionale Filter brauchen, Joins und Fetching relevant sind oder Performance explizit kontrolliert werden muss. Dann sind @Query , Specifications, Querydsl, EntityGraph oder eigene Repository-Implementierungen oft besser.

Senior-Antwort:

„Derived Queries sind gut, solange sie lesbar und kontrollierbar bleiben. Wenn der Methodenname zur Query-Sprache wird, ist es Zeit für ein expliziteres Modell.“

Häufige Fehler:
  • Sehr lange Query-Methodennamen schreiben.
  • Optional-Filter in Methodennamen erzwingen.
  • Keine Indizes passend zur Query anlegen.
  • findBy... ohne Limit auf große Datenmengen.
  • Query-Ergebnis als Entity laden, obwohl nur wenige Felder gebraucht werden.
Best Practices:
  • Derived Queries für einfache Fälle verwenden.
  • Lange Namen durch explizite Queries oder Specifications ersetzen.
  • Pageable und Limits nutzen.
  • SQL und Query-Plan prüfen.
  • Indizes passend zu Filter und Sortierung designen.
  • existsBy für Existenzprüfungen statt Entity-Laden verwenden.
Stolperfalle:

Eine Derived Query ist nicht automatisch performant, nur weil sie elegant aussieht.

Merksatz:

Query Methods sind für einfache Queries, nicht für versteckte Query-Komplexität.

B3-K04-Q003 – @Query, JPQL und native QueriesJe komplexer die Datenfrage, desto expliziter sollte die Query sein.
Frage:

„Wann verwenden Sie JPQL, wann native SQL in Spring Data JPA?“

Kurz erklärt:

@Query erlaubt explizite JPQL- oder native SQL-Queries. Sie ist sinnvoll, wenn Derived Queries nicht ausreichen oder Query-Form und Projection kontrolliert werden müssen.

Musterantwort:

JPQL verwende ich, wenn die Query auf dem Entity-Modell operiert und portabel bleiben soll. Sie ist gut für viele fachliche Queries, DTO-Projections und einfache Joins.

Native SQL verwende ich, wenn ich Datenbank-spezifische Features brauche: Window Functions, CTEs, spezielle Indizes, JSON-Funktionen, Materialized Views, Performance-optimierte Queries oder Vendor-spezifische Optimierungen. Der Preis ist geringere Portabilität und oft mehr Mapping-Aufwand.

Wichtig ist: Komplexe Reporting- oder Analytics-Queries gehören nicht zwangsläufig in JPA. Manchmal sind Views, Read Models, jOOQ oder direkte SQL-Abstraktionen besser.

Senior-Antwort:

„ @Query ist der Punkt, an dem ich Query-Design explizit mache. JPQL hält mich am Entity-Modell, native SQL gibt mir Datenbankmacht, aber auch Datenbankbindung.“

Häufige Fehler:
  • JPQL mit Tabellennamen schreiben.
  • Native Queries ohne Integrationstest.
  • Entities laden und in Java aggregieren.
  • Query-Parameter per String-Konkatenation bauen.
  • Datenbank-spezifische SQL ohne Dokumentation verwenden.
Best Practices:
  • JPQL für portable Entity-basierte Queries.
  • Native SQL für bewusst datenbankspezifische Features.
  • Parameter binding immer verwenden.
  • DTO- oder Interface-Projections für Read Models.
  • Query-Pläne prüfen.
  • Komplexe native Queries integrationstesten.
  • Reporting vom Write Model trennen, wenn nötig.
Stolperfalle:

JPQL arbeitet mit Entities und Properties, nicht mit Tabellen und Spalten.

Merksatz:

Je komplexer die Datenfrage, desto expliziter sollte die Query sein.

B3-K04-Q004 – Transaktionen und Repository-GrenzenTransaktionen gehören zu Geschäftsprozessen, nicht zu Zufallsstellen im Repository.
Frage:

„Wo setzen Sie @Transactional in einer Spring-Data-JPA-Anwendung sinnvoll ein?“

Kurz erklärt:

Transaktionen sollten an Use-Case-Grenzen liegen, nicht zufällig an einzelnen Repository-Methoden. Ein Geschäftsprozess braucht eine konsistente Transaktionsgrenze.

Musterantwort:

Ich setze @Transactional typischerweise auf Application-Service- oder Use-Case-Methoden, die eine fachliche Arbeitseinheit bilden. Repository-Methoden sind Bausteine; der Use Case entscheidet, welche Lese- und Schreiboperationen gemeinsam atomar sein müssen.

Ein Money Transfer darf nicht nur halb ausgeführt werden. Debit, Credit und Transfer-Record gehören in eine Transaktion. Wenn ich Transaktionen nur zufällig auf Repository-Methoden verlasse, kann die fachliche Konsistenz auseinanderfallen.

Senior-Antwort:

„Repositories sind Datenzugriffsbausteine. Die Transaktion gehört zur fachlichen Arbeitseinheit, meistens in den Application Service.“

Häufige Fehler:
  • Transaktionen auf Controller legen.
  • Remote HTTP Calls innerhalb DB-Transaktion.
  • @Transactional wegen Self-Invocation wirkungslos machen.
  • Read-only-Transaktionen ignorieren.
  • Lange Batch-Transaktionen mit riesigem Persistence Context.
  • Fachliche Konsistenz auf einzelne Repository-Methoden verteilen.
Best Practices:
  • Transaktionsgrenzen an Use Cases modellieren.
  • Transaktionen kurz halten.
  • Keine langsamen Remote Calls in DB-Transaktion.
  • readOnly = true für reine Lesepfade nutzen.
  • Flush-Verhalten verstehen.
  • Self-Invocation vermeiden.
  • Batch-Verarbeitung chunkweise durchführen.
Stolperfalle:

Ein managed Entity wird beim Commit automatisch synchronisiert. Ein reflexartiges save nach jeder Änderung ist oft unnötig und zeigt fehlendes Persistence-Context-Verständnis.

Merksatz:

Transaktionen gehören zu Geschäftsprozessen, nicht zu Zufallsstellen im Repository.

B3-K04-Q005 – Pagination, Sorting und SlicePagination begrenzt nicht nur Antworten, sondern schützt das System.
Frage:

„Was ist der Unterschied zwischen Page , Slice und List in Spring Data JPA?“

Kurz erklärt:

Pagination begrenzt Ergebnismengen. Page liefert zusätzlich Gesamtanzahl, Slice nur Information über nächste Seite.

Musterantwort:

List liefert einfach Ergebnisse, ohne Paging-Metadaten. Page liefert Ergebnisse plus Gesamtanzahl und Seiteninformationen. Dafür benötigt Spring häufig eine zusätzliche Count-Query. Slice liefert eine Seite plus Information, ob es eine nächste Seite gibt, aber keine Gesamtanzahl.

Für große Tabellen kann Page teuer sein, weil die Count-Query teuer sein kann. Wenn der Client nur „mehr laden“ braucht, ist Slice oft effizienter. Für sehr große Datenmengen kann Keyset Pagination besser sein als Offset Pagination.

Senior-Antwort:

„Pagination ist kein UI-Detail, sondern Schutz für Datenbank und Service. Page ist komfortabel, aber die Count-Query ist nicht kostenlos.“

Häufige Fehler:
  • findAll() für API-Listen verwenden.
  • Page Size nicht begrenzen.
  • Page verwenden, obwohl Gesamtanzahl nicht gebraucht wird.
  • Sortierung ohne Index.
  • Offset Pagination für sehr große Datenmengen.
  • Instabile Sortierung ohne eindeutigen Tie-Breaker.
Best Practices:
  • Listen-Endpunkte immer paginieren.
  • Maximale Page Size erzwingen.
  • Slice nutzen, wenn Gesamtanzahl nicht nötig ist.
  • Sortierung indexfreundlich wählen.
  • Keyset Pagination für große Datenmengen prüfen.
  • DTO-/Projection-Ladung statt voller Entities prüfen.
Stolperfalle:

Ein paginierter Endpoint ohne maximale Page Size ist praktisch unlimitiert.

Merksatz:

Pagination begrenzt nicht nur Antworten, sondern schützt das System.

B3-K04-Q006 – Projections und DTO-QueriesLaden Sie, was der Use Case braucht, nicht was die Entity zufällig hat.
Frage:

„Warum und wann verwenden Sie Spring Data JPA Projections?“

Kurz erklärt:

Projections erlauben, nur benötigte Daten zu laden und nicht immer vollständige Entities zu materialisieren.

Musterantwort:

Projections nutze ich für Read-Use-Cases, die nur einen Teil einer Entity benötigen, etwa Listenansichten, Suchergebnisse oder Dashboards. Statt vollständige Entities mit allen Feldern und Beziehungen zu laden, lade ich ein Interface oder DTO mit genau den benötigten Attributen.

Das reduziert Datenübertragung, Entity-Hydration, Persistence-Context-Größe und Risiko, versehentlich Lazy-Relations zu triggern. Für Schreib-Use-Cases und Aggregate-Änderungen lade ich dagegen meistens Entities, weil ich Invarianten und Dirty Checking brauche.

Senior-Antwort:

„Ich lade nicht reflexartig Entities. Für Read-Use-Cases sind Projections oft präziser, schneller und sicherer.“

Häufige Fehler:
  • Entities für reine Listenansichten laden.
  • Projection-SQL nicht prüfen.
  • Nested Projections unkritisch verwenden.
  • Projections für Schreiblogik missbrauchen.
  • Sensitive Felder versehentlich exponieren.
Best Practices:
  • Projections für Read Models nutzen.
  • DTO-Projections für stabile API-/Use-Case-Views.
  • Entity-Ladung für Schreiboperationen.
  • SQL-Ausgabe prüfen.
  • Keine sensiblen Felder in Projection aufnehmen.
  • Pagination und Projection kombinieren.
Stolperfalle:

Eine Projection garantiert nicht automatisch optimale SQL. Die erzeugte Query muss geprüft werden.

Merksatz:

Laden Sie, was der Use Case braucht, nicht was die Entity zufällig hat.

B3-K04-Q007 – Specifications und dynamische SucheSpecifications ersetzen Methodenexplosion, nicht Query-Verantwortung.
Frage:

„Wann verwenden Sie Specifications in Spring Data JPA?“

Kurz erklärt:

Specifications erlauben dynamische, kombinierbare Kriterien für Suchabfragen. Sie sind hilfreich, wenn optionale Filter nicht mehr sinnvoll durch Methodennamen modelliert werden können.

Musterantwort:

Ich verwende Specifications für dynamische Suchmasken mit optionalen Filtern, bei denen Derived Query Methods unlesbar würden. Jede Specification beschreibt ein kleines Kriterium, und mehrere Kriterien lassen sich mit and oder or kombinieren.

Der Vorteil ist Komposition. Der Nachteil ist, dass die Criteria-API weniger lesbar ist als JPQL oder SQL. Für sehr komplexe Queries, Reporting oder datenbankspezifische Optimierung können Querydsl, jOOQ oder native SQL besser sein.

Senior-Antwort:

„Specifications sind gut für kombinierbare Filter. Aber dynamische Suche bleibt Query-Design und braucht Limits, Indizes und Tests.“

Häufige Fehler:
  • Unlimitierte dynamische Suche erlauben.
  • Specifications mit komplexer Businesslogik vermischen.
  • String-basierte Property-Namen nicht testen.
  • Indizes für optionale Filter vergessen.
  • Fetch Joins in Count Queries problematisch machen.
Best Practices:
  • Kleine, wiederverwendbare Specifications.
  • Suchkriterien als eigenes Objekt modellieren.
  • Pagination erzwingen.
  • SQL und Query-Pläne prüfen.
  • Komplexe Queries integrationstesten.
  • Für große Suchsysteme Elasticsearch oder spezialisierte Read Models prüfen.
Stolperfalle:

Dynamisch bedeutet nicht automatisch effizient. Jede Filterkombination kann andere Query-Pläne erzeugen.

Merksatz:

Specifications ersetzen Methodenexplosion, nicht Query-Verantwortung.

B3-K04-Q008 – Auditing und technische MetadatenAuditing-Felder sagen, dass etwas geändert wurde; ein Audit Log erklärt, was passiert ist.
Frage:

„Wie funktioniert Auditing in Spring Data JPA, und wo liegen die Grenzen?“

Kurz erklärt:

Spring Data JPA Auditing kann technische Metadaten wie Erstellzeitpunkt, Änderungszeitpunkt, Ersteller und Bearbeiter automatisch pflegen.

Musterantwort:

Spring Data JPA Auditing nutzt Annotationen wie @CreatedDate , @LastModifiedDate , @CreatedBy und @LastModifiedBy . Über einen Entity Listener werden diese Felder beim Persistieren und Aktualisieren gesetzt. Für Benutzerinformationen braucht man typischerweise einen AuditorAware .

Die Grenze: Auditing ist kein vollständiges fachliches Änderungsjournal. Es speichert typische technische Metadaten auf der aktuellen Zeile. Wenn ich nachvollziehen muss, welche Felder wann wie geändert wurden, brauche ich Envers, ein eigenes Audit Log, Event Sourcing oder eine Change-Data-Capture-Lösung.

Senior-Antwort:

„Spring Data Auditing ist gut für created/updated-Metadaten. Für echte fachliche Historie brauche ich ein eigenes Audit-Konzept.“

Häufige Fehler:
  • Auditing mit fachlicher Historisierung verwechseln.
  • Auditor-Kontext in Async-Tasks verlieren.
  • Bulk Updates erwarten, dass Entity Listener laufen.
  • Audit-Felder in API-DTOs unkontrolliert exponieren.
  • Zeitzonenmodell nicht definieren.
Best Practices:
  • Technische Audit-Felder standardisieren.
  • Instant für technische Zeitpunkte verwenden.
  • Auditor-Quelle klar definieren.
  • Fachliches Audit separat modellieren.
  • Bulk-Operationen bewusst behandeln.
  • Audit-Felder nicht blind an Clients ausgeben.
Stolperfalle:

Bulk Updates umgehen häufig Entity-Lifecycle-Mechanismen und damit auch Auditing-Callbacks.

Merksatz:

Auditing-Felder sagen, dass etwas geändert wurde; ein Audit Log erklärt, was passiert ist.

B3-K04-Q009 – Locking, Versioning und konkurrierende UpdatesLocking ist Konsistenz gegen Parallelität, nicht kostenloser Schutz.
Frage:

„Wann verwenden Sie optimistic und wann pessimistic locking mit Spring Data JPA?“

Kurz erklärt:

Optimistic Locking erkennt konkurrierende Updates über @Version . Pessimistic Locking sperrt Datenbankzeilen während der Transaktion.

Musterantwort:

Optimistic Locking ist gut, wenn Konflikte selten sind. Über @Version erkennt JPA beim Commit, ob ein Datensatz seit dem Lesen geändert wurde. Dann kann der Use Case neu versuchen oder einen Konflikt melden.

Pessimistic Locking ist sinnvoll, wenn Konflikte häufig sind oder eine Ressource während der Transaktion exklusiv geschützt werden muss. Es sperrt Datenbankzeilen, kann aber Deadlocks, Wartezeiten und geringeren Durchsatz erzeugen.

Die Wahl hängt von Konfliktwahrscheinlichkeit, Geschäftsanforderung, Latenz, Datenbanklast und Retry-Strategie ab.

Senior-Antwort:

„Optimistic Locking ist mein Default bei seltenen Konflikten. Pessimistic Locking nutze ich nur, wenn ich bewusst Datenbanklocks akzeptiere und die Transaktion kurz halte.“

Häufige Fehler:
  • Keine Versionierung bei konkurrierenden Updates.
  • Pessimistic Locking mit langen Transaktionen.
  • Konflikte als 500 behandeln.
  • Blindes Retry ohne Limit.
  • Datenbanklock und externe Calls kombinieren.
  • Locking ohne Indexe verwenden.
Best Practices:
  • Optimistic Locking als Default für seltene Konflikte.
  • Pessimistic Locking nur bewusst und kurz.
  • Konflikte fachlich als 409 modellieren.
  • Retry mit Backoff und Limit.
  • Keine Remote Calls unter DB-Lock.
  • Deadlock-Metriken und DB-Wartezeiten beobachten.
Stolperfalle:

Ein Datenbanklock schützt nicht vor schlechter Architektur. Wenn Sie während des Locks einen Remote Call machen, bauen Sie sich Latenz und Deadlock-Risiko ein.

Merksatz:

Locking ist Konsistenz gegen Parallelität, nicht kostenloser Schutz.

B3-K04-Q010 – N+1, Fetching und EntityGraphFetching ist Teil des Use-Case-Designs. 24h
Frage:

„Wie erkennen und vermeiden Sie N+1-Probleme in Spring Data JPA?“

Kurz erklärt:

N+1 entsteht, wenn eine Query N Entities lädt und danach für jede Entity zusätzliche Queries für lazy Beziehungen ausgeführt werden. Fetching muss pro Use Case geplant werden.

Musterantwort:

Ich erkenne N+1 durch SQL-Logs, JFR, Hibernate Statistics, Datenbankmetriken oder auffällige Query-Anzahl pro Request. Typisches Muster: Eine Query lädt eine Liste, danach folgen viele einzelne Selects für Beziehungen.

Vermeiden kann ich N+1 durch gezielte Fetch Joins, @EntityGraph , DTO-Projections, Batch Fetching oder anderes Read-Model-Design. Wichtig ist: Ich setze nicht pauschal alles auf EAGER. Fetching ist Use-Case-spezifisch. EAGER-Beziehungen erzeugen oft noch schlimmere Performanceprobleme.

Senior-Antwort:

„N+1 löse ich nicht durch pauschales EAGER Fetching. Ich plane pro Use Case, welche Daten gebraucht werden, und wähle Projection, EntityGraph, Fetch Join oder ein separates Read Model.“

Häufige Fehler:
  • Alles auf FetchType.EAGER setzen.
  • Open Session in View als Performance-Lösung sehen.
  • SQL-Logs in Tests ignorieren.
  • Entities für API-Listen laden.
  • Fetch Joins mit großen Collections und Pagination kombinieren.
  • LazyInitializationException mit größeren Transaktionen kaschieren.
Best Practices:
  • Fetching pro Use Case planen.
  • SQL-Anzahl in Tests beobachten.
  • DTO-Projections für Listenansichten nutzen.
  • @EntityGraph gezielt einsetzen.
  • EAGER als Default vermeiden.
  • Pagination und Collection Fetching vorsichtig kombinieren.
  • Query-Pläne und Result-Set-Größen prüfen.
Stolperfalle:

FetchType.EAGER verschiebt das Problem oft nur: Es lädt zu viel, zu früh und in Kontexten, die es gar nicht brauchen.

Merksatz:

Fetching ist Teil des Use-Case-Designs.

B3-K05 – Kapitel 5: Hibernate 10 Karten

B3-K05-Q001 – Session, EntityManager und Persistence ContextDer Persistence Context ist die Unit of Work von Hibernate. 24h
Frage:

„Was ist der Persistence Context in Hibernate, und warum ist er so wichtig?“

Kurz erklärt:

Der Persistence Context ist die zentrale Hibernate-Arbeitseinheit innerhalb einer Transaktion. Er verwaltet Entity-Instanzen, Identität, Änderungen und Synchronisation mit der Datenbank.

Musterantwort:

Der Persistence Context ist die Menge der aktuell verwalteten Entity-Instanzen. Innerhalb dieses Contexts garantiert Hibernate, dass dieselbe Datenbankzeile durch dieselbe Java-Objektinstanz repräsentiert wird. Er ist außerdem die Basis für Dirty Checking, Lazy Loading, Flush, First-Level Cache und Entity-State-Übergänge.

Wenn ich eine Entity innerhalb einer Transaktion lade, ist sie managed. Änderungen an managed Entities werden beim Flush oder Commit automatisch erkannt und in SQL übersetzt. Deshalb ist ein reflexartiges save oder merge nach jeder Änderung nicht nötig und oft ein Zeichen, dass der Persistence Context nicht verstanden wurde.

Senior-Antwort:

„Der Persistence Context ist Hibernate in Aktion: Identität, First-Level Cache, Dirty Checking und Flush hängen daran. Ohne dieses Modell versteht man Hibernate-Performance und Entity-Verhalten nicht.“

Häufige Fehler:
  • Nach jeder Entity-Änderung save oder merge aufrufen.
  • Persistence Context in Batch-Jobs riesig wachsen lassen.
  • Detached Entities wie managed Entities behandeln.
  • EntityManager außerhalb der Transaktion nutzen wollen.
  • First-Level Cache mit Second-Level Cache verwechseln.
Best Practices:
  • Transaktionsgrenzen bewusst setzen.
  • Managed Entity Änderungen durch Dirty Checking nutzen.
  • Batch-Verarbeitung in Chunks durchführen.
  • Persistence Context bei großen Batches kontrolliert flush / clear .
  • Keine Entities über API-Grenzen hinweg als langlebige Objekte verwenden.
  • EntityManager/Session nicht als globale Registry missbrauchen.
Stolperfalle:

save ist nicht der Mechanismus, der jede Änderung an einer managed Entity persistiert. Dirty Checking und Flush sind der zentrale Mechanismus.

Merksatz:

Der Persistence Context ist die Unit of Work von Hibernate.

B3-K05-Q002 – Entity States: transient, managed, detached, removedEntity-Zustand entscheidet, ob Hibernate zuhört.
Frage:

„Welche Entity States gibt es in Hibernate/JPA, und warum sind sie wichtig?“

Kurz erklärt:

Hibernate unterscheidet Entity-Zustände. Nur managed Entities werden automatisch vom Persistence Context überwacht.

Musterantwort:

Typische Zustände sind transient, managed, detached und removed. Transient bedeutet: normales Java-Objekt, nicht im Persistence Context. Managed bedeutet: vom Persistence Context verwaltet, Dirty Checking aktiv. Detached bedeutet: hatte einmal Datenbankidentität, ist aber nicht mehr managed. Removed bedeutet: zur Löschung markiert.

Das ist wichtig, weil nur managed Entities automatisch synchronisiert werden. Änderungen an detached Entities werden nicht automatisch persistiert. merge nimmt eine detached Entity und kopiert ihren Zustand in eine managed Instanz; es macht nicht zwingend dieselbe Objektinstanz wieder managed.

Senior-Antwort:

„Nur managed Entities werden automatisch verfolgt. Detached Entities sind normale Objekte mit Datenbankidentität, aber ohne automatische Synchronisation.“

Häufige Fehler:
  • Detached Entity ändern und Persistenz erwarten.
  • Rückgabewert von merge ignorieren.
  • Request-Body direkt als Entity mergen.
  • Entity-Zustand über mehrere Requests tragen.
  • persist und merge verwechseln.
Best Practices:
  • Request-DTOs nicht als Entities verwenden.
  • Für Updates managed Entity laden und gezielt ändern.
  • merge bewusst und sparsam verwenden.
  • Entity-Zustände in Tests demonstrieren.
  • Lange Entity-Lebensdauer außerhalb von Transaktionen vermeiden.
  • Entity-Graphen nicht blind aus Clients übernehmen.
Stolperfalle:

merge macht nicht einfach Ihr vorhandenes Objekt wieder managed; es kopiert Zustand in eine managed Instanz und gibt diese zurück.

Merksatz:

Entity-Zustand entscheidet, ob Hibernate zuhört.

B3-K05-Q003 – Dirty Checking, Flush und Write-BehindDirty Checking ist bequem, aber nicht kostenlos.
Frage:

„Was ist Dirty Checking, und wann führt Hibernate SQL aus?“

Kurz erklärt:

Dirty Checking erkennt Änderungen an managed Entities. Flush synchronisiert den Persistence Context mit der Datenbank, ohne zwingend die Transaktion zu committen.

Musterantwort:

Dirty Checking bedeutet, dass Hibernate managed Entities mit ihrem geladenen Zustand vergleicht. Wenn sich Felder geändert haben, erzeugt Hibernate beim Flush passende SQL-Statements. Flush passiert typischerweise vor Commit und manchmal vor Queries, wenn Hibernate sicherstellen muss, dass Query-Ergebnisse den aktuellen Persistence Context berücksichtigen.

Wichtig ist: Eine Java-Änderung erzeugt nicht sofort SQL. Hibernate arbeitet mit Write-Behind. Das ermöglicht Optimierung, Statement-Reihenfolge und Batching, kann aber überraschen, wenn man sofortige Datenbanksynchronisation erwartet.

Senior-Antwort:

„Hibernate schreibt nicht bei jedem Setter sofort SQL. Es sammelt Änderungen im Persistence Context und synchronisiert sie beim Flush.“

Häufige Fehler:
  • Sofortiges SQL nach jeder Setter-Änderung erwarten.
  • flush() als Commit missverstehen.
  • Große Transaktionen mit tausenden managed Entities.
  • Read-only-Abfragen als Entities laden und managed halten.
  • Bulk Updates mit Dirty Checking verwechseln.
Best Practices:
  • Flush-Verhalten verstehen.
  • Transaktionen kurz und fokussiert halten.
  • Read-only-Pfade als read-only markieren.
  • Projections für reine Reads verwenden.
  • Batch-Jobs mit flush/clear steuern.
  • SQL-Logging in Tests nutzen.
Stolperfalle:

flush() schreibt SQL zur Datenbank, aber es commitet die Transaktion nicht.

Merksatz:

Dirty Checking ist bequem, aber nicht kostenlos.

B3-K05-Q004 – Lazy Loading, Proxies und LazyInitializationExceptionLazy Loading braucht einen aktiven Kontext und einen klaren Plan.
Frage:

„Was ist Lazy Loading, und warum entsteht eine LazyInitializationException ?“

Kurz erklärt:

Lazy Loading lädt Beziehungen erst bei Zugriff. Dafür braucht Hibernate einen aktiven Persistence Context. Ohne ihn entsteht häufig LazyInitializationException .

Musterantwort:

Lazy Loading bedeutet, dass Hibernate eine Beziehung nicht sofort lädt, sondern erst, wenn sie gebraucht wird. Dafür nutzt Hibernate Proxies oder spezielle Collection Wrapper. Wenn die Beziehung außerhalb eines aktiven Persistence Contexts gelesen wird, kann Hibernate keine zusätzliche Query mehr ausführen. Dann entsteht eine LazyInitializationException .

Die Lösung ist nicht, alles EAGER zu setzen. Die Lösung ist, pro Use Case gezielt zu laden: DTO-Query, Fetch Join, EntityGraph oder explizites Mapping innerhalb der Transaktion.

Senior-Antwort:

„Lazy Loading ist ein Werkzeug, kein Fehler. Der Fehler ist, Entity-Graphen ohne klaren Use-Case-Fetch-Plan über Schichten hinweg zu tragen.“

Häufige Fehler:
  • Entities direkt serialisieren.
  • LazyInitializationException durch EAGER lösen.
  • Open Session in View als Allheilmittel nutzen.
  • Lazy-Beziehungen außerhalb der Transaktion lesen.
  • DTO-Mapping im Controller statt im Use Case machen.
Best Practices:
  • Entities nicht als API Response nutzen.
  • DTOs innerhalb klarer Transaktionsgrenze erzeugen.
  • Fetching pro Use Case planen.
  • EntityGraph oder Fetch Join gezielt nutzen.
  • Open Session in View kritisch bewerten.
  • SQL-Anzahl testen.
Stolperfalle:

EAGER Loading behebt eine LazyInitializationException oft nur lokal und erzeugt global neue Performanceprobleme.

Merksatz:

Lazy Loading braucht einen aktiven Kontext und einen klaren Plan.

B3-K05-Q005 – Fetching-Strategien, Join Fetch, Batch Fetching und EntityGraphGutes Fetching minimiert Queries und Datenmenge gleichzeitig.
Frage:

„Welche Fetching-Strategien kennen Sie in Hibernate, und wie wählen Sie die passende?“

Kurz erklärt:

Fetching-Strategien bestimmen, wann und wie Hibernate Beziehungen lädt. Sie sind einer der wichtigsten Performancehebel.

Musterantwort:

Wichtige Strategien sind Lazy Loading, Eager Loading, Join Fetch, EntityGraph, Batch Fetching und DTO-Projections. Lazy ist als Default für Beziehungen oft sinnvoll, aber pro Use Case muss entschieden werden, welche Daten benötigt werden.

Join Fetch ist gut, wenn ich eine Beziehung sicher sofort brauche und die Result-Set-Größe kontrolliert bleibt. EntityGraph ist eine deklarative Möglichkeit, Fetching für bestimmte Queries zu steuern. Batch Fetching reduziert N+1, indem Hibernate mehrere lazy Assoziationen in einer Query lädt. DTO-Projections sind oft am besten für reine Read-Modelle.

Senior-Antwort:

„Fetching ist kein Mapping-Detail, sondern Use-Case-Design. Ich lade weder zu wenig noch zu viel, sondern genau das, was der Use Case braucht.“

Häufige Fehler:
  • Globale EAGER-Beziehungen definieren.
  • Große Collections join-fetchen.
  • Fetching nicht pro Use Case planen.
  • Pagination und Collection Fetching blind kombinieren.
  • DTO-Projection vermeiden, obwohl keine Entity-Änderung nötig ist.
Best Practices:
  • LAZY als Default für Beziehungen.
  • Fetching pro Use Case definieren.
  • To-One-Beziehungen gezielt join-fetchen.
  • Große To-Many-Beziehungen vorsichtig laden.
  • DTO-Projections für Listen und Read Models.
  • Query-Anzahl und Result-Set-Größe messen.
  • EntityGraph für wiederverwendbare Fetch-Pläne nutzen.
Stolperfalle:

Ein Join Fetch auf große Collections kann weniger Queries erzeugen, aber viel mehr Daten übertragen.

Merksatz:

Gutes Fetching minimiert Queries und Datenmenge gleichzeitig.

B3-K05-Q006 – Mapping, Value Objects und Identifier-StrategienGutes Mapping respektiert Objektmodell und Datenbankmodell.
Frage:

„Was macht ein gutes Hibernate-Mapping aus?“

Kurz erklärt:

Hibernate Mapping verbindet Java-Objektmodell und relationales Modell. Gute Mappings respektieren beide Seiten: Domain-Invarianten und Datenbankschema.

Musterantwort:

Ein gutes Mapping ist explizit, stabil und performant. Es modelliert IDs, Spalten, Constraints, Embeddables, Enums, Beziehungen und Versionierung so, dass Domain-Code verständlich bleibt und die Datenbank effizient arbeiten kann.

Ich mappe Value Objects gern als @Embeddable , nutze EnumType.STRING statt ordinaler Enums, definiere Spaltenlängen und Präzision bewusst und wähle Identifier-Strategien passend zur Datenbank und zum Batch-Verhalten. Mapping ist kein reines Annotationsthema, sondern Schema-Design.

Senior-Antwort:

„Hibernate-Mapping ist die Übersetzung zwischen zwei Modellen. Ich will weder ein datenbankblindes Objektmodell noch ein domainloses Tabellenabbild.“

Häufige Fehler:
  • Enums ordinal speichern.
  • Spaltenpräzision für Geld vergessen.
  • Datenbankconstraints nur im Java-Code modellieren.
  • Entities zu stark an API-DTOs ausrichten.
  • ID-Strategie ohne Batch- und DB-Verständnis wählen.
Best Practices:
  • Mapping explizit halten.
  • Geldbeträge mit Präzision und Scale definieren.
  • Enums als STRING speichern.
  • Value Objects als Embeddables modellieren.
  • Datenbankconstraints zusätzlich setzen.
  • Identifier-Strategie bewusst wählen.
  • Schema-Migrationen versioniert verwalten.
Stolperfalle:

EnumType.ORDINAL sieht kompakt aus, aber eine Änderung der Enum-Reihenfolge kann Daten semantisch zerstören.

Merksatz:

Gutes Mapping respektiert Objektmodell und Datenbankmodell.

B3-K05-Q007 – Second-Level Cache und Query CacheHibernate Cache lohnt sich nur, wenn Datenzugriffsmuster und Konsistenzmodell passen.
Frage:

„Wann ist Hibernate Second-Level Cache sinnvoll, und wann ist er gefährlich?“

Kurz erklärt:

Der First-Level Cache ist der Persistence Context. Der Second-Level Cache ist ein optionaler, Session-übergreifender Cache. Der Query Cache cached Query-Ergebnislisten, nicht automatisch Entity-Zustände allein.

Musterantwort:

Second-Level Cache ist sinnvoll für häufig gelesene, selten geänderte Daten, zum Beispiel Referenzdaten, Länder, Währungen, Produktkategorien oder Konfiguration. Er kann Datenbanklast reduzieren und Latenz verbessern.

Gefährlich wird er bei häufig geänderten Daten, komplexer Invalidierung, mehreren Anwendungen, inkonsistentem Cache-Provider-Setup oder falscher Cache-Concurrency-Strategie. Query Cache ist besonders vorsichtig einzusetzen, weil Query-Ergebnisse invalidiert werden müssen und die Hit Ratio oft überschätzt wird.

Senior-Antwort:

„Second-Level Cache ist stark für stabile Referenzdaten. Für transaktionale, häufig geänderte Daten ist er oft mehr Risiko als Nutzen.“

Häufige Fehler:
  • Second-Level Cache global für alle Entities aktivieren.
  • Häufig geänderte Daten cachen.
  • Query Cache ohne Hit-Ratio-Messung aktivieren.
  • Cache-Invalidierung unterschätzen.
  • Cache als Ersatz für fehlende Indizes nutzen.
  • Stale Data fachlich ignorieren.
Best Practices:
  • Nur geeignete Daten cachen.
  • READ_ONLY für echte Referenzdaten nutzen.
  • Hit/Miss/Eviction messen.
  • Query Cache sehr gezielt einsetzen.
  • Cache-Konfiguration dokumentieren.
  • Datenkonsistenz fachlich bewerten.
  • Cache nicht als erste Performancewaffe einsetzen.
Stolperfalle:

Der Query Cache cached Ergebnis-IDs beziehungsweise Query-Ergebnisse, aber er ersetzt kein gutes Entity- und Datenbank-Caching-Konzept.

Merksatz:

Hibernate Cache lohnt sich nur, wenn Datenzugriffsmuster und Konsistenzmodell passen.

B3-K05-Q008 – JDBC Batching, Bulk Operations und Batch ProcessingBatching braucht kleine Arbeitseinheiten, nicht nur eine Batch-Size-Property.
Frage:

„Wie optimieren Sie große Inserts oder Updates mit Hibernate?“

Kurz erklärt:

JDBC Batching reduziert Datenbank-Roundtrips, indem mehrere gleichartige Statements gesammelt gesendet werden. Hibernate kann JDBC Batching nutzen, aber Mapping und ID-Strategie beeinflussen es stark.

Musterantwort:

Zuerst prüfe ich, ob Hibernate überhaupt das richtige Werkzeug ist. Für moderate Batch-Größen kann JDBC Batching mit Hibernate gut funktionieren. Ich setze eine sinnvolle Batch Size, verwende batch-freundliche ID-Strategien, arbeite in Chunks und rufe regelmäßig flush und clear , damit der Persistence Context nicht wächst.

Für sehr große Bulk-Operationen, Importjobs oder Massenupdates können native SQL, JDBC, jOOQ, Datenbank-spezifische Bulk Loader oder staging tables besser sein. Hibernate ist stark bei Entity-Lifecycle und Invarianten, aber nicht immer optimal für Massendatenbewegung.

Senior-Antwort:

„Hibernate kann batching, aber ich muss Persistence Context, ID-Strategie, Batch Size und Transaktionsgröße bewusst steuern. Für echte Massendaten ist ORM nicht immer die beste Wahl.“

Häufige Fehler:
  • Große Imports in einer Transaktion ohne clear.
  • Identity-ID-Strategie und Insert-Batching erwarten.
  • Batch Size extrem groß setzen.
  • Entity Listener und Auditing bei Bulk Updates vergessen.
  • Bulk Update und Persistence Context Inkonsistenz ignorieren.
Best Practices:
  • Chunkweise verarbeiten.
  • JDBC Batch Size messen.
  • Persistence Context regelmäßig leeren.
  • ID-Strategie batch-freundlich wählen.
  • Bulk-Operationen bewusst von Entity-Lifecycle trennen.
  • Für Massendaten spezialisierte Werkzeuge prüfen.
  • Fehler- und Retry-Strategie pro Chunk definieren.
Stolperfalle:

JDBC Batching reduziert Roundtrips, löst aber nicht das Problem eines riesigen Persistence Contexts.

Merksatz:

Batching braucht kleine Arbeitseinheiten, nicht nur eine Batch-Size-Property.

B3-K05-Q009 – HQL, Criteria und SQL-VerständnisORM versteckt SQL nicht vor der Datenbank.
Frage:

„Warum reicht es nicht, HQL oder JPQL zu schreiben, ohne die erzeugte SQL zu prüfen?“

Kurz erklärt:

HQL/JPQL operiert auf Entities und Properties, nicht direkt auf Tabellen und Spalten. Trotzdem muss man die erzeugte SQL und deren Query-Plan verstehen.

Musterantwort:

HQL und JPQL sind Abstraktionen über das Entity-Modell. Hibernate übersetzt sie in SQL. Diese SQL entscheidet letztlich über Datenbanklast, Indizes, Join-Reihenfolge, Sortierung, Aggregation und Result-Set-Größe.

Eine Query kann im Java-Code sauber aussehen und trotzdem schlechte SQL erzeugen oder falsche Indizes nutzen. Deshalb prüfe ich bei wichtigen Queries SQL-Logging, Bind-Parameter, Query-Plan und Metriken. ORM befreit mich nicht von Datenbankverständnis.

Senior-Antwort:

„HQL abstrahiert Tabellen weg, aber nicht Datenbankkosten. Ich prüfe die erzeugte SQL bei jeder wichtigen Query.“

Häufige Fehler:
  • HQL mit Tabellennamen schreiben.
  • Query-Pläne nicht prüfen.
  • Alle Daten laden und in Java aggregieren.
  • Dynamische Queries mit String-Konkatenation bauen.
  • Datenbankdialekt ignorieren.
Best Practices:
  • HQL für Entity-nahe Queries verwenden.
  • DTO-Queries für Read Models.
  • Native SQL für datenbankspezifische Features.
  • SQL und Query-Plan prüfen.
  • Parameter Binding nutzen.
  • Aggregation in der Datenbank ausführen.
  • Query-Komplexität bewusst begrenzen.
Stolperfalle:

Eine Query ist nicht performant, nur weil sie objektorientiert lesbar ist.

Merksatz:

ORM versteckt SQL nicht vor der Datenbank.

B3-K05-Q010 – Hibernate Performance Diagnostics und Anti-PatternsHibernate-Performance ist messbares Datenzugriffsdesign. 24h
Frage:

„Wie gehen Sie vor, wenn eine Hibernate-basierte Anwendung langsam ist?“

Kurz erklärt:

Hibernate-Performance diagnostiziert man über SQL, Query-Anzahl, Fetching, Flushes, Persistence Context, Cache-Metriken, Datenbankmetriken und Lasttests.

Musterantwort:

Ich beginne mit Symptomen: CPU hoch oder niedrig? Datenbankzeit hoch? Query-Anzahl auffällig? p99 betroffen? Dann aktiviere ich gezielte Diagnose: SQL-Logging in Test/Staging, Hibernate Statistics, JFR, Datenbank-Query-Pläne, Connection-Pool-Metriken und Tracing.

Typische Ursachen sind N+1, zu große Result Sets, fehlende Indizes, unnötige Entity-Ladung, falsches Fetching, riesiger Persistence Context, lange Transaktionen, Count-Queries, schlechte Batches oder Cache-Missbrauch. Ich optimiere nicht blind mit EAGER , Cache oder mehr Connection-Pool-Größe.

Senior-Antwort:

„Hibernate-Performance analysiere ich systematisch: Query-Anzahl, SQL-Plan, Fetching, Flush, Persistence Context, Cache und Datenbankmetriken. Ich tune nicht blind.“

Häufige Fehler:
  • Symptome mit mehr Connections überdecken.
  • SQL-Logging in Produktion unkontrolliert aktivieren.
  • FetchType.EAGER als Fix verwenden.
  • Hibernate-Statistiken ohne Lastkontext interpretieren.
  • Datenbankmetriken ignorieren.
  • Performance nur lokal mit leerer Datenbank testen.
Best Practices:
  • Query-Anzahl in Tests prüfen.
  • SQL und Query-Pläne für kritische Pfade analysieren.
  • Hibernate Statistics gezielt nutzen.
  • JFR und DB-Metriken korrelieren.
  • Persistence Context Größe kontrollieren.
  • Fetching und Projections pro Use Case designen.
  • Performance-Regressionen automatisiert erkennen.
Stolperfalle:

Ein größerer Connection Pool macht schlechte Queries nicht besser; er erlaubt nur mehr schlechte Queries gleichzeitig.

Merksatz:

Hibernate-Performance ist messbares Datenzugriffsdesign.

B3-K06 – Kapitel 6: Spring Security 10 Karten

B3-K06-Q001 – SecurityFilterChain und FilterChainProxySpring Security schützt Requests in der Filterkette, nicht erst im Controller. 24h
Frage:

„Wie funktioniert Spring Security im Servlet-Request-Lifecycle?“

Kurz erklärt:

Spring Security schützt Servlet-Anwendungen über eine Filterkette. Der zentrale Einstiegspunkt ist FilterChainProxy , der an passende SecurityFilterChain s delegiert.

Musterantwort:

Spring Security sitzt vor dem Controller in der Servlet-Filterkette. Der Servlet Container ruft einen Filter auf, der an Spring Security delegiert. Innerhalb von Spring Security entscheidet FilterChainProxy , welche SecurityFilterChain für den Request gilt. Diese Kette enthält Security-Filter für Themen wie Authentifizierung, SecurityContext, CSRF, Logout, Authorization, Exception Handling und weitere Mechanismen.

Wichtig ist: Security passiert nicht erst im Controller. Viele Requests werden bereits vor dem Controller authentifiziert, autorisiert oder abgelehnt. Deshalb muss man Security-Fehlerpfade, Filter-Reihenfolge und Matcher verstehen.

Senior-Antwort:

„Spring Security ist filterbasiert. Bevor ein Request meinen Controller erreicht, hat die SecurityFilterChain Authentifizierung, Autorisierung und Fehlerbehandlung bereits entschieden.“

Häufige Fehler:
  • Security als Controller-Interceptor verstehen.
  • Filter-Reihenfolge ignorieren.
  • Mehrere Chains mit überlappenden Matchern falsch sortieren.
  • anyRequest().permitAll() versehentlich zu breit setzen.
  • Custom Filter ohne klare Position einfügen.
  • Security-Tests ohne echte Filterkette schreiben.
Best Practices:
  • Security-Konfiguration als explizite Filterketten modellieren.
  • Spezifische Matcher vor generischen Matchern platzieren.
  • Default-deny-Prinzip verwenden.
  • Public Endpoints bewusst und minimal freigeben.
  • Custom Filter nur mit klarer Verantwortung einsetzen.
  • Security-Filterkette in Integrationstests aktivieren.
Stolperfalle:

Die Reihenfolge mehrerer SecurityFilterChain s kann entscheiden, ob ein Endpoint geschützt oder versehentlich öffentlich ist.

Merksatz:

Spring Security schützt Requests in der Filterkette, nicht erst im Controller.

B3-K06-Q002 – Authentication, SecurityContext und PrincipalDer SecurityContext beschreibt, wer technisch handelt und mit welchen Authorities.
Frage:

„Was ist der Unterschied zwischen Principal, Authentication und SecurityContext?“

Kurz erklärt:

Authentication beschreibt den aktuell authentifizierten Aufrufer. Der SecurityContext hält diese Authentication für den aktuellen Request.

Musterantwort:

Der Principal ist die Identität des Aufrufers, zum Beispiel ein Benutzername, eine UserDetails-Instanz oder ein JWT-Subject. Authentication enthält neben dem Principal auch Credentials, Authorities und Authentifizierungsstatus. Der SecurityContext hält die aktuelle Authentication .

In einer Servlet-Anwendung wird dieser Kontext typischerweise requestbezogen verwaltet. Nach dem Request muss der Kontext sauber entfernt werden, damit keine Identität in einen anderen Thread oder Request leakt.

Senior-Antwort:

„Authentication ist der technische Sicherheitskontext des Requests. Daraus leite ich Berechtigungen ab, aber ich verwechsle ihn nicht blind mit meinem fachlichen Benutzeraggregat.“

Häufige Fehler:
  • Principal und fachlichen Benutzer verwechseln.
  • SecurityContext in eigene statische Holder kopieren.
  • Kontext in Async-Tasks verlieren.
  • Token-Claims ungeprüft als fachliche Wahrheit behandeln.
  • Authorities ohne Namenskonvention mischen.
Best Practices:
  • Authentication als technische Identität behandeln.
  • Fachlichen Benutzer bei Bedarf kontrolliert aus Use Case laden.
  • SecurityContext nicht unnötig in Domain-Code leaken.
  • Async-Kontextpropagation bewusst lösen.
  • Authorities normalisieren und dokumentieren.
  • Keine sensiblen Credentials loggen.
Stolperfalle:

Ein JWT-Claim ist nur so vertrauenswürdig wie Validierung, Issuer, Audience und Mapping-Konfiguration.

Merksatz:

Der SecurityContext beschreibt, wer technisch handelt und mit welchen Authorities.

B3-K06-Q003 – HTTP Authorization und Least PrivilegeAuthentifiziert heißt nicht autorisiert.
Frage:

„Wie modellieren Sie Autorisierung auf HTTP-Ebene in Spring Security?“

Kurz erklärt:

HTTP Authorization definiert, welche Requests welche Berechtigungen benötigen. Gute Security arbeitet mit expliziten Regeln und Default-Deny.

Musterantwort:

Ich definiere Authorization Rules explizit nach Pfad, HTTP-Methode und benötigten Authorities oder Rollen. Public Endpoints sind Ausnahmen und werden bewusst markiert. Für alles andere setze ich mindestens authenticated() oder besser spezifische Authorities. Ein sicherer Default ist denyAll() oder eine sehr klare Catch-all-Regel.

Wichtig ist Least Privilege: Lesen, Schreiben, Admin und interne Operationen sollten getrennte Authorities haben. Außerdem unterscheide ich Request-Level-Autorisierung von fachlicher Objekt-Autorisierung. Ein User mit transfers.read darf nicht automatisch jeden Transfer lesen.

Senior-Antwort:

„HTTP-Autorisierung ist meine erste Schutzlinie. Sie entscheidet grob, ob ein Request überhaupt in die Anwendung darf. Fachliche Objektberechtigungen prüfe ich zusätzlich im Use Case.“

Häufige Fehler:
  • anyRequest().permitAll() während Entwicklung stehen lassen.
  • HTTP-Methode nicht berücksichtigen.
  • Rollen und Scopes vermischen.
  • Public Endpoints zu breit matchen.
  • Objektberechtigung nur über URL-Pfad ableiten.
  • Admin-Rechte als allgemeine Lösung nutzen.
Best Practices:
  • Default-deny verwenden.
  • Pfad und HTTP-Methode gemeinsam betrachten.
  • Authorities feingranular modellieren.
  • Public Endpoints minimal halten.
  • Actuator getrennt absichern.
  • Objektberechtigungen zusätzlich fachlich prüfen.
  • Authorization-Regeln testen.
Stolperfalle:

Ein User mit Lesescope darf nicht automatisch jede Ressource lesen. Resource Ownership ist eine zusätzliche fachliche Prüfung.

Merksatz:

Authentifiziert heißt nicht autorisiert.

B3-K06-Q004 – PasswordEncoder und sichere PasswortspeicherungPasswörter werden gehasht, nie gespeichert.
Frage:

„Wie speichert man Passwörter sicher mit Spring Security?“

Kurz erklärt:

Passwörter werden niemals im Klartext gespeichert. Spring Security nutzt PasswordEncoder, typischerweise DelegatingPasswordEncoder, um verschiedene Hashing-Verfahren und Migrationen zu unterstützen.

Musterantwort:

Passwörter speichert man niemals im Klartext und auch nicht mit schnellen Hashes wie SHA-256 allein. Man verwendet adaptive Passwort-Hashing-Verfahren über PasswordEncoder , etwa bcrypt, PBKDF2, scrypt oder Argon2, abhängig von Plattformstandard und Compliance.

Spring Securitys DelegatingPasswordEncoder speichert einen Algorithmus-Präfix wie {bcrypt} mit dem Hash. Dadurch kann die Anwendung alte und neue Hashes parallel validieren und Migrationen durchführen. Für neue Passwörter wird der aktuelle Default Encoder verwendet.

Senior-Antwort:

„Passwortspeicherung ist bewusst langsam, versioniert und migrationsfähig. DelegatingPasswordEncoder hilft, Algorithmen sicher weiterzuentwickeln.“

Häufige Fehler:
  • Passwörter im Klartext speichern.
  • NoOpPasswordEncoder in Produktion nutzen.
  • Passwort-Hashing mit Verschlüsselung verwechseln.
  • Hash-Algorithmus ohne Migrationsstrategie wechseln.
  • Raw Passwords loggen.
  • Zu niedrigen Work Factor verwenden.
Best Practices:
  • PasswordEncoder als Bean definieren.
  • DelegatingPasswordEncoder nutzen.
  • Klartextpasswörter nie loggen.
  • Work Factor regelmäßig bewerten.
  • Alte Hashes bei erfolgreichem Login migrieren.
  • Login-Rate-Limits und Brute-Force-Schutz ergänzen.
  • Passwortpolitik und MFA getrennt betrachten.
Stolperfalle:

Verschlüsselung ist reversibel; Passwort-Hashing soll gerade nicht reversibel sein.

Merksatz:

Passwörter werden gehasht, nie gespeichert.

B3-K06-Q005 – CSRF, Sessions und stateless APIsCSRF schützt dort, wo der Browser ungewollt authentifiziert handeln kann. 24h
Frage:

„Wann darf man CSRF in Spring Security deaktivieren, und wann nicht?“

Kurz erklärt:

CSRF schützt vor Angriffen, bei denen ein Browser automatisch Cookies mitsendet. Ob CSRF gebraucht wird, hängt stark vom Authentifizierungsmodell ab.

Musterantwort:

CSRF ist relevant, wenn ein Browser automatisch Authentifizierungsinformationen mitsendet, typischerweise Session-Cookies. Dann kann eine fremde Seite den Browser dazu bringen, einen unerwünschten POST auszuführen.

Bei stateless APIs, die ausschließlich Bearer Tokens im Authorization Header verwenden und keine Cookie-basierte Authentifizierung akzeptieren, ist CSRF häufig nicht erforderlich. Aber man muss ehrlich prüfen: Gibt es Cookies? Gibt es Browser-Clients? Gibt es Form Login? Gibt es Session-basierte Authentifizierung? Dann sollte CSRF nicht pauschal deaktiviert werden.

Senior-Antwort:

„CSRF ist keine Frage von REST oder nicht REST, sondern eine Frage, ob der Browser Authentifizierung automatisch mitsendet.“

Häufige Fehler:
  • CSRF pauschal deaktivieren.
  • Bearer Tokens zusätzlich in Cookies speichern und CSRF ignorieren.
  • Form Login und REST API in derselben Chain vermischen.
  • 403 bei Tests nicht als CSRF-Fehler erkennen.
  • CORS mit CSRF verwechseln.
Best Practices:
  • Authentifizierungsmodell zuerst klären.
  • CSRF für Cookie-/Session-basierte Browserflows aktiviert lassen.
  • Stateless Bearer APIs getrennt konfigurieren.
  • Keine Tokens unsicher in Cookies speichern.
  • CSRF-Fehler explizit testen.
  • Web- und API-Security-Chains trennen.
Stolperfalle:

JWT im Cookie kann wieder CSRF-relevant sein, wenn der Browser dieses Cookie automatisch mitsendet.

Merksatz:

CSRF schützt dort, wo der Browser ungewollt authentifiziert handeln kann.

B3-K06-Q006 – CORS und Security-GrenzenCORS kontrolliert Browser, nicht Angreifer.
Frage:

„Wie konfigurieren Sie CORS mit Spring Security, und was schützt CORS nicht?“

Kurz erklärt:

CORS ist ein Browser-Sicherheitsmechanismus, der Cross-Origin-Zugriffe kontrolliert. CORS ist keine Authentifizierung und keine Autorisierung.

Musterantwort:

CORS muss vor Spring Security verarbeitet werden, weil Preflight-Requests häufig keine Cookies oder Authorization-Daten enthalten. In Spring Security integriere ich CORS über cors() und eine CorsConfigurationSource .

CORS schützt nicht Ihre API vor nicht-browserbasierten Clients. Ein Server-to-Server-Client kann CORS ignorieren. CORS entscheidet nur, ob ein Browser JavaScript aus einer fremden Origin die Antwort zugänglich macht. Authentifizierung, Autorisierung, Rate Limits und Input Validation bleiben weiterhin notwendig.

Senior-Antwort:

„CORS ist Browser-Zugriffskontrolle, keine API-Sicherheit. Meine API muss auch dann sicher sein, wenn der Client kein Browser ist.“

Häufige Fehler:
  • CORS mit Authentifizierung verwechseln.
  • allowedOrigins("*") in Produktion verwenden.
  • Credentials und breite Origins kombinieren.
  • OPTIONS-Requests durch Auth blockieren.
  • CORS-Probleme im Backend falsch debuggen.
  • CORS als Ersatz für Authorization sehen.
Best Practices:
  • Erlaubte Origins explizit konfigurieren.
  • Methoden und Header begrenzen.
  • Credentials nur bewusst erlauben.
  • Preflight-Verhalten testen.
  • CORS-Konfiguration pro API-Zone definieren.
  • Auth und Authorization unabhängig von CORS durchsetzen.
Stolperfalle:

Wenn ein Angreifer mit curl oder einem Backend-Client kommt, spielt CORS keine Rolle.

Merksatz:

CORS kontrolliert Browser, nicht Angreifer.

B3-K06-Q007 – OAuth2 Resource Server und JWTJWTs transportieren Claims; Security entsteht durch Validierung und Autorisierung.
Frage:

„Wie validiert Spring Security JWTs in einem OAuth2 Resource Server, und worauf achten Sie?“

Kurz erklärt:

Ein OAuth2 Resource Server schützt APIs mit Bearer Tokens. Bei JWT validiert Spring Security Token-Signatur, Zeitclaims, Issuer und mapped Claims auf Authorities.

Musterantwort:

Ein Resource Server erwartet Bearer Tokens. Bei JWT validiert Spring Security Signatur, Ablaufzeit, Not-Before, Issuer und weitere konfigurierte Claims. Schlüssel werden typischerweise über JWKS vom Authorization Server bezogen. Danach werden Claims wie scope oder scp in Authorities gemappt, oft mit SCOPE_ -Präfix.

Wichtig ist, nicht nur zu prüfen, ob ein Token syntaktisch gültig ist. Ich prüfe Issuer, Audience, Ablauf, Algorithmen, Clock Skew, Authority Mapping, Mandantenkontext und Token-Lebensdauer. Außerdem darf die API keine fachlichen Rechte allein aus unkontrollierten Claims ableiten.

Senior-Antwort:

„Ein JWT ist kein Vertrauen an sich. Vertrauen entsteht durch Signaturprüfung, Issuer, Audience, Ablauf, Claim-Mapping und fachliche Autorisierung.“

Häufige Fehler:
  • JWT nur decodieren, aber nicht validieren.
  • Audience nicht prüfen.
  • Zu lange Token-Lebensdauer.
  • Rollen und Scopes unklar mischen.
  • JWT-Claims direkt als fachliche Rechte verwenden.
  • Signing Algorithmus nicht kontrollieren.
Best Practices:
  • Issuer und Audience validieren.
  • Scopes und Authorities sauber mappen.
  • Token-Lebensdauer begrenzen.
  • JWKS-Verfügbarkeit und Rotation berücksichtigen.
  • Keine sensiblen Daten unnötig ins JWT packen.
  • Resource Server Tests mit JWT-Mockdaten schreiben.
  • Service-to-Service-Scopes separat modellieren.
Stolperfalle:

Base64-Decoding eines JWT ist keine Validierung.

Merksatz:

JWTs transportieren Claims; Security entsteht durch Validierung und Autorisierung.

B3-K06-Q008 – Method Security und fachliche AutorisierungAutorisierung gehört an jede relevante Grenze, nicht nur an den ersten Controller.
Frage:

„Wann verwenden Sie Method Security statt oder zusätzlich zu HTTP Authorization?“

Kurz erklärt:

Method Security schützt Service-Methoden über Annotationen wie @PreAuthorize . Sie ergänzt HTTP-Autorisierung, ersetzt aber keine fachliche Policy.

Musterantwort:

HTTP Authorization ist gut für grobe, requestbasierte Regeln: Pfad, Methode, Scope. Method Security ist gut für feinere Regeln an Service- oder Use-Case-Grenzen, besonders wenn derselbe Use Case über mehrere Eingänge erreichbar ist oder fachliche Berechtigungen nahe an der Methode dokumentiert werden sollen.

Trotzdem nutze ich @PreAuthorize nicht als Ersatz für komplexe Domain-Policies. Eine Annotation kann prüfen, ob ein Scope vorhanden ist. Ob ein konkreter User diesen konkreten Transfer freigeben darf, ist oft fachliche Autorisierung und gehört in eine Policy-Komponente.

Senior-Antwort:

„Request-Level Security entscheidet, ob ein Request grob erlaubt ist. Method Security schützt Use Cases. Fachliche Policies entscheiden, ob genau diese Handlung auf genau diesem Objekt erlaubt ist.“

Häufige Fehler:
  • Nur Controller absichern und interne Use Cases ungeschützt lassen.
  • Method Security als vollständige Fachpolicy missbrauchen.
  • Self-Invocation bei Method Security ignorieren.
  • SpEL-Ausdrücke zu komplex machen.
  • Unannotierte Methoden versehentlich offen lassen.
  • Tests ohne SecurityContext schreiben.
Best Practices:
  • HTTP Security und Method Security kombinieren.
  • Method Security an Use-Case-Grenzen platzieren.
  • Fachliche Autorisierung in Policies modellieren.
  • SpEL kurz und verständlich halten.
  • Proxy-Grenzen beachten.
  • Security-Tests für erlaubte und verbotene Pfade schreiben.
Stolperfalle:

Eine Annotation mit hasRole('ADMIN') ist selten ein vollständiges Berechtigungskonzept.

Merksatz:

Autorisierung gehört an jede relevante Grenze, nicht nur an den ersten Controller.

B3-K06-Q009 – Security Testing mit MockMvc und Method SecuritySecurity-Tests müssen verbotene Wege beweisen.
Frage:

„Wie testen Sie Spring Security Regeln zuverlässig?“

Kurz erklärt:

Security muss getestet werden. Controller-Tests ohne Security-Filterkette geben falsche Sicherheit.

Musterantwort:

Ich teste mindestens drei Fälle: anonym, authentifiziert ohne ausreichende Berechtigung und authentifiziert mit ausreichender Berechtigung. Für MVC-Tests integriere ich die Spring-Security-Filterkette, damit 401/403, CSRF, JWT, Rollen und Authorities realistisch wirken.

Für Method Security teste ich Service-Methoden mit @WithMockUser , custom SecurityContext oder echten Authentication-Objekten. Wichtig ist, nicht nur Happy Paths zu testen. Security-Tests müssen explizit verbotene Zugriffe prüfen.

Senior-Antwort:

„Security, die nicht getestet ist, ist Konfiguration auf Vertrauen. Ich teste explizit, wer abgelehnt wird, nicht nur wer durchkommt.“

Häufige Fehler:
  • Nur erfolgreiche Zugriffe testen.
  • Security-Filter in MockMvc nicht aktivieren.
  • Rollen statt Authorities falsch simulieren.
  • CSRF in Tests deaktivieren statt korrekt senden.
  • Method Security ohne Proxy testen.
  • 401 und 403 verwechseln.
Best Practices:
  • Negative Security-Tests schreiben.
  • 401, 403 und 200 getrennt testen.
  • SecurityFilterChain in MVC-Tests einbinden.
  • JWT-Claims und Authorities realistisch simulieren.
  • Method Security mit SecurityContext testen.
  • Kritische Regeln als Regressionstests absichern.
Stolperfalle:

Ein grüner Controller-Test ohne Security-Filter sagt nichts darüber aus, ob der Endpoint wirklich geschützt ist.

Merksatz:

Security-Tests müssen verbotene Wege beweisen.

B3-K06-Q010 – Production Security, Defense in Depth und typische SchwachstellenSecurity ist Schichtenarchitektur, nicht eine Annotation. 24h
Frage:

„Was macht eine Spring-Security-Konfiguration produktionsreif?“

Kurz erklärt:

Produktionsreife Security besteht aus mehreren Schutzschichten: sichere Defaults, Authentifizierung, Autorisierung, Input Validation, sichere Header, Secrets, Logging, Monitoring, Rate Limits, Audits und Tests.

Musterantwort:

Produktionsreife Security folgt Defense in Depth. Ich verlasse mich nicht auf eine einzelne Maßnahme. Ich brauche sichere Default-Regeln, explizite Authorization, starke Authentifizierung, sichere Passwort- oder Token-Verarbeitung, CSRF/CORS passend zum Clientmodell, Security Header, Fehlerbehandlung ohne Informationslecks, Secrets Management, Audit Logging, Rate Limits, Monitoring und Security-Tests.

Außerdem prüfe ich Betriebsaspekte: Actuator absichern, Debug-Endpoints schließen, sensible Logs vermeiden, Token- und Key-Rotation planen, Abhängigkeiten aktuell halten, CVEs verfolgen, TLS korrekt terminieren und Security-Events observieren.

Senior-Antwort:

„Spring Security ist ein Framework, aber Produktionssicherheit ist ein System. Ich brauche Filterregeln, fachliche Policies, sichere Konfiguration, Tests, Monitoring und Betriebsprozesse.“

Häufige Fehler:
  • Security nur als Login betrachten.
  • Actuator ungeschützt exponieren.
  • Debug- oder Error-Details leaken.
  • Zu grobe Rollen verwenden.
  • Secrets in Konfigurationsdateien speichern.
  • Keine negativen Security-Tests.
  • Authorization nur im Frontend erzwingen.
Best Practices:
  • Default-deny-Konfiguration.
  • Least Privilege für Scopes und Rollen.
  • Security-Header bewusst setzen.
  • Actuator separat absichern.
  • Secrets über Secret-Management.
  • Sensitive Daten maskieren.
  • Security-Events auditieren.
  • Dependencies und CVEs regelmäßig prüfen.
Stolperfalle:

Authorization nur im Frontend ist keine Security. Der Server muss jede kritische Entscheidung selbst prüfen.

Merksatz:

Security ist Schichtenarchitektur, nicht eine Annotation.

B3-K07 – Kapitel 7: REST 10 Karten

B3-K07-Q001 – REST-Grundprinzipien und RessourcenmodellREST ist Ressourcen- und HTTP-Semantik, nicht nur JSON-Transport. 24h
Frage:

„Was bedeutet REST für das Design einer HTTP-API wirklich?“

Kurz erklärt:

REST modelliert Ressourcen und deren Repräsentationen. Ein Endpoint sollte nicht nur eine technische Funktion ausführen, sondern einen nachvollziehbaren Ressourcenvertrag anbieten.

Musterantwort:

REST bedeutet, dass ich eine API um Ressourcen und Repräsentationen herum entwerfe. Eine Ressource hat eine stabile URI, Clients interagieren über standardisierte HTTP-Methoden, Antworten nutzen Statuscodes und Header korrekt, und der Server speichert keinen versteckten Client-Session-Zustand zwischen Requests.

In der Praxis heißt das: Ich entwerfe /payments/{id} als Ressource, verwende GET zum Lesen, POST zum Erstellen, PUT oder PATCH zum Ändern und DELETE zum Löschen oder Stornieren, wenn fachlich passend. Ich verstecke nicht jede Aktion hinter /doSomething , sondern nutze HTTP-Semantik bewusst.

Senior-Antwort:

„REST beginnt nicht bei Annotationen, sondern beim Ressourcenmodell. Ich frage zuerst: Welche fachlichen Ressourcen existieren, welche Repräsentationen brauchen Clients und welche HTTP-Semantik passt dazu?“

Häufige Fehler:
  • RPC-Stil als REST verkaufen.
  • Jede Aktion als /doX modellieren.
  • HTTP-Methoden ignorieren.
  • Ressourcen und Datenbanktabellen verwechseln.
  • Interne Entities als externe Ressourcenverträge exponieren.
  • API-Design aus Controller-Methoden statt aus Use Cases ableiten.
Best Practices:
  • Ressourcen fachlich modellieren.
  • URIs stabil, lesbar und konsistent halten.
  • HTTP-Methoden semantisch verwenden.
  • DTOs als API-Vertrag nutzen.
  • Ressourcen nicht blind an Datenbanktabellen koppeln.
  • Collection-, Item- und Subresource-Patterns standardisieren.
  • API-Design vor Implementierung dokumentieren.
Stolperfalle:

Eine URL ohne Verb ist nicht automatisch REST. Entscheidend ist, ob Ressource, Methode, Statuscode, Header und Repräsentation semantisch zusammenpassen.

Merksatz:

REST ist Ressourcen- und HTTP-Semantik, nicht nur JSON-Transport.

B3-K07-Q002 – HTTP-Methoden: Safe, Idempotent und CacheableHTTP-Methoden sind nicht Dekoration, sondern verteilte Systemsemantik.
Frage:

„Was bedeuten safe und idempotent bei HTTP-Methoden?“

Kurz erklärt:

HTTP-Methoden haben Semantik. GET ist safe, PUT und DELETE sind idempotent, POST ist es nicht automatisch. Diese Eigenschaften beeinflussen Retry, Caching, Proxies und Client-Verhalten.

Musterantwort:

Safe bedeutet, dass eine Methode keine vom Client gewünschte Zustandsänderung auf dem Server auslöst. GET , HEAD , OPTIONS und TRACE gelten als safe. Idempotent bedeutet, dass mehrere identische Requests denselben gewünschten Effekt haben wie ein einzelner Request. Safe Methoden sind idempotent; außerdem sind PUT und DELETE idempotent.

POST ist nicht automatisch idempotent. Wenn ein Client denselben POST /payments zweimal sendet, können zwei Zahlungen entstehen. Deshalb braucht man bei kritischen Schreiboperationen oft Idempotency Keys oder ein anderes Duplikatschutzmodell.

Senior-Antwort:

„HTTP-Methoden sind Verträge. Wenn ich GET für Zustandsänderungen nutze oder POST blind retrye, verletze ich Annahmen von Clients, Proxies und Infrastruktur.“

Häufige Fehler:
  • Zustandsänderung per GET .
  • POST automatisch retryen.
  • DELETE als „immer 200“ missverstehen.
  • PUT für partielle Updates verwenden, ohne Semantik zu klären.
  • Safe und idempotent verwechseln.
  • Client-Retry-Strategie ohne Methodensemantik bauen.
Best Practices:
  • GET strikt read-only halten.
  • PUT für vollständigen Ersatz verwenden.
  • PATCH für partielle Änderung bewusst definieren.
  • Kritische POST s mit Idempotency Key schützen.
  • Retries an Methodensemantik koppeln.
  • API-Tests für falsche Methodennutzung ergänzen.
Stolperfalle:

Idempotent heißt nicht, dass die Antwort immer identisch sein muss. Es heißt, dass der gewünschte Server-Effekt identisch ist.

Merksatz:

HTTP-Methoden sind nicht Dekoration, sondern verteilte Systemsemantik.

B3-K07-Q003 – Statuscodes und ResponseEntityDer HTTP-Status ist das erste maschinenlesbare Ergebnis eines REST-Requests.
Frage:

„Welche HTTP-Statuscodes verwenden Sie typischerweise in REST-APIs, und warum?“

Kurz erklärt:

HTTP-Statuscodes sind Teil des API-Vertrags. Sie zeigen nicht nur Erfolg oder Fehler, sondern beschreiben die Bedeutung der Antwort.

Musterantwort:

Für erfolgreiche Requests nutze ich typischerweise 200 OK für normale Antworten, 201 Created für neu erstellte Ressourcen mit Location , 202 Accepted für asynchrone Verarbeitung, 204 No Content für erfolgreiche Antworten ohne Body und 304 Not Modified bei Conditional Requests.

Für Clientfehler nutze ich 400 Bad Request für ungültige Eingaben, 401 Unauthorized für fehlende oder ungültige Authentifizierung, 403 Forbidden für fehlende Berechtigung, 404 Not Found , 409 Conflict bei fachlichen Konflikten und 422 Unprocessable Content , wenn der Request syntaktisch korrekt ist, aber semantisch nicht verarbeitet werden kann. Für Serverfehler bleibt 500 generisch, ohne interne Details zu leaken.

Senior-Antwort:

„Statuscodes sind maschinenlesbare Semantik. Ich will nicht, dass Clients JSON parsen müssen, um zu erkennen, ob ein Request erfolgreich, ungültig, verboten oder konfliktbehaftet war.“

Häufige Fehler:
  • Immer 200 OK zurückgeben.
  • Fachliche Konflikte als 500 melden.
  • Authentifizierung und Autorisierung verwechseln.
  • 204 mit Body senden.
  • 201 ohne Location bei Resource Creation.
  • Fehlerstatus nur im JSON-Body codieren.
Best Practices:
  • Statuscodes als API-Vertrag dokumentieren.
  • ResponseEntity für explizite Status-/Header-Kontrolle nutzen.
  • 201 Created mit Location zurückgeben.
  • Fachliche Konflikte als 409 modellieren.
  • Validation-Fehler konsistent als 400/422 behandeln.
  • 5xx für unerwartete Serverfehler reservieren.
  • Statuscodes in Controller-Tests prüfen.
Stolperfalle:

401 Unauthorized klingt sprachlich nach Autorisierung, bedeutet im HTTP-Kontext aber fehlende oder ungültige Authentifizierung.

Merksatz:

Der HTTP-Status ist das erste maschinenlesbare Ergebnis eines REST-Requests.

B3-K07-Q004 – Problem Details und konsistente FehlerverträgeFehler sind Teil des Vertrags, nicht Ausnahme vom Vertrag.
Frage:

„Wie gestalten Sie Fehlerantworten in REST-APIs professionell?“

Kurz erklärt:

Fehlerantworten sind API-Verträge. Problem Details bieten ein standardisiertes Format für maschinenlesbare HTTP-API-Fehler.

Musterantwort:

Ich definiere ein konsistentes Fehlerformat und mappe Exceptions zentral. Für moderne HTTP-APIs ist Problem Details eine sehr gute Basis: type , title , status , detail , instance plus API-spezifische Erweiterungen wie code , violations oder correlationId .

Wichtig ist, dass Fehler stabil und maschinenlesbar sind. Clients sollten nicht auf frei formulierte Texte parsen müssen. Gleichzeitig dürfen Fehlerantworten keine Stacktraces, SQL-Details, interne Klassennamen oder sensiblen Daten leaken.

Senior-Antwort:

„Fehlerantworten sind keine Nebensache. Clients brauchen stabile Codes und Struktur; Menschen brauchen verständliche Details; der Betrieb braucht Correlation und Logs.“

Häufige Fehler:
  • Fehlertexte als einzige maschinenlesbare Information verwenden.
  • Stacktraces an Clients senden.
  • Verschiedene Fehlerformate pro Controller.
  • Validation-Fehler ohne Feldbezug zurückgeben.
  • Fachliche Fehler als 500 melden.
  • Interne Exception-Klassen als API-Vertrag leaken.
Best Practices:
  • Einheitliches ProblemDetail-basiertes Format.
  • Stabile technische Fehlercodes definieren.
  • Correlation ID ergänzen.
  • Validation-Fehler strukturiert ausgeben.
  • Interne Details serverseitig loggen, extern abstrahieren.
  • Fehlerverträge dokumentieren und testen.
  • Auth/Security-Fehlerpfade separat beachten.
Stolperfalle:

Ein detail -Text ist nicht stabil genug für Clientlogik. Dafür braucht es stabile Fehlercodes oder type -URIs.

Merksatz:

Fehler sind Teil des Vertrags, nicht Ausnahme vom Vertrag.

B3-K07-Q005 – Idempotenz, Retries und DuplikatschutzRetries sind unvermeidbar; Idempotenz macht sie sicher. 24h
Frage:

„Wie gestalten Sie Idempotenz für kritische REST-POST-Operationen?“

Kurz erklärt:

Idempotenz schützt kritische Schreiboperationen vor Duplikaten, wenn Clients bei Timeouts oder Netzwerkfehlern retryen.

Musterantwort:

Bei kritischen POST -Operationen wie Payment, Transfer oder Order Creation verlange ich einen Idempotency Key. Der Server speichert Key, Request-Fingerprint und Ergebnis in einer transaktional konsistenten Struktur. Wenn derselbe Key erneut kommt, liefert der Server das ursprüngliche Ergebnis zurück oder lehnt ab, falls der Request-Inhalt nicht zum gespeicherten Fingerprint passt.

Wichtig ist, Idempotenz nicht nur im Controller zu prüfen. Sie muss mit der fachlichen Operation atomar verbunden sein, sonst kann ein Crash zwischen Operation und Idempotenzspeicherung wieder Duplikate erzeugen.

Senior-Antwort:

„Idempotenz ist kein Header allein. Der Server muss den Key transaktional mit dem Ergebnis verbinden und Wiederholungen deterministisch behandeln.“

Häufige Fehler:
  • Idempotency Key nur loggen, aber nicht erzwingen.
  • Ergebnis nicht speichern.
  • Key ohne Request-Fingerprint akzeptieren.
  • Idempotenz außerhalb der Transaktion speichern.
  • Cleanup und TTL vergessen.
  • Idempotenz nur pro Server-Instanz im Memory halten.
Best Practices:
  • Idempotency Key für kritische POSTs verlangen.
  • Key eindeutig pro Client/Mandant scopen.
  • Request-Fingerprint speichern.
  • Ergebnis oder Ergebnisreferenz speichern.
  • Datenbank-Unique-Constraint nutzen.
  • Atomar mit fachlicher Operation verbinden.
  • TTL und Cleanup definieren.
  • Wiederholungsfälle testen.
Stolperfalle:

Ein In-Memory-Idempotenzcache funktioniert nicht zuverlässig bei mehreren Instanzen, Restarts oder Crashs.

Merksatz:

Retries sind unvermeidbar; Idempotenz macht sie sicher.

B3-K07-Q006 – Pagination, Filtering, Sorting und große DatenmengenJeder Listenendpoint braucht eine Bremse.
Frage:

„Wie gestalten Sie REST-Endpoints für große Listen oder Suchergebnisse?“

Kurz erklärt:

REST-Listenendpunkte brauchen Limits. Pagination, Filtering und Sorting sind API- und Performance-Themen, nicht nur UI-Komfort.

Musterantwort:

Ich vermeide unlimitierte Listen. Jeder Listenendpoint braucht eine maximale Page Size, stabile Sortierung, klare Filterparameter und dokumentierte Semantik. Für normale UI-Listen reicht oft Offset Pagination. Für sehr große Datenmengen, hohe Seitenzahlen oder Event-Streams ist Cursor- oder Keyset Pagination besser.

Außerdem whitelist ich Sortierfelder. Clients dürfen nicht beliebige Entity-Properties als Sortfeld durchreichen, weil das zu schlechten Query-Plänen, Fehlern oder Datenleaks führen kann.

Senior-Antwort:

„Unlimitierte Listen sind Produktionsrisiko. Pagination schützt Datenbank, Service, Netzwerk und Clients.“

Häufige Fehler:
  • Unlimitierte GET /items .
  • size ohne Maximum.
  • Beliebige Sortfelder erlauben.
  • Count-Query-Kosten ignorieren.
  • Instabile Sortierung ohne Tie-Breaker.
  • Export über riesige Page Size lösen.
Best Practices:
  • Max Page Size erzwingen.
  • Sortfelder whitelisten.
  • Stabile Sortierung definieren.
  • Slice oder Cursor verwenden, wenn Count unnötig ist.
  • Filterparameter dokumentieren.
  • Exporte als separaten Use Case modellieren.
  • Query-Pläne für häufige Filter prüfen.
Stolperfalle:

Ein paginierter Endpoint ist nicht sicher, wenn der Client size=1000000 setzen kann.

Merksatz:

Jeder Listenendpoint braucht eine Bremse.

B3-K07-Q007 – Caching, ETags und Conditional RequestsCaching beschleunigt nur sicher, wenn Semantik und Sensibilität stimmen.
Frage:

„Wie nutzen Sie HTTP-Caching in REST-APIs?“

Kurz erklärt:

HTTP-Caching kann Latenz, Bandbreite und Serverlast reduzieren. ETags ermöglichen Conditional Requests und 304 Not Modified .

Musterantwort:

Ich nutze HTTP-Caching bewusst für Ressourcen, die cachebar sind: Produktdaten, Referenzdaten, öffentliche Konfiguration oder statische Ressourcen. Über Cache-Control definiere ich Freshness und Sichtbarkeit, etwa public/private und max-age. Über ETags oder Last-Modified ermögliche ich Revalidierung.

Für personenbezogene oder sicherheitskritische Daten bin ich vorsichtig: Dort nutze ich eher Cache-Control: no-store oder private Caches, je nach Anforderung. Caching ist ein Vertrag mit Browsern, Proxies, CDNs und Clients.

Senior-Antwort:

„HTTP-Caching ist ein Vertrag mit Infrastruktur und Clients. Ich entscheide bewusst, was public, private, revalidierbar oder niemals speicherbar ist.“

Häufige Fehler:
  • Private Daten öffentlich cachebar machen.
  • ETag aus falschem Wert bilden.
  • Cache-Control gar nicht setzen.
  • Caching nur im Backend betrachten und CDN/Proxy vergessen.
  • no-cache und no-store verwechseln.
  • Conditional Requests nicht testen.
Best Practices:
  • Cachebarkeit pro Ressourcentyp definieren.
  • ETag oder Last-Modified für revalidierbare Ressourcen nutzen.
  • Personenbezogene Daten nicht öffentlich cachebar machen.
  • no-store für hochsensible Antworten.
  • CDN-/Proxy-Verhalten berücksichtigen.
  • 304-Pfade testen.
  • Cache-Hit-Ratio beobachten.
Stolperfalle:

Cache-Control: no-cache bedeutet nicht zwingend „nicht speichern“, sondern Revalidierung vor Nutzung. Für wirklich nicht speichern braucht man no-store .

Merksatz:

Caching beschleunigt nur sicher, wenn Semantik und Sensibilität stimmen.

B3-K07-Q008 – Versionierung, Kompatibilität und DeprecationAPI-Evolution beginnt mit Kompatibilität, nicht mit /v2 .
Frage:

„Wie versionieren und evolvieren Sie REST-APIs professionell?“

Kurz erklärt:

REST-APIs müssen kompatibel weiterentwickelt werden. Versionierung ist nur eine Maßnahme; bessere API-Evolution beginnt mit Kompatibilitätsregeln.

Musterantwort:

Ich versuche zuerst, rückwärtskompatibel zu evolvieren: optionale Felder hinzufügen, neue Endpoints ergänzen, alte Felder nicht umdeuten, bestehende Statuscodes nicht überraschend ändern und Fehlerformate stabil halten. Versionierung brauche ich, wenn ich inkompatible Änderungen nicht vermeiden kann.

Versionierung kann über URL, Header oder Media Type erfolgen. URL-Versionierung ist operativ einfach und sichtbar. Header- oder Media-Type-Versionierung ist HTTP-semantisch eleganter, aber schwerer zu debuggen und zu dokumentieren. Wichtig ist ein einheitlicher Standard mit Deprecation-, Sunset- und Migrationskommunikation.

Senior-Antwort:

„Versionierung ist teuer. Ich versioniere nicht jede Ergänzung, sondern definiere Kompatibilitätsregeln und versioniere bewusst bei echten Breaking Changes.“

Häufige Fehler:
  • Versionierung für jede kleine Ergänzung.
  • Felder umdeuten statt neue Felder hinzufügen.
  • Fehlerformat inkompatibel ändern.
  • Alte Versionen ohne Sunset-Plan behalten.
  • OpenAPI nicht aktuell halten.
  • API-Kompatibilität nicht automatisiert testen.
Best Practices:
  • Kompatibilitätsregeln definieren.
  • Neue optionale Felder bevorzugen.
  • Inkompatible Änderungen versionieren.
  • Deprecation und Sunset kommunizieren.
  • OpenAPI pro Version pflegen.
  • Consumer-driven Contract Tests nutzen.
  • API-Nutzung pro Version metrisch erfassen.
Stolperfalle:

Ein zusätzliches Feld ist nur dann kompatibel, wenn Clients unbekannte Felder tolerieren.

Merksatz:

API-Evolution beginnt mit Kompatibilität, nicht mit /v2 .

B3-K07-Q009 – REST-Clients, Timeouts und ResilienzOutbound HTTP ist ein verteiltes System, kein Methodenaufruf.
Frage:

„Wie gestalten Sie robuste REST-Clients in Spring?“

Kurz erklärt:

REST-Clients sind Teil der Systemarchitektur. Timeouts, Retries, Circuit Breaker, Error Mapping, Observability und Idempotenz entscheiden über Stabilität.

Musterantwort:

Ein robuster REST-Client hat explizite Connect-, Read- und Response-Timeouts, begrenzte Retries, Circuit Breaker oder Bulkheads bei Bedarf, klare Fehlerabbildung, Correlation IDs, Metriken und Logging ohne sensible Daten. Er unterscheidet 4xx und 5xx, behandelt Timeouts anders als fachliche Ablehnungen und retryt nur idempotente oder idempotent gemachte Operationen.

In modernem Spring kann ich RestClient für synchrone HTTP-Calls verwenden. Für deklarative Clients können HTTP Interfaces sinnvoll sein. Die Wahl ist weniger wichtig als die Resilienzregeln.

Senior-Antwort:

„Ein REST-Client ohne Timeout ist ein Produktionsrisiko. Ein REST-Client mit blindem Retry ist ein Ausfallverstärker.“

Häufige Fehler:
  • Keine Timeouts konfigurieren.
  • Unbegrenzte Retries.
  • Nicht-idempotente POSTs retryen.
  • 4xx und 5xx gleich behandeln.
  • Downstream-Fehler als interne 500 ohne Kontext mappen.
  • REST-Client direkt im Controller bauen.
Best Practices:
  • Timeouts pro Client definieren.
  • Retries begrenzen und nur bei geeigneter Semantik.
  • Circuit Breaker/Bulkhead für kritische Downstreams.
  • Fehler in fachliche Exceptions mappen.
  • Correlation IDs propagieren.
  • Metriken pro Downstream erfassen.
  • Clients als Beans konfigurieren und testen.
Stolperfalle:

Retries machen ein System nicht automatisch robuster. Ohne Idempotenz, Backoff und Budget können sie den Downstream endgültig überlasten.

Merksatz:

Outbound HTTP ist ein verteiltes System, kein Methodenaufruf.

B3-K07-Q010 – OpenAPI, Contract Testing und API GovernanceEin REST-Vertrag muss lesbar, maschinenprüfbar und regressionssicher sein. 24h
Frage:

„Welche Rolle spielen OpenAPI und Contract Testing in professionellen REST-APIs?“

Kurz erklärt:

OpenAPI beschreibt HTTP APIs maschinenlesbar. Contract Tests stellen sicher, dass Implementierung und Vertrag nicht auseinanderlaufen.

Musterantwort:

OpenAPI ist der maschinenlesbare API-Vertrag. Er beschreibt Pfade, Methoden, Parameter, Request/Response-Schemas, Statuscodes, Fehler und Security-Anforderungen. Das hilft Dokumentation, Client-Generierung, Mocking, Validierung und Governance.

Contract Testing prüft, ob Provider und Consumer dieselbe Erwartung teilen. Besonders bei Microservices reicht es nicht, dass ein Controller-Test grün ist. Ich will wissen, ob die tatsächliche API mit dem veröffentlichten Vertrag kompatibel ist und ob Änderungen Consumer brechen.

Senior-Antwort:

„OpenAPI ist kein Deko-Swagger. Es ist der Vertrag zwischen Provider, Consumer, Tests und Governance.“

Häufige Fehler:
  • OpenAPI als hübsche Doku, aber nicht als Vertrag behandeln.
  • Fehlerantworten nicht dokumentieren.
  • Statuscodes unvollständig dokumentieren.
  • Code und Spezifikation auseinanderlaufen lassen.
  • Generierte Clients blind verwenden.
  • Keine Breaking-Change-Prüfung.
Best Practices:
  • OpenAPI als versionierten Vertrag speichern.
  • Fehlerformate und Security dokumentieren.
  • Breaking Changes automatisiert prüfen.
  • Consumer-driven Contracts für kritische Integrationen.
  • Beispiele für Requests und Responses pflegen.
  • API Review vor Veröffentlichung.
  • Vertrag und Implementierung in CI vergleichen.
Stolperfalle:

Eine automatisch generierte Spezifikation ist nur so gut wie die Controller- und DTO-Semantik, aus der sie erzeugt wird.

Merksatz:

Ein REST-Vertrag muss lesbar, maschinenprüfbar und regressionssicher sein.

B3-K08 – Kapitel 8: Testing 10 Karten

B3-K08-Q001 – Teststrategie, Testpyramide und RisikoabdeckungTeste so klein wie möglich und so realistisch wie nötig. 24h
Frage:

„Wie würden Sie eine Teststrategie für eine Spring-Boot-Anwendung auf Senior-Level aufbauen?“

Kurz erklärt:

Eine gute Teststrategie wählt die kleinste sinnvolle Testebene. Nicht jede Logik braucht Spring Context, Datenbank oder HTTP-Server.

Musterantwort:

Ich baue eine Teststrategie risikobasiert auf. Fachlogik teste ich als schnelle Unit-Tests ohne Spring. Webverträge teste ich mit MVC-Slice-Tests oder MockMvc. Persistenz teste ich mit Repository-Tests und realitätsnaher Datenbank, häufig Testcontainers. Sicherheitsregeln teste ich explizit mit 401-, 403- und Erfolgsfällen. End-to-End-Tests setze ich sparsam für kritische Happy Paths ein.

Wichtig ist die Balance: Zu viele reine Unit-Tests geben wenig Integrationsvertrauen. Zu viele @SpringBootTest s machen die Suite langsam und fragil. Senior-Testing bedeutet, bewusst zu entscheiden, welche Testart welches Risiko abdeckt.

Senior-Antwort:

„Ich starte Spring nur, wenn ich Spring testen will. Fachlogik teste ich als Java-Code; Integration teste ich dort, wo Infrastrukturverhalten wirklich relevant ist.“

Häufige Fehler:
  • Jeden Test mit Spring Context starten.
  • Fachlogik nur über Controller testen.
  • Integrationstests ohne echte Infrastruktur schreiben.
  • Nur Happy Paths testen.
  • Security- und Fehlerfälle vergessen.
  • Testdaten unkontrolliert zwischen Tests teilen.
Best Practices:
  • Fachlogik ohne Spring testen.
  • Test-Slices für Web, JPA, JSON und Security nutzen.
  • @SpringBootTest gezielt einsetzen.
  • Negative Fälle explizit testen.
  • Testdaten isolieren.
  • Context-Varianten reduzieren.
  • Kritische Integrationen realitätsnah testen.
  • Testpyramide regelmäßig überprüfen.
Stolperfalle:

Eine hohe Testanzahl bedeutet nicht automatisch hohe Testqualität. Entscheidend ist, welche Risiken abgedeckt werden.

Merksatz:

Teste so klein wie möglich und so realistisch wie nötig.

B3-K08-Q002 – Unit-Tests ohne Spring ContextUnit-Tests prüfen Fachlogik, nicht Framework-Verdrahtung.
Frage:

„Warum sollten viele Spring-Anwendungstests gar keinen Spring Context starten?“

Kurz erklärt:

Unit-Tests testen eine kleine fachliche Einheit ohne Spring Container. Constructor Injection und klare Ports machen das einfach.

Musterantwort:

Weil Fachlogik nicht vom Container abhängen sollte. Wenn eine Klasse über Konstruktoren klare Abhängigkeiten bekommt, kann ich sie direkt instanziieren und testen. Das ist schneller, stabiler und zeigt Designprobleme früh.

Wenn ein einfacher Preisrechner oder Use Case nur mit @SpringBootTest testbar ist, ist die Klasse wahrscheinlich zu stark an Framework, globale Konfiguration oder Infrastruktur gekoppelt. Unit-Tests sind nicht weniger wert, nur weil sie kein Spring verwenden. Sie sind der schnellste Feedback-Kanal.

Senior-Antwort:

„Spring ist ein Integrationsframework. Meine Fachlogik sollte nicht Spring brauchen, um getestet zu werden.“

Häufige Fehler:
  • Spring Context für reine Fachlogik starten.
  • Zu viele Mocks für schlechtes Design verwenden.
  • Private Methoden direkt testen wollen.
  • Implementation Details statt Verhalten testen.
  • Framework-Annotationen mit Geschäftslogik vermischen.
Best Practices:
  • Fachlogik frameworkfrei halten.
  • Constructor Injection verwenden.
  • Ports als Interfaces modellieren.
  • Kleine Testdaten verwenden.
  • Verhalten statt Implementierung testen.
  • Edge Cases und Fehlerfälle abdecken.
  • Keine Spring-Annotation als Voraussetzung für reine Logik.
Stolperfalle:

Viele Mocks in einem Unit-Test sind oft ein Designgeruch: Die Klasse hat möglicherweise zu viele Verantwortlichkeiten.

Merksatz:

Unit-Tests prüfen Fachlogik, nicht Framework-Verdrahtung.

B3-K08-Q003 – @SpringBootTest und ApplicationContext-IntegrationFull-Context-Tests sind wertvoll, wenn sie Integration beweisen.
Frage:

„Wann ist @SpringBootTest die richtige Wahl, und wann nicht?“

Kurz erklärt:

@SpringBootTest lädt einen vollständigen Spring-Boot-ApplicationContext. Das ist sinnvoll für echte Integrationsprüfungen, aber teuer als Default.

Musterantwort:

@SpringBootTest ist richtig, wenn ich das Zusammenspiel der Boot-Anwendung testen will: Auto-Configuration, Configuration Properties, Bean-Verdrahtung, Transaktionen, Security, Actuator, echte Infrastruktur oder mehrere Schichten zusammen. Es ist nicht richtig als Standard für jede Fachlogik oder jeden Controller-Test.

Der große Vorteil ist Realismus. Der Preis ist Startzeit, Komplexität und potenziell fragilere Tests. Deshalb nutze ich @SpringBootTest gezielt für Integrationsszenarien, nicht als Ersatz für Unit- und Slice-Tests.

Senior-Antwort:

„ @SpringBootTest ist mein Integrationswerkzeug, nicht mein Hammer für alles.“

Häufige Fehler:
  • @SpringBootTest für jeden Test verwenden.
  • Test-Properties pro Testklasse unnötig variieren.
  • Viele Mock-Beans einsetzen und Context Caching zerstören.
  • Externe Systeme beim Context-Start unkontrolliert aufrufen.
  • Fachlogik nur über vollständige Integration testen.
Best Practices:
  • @SpringBootTest für echte Integrationsszenarien nutzen.
  • Context-Konfigurationen wiederverwenden.
  • Properties und Profiles standardisieren.
  • Test-Slices für fokussierte Tests bevorzugen.
  • Externe Abhängigkeiten kontrolliert ersetzen.
  • Context-Startzeit beobachten.
  • Full-Context-Tests sparsam und wertvoll halten.
Stolperfalle:

Ein Test mit vollem Spring Context wirkt realistisch, kann aber eine einfache fachliche Regel unnötig langsam und schwer diagnostizierbar machen.

Merksatz:

Full-Context-Tests sind wertvoll, wenn sie Integration beweisen.

B3-K08-Q004 – Test-Slices: @WebMvcTest, @DataJpaTest und fokussierte KontexteEin Slice-Test testet einen Ausschnitt, nicht die ganze Welt.
Frage:

„Was sind Spring Boot Test-Slices, und warum sind sie wichtig?“

Kurz erklärt:

Test-Slices laden nur einen fokussierten Teil der Anwendung, etwa MVC, JPA oder JSON. Dadurch sind sie schneller und zielgerichteter als Full-Context-Tests.

Musterantwort:

Test-Slices sind spezialisierte Testkontexte für einzelne Schichten oder technische Bereiche. @WebMvcTest lädt MVC-relevante Infrastruktur und Controller, aber nicht die ganze Anwendung. @DataJpaTest lädt JPA-relevante Infrastruktur und Repositories. Dadurch kann ich Web-Mapping, Validation, JSON oder Repository-Queries gezielt testen.

Der Vorteil ist Fokus: Wenn ein Web-Slice-Test fehlschlägt, liegt das Problem wahrscheinlich in der Webschicht, nicht in Kafka, Scheduler oder irgendeiner Auto-Configuration. Gleichzeitig sind Slices schneller als vollständige @SpringBootTest s.

Senior-Antwort:

„Test-Slices geben mir Fokus. Ich teste die Webschicht als Webschicht und die Persistenz als Persistenz, statt alles in jedem Test gleichzeitig zu starten.“

Häufige Fehler:
  • Slice-Tests erwarten vollständige Anwendung.
  • Fehlende Beans als Frameworkfehler interpretieren.
  • Zu viel Testkonfiguration in Slices laden.
  • Slices verwenden, obwohl Integration über mehrere Schichten getestet werden soll.
  • Security im Web-Slice unbewusst deaktivieren.
Best Practices:
  • Slice für klare Schichttests nutzen.
  • Abhängigkeiten bewusst ersetzen.
  • Web-Verträge mit @WebMvcTest testen.
  • Repository-Queries mit @DataJpaTest testen.
  • Full-Context-Tests für echte Integration behalten.
  • Testkonfigurationen wiederverwenden.
  • Slice-Grenzen im Team standardisieren.
Stolperfalle:

Ein Slice-Test ist kein kaputter Full-Context-Test. Fehlende Beans sind oft genau der Sinn des Slices.

Merksatz:

Ein Slice-Test testet einen Ausschnitt, nicht die ganze Welt.

B3-K08-Q005 – MockMvc, Controller-Verträge und FehlerfälleMockMvc testet HTTP-Verträge durch Spring MVC, aber ohne echten Socket.
Frage:

„Was testet MockMvc genau, und was testet es nicht?“

Kurz erklärt:

MockMvc testet Spring MVC ohne echten HTTP-Server. Es prüft Mapping, Binding, Validation, Message Conversion und Exception Handling über den MVC-Lifecycle.

Musterantwort:

MockMvc testet Spring MVC Request Handling über Mock-Request- und Mock-Response-Objekte. Es geht durch den MVC-Lifecycle mit DispatcherServlet , Handler Mapping, Argument Resolution, Validation, Message Conversion und Exception Handling. Damit ist es sehr gut für Controller-Verträge.

Es testet aber keinen echten Servlet Container, keine echten Netzwerkverbindungen, keine TLS-Schicht und nicht zwingend dieselbe Runtime wie ein End-to-End-Test mit laufendem Server. Für viele Web-Layer-Tests ist MockMvc perfekt, aber nicht für jedes Produktionsverhalten ausreichend.

Senior-Antwort:

„MockMvc ist mein Werkzeug für Webverträge: Mapping, Validation, JSON, Fehlerformat und Security-Verhalten ohne echten Server.“

Häufige Fehler:
  • Nur 200-Happy-Path testen.
  • Fehlerformat nicht prüfen.
  • Content-Type und Accept ignorieren.
  • Security-Filter nicht einbinden.
  • Standalone-Setup verwenden und globale ControllerAdvice vergessen.
  • JSON-Struktur zu locker prüfen.
Best Practices:
  • Status, Header und Body testen.
  • Validation- und Binding-Fehler testen.
  • Globales Exception Handling einbinden.
  • Security-Fälle integrieren.
  • Content-Type und Accept prüfen.
  • API-Verträge durch Tests stabilisieren.
  • Negative Fälle genauso ernst nehmen wie Happy Paths.
Stolperfalle:

Ein MockMvc-Test ohne echte MVC-Konfiguration kann globale Advice-, Converter- oder Security-Probleme übersehen.

Merksatz:

MockMvc testet HTTP-Verträge durch Spring MVC, aber ohne echten Socket.

B3-K08-Q006 – Repository-Tests, Datenbankrealismus und TestcontainersPersistenztests brauchen Persistenzrealität.
Frage:

„Warum verwenden Sie für Spring-Data-JPA-Tests häufig Testcontainers statt H2?“

Kurz erklärt:

Repository-Tests sollten Query, Mapping, Constraints und Datenbankverhalten realistisch prüfen. In-Memory-Datenbanken sind nicht immer ausreichend.

Musterantwort:

Weil JPA- und SQL-Verhalten datenbankspezifisch sein kann. PostgreSQL, MySQL, Oracle und H2 unterscheiden sich bei SQL-Dialekt, Constraints, JSON, Locking, Isolation, Indexnutzung, Datumsfunktionen, Pagination und Fehlercodes. Ein Test mit H2 kann grün sein, während Produktion fehlschlägt.

Testcontainers erlaubt, Tests gegen eine echte Datenbanktechnologie zu fahren. Das ist langsamer als ein reiner Unit-Test, aber für Repository- und Integrationsverhalten deutlich vertrauenswürdiger.

Senior-Antwort:

„Repository-Tests sollen nicht nur Java-Code testen, sondern Datenbankrealität: Dialekt, Constraints, Transaktionen und Mapping.“

Häufige Fehler:
  • H2 als Produktionsersatz betrachten.
  • Native Queries ohne echte Datenbank testen.
  • Datenbank-Migrationen nicht in Tests ausführen.
  • Testdaten zwischen Tests leaken lassen.
  • Container pro Testmethode neu starten.
  • Query-Pläne nie prüfen.
Best Practices:
  • Kritische Repository-Queries gegen echte DB testen.
  • Testcontainers für produktionsnahe Dialekte nutzen.
  • Migrationen in Integrationspfaden validieren.
  • Testdaten isoliert aufbauen.
  • Container-Lifecycle optimieren.
  • Constraints und Fehlerfälle testen.
  • Query Count und N+1 prüfen.
Stolperfalle:

H2-kompatible Tests können Produktionsfehler verstecken, besonders bei nativen Queries, Locking und Constraints.

Merksatz:

Persistenztests brauchen Persistenzrealität.

B3-K08-Q007 – Integrationstests mit externen Services und RestClientOutbound-Verträge verdienen eigene Tests.
Frage:

„Wie testen Sie REST-Clients in Spring-Anwendungen?“

Kurz erklärt:

Outbound HTTP-Clients müssen getestet werden: URL, Methode, Header, Body, Fehlerabbildung, Timeouts, Retries und Response-Mapping.

Musterantwort:

Ich teste REST-Clients an der Client-Grenze. Dabei prüfe ich, ob der Client den richtigen HTTP-Request erzeugt und Responses korrekt mapped. Für einfache Client-Tests kann ein Mock-Server sinnvoll sein. Für kritische Provider-Consumer-Beziehungen nutze ich Contract Tests. Für Resilienz teste ich Timeouts, 4xx, 5xx, leere Responses, ungültiges JSON und Retry-Verhalten.

Wichtig ist: Ich mocke nicht nur die Java-Methode fraudClient.score() , wenn ich eigentlich den HTTP-Vertrag testen will. Dann würde ich den entscheidenden Teil überspringen.

Senior-Antwort:

„Wenn ich einen REST-Client teste, teste ich nicht nur Java-Aufruflogik, sondern den HTTP-Vertrag an der Systemgrenze.“

Häufige Fehler:
  • REST-Client komplett mocken und HTTP-Vertrag nie testen.
  • Nur 200-Response testen.
  • Fehlerbody nicht prüfen.
  • Timeout-Verhalten nicht testen.
  • Retry-Verhalten nicht kontrollieren.
  • Provider-Vertrag nicht versionieren.
Best Practices:
  • Client-Grenzen separat testen.
  • Fehlerfälle explizit testen.
  • Contract Tests für kritische Integrationen.
  • Timeouts und Retries testbar konfigurieren.
  • Correlation Header prüfen.
  • Keine echten externen Systeme in normalen CI-Tests verwenden.
  • Testdaten und Beispielresponses versionieren.
Stolperfalle:

Ein gemockter Java-Client beweist nicht, dass URI, Header, Body, Statuscodes oder JSON-Vertrag stimmen.

Merksatz:

Outbound-Verträge verdienen eigene Tests.

B3-K08-Q008 – Spring Security TestsSecurity-Tests brauchen negative Beweise.
Frage:

„Wie testen Sie Spring Security so, dass Sie echte Sicherheitsregeln absichern?“

Kurz erklärt:

Security-Tests müssen erlaubte und verbotene Zugriffe prüfen. Besonders wichtig sind 401, 403, CSRF und Authority-Mapping.

Musterantwort:

Ich teste nicht nur den erfolgreichen Zugriff. Ich teste anonymen Zugriff, authentifizierten Zugriff ohne Berechtigung und Zugriff mit korrekter Berechtigung. Für CSRF-geschützte Methoden teste ich Requests mit und ohne CSRF-Token. Für JWT Resource Server teste ich Claims und Authorities so realistisch wie möglich.

Wichtig ist, dass die Spring-Security-Filterkette in MockMvc eingebunden ist. Sonst testet man nur Controllerlogik und übersieht genau die Security-Regeln.

Senior-Antwort:

„Security-Tests müssen beweisen, dass verbotene Zugriffe verboten bleiben. Nur erfolgreiche Zugriffe zu testen ist gefährlich.“

Häufige Fehler:
  • Nur Happy Path testen.
  • SecurityFilterChain nicht aktivieren.
  • Falsche Authorities simulieren.
  • CSRF in Tests deaktivieren, statt korrekt zu testen.
  • Rollen und Scopes verwechseln.
  • Method Security ohne Proxy testen.
Best Practices:
  • 401, 403 und Erfolg testen.
  • JWT-Claims realistisch simulieren.
  • CSRF explizit testen.
  • Security-Konfiguration nicht in Tests umgehen.
  • Method Security separat prüfen.
  • Kritische Endpunkte mit Regressionstests absichern.
  • Actuator- und Admin-Endpunkte testen.
Stolperfalle:

Ein Controller-Test ohne SecurityFilterChain kann grün sein, obwohl der produktive Endpoint offen oder falsch geschützt ist.

Merksatz:

Security-Tests brauchen negative Beweise.

B3-K08-Q009 – Testdaten, Transaktionen und IsolationGute Tests besitzen ihre Daten, statt sie zu erben.
Frage:

„Wie organisieren Sie Testdaten und Isolation in Spring-Integrationstests?“

Kurz erklärt:

Tests müssen isoliert sein. Jeder Test sollte seine eigenen Daten kontrollieren und nicht von Ausführungsreihenfolge oder Restzustand abhängen.

Musterantwort:

Ich baue Testdaten gezielt pro Test oder pro Testklasse auf, mit klaren Factory-Methoden oder Test Data Buildern. Tests dürfen nicht von der Reihenfolge abhängen. Für Datenbanktests nutze ich Transaktionsrollback, Datenbank-Cleanup, frische Schemas oder Container-Strategien je nach Testart.

Wichtig ist, realistische Testdaten zu haben, aber nicht zu viel. Große globale Fixtures machen Tests schwer verständlich. Besser sind kleine, aussagekräftige Daten pro Szenario.

Senior-Antwort:

„Testdaten sind Teil des Tests. Wenn ich nicht verstehe, welche Daten ein Test braucht, verstehe ich den Test nicht.“

Häufige Fehler:
  • Tests abhängig von Ausführungsreihenfolge.
  • Globale Testfixtures mit zu vielen Daten.
  • First-Level Cache statt Datenbankverhalten testen.
  • Rollback verdeckt Commit-Probleme.
  • Testdaten in Produktion-ähnlichen Namen kollidieren lassen.
  • Parallele Tests ohne Isolation schreiben.
Best Practices:
  • Pro Test nur notwendige Daten erstellen.
  • Test Data Builder nutzen.
  • Nach Persistenztests flush/clear verwenden.
  • Isolation explizit wählen.
  • Keine Reihenfolgeabhängigkeit.
  • Parallelausführung berücksichtigen.
  • Commit-relevante Logik separat testen.
Stolperfalle:

Ein grüner JPA-Test kann nur den Persistence Context geprüft haben, wenn nicht geflusht, gecleart und neu geladen wurde.

Merksatz:

Gute Tests besitzen ihre Daten, statt sie zu erben.

B3-K08-Q010 – Contract Tests, Regression und Test-GovernanceVerträge, die nicht geprüft werden, sind nur Dokumentation. 24h
Frage:

„Wie verhindern Sie, dass Änderungen an einer Spring-API Consumer brechen?“

Kurz erklärt:

Contract Tests und Governance sichern API-Verträge, Integrationsregeln und Regressionen über Team- und Servicegrenzen hinweg ab.

Musterantwort:

Ich behandle API-Verträge als versionierte Artefakte. OpenAPI beschreibt den HTTP-Vertrag. Contract Tests prüfen, ob Provider und Consumer kompatibel bleiben. Zusätzlich nutze ich API-Diff-Prüfungen, MockMvc-Vertragstests, Consumer-driven Contracts und CI-Gates für Breaking Changes.

Wichtig ist nicht nur Technik, sondern Governance: Teams brauchen Regeln, was eine breaking change ist, wie Deprecation funktioniert, wie lange alte Versionen unterstützt werden und wie Consumer informiert werden.

Senior-Antwort:

„API-Kompatibilität darf nicht von Erinnerung abhängen. Verträge, Diffs und Contract Tests müssen in die Pipeline.“

Häufige Fehler:
  • OpenAPI nur als Doku behandeln.
  • Required-Felder ohne Versionierung entfernen.
  • Fehlerverträge nicht testen.
  • Consumer-Annahmen ignorieren.
  • Contract Tests nur lokal ausführen.
  • Governance als manuelle Review-Checkliste ohne CI-Unterstützung.
Best Practices:
  • API-Verträge versionieren.
  • Breaking Changes automatisiert erkennen.
  • Required-Felder stabil halten.
  • Fehlerformate in Contracts aufnehmen.
  • Consumer-driven Contracts für kritische Abhängigkeiten.
  • Contract Tests in CI/CD integrieren.
  • Deprecation und Sunset dokumentieren.
  • API-Metriken pro Version erfassen.
Stolperfalle:

Ein grüner Provider-Test beweist nicht, dass reale Consumer nicht brechen.

Merksatz:

Verträge, die nicht geprüft werden, sind nur Dokumentation.

B3-K09 – Kapitel 9: Spring Cloud 10 Karten

B3-K09-Q001 – Distributed Systems Patterns und Spring Cloud RolleSpring Cloud ist Werkzeugkasten, nicht Architektur. 24h
Frage:

„Welche Probleme löst Spring Cloud, und welche nicht?“

Kurz erklärt:

Spring Cloud stellt Infrastrukturbausteine für verteilte Systeme bereit. Es ersetzt aber keine saubere Service-Architektur.

Musterantwort:

Spring Cloud hilft bei wiederkehrenden Infrastrukturproblemen verteilter Systeme: zentrale Konfiguration, Service Discovery, Client Load Balancing, Gateway-Routing, Circuit Breaker, Messaging, Contract Testing, Kubernetes-Integration und Secrets. Diese Themen sind in Microservice-Architekturen häufig und sollten nicht in jedem Team neu erfunden werden.

Spring Cloud löst aber nicht automatisch fachliche Kopplung, falsche Service-Grenzen, verteilte Transaktionsprobleme, fehlende Idempotenz, schlechte Datenmodelle oder unklare Ownership. Wenn Services falsch geschnitten sind, macht Spring Cloud sie nur besser erreichbar, aber nicht besser entworfen.

Senior-Antwort:

„Spring Cloud liefert Bausteine für verteilte Systeme. Es löst Infrastrukturmuster, aber keine schlechten fachlichen Servicegrenzen.“

Häufige Fehler:
  • Spring Cloud mit Microservice-Architektur verwechseln.
  • Zu viele Spring-Cloud-Komponenten ohne konkreten Bedarf einführen.
  • Infrastrukturtools als Ersatz für Service-Schnitt betrachten.
  • Fehlende Timeouts und Idempotenz ignorieren.
  • Config, Secrets und Feature Flags vermischen.
  • Gateway als Ort für Fachlogik missbrauchen.
Best Practices:
  • Spring Cloud gezielt pro Problem einsetzen.
  • Release Train und Spring-Boot-Version kompatibel halten.
  • Infrastrukturstandards zentral definieren.
  • Service-Grenzen fachlich entwerfen.
  • Timeouts, Retries und Circuit Breaker mit Budgets konfigurieren.
  • Observability von Anfang an einplanen.
  • Nicht jedes Pattern sofort einführen.
Stolperfalle:

Ein Microservice mit Spring Cloud ist nicht automatisch lose gekoppelt. Kopplung entsteht durch Daten, APIs, Events, Deployments und Ownership.

Merksatz:

Spring Cloud ist Werkzeugkasten, nicht Architektur.

B3-K09-Q002 – Spring Cloud Config und zentrale KonfigurationKonfiguration ist produktionsrelevanter Code ohne Compiler.
Frage:

„Wann ist Spring Cloud Config sinnvoll, und welche Risiken bringt zentrale Konfiguration mit?“

Kurz erklärt:

Spring Cloud Config zentralisiert externe Konfiguration für verteilte Systeme. Clients laden Properties über Spring Boot Config Data in ihr Environment.

Musterantwort:

Spring Cloud Config ist sinnvoll, wenn viele Services konsistente, versionierte und umgebungsspezifische Konfiguration brauchen. Ein Config Server kann Konfiguration zentral bereitstellen, häufig aus Git oder einem anderen Backend. Dadurch werden Änderungen auditierbarer, wiederholbarer und für viele Services konsistent.

Risiken entstehen durch zentrale Abhängigkeit, falsche Secrets-Behandlung, unvalidierte Konfiguration, fehlende Rollback-Strategie und Konfigurationsänderungen ohne Deployment-Pipeline. Konfiguration ist produktionskritisch. Deshalb brauche ich Validierung, Ownership, Versionierung, Review, Monitoring und klare Regeln, welche Werte dynamisch geändert werden dürfen.

Senior-Antwort:

„Zentrale Konfiguration ist mächtig, aber gefährlich. Ich behandle sie wie Code: versioniert, reviewed, validiert, beobachtet und rollbackfähig.“

Häufige Fehler:
  • Konfiguration nicht validieren.
  • Secrets wie normale Properties behandeln.
  • Config Server als Single Point of Failure ohne Betriebsmodell.
  • Kritische Werte dynamisch ändern, ohne Tests.
  • Unsichere Defaults erlauben.
  • Konfiguration ohne Ownership und Review ändern.
Best Practices:
  • @ConfigurationProperties mit Validierung nutzen.
  • Konfiguration versionieren und reviewen.
  • Secrets getrennt und sicher behandeln.
  • Fail-fast für kritische Konfiguration.
  • Defaults bewusst und dokumentiert setzen.
  • Konfigurationsänderungen observieren.
  • Rollback-Strategie definieren.
  • Config Server hochverfügbar betreiben, wenn produktionskritisch.
Stolperfalle:

Eine Konfigurationsänderung kann denselben Impact haben wie ein Code-Deployment, wird aber oft mit weniger Sorgfalt durchgeführt.

Merksatz:

Konfiguration ist produktionsrelevanter Code ohne Compiler.

B3-K09-Q003 – Service Discovery und Client-side Load BalancingDiscovery findet Instanzen; Architektur entscheidet, ob sie gebraucht wird.
Frage:

„Wie funktionieren Service Discovery und Load Balancing in Spring Cloud, und wann brauchen Sie sie noch in Kubernetes?“

Kurz erklärt:

Service Discovery erlaubt, Services über logische Namen statt feste Hostnamen zu finden. Client-side Load Balancing verteilt Aufrufe auf verfügbare Instanzen.

Musterantwort:

Service Discovery bedeutet, dass ein Client nicht hart auf host:port konfiguriert ist, sondern Service-Instanzen über eine Registry oder Plattform entdeckt. Spring Cloud bietet dafür Abstraktionen wie DiscoveryClient. Client-side Load Balancing wählt aus den gefundenen Instanzen eine Zielinstanz aus.

In Kubernetes ist häufig bereits ein Service-DNS- und Load-Balancing-Modell vorhanden. Dann braucht man nicht zwingend Eureka oder clientseitige Discovery. Spring Cloud Discovery kann trotzdem sinnvoll sein, wenn man plattformübergreifend arbeitet, besondere Routing-Logik braucht oder mit Consul/Eureka/Zookeeper integriert ist. Die Entscheidung hängt vom Deploymentmodell ab.

Senior-Antwort:

„Service Discovery löst Adressierung, nicht Resilienz. Ich brauche weiterhin Timeouts, Health, Circuit Breaker und klare API-Verträge.“

Häufige Fehler:
  • Eureka zusätzlich zu Kubernetes einführen, ohne Bedarf.
  • Service Discovery als Ersatz für API-Verträge sehen.
  • Hardcoded URLs in vielen Services verteilen.
  • Health-Status und Readiness ignorieren.
  • Load Balancing ohne Timeouts und Retries betrachten.
  • Sticky Sessions unbewusst erzeugen.
Best Practices:
  • Deploymentplattform zuerst verstehen.
  • In Kubernetes einfache DNS-/Service-Mechanismen bevorzugen, wenn ausreichend.
  • Discovery-Abstraktionen nur bei Bedarf einsetzen.
  • Timeouts und Circuit Breaker ergänzen.
  • Readiness und Health korrekt pflegen.
  • Service-Namen standardisieren.
  • Discovery nicht mit fachlicher Routinglogik überladen.
Stolperfalle:

Nur weil Spring Cloud Eureka kann, heißt das nicht, dass man Eureka in Kubernetes automatisch braucht.

Merksatz:

Discovery findet Instanzen; Architektur entscheidet, ob sie gebraucht wird.

B3-K09-Q004 – OpenFeign und deklarative Service ClientsDeklarativ heißt nicht lokal.
Frage:

„Wann verwenden Sie Spring Cloud OpenFeign, und worauf achten Sie?“

Kurz erklärt:

OpenFeign ermöglicht deklarative HTTP-Clients über Java-Interfaces. Das reduziert Boilerplate, ersetzt aber nicht Timeout-, Fehler- und Contract-Design.

Musterantwort:

OpenFeign ist sinnvoll, wenn ein Service viele HTTP-Clients hat und deklarative Interfaces die Lesbarkeit verbessern. Ein Interface beschreibt den HTTP-Vertrag, Spring Cloud erzeugt zur Laufzeit eine Implementierung.

Wichtig ist: Feign darf nicht dazu führen, dass Remote Calls wie lokale Methoden wirken. Ich konfiguriere Timeouts, ErrorDecoder, Logging, Retry-Policy, Correlation Headers, Security, Observability und Tests. Ich prüfe außerdem, ob ein expliziter RestClient für komplexe Fälle klarer ist.

Senior-Antwort:

„Feign reduziert HTTP-Boilerplate, aber ein Remote Call bleibt ein Remote Call: langsam, fehleranfällig und vertragsabhängig.“

Häufige Fehler:
  • Remote Calls wie lokale Methoden behandeln.
  • Keine Timeouts konfigurieren.
  • ErrorDecoder ignorieren.
  • Feign-Interfaces direkt als Domain-Port verwenden.
  • Fallbacks verwenden, die fachlich falsche Antworten liefern.
  • Sensitive Payloads in Feign Logs ausgeben.
Best Practices:
  • Feign-Interfaces klein und vertragsnah halten.
  • Timeouts pro Client konfigurieren.
  • ErrorDecoder für fachliche Fehlerabbildung nutzen.
  • RequestInterceptor für Correlation und Security.
  • Feign nicht in Domain-Core leaken lassen.
  • Contract Tests oder Mock-Server-Tests schreiben.
  • Logging in Produktion begrenzen.
Stolperfalle:

Ein Feign-Interface sieht wie ein lokales Java-Interface aus. Diese Optik darf nicht zu lokalem Fehlermodell und fehlenden Timeouts führen.

Merksatz:

Deklarativ heißt nicht lokal.

B3-K09-Q005 – Spring Cloud Gateway und API Gateway PatternsGateway für Cross-Cutting Concerns, Services für Fachlogik. 24h
Frage:

„Welche Aufgaben gehören in ein API Gateway, und welche nicht?“

Kurz erklärt:

Spring Cloud Gateway ist ein API-Gateway für Routing und Cross-Cutting Concerns wie Security, Monitoring, Resilience und Header-/Path-Manipulation.

Musterantwort:

Ein API Gateway eignet sich für technische Querschnittsthemen am Rand: Routing, TLS-Terminierung, Authentifizierungsvorprüfung, Rate Limits, Header-Normalisierung, Correlation IDs, Request-/Response-Transformation in engen Grenzen, Canary-Routing, Monitoring und zentrale Schutzmechanismen.

Nicht ins Gateway gehören komplexe Fachlogik, Transaktionen, Domain-Regeln oder tiefe Datenaggregation. Wenn das Gateway zu viel Fachlogik enthält, wird es ein verteilter Monolith und Single Point of Change.

Senior-Antwort:

„Ein Gateway ist ein Rand- und Routingbaustein. Wenn es Domain-Entscheidungen trifft, verschieben wir Kopplung nur an eine zentralere und gefährlichere Stelle.“

Häufige Fehler:
  • Fachlogik ins Gateway verschieben.
  • Gateway als Aggregationsmonolith nutzen.
  • Keine Rate Limits setzen.
  • Retries im Gateway blind aktivieren.
  • Sensitive Header ungeprüft weiterleiten.
  • Gateway-Ausfall nicht als Plattformrisiko betrachten.
Best Practices:
  • Gateway auf technische Cross-Cutting Concerns begrenzen.
  • Routen klar versionieren.
  • Rate Limits und Request Size Limits definieren.
  • Correlation und Observability integrieren.
  • Security-Kontext sauber weiterreichen.
  • Keine komplexen Domain-Entscheidungen im Gateway.
  • Gateway horizontal skalieren und überwachen.
Stolperfalle:

Ein Gateway kann schnell zum neuen Monolithen werden, wenn jedes Team dort Sonderlogik einbaut.

Merksatz:

Gateway für Cross-Cutting Concerns, Services für Fachlogik.

B3-K09-Q006 – Circuit Breaker, Bulkhead, Retry und Resilience4jResilience begrenzt Schaden, sie eliminiert ihn nicht.
Frage:

„Wie setzen Sie Circuit Breaker, Retry und Bulkhead in Spring Cloud sinnvoll ein?“

Kurz erklärt:

Resilience Patterns begrenzen Ausfallausbreitung. Circuit Breaker, Bulkhead und Retry haben unterschiedliche Aufgaben und dürfen nicht blind kombiniert werden.

Musterantwort:

Ein Circuit Breaker schützt vor wiederholten Aufrufen an einen fehlerhaften Downstream. Ein Bulkhead begrenzt parallele Aufrufe, damit ein langsamer Downstream nicht alle Ressourcen blockiert. Retries helfen bei temporären Fehlern, dürfen aber nur mit Timeout, Backoff, Budget und Idempotenz eingesetzt werden.

Ich konfiguriere diese Patterns pro Downstream und Use Case. Ein Payment-Call hat andere Regeln als eine optionale Empfehlung. Fallbacks müssen fachlich korrekt sein. Ein falscher Fallback ist gefährlicher als ein sichtbarer Fehler.

Senior-Antwort:

„Resilience ist nicht @Retry überall. Ich begrenze Zeit, Parallelität und Wiederholungen bewusst und nur dort, wo die Fachsemantik es erlaubt.“

Häufige Fehler:
  • Retries ohne Idempotenz.
  • Fallbacks, die fachlich falsche Erfolge liefern.
  • Circuit Breaker global statt pro Downstream konfigurieren.
  • Timeouts vergessen.
  • Resilience ohne Metriken.
  • Self-Invocation bei Annotationen ignorieren.
Best Practices:
  • Timeouts zuerst definieren.
  • Retries begrenzen und nur bei geeigneter Semantik.
  • Circuit Breaker pro Downstream und Use Case.
  • Bulkheads für kritische Ressourcen.
  • Fallbacks fachlich prüfen.
  • Metriken und Alerts konfigurieren.
  • Resilience-Tests für Fehlerfälle schreiben.
Stolperfalle:

Ein Fallback, der Erfolg vortäuscht, kann Daten korrumpieren. Nicht jeder Fehler sollte kaschiert werden.

Merksatz:

Resilience begrenzt Schaden, sie eliminiert ihn nicht.

B3-K09-Q007 – Spring Cloud Stream und eventgetriebene MicroservicesMessaging entkoppelt Zeit, nicht Verantwortung.
Frage:

„Wann verwenden Sie Spring Cloud Stream, und welche Eventing-Probleme löst es nicht automatisch?“

Kurz erklärt:

Spring Cloud Stream abstrahiert Messaging-Middleware über Binder. Anwendungen arbeiten mit Funktionen, Consumers und Suppliers statt direkt mit Broker-APIs.

Musterantwort:

Spring Cloud Stream ist sinnvoll, wenn Services message-driven arbeiten und man Kafka, RabbitMQ oder andere Broker über ein einheitliches Spring-Modell integrieren möchte. Es bietet Binder-Abstraktion, Consumer Groups, Partitionierung und Integration mit Spring Boot.

Es löst aber nicht automatisch Event-Design, Schema-Evolution, Idempotenz, Exactly-once-Illusionen, Outbox, Ordering, Replay, Dead Letter Handling oder fachliche Konsistenz. Diese Themen müssen architektonisch gelöst werden.

Senior-Antwort:

„Spring Cloud Stream abstrahiert Broker-Integration. Event-getriebene Korrektheit entsteht aber durch Idempotenz, Outbox, Schema-Design und Betriebsmodelle.“

Häufige Fehler:
  • Events als versteckte synchrone APIs nutzen.
  • Consumer nicht idempotent bauen.
  • Schema-Evolution ignorieren.
  • DLQ und Retry nicht konfigurieren.
  • Ordering-Annahmen ohne Partitionierungsmodell treffen.
  • Event-Publishing ohne Outbox an DB-Transaktion koppeln.
Best Practices:
  • Events fachlich klar modellieren.
  • Consumer idempotent machen.
  • Schema-Versionierung definieren.
  • Consumer Groups bewusst wählen.
  • Partitionierungskey fachlich auswählen.
  • DLQ, Retry und Monitoring konfigurieren.
  • Outbox für transaktionales Event-Publishing prüfen.
  • Lag und Fehlerquoten beobachten.
Stolperfalle:

Ein Event ist kein Methodenaufruf. Zustellung, Reihenfolge und Wiederholung müssen explizit behandelt werden.

Merksatz:

Messaging entkoppelt Zeit, nicht Verantwortung.

B3-K09-Q008 – Spring Cloud Contract und Consumer-driven ContractsVerteilte Systeme brauchen maschinenprüfbare Verträge.
Frage:

„Was ist Consumer-driven Contract Testing, und wann verwenden Sie Spring Cloud Contract?“

Kurz erklärt:

Spring Cloud Contract hilft, Provider- und Consumer-Erwartungen über HTTP- und Messaging-Verträge abzusichern.

Musterantwort:

Consumer-driven Contract Testing bedeutet, dass Consumer ihre Erwartungen an Provider maschinenlesbar ausdrücken. Der Provider testet gegen diese Verträge und veröffentlicht Stubs, damit Consumer gegen realistische Provider-Simulationen testen können.

Spring Cloud Contract ist sinnvoll, wenn Services unabhängig deployen und API- oder Message-Verträge stabil bleiben müssen. Es ersetzt nicht alle Integrationstests, reduziert aber das Risiko, dass ein Provider scheinbar intern korrekt ist, aber reale Consumer bricht.

Senior-Antwort:

„Contract Tests schließen die Lücke zwischen Unit-Test und End-to-End-Test. Sie prüfen nicht alles, aber genau den Integrationsvertrag.“

Häufige Fehler:
  • Contracts als vollständige Business-Tests missbrauchen.
  • Nur Happy Paths contracten.
  • Fehlerantworten nicht contracten.
  • Stubs nicht versionieren.
  • Consumer-Annahmen informell halten.
  • Contract Tests nicht in CI integrieren.
Best Practices:
  • Kritische Provider-Consumer-Verträge contracten.
  • Fehlerfälle und Statuscodes aufnehmen.
  • Contracts versionieren.
  • Stubs veröffentlichen.
  • Provider-Verifikation in CI.
  • Consumer-Tests gegen Stubs.
  • Contracts nicht zu implementierungsnah schreiben.
  • OpenAPI und Contract Tests kombinieren.
Stolperfalle:

Ein Provider kann alle eigenen Tests bestehen und trotzdem Consumer brechen. Contract Tests adressieren genau dieses Risiko.

Merksatz:

Verteilte Systeme brauchen maschinenprüfbare Verträge.

B3-K09-Q009 – Spring Cloud Kubernetes und Cloud-native IntegrationCloud-native heißt Plattformmechanismen bewusst nutzen, nicht alles im Framework duplizieren.
Frage:

„Welche Rolle spielt Spring Cloud Kubernetes in einer Kubernetes-basierten Spring-Plattform?“

Kurz erklärt:

Spring Cloud Kubernetes integriert Spring Boot mit Kubernetes-nativen Services wie ConfigMaps, Secrets und Discovery.

Musterantwort:

Spring Cloud Kubernetes kann Kubernetes-native Ressourcen in Spring Boot integrieren: ConfigMaps, Secrets, Service Discovery und weitere Plattforminformationen. Es ist hilfreich, wenn eine Spring-Anwendung Kubernetes-Umgebung bewusst als Property Source oder Discovery-Quelle nutzen soll.

Aber Kubernetes selbst bringt bereits Service Discovery, ConfigMaps, Secrets, Probes und Deploymentmechanismen mit. Man sollte nicht jedes Spring-Cloud-Pattern zusätzlich einführen, wenn Kubernetes es bereits einfacher und stabiler löst. Die Frage ist: Braucht die Anwendung diese Integration im Code, oder reicht Plattformkonfiguration?

Senior-Antwort:

„In Kubernetes muss ich nicht jedes Spring-Cloud-Pattern erneut aufbauen. Ich integriere dort, wo Spring davon profitiert, und nutze Plattformmechanismen dort, wo sie reichen.“

Häufige Fehler:
  • Kubernetes API-Rechte zu breit vergeben.
  • Secrets als normale Config behandeln.
  • ConfigMaps ohne Validierung nutzen.
  • Discovery doppelt über Kubernetes und Eureka betreiben.
  • Plattformmechanismen im Anwendungscode unnötig duplizieren.
  • Reload-Verhalten ohne Betriebsfreigabe aktivieren.
Best Practices:
  • Kubernetes-native Mechanismen zuerst verstehen.
  • ConfigMaps für nicht-sensitive Konfiguration.
  • Secrets sicher und minimal exponieren.
  • RBAC minimal halten.
  • Konfiguration in @ConfigurationProperties validieren.
  • Probes, Limits und Resources über Plattformstandards definieren.
  • Discovery nicht doppelt betreiben, wenn Kubernetes Services reichen.
Stolperfalle:

Mehr Infrastruktur bedeutet nicht automatisch mehr Cloud-Native. Manchmal ist ein Kubernetes Service einfacher und robuster als zusätzliche Discovery-Infrastruktur.

Merksatz:

Cloud-native heißt Plattformmechanismen bewusst nutzen, nicht alles im Framework duplizieren.

B3-K09-Q010 – Secrets, Vault, Bus und Betriebs-GovernanceSecrets brauchen Lifecycle, nicht nur Speicherort. 24h
Frage:

„Wie behandeln Sie Secrets und dynamische Konfiguration in einer Spring-Cloud-Plattform?“

Kurz erklärt:

Secrets und Runtime-Refresh sind Betriebsfragen. Spring Cloud Vault und Spring Cloud Bus können helfen, müssen aber mit Governance, Security und Observability kombiniert werden.

Musterantwort:

Secrets behandle ich getrennt von normaler Konfiguration. Sie gehören nicht in Git, nicht in Logs und nicht in frei zugängliche Actuator-Endpunkte. Für zentrale Secrets kann Spring Cloud Vault sinnvoll sein, weil Vault statische und dynamische Secrets verwalten kann. Wichtig sind Authentifizierung, Rotation, TTL, minimale Rechte und Monitoring.

Dynamische Konfigurationsänderungen über Spring Cloud Bus oder Refresh-Mechanismen nutze ich vorsichtig. Nicht jede Property darf zur Laufzeit geändert werden. Für kritische Werte ist ein kontrolliertes Deployment oft sicherer als ein stiller Runtime-Refresh.

Senior-Antwort:

„Secrets sind keine Properties mit anderem Namen. Sie brauchen eigene Speicherung, Rechte, Rotation, Maskierung und Betriebsprozesse.“

Häufige Fehler:
  • Secrets in Git oder ConfigMap speichern.
  • Secrets in Logs oder Actuator /env leaken.
  • Vault-Rechte zu breit vergeben.
  • Runtime-Refresh für nicht refresh-sichere Beans.
  • Secret-Rotation nicht testen.
  • Bus-Events ungesichert broadcasten.
Best Practices:
  • Secrets getrennt von normaler Konfiguration verwalten.
  • Minimalrechte und kurze TTLs nutzen.
  • Secret-Rotation automatisiert testen.
  • Sensitive Actuator-Endpunkte schließen.
  • Properties validieren.
  • Refresh nur für freigegebene Werte.
  • Bus-Events absichern und auditieren.
  • Fallbacks für Secret-System-Ausfälle definieren.
Stolperfalle:

Runtime-Refresh klingt bequem, kann aber gefährlich sein, wenn Beans, Connection Pools oder Security-Komponenten nicht refresh-sicher sind.

Merksatz:

Secrets brauchen Lifecycle, nicht nur Speicherort.

B4 – Band 4: Software Architecture

B4-K01 – Kapitel 1: SOLID 10 Karten

B4-K01-Q001 – Überblick und praktischer NutzenSOLID schützt Änderbarkeit, nicht das Ego des Architekten. 24h
Frage:

„Was bedeutet SOLID für Sie in einer realen Java-/Spring-Anwendung?“

Kurz erklärt:

SOLID hilft, Verantwortlichkeiten, Erweiterungspunkte, Vererbung, Schnittstellen und Abhängigkeiten bewusst zu gestalten.

Musterantwort:

SOLID ist für mich ein Satz von Designprinzipien, die Änderbarkeit verbessern. Single Responsibility hilft, Klassen nach Änderungsgründen zu schneiden. Open/Closed hilft, neue Varianten ohne Änderung stabiler Kernlogik einzuführen. Liskov verhindert gefährliche Vererbung. Interface Segregation hält Abhängigkeiten klein. Dependency Inversion sorgt dafür, dass Fachlogik von Abstraktionen abhängt, nicht von Frameworks, Datenbanken oder HTTP-Clients.

In einer Spring-Anwendung heißt das konkret: Use Cases orchestrieren fachliche Abläufe, Policies enthalten Regeln, Repositories und Clients sind Ports, Adapter implementieren Infrastruktur, Controller bleiben dünn, und Abhängigkeiten zeigen nach innen zur Fachlogik.

Senior-Antwort:

„SOLID bedeutet für mich nicht mehr Klassen, sondern bessere Änderungsgrenzen. Ich will, dass eine fachliche Regel geändert werden kann, ohne Controller, Datenbankadapter und HTTP-Clients anzufassen.“

Häufige Fehler:
  • SOLID nur auswendig lernen.
  • Für jede Klasse reflexartig ein Interface erstellen.
  • Fachlogik in Controller oder Repository verstecken.
  • Spring-Annotationen mit Architektur verwechseln.
  • Abstraktion ohne realen Änderungsgrund bauen.
  • Prinzipien dogmatisch statt pragmatisch anwenden.
Best Practices:
  • SOLID an Änderungsgründen ausrichten.
  • Use Cases, Policies, Ports und Adapter trennen.
  • Interfaces dort definieren, wo Abhängungsrichtung oder Varianten wichtig sind.
  • Fachlogik ohne Spring testbar halten.
  • Infrastrukturdetails am Rand halten.
  • Abstraktionen regelmäßig löschen, wenn sie keinen Nutzen haben.
  • Prinzipien als Designhilfe, nicht als Religion verwenden.
Stolperfalle:

SOLID ist kein Ziel an sich. Ein Design kann formal viele Interfaces haben und trotzdem schlecht verständlich, hoch gekoppelt und schwer wartbar sein.

Merksatz:

SOLID schützt Änderbarkeit, nicht das Ego des Architekten.

B4-K01-Q002 – Single Responsibility PrincipleEine Klasse sollte einem Änderungsgrund gehören, nicht fünf Teams.
Frage:

„Wie interpretieren Sie das Single Responsibility Principle in Enterprise-Code?“

Kurz erklärt:

Single Responsibility bedeutet: Eine Klasse sollte nur einen Grund haben, sich zu ändern. Es geht nicht um „nur eine Methode“, sondern um einen Änderungsgrund.

Musterantwort:

Ich interpretiere SRP über Änderungsgründe. Eine Klasse verletzt SRP, wenn sie sich ändern muss, weil sich fachliche Regeln, Persistenz, HTTP-Kommunikation, Formatierung, Security oder Messaging ändern. Ein Use Case darf orchestrieren, aber er sollte nicht selbst Steuerregeln berechnen, SQL bauen, PDFs formatieren und Events serialisieren.

Im Beispiel finalisiert der Use Case eine Rechnung. Berechnung, Steuerlogik, Nummerngenerator, Persistenz und Event-Publishing sind getrennte Verantwortlichkeiten. Dadurch kann ich Steuerlogik ändern, ohne Event-Publishing zu berühren.

Senior-Antwort:

„SRP frage ich nicht mit ‘Wie viele Methoden hat die Klasse?’, sondern mit ‘Welche Arten von Änderungen landen hier?’ Wenn Fachregel, Datenbank und Messaging dieselbe Klasse ändern, ist sie zu breit.“

Häufige Fehler:
  • SRP an Methodenzahl statt Änderungsgrund messen.
  • Fachliche und technische Verantwortungen mischen.
  • God Services mit hunderten Zeilen akzeptieren.
  • Jede private Hilfsmethode in eigene Klasse auslagern.
  • Klassen nach technischen Schichten statt fachlichen Verantwortungen benennen.
  • Orchestrierung und Entscheidung nicht trennen.
Best Practices:
  • Änderungsgründe explizit benennen.
  • Fachliche Policies aus Use Cases extrahieren.
  • Infrastruktur hinter Ports verstecken.
  • Klassen nach Verantwortung benennen.
  • Große Services inkrementell zerlegen.
  • Tests als Feedback für Verantwortlichkeiten nutzen.
  • Nicht jede kleine Berechnung sofort abstrahieren.
Stolperfalle:

Zu kleine Klassen mit schlechten Namen sind nicht automatisch besser. SRP braucht Kohäsion, nicht Fragmentierung.

Merksatz:

Eine Klasse sollte einem Änderungsgrund gehören, nicht fünf Teams.

B4-K01-Q003 – Open/Closed PrincipleErweiterbarkeit lohnt sich dort, wo Änderung wiederkehrt.
Frage:

„Wie wenden Sie das Open/Closed Principle an, ohne unnötige Abstraktion zu erzeugen?“

Kurz erklärt:

Open/Closed bedeutet: Softwareeinheiten sollten für Erweiterung offen, aber für Änderung geschlossen sein. Neue Varianten sollten möglichst durch neue Komponenten statt durch Änderung stabiler Kernlogik entstehen.

Musterantwort:

Ich wende OCP dort an, wo Varianten wahrscheinlich sind oder bereits existieren. Wenn Rabatte, Versandarten, Zahlungsanbieter oder Validierungsregeln regelmäßig erweitert werden, ist eine Strategie- oder Regelstruktur sinnvoll. Neue Regeln können hinzugefügt werden, ohne die Engine ständig zu ändern.

Ich abstrahiere aber nicht spekulativ jede if -Anweisung. Wenn es nur zwei stabile Fälle gibt, ist ein klares if oft besser als ein Plugin-System. OCP ist wertvoll, wenn Änderungshäufigkeit und Variantenvielfalt die Abstraktion rechtfertigen.

Senior-Antwort:

„OCP heißt nicht, nie Code zu ändern. Es heißt, stabile Kernlogik vor häufigen Variantenänderungen zu schützen.“

Häufige Fehler:
  • OCP dogmatisch anwenden.
  • Zu früh ein generisches Framework bauen.
  • Regelreihenfolge nicht definieren.
  • Neue Varianten doch wieder in zentraler switch -Logik einbauen.
  • Fachliche Konflikte zwischen Regeln nicht modellieren.
  • Tests pro Regel und Gesamtverhalten vergessen.
Best Practices:
  • OCP dort anwenden, wo Varianten real sind.
  • Strategy-Interfaces klein halten.
  • Regelreihenfolge explizit machen.
  • Konfliktregeln definieren.
  • Neue Varianten isoliert testen.
  • Kernlogik stabil halten.
  • Abstraktionen löschen, wenn Varianten ausbleiben.
Stolperfalle:

Ein Plugin-Design für eine Variante, die nie variiert, ist Overengineering.

Merksatz:

Erweiterbarkeit lohnt sich dort, wo Änderung wiederkehrt.

B4-K01-Q004 – Liskov Substitution PrincipleEin Subtyp darf den Vertrag nicht brechen, den der Basistyp verspricht.
Frage:

„Was ist das Liskov Substitution Principle, und wie erkennen Sie Verstöße?“

Kurz erklärt:

Liskov Substitution bedeutet: Ein Subtyp muss überall dort verwendbar sein, wo der Basistyp erwartet wird, ohne die Korrektheit des Programms zu verletzen.

Musterantwort:

LSP bedeutet, dass ein Subtyp den Vertrag des Basistyps erfüllen muss. Wenn eine Methode einen PaymentMethod erwartet, muss jede Implementierung sinnvoll nach dessen Vertrag funktionieren. Wenn ein Subtyp Methoden mit UnsupportedOperationException beantwortet, Vorbedingungen verschärft oder Nachbedingungen schwächt, ist das ein Warnsignal.

Im Beispiel ist BankTransferPaymentMethod problematisch, weil PaymentMethod eine authorize -Methode verspricht, Bank Transfer aber nicht autorisierbar ist. Ein besseres Design trennt Fähigkeiten: AuthorizablePaymentMethod , RefundablePaymentMethod , CapturablePaymentMethod . Dann implementiert ein Typ nur, was er wirklich kann.

Senior-Antwort:

„LSP schützt mich vor Abstraktionen, die mehr versprechen, als ihre Implementierungen halten können.“

Häufige Fehler:
  • Breite Basisklassen für fachlich unterschiedliche Typen.
  • Subtypen verschärfen Vorbedingungen.
  • Subtypen geben schwächere Garantien.
  • instanceof -Logik überall im Client-Code.
  • Vererbung zur Code-Wiederverwendung statt Vertragserweiterung.
  • Methoden implementieren, die fachlich nicht unterstützt werden.
Best Practices:
  • Vererbung nur bei echtem Substitutionsvertrag nutzen.
  • Fähigkeiten in kleinere Interfaces trennen.
  • Komposition für Wiederverwendung bevorzugen.
  • Contracts dokumentieren und testen.
  • UnsupportedOperationException in Domain-Interfaces vermeiden.
  • Sealed Types für geschlossene, wohldefinierte Hierarchien prüfen.
  • Client-Code ohne Subtypwissen ermöglichen.
Stolperfalle:

Eine gemeinsame Oberklasse ist nicht automatisch eine gute Abstraktion. Wenn Subtypen ständig Ausnahmen machen, ist der Basistyp falsch.

Merksatz:

Ein Subtyp darf den Vertrag nicht brechen, den der Basistyp verspricht.

B4-K01-Q005 – Interface Segregation PrincipleClients sollten nur wissen, was sie wirklich brauchen.
Frage:

„Wie wenden Sie das Interface Segregation Principle in Spring-Anwendungen an?“

Kurz erklärt:

Interface Segregation bedeutet: Clients sollten nicht von Methoden abhängen, die sie nicht brauchen. Kleine, rollenbezogene Interfaces sind oft besser als breite Alleskönner-Interfaces.

Musterantwort:

Ich schneide Interfaces nach Client-Bedarf und Rollen. Ein Service, der nur E-Mails senden muss, sollte nicht von einem NotificationService abhängen, der zusätzlich SMS, Push, Audit, Templates, Retry, Metrics und Admin-Funktionen anbietet. Kleine Interfaces machen Tests einfacher und reduzieren Kopplung.

ISP bedeutet aber nicht, jedes Interface künstlich maximal klein zu machen. Es bedeutet, dass ein Client nur die Abhängigkeiten sieht, die er tatsächlich benötigt.

Senior-Antwort:

„Ein gutes Interface beschreibt, was dieser Client braucht, nicht alles, was eine Implementierung theoretisch kann.“

Häufige Fehler:
  • Fat Interfaces definieren.
  • Implementierungen mit leeren Methoden.
  • Clients hängen an Admin-/Infrastrukturmethoden.
  • Interfaces nach Implementierung statt Client-Bedarf schneiden.
  • Jedes Interface ohne fachlichen Namen künstlich auf eine Methode reduzieren.
  • Test-Mocks müssen unnötige Methoden stubben.
Best Practices:
  • Interfaces aus Client-Sicht schneiden.
  • Rollen statt Implementierungen benennen.
  • Große Interfaces in fachliche Capabilities teilen.
  • Implementierungen dürfen mehrere Rollen erfüllen.
  • Testbarkeit als Feedback nutzen.
  • Keine leeren Methoden erzwingen.
  • Interfaces nicht ohne Nutzen erzeugen.
Stolperfalle:

Ein Interface, das aus Sicht der Implementierung geschnitten ist, ist oft zu breit für seine Clients.

Merksatz:

Clients sollten nur wissen, was sie wirklich brauchen.

B4-K01-Q006 – Dependency Inversion PrincipleDie Fachlogik definiert, was sie braucht; Infrastruktur liefert, wie es geht.
Frage:

„Was bedeutet Dependency Inversion in einer Spring-/Hexagonal-Architecture-Anwendung?“

Kurz erklärt:

Dependency Inversion bedeutet: High-Level-Module sollen nicht von Low-Level-Details abhängen. Beide sollten von Abstraktionen abhängen.

Musterantwort:

Dependency Inversion bedeutet, dass mein Use Case nicht von JpaRepository , RestClient , Kafka oder Systemzeit abhängt, sondern von fachlich benannten Ports. Die Infrastruktur implementiert diese Ports. Dadurch zeigt die Abhängigkeitsrichtung zur Fachlogik.

In Spring wird das durch Dependency Injection praktisch umgesetzt, aber DI allein ist nicht DIP. Wenn mein Use Case direkt EntityManager und RestClient injiziert bekommt, nutze ich zwar DI, verletze aber möglicherweise DIP, weil die Fachlogik von Low-Level-Details abhängt.

Senior-Antwort:

„Dependency Injection ist die Mechanik. Dependency Inversion ist die Architekturentscheidung, welche Richtung Abhängigkeiten haben.“

Häufige Fehler:
  • DI mit DIP verwechseln.
  • Port-Interfaces im Infrastrukturmodul definieren.
  • Fachlogik direkt von Spring Data abhängig machen.
  • RestClient direkt in Use Cases verwenden.
  • Zu generische Ports wie DataProvider .
  • Infrastrukturdetails in Domain Exceptions leaken.
Best Practices:
  • Ports fachlich benennen.
  • Port-Interfaces nahe am Use Case definieren.
  • Adapter in Infrastrukturmodulen implementieren.
  • Domain und Application ohne Spring testbar halten.
  • Framework-Abhängigkeiten am Rand halten.
  • Nur dort abstrahieren, wo Austauschbarkeit oder Abhängigkeitsrichtung wichtig ist.
  • Ports klein und use-case-orientiert halten.
Stolperfalle:

Ein Interface im falschen Modul invertiert nichts. Wenn die Fachlogik weiterhin Infrastrukturkonzepte kennt, bleibt die Kopplung bestehen.

Merksatz:

Die Fachlogik definiert, was sie braucht; Infrastruktur liefert, wie es geht.

B4-K01-Q007 – Komposition statt VererbungVererbung modelliert Typbeziehungen; Komposition modelliert Verhalten.
Frage:

„Warum bevorzugen Senior-Entwickler häufig Komposition gegenüber Vererbung?“

Kurz erklärt:

Komposition ist oft flexibler als Vererbung. Verhalten wird aus kleinen Komponenten zusammengesetzt, statt über starre Klassenhierarchien vererbt.

Musterantwort:

Vererbung koppelt Subklassen stark an Basisklassen. Änderungen in der Basisklasse können viele Subklassen beeinflussen. Außerdem passt Vererbung nur gut, wenn eine echte „ist-ein“-Beziehung mit stabilem Vertrag existiert.

Komposition baut Verhalten aus austauschbaren Kollaboratoren. Im Shipping-Beispiel brauche ich keine Hierarchie aus ExpressInternationalVipHeavyShippingCalculator . Ich kombiniere Base Cost, Surcharges und Discounts. Das ist flexibler, testbarer und besser erweiterbar.

Senior-Antwort:

„Vererbung fixiert Beziehungen zur Compile-Zeit. Komposition erlaubt mir, Verhalten nach Use Case, Konfiguration oder Markt zusammenzustellen.“

Häufige Fehler:
  • Vererbung zur Code-Wiederverwendung missbrauchen.
  • Tiefe Klassenhierarchien bauen.
  • Basisklassen mit vielen Hooks und Flags.
  • Subklassen überschreiben Verhalten inkonsistent.
  • Komposition ohne klare Verantwortlichkeiten.
  • Zu generische Policy-Namen verwenden.
Best Practices:
  • Vererbung nur bei echtem Substitutionsvertrag.
  • Verhalten über kleine Policies komponieren.
  • Strategien fachlich benennen.
  • Reihenfolge von Policies bewusst machen.
  • Basisklassen schlank halten.
  • Tests pro Policy und Gesamtkomposition.
  • Vererbungshierarchien regelmäßig kritisch prüfen.
Stolperfalle:

Komposition ist nicht automatisch gut. Wenn niemand mehr versteht, welche Policies in welcher Reihenfolge laufen, ist das Design ebenfalls schlecht.

Merksatz:

Vererbung modelliert Typbeziehungen; Komposition modelliert Verhalten.

B4-K01-Q008 – SOLID mit Spring Dependency InjectionDependency Injection verdrahtet Design; sie ersetzt es nicht.
Frage:

„Wie hilft Spring DI bei SOLID, und wo kann es SOLID verschlechtern?“

Kurz erklärt:

Spring Dependency Injection kann SOLID unterstützen, wenn Beans nach Verantwortlichkeiten und Ports geschnitten sind. DI allein garantiert aber kein gutes Design.

Musterantwort:

Spring DI hilft, Abhängigkeiten explizit zu machen und Komponenten zu komponieren. Constructor Injection unterstützt DIP und Testbarkeit. Bean Collections unterstützen OCP, weil neue Strategien ergänzt werden können. Kleine Ports unterstützen ISP.

Spring kann SOLID aber auch verschlechtern, wenn man große Services mit zehn Abhängigkeiten baut, überall Framework-Annotationen verteilt, fachliche Klassen zu Spring Beans macht, globale Context-Zugriffe nutzt oder technische Auto-Configuration mit Architektur verwechselt. DI ist ein Werkzeug, kein Designersatz.

Senior-Antwort:

„Spring DI macht Abhängigkeiten injizierbar. Ob diese Abhängigkeiten sinnvoll geschnitten sind, ist weiterhin Architekturarbeit.“

Häufige Fehler:
  • Field Injection verwenden.
  • Service Locator über ApplicationContext .
  • Riesige Services mit vielen Abhängigkeiten.
  • Domain-Objekte als Spring Beans modellieren.
  • Framework-Annotationen in Domain Core streuen.
  • Bean-Listen ohne definierte Reihenfolge.
Best Practices:
  • Constructor Injection bevorzugen.
  • Abhängigkeiten explizit halten.
  • Fachliche Klassen frameworkfrei halten, wenn möglich.
  • Configuration Classes für Verdrahtung nutzen.
  • Bean Collections bewusst ordnen.
  • Große Dependency-Listen als Designgeruch betrachten.
  • Spring DI für Komposition, nicht als Architekturkrücke nutzen.
Stolperfalle:

Viele kleine Spring Beans sind nicht automatisch SOLID. Ohne klare Verantwortlichkeiten entsteht nur verteilter Spaghetti-Code.

Merksatz:

Dependency Injection verdrahtet Design; sie ersetzt es nicht.

B4-K01-Q009 – SOLID in modularer ArchitekturModularität ist SOLID auf größerer Flughöhe.
Frage:

„Wie übertragen Sie SOLID-Prinzipien auf Module oder Architekturgrenzen?“

Kurz erklärt:

SOLID gilt nicht nur für Klassen, sondern auch für Module. Module sollten klare Verantwortlichkeiten, stabile APIs und kontrollierte Abhängigkeiten haben.

Musterantwort:

SRP bedeutet auf Modulebene: Ein Modul hat eine fachliche Verantwortung und klare Ownership. OCP bedeutet: Neue Varianten können über interne Erweiterungspunkte ergänzt werden, ohne öffentliche Modulverträge ständig zu brechen. LSP betrifft austauschbare Adapter und Implementierungen. ISP bedeutet: Modul-APIs sind klein und clientorientiert. DIP bedeutet: innere Fachmodule hängen nicht von äußeren Infrastrukturmodulen ab.

In einem modularen Monolithen ist das besonders wichtig. Wenn jedes Modul direkt auf interne Klassen anderer Module zugreift, hat man zwar Packages, aber keine Architekturgrenzen.

Senior-Antwort:

„SOLID endet nicht an der Klassengrenze. Ein Modul kann genauso zu viele Gründe zur Änderung haben wie eine Klasse.“

Häufige Fehler:
  • Packages als Module bezeichnen, aber keine Grenzen erzwingen.
  • Direkte Zugriffe auf fremde Entities.
  • Öffentliche Klassen überall.
  • Gemeinsame Utility-Module als Kopplungsmüllhalde.
  • Spring Component Scanning über alle Module ohne Regeln.
  • Modul-APIs zu breit machen.
Best Practices:
  • Module nach fachlicher Verantwortung schneiden.
  • Öffentliche Modul-API klein halten.
  • Interne Klassen package-private machen, wo möglich.
  • Keine fremden Entities verwenden.
  • Abhängigkeiten in eine Richtung erzwingen.
  • Architekturtests einsetzen.
  • Gemeinsame Kernel-Typen sparsam halten.
  • Module mit eigener Teststrategie versehen.
Stolperfalle:

Ein Package ist keine Architekturgrenze, wenn jeder alles importieren darf.

Merksatz:

Modularität ist SOLID auf größerer Flughöhe.

B4-K01-Q010 – Trade-offs, Overengineering und Senior-UrteilSOLID ist ein Kompass, kein Autopilot. 24h
Frage:

„Kann man SOLID übertreiben? Wie treffen Sie pragmatische Designentscheidungen?“

Kurz erklärt:

SOLID-Prinzipien sind Entscheidungshilfen. Senior-Entwickler wenden sie kontextabhängig an und vermeiden sowohl Chaos als auch Overengineering.

Musterantwort:

Ja, man kann SOLID übertreiben. Wenn eine einfache, stabile Reporting-Funktion fünf Interfaces, drei Factories, zwei Strategies und ein Plugin-System bekommt, ist das vermutlich Overengineering. SOLID soll Änderungen erleichtern, nicht Code abstrakter machen.

Ich frage: Wie wahrscheinlich ist Änderung? Wie kritisch ist der Bereich? Wie viele Varianten gibt es? Wie teuer wäre spätere Änderung? Wie groß ist das Team? Wie lange lebt der Code? Bei stabiler, einfacher Logik bevorzuge ich Direktheit. Bei variantenreicher, geschäftskritischer Logik investiere ich früher in Abstraktion.

Senior-Antwort:

„SOLID ist kein Punktesystem. Ich abstrahiere dort, wo Änderung wahrscheinlich oder teuer ist, und bleibe direkt, wo Einfachheit langfristig günstiger ist.“

Häufige Fehler:
  • SOLID dogmatisch anwenden.
  • Für hypothetische Varianten abstrahieren.
  • Legacy-Chaos mit „Pragmatismus“ rechtfertigen.
  • Keine Refactoring-Zeit einplanen.
  • Einfachheit mit Nachlässigkeit verwechseln.
  • Designentscheidungen nicht dokumentieren.
Best Practices:
  • Abstraktion durch reale Änderungsgründe motivieren.
  • Einfach starten, aber refaktorierbar halten.
  • Tests als Sicherheitsnetz aufbauen.
  • Komplexität bewusst begründen.
  • Designentscheidungen dokumentieren.
  • Wiederkehrende Varianten abstrahieren.
  • Dogmatismus vermeiden.
  • Code regelmäßig auf tatsächliche Änderungspfade prüfen.
Stolperfalle:

Overengineering tarnt sich oft als Architekturqualität. Gute Architektur ist nicht maximal abstrakt, sondern angemessen.

Merksatz:

SOLID ist ein Kompass, kein Autopilot.

B4-K02 – Kapitel 2: Clean Code 10 Karten

B4-K02-Q001 – Aussagekräftige Namen und fachliche SpracheGute Namen sparen Erklärungen. 24h
Frage:

„Was macht einen guten Namen in Java-Enterprise-Code aus?“

Kurz erklärt:

Gute Namen reduzieren Erklärungsbedarf. Sie transportieren Fachlichkeit, Absicht und Verantwortlichkeit.

Musterantwort:

Ein guter Name beantwortet, was etwas fachlich bedeutet und welche Verantwortung es hat. PaymentAuthorizationDecisionService ist besser als PaymentService , wenn die Klasse genau Autorisierungsentscheidungen trifft. FraudRiskPolicy ist besser als Validator , wenn sie fachliche Risikoregeln auswertet. Gute Namen vermeiden Abkürzungen, technische Allgemeinplätze und Mehrdeutigkeiten.

Ich achte besonders auf die Ubiquitous Language der Domain. Namen sollten nicht nur für Entwickler verständlich sein, sondern auch mit Fachbegriffen übereinstimmen. Wenn Fachbereich, Tests, API und Code unterschiedliche Begriffe verwenden, entstehen Missverständnisse.

Senior-Antwort:

„Ein guter Name ist ein Vertrag mit dem Leser. Wenn ich eine Klasse öffnen muss, um ihre Verantwortung zu erraten, ist der Name wahrscheinlich zu schwach.“

Häufige Fehler:
  • Generische Namen wie Manager , Processor , Helper , Util .
  • Abkürzungen wie pmt , usr , cfg .
  • Methoden wie handle , execute , process ohne Kontext.
  • Fachliche Begriffe inkonsistent verwenden.
  • Klassen nach Technologie statt Verantwortung benennen.
  • DTO, Entity und Domain-Objekt gleich benennen, obwohl sie unterschiedliche Rollen haben.
Best Practices:
  • Namen aus der Fachsprache ableiten.
  • Verantwortung im Namen sichtbar machen.
  • Abkürzungen vermeiden.
  • Generische Suffixe kritisch prüfen.
  • Methoden als fachliche Aktionen benennen.
  • Typen nach Rolle benennen: Command , Policy , Port , Adapter , Event , View .
  • Namen in Reviews aktiv diskutieren.
Stolperfalle:

Kurze Namen sind nicht automatisch lesbarer. Ein präziser längerer Name ist besser als ein kurzer generischer Name.

Merksatz:

Gute Namen sparen Erklärungen.

B4-K02-Q002 – Kleine Funktionen, klare AbstraktionsebenenEine Funktion sollte ihren Zweck schneller zeigen als ihre Implementierung.
Frage:

„Was macht eine Funktion sauber und gut lesbar?“

Kurz erklärt:

Funktionen sollten eine klare Aufgabe auf einer Abstraktionsebene erfüllen. Lange Methoden sind nicht automatisch schlecht, aber gemischte Abstraktionsebenen sind fast immer problematisch.

Musterantwort:

Eine gute Funktion hat eine klare Absicht, wenige Verantwortlichkeiten und arbeitet auf einer konsistenten Abstraktionsebene. Im Checkout-Beispiel liest die öffentliche Methode wie ein fachlicher Ablauf: Order laden, prüfen, Bestand reservieren, Zahlung autorisieren, Order abschließen, speichern, Event publizieren.

Details wie Mapping von Order Lines zu Reservation Requests sind in private Methoden ausgelagert. Das Ziel ist nicht, jede Methode künstlich klein zu machen, sondern den Hauptfluss lesbar zu halten und Details an passende Stellen zu verschieben.

Senior-Antwort:

„Eine gute Use-Case-Methode liest sich wie der fachliche Ablauf. Technische Details dürfen vorhanden sein, aber sie sollten den Hauptfluss nicht verdecken.“

Häufige Fehler:
  • Methoden nach Zeilenzahl statt Verantwortung bewerten.
  • Fachlichen Ablauf und technische Details mischen.
  • Private Methoden ohne fachlichen Namen extrahieren.
  • Seiteneffekte in scheinbar harmlosen Methoden verstecken.
  • Framework-Proxies bei Selbstaufrufen ignorieren.
  • Eine Methode „clean“ machen, indem Logik nur verteilt wird.
Best Practices:
  • Hauptfluss zuerst lesbar machen.
  • Details in gut benannte Methoden oder Klassen auslagern.
  • Eine Methode sollte auf einer Abstraktionsebene bleiben.
  • Private Methoden nicht als Ersatz für fehlende Verantwortlichkeiten missbrauchen.
  • Bei wiederverwendbarer oder eigenständiger Fachlogik neue Klasse extrahieren.
  • Framework-Annotationen an Proxy-Grenzen beachten.
  • Tests nach Verhalten strukturieren.
Stolperfalle:

Viele kleine Methoden machen Code nicht automatisch sauber. Wenn die Namen schwach sind, muss der Leser nur häufiger springen.

Merksatz:

Eine Funktion sollte ihren Zweck schneller zeigen als ihre Implementierung.

B4-K02-Q003 – Seiteneffekte, Immutability und ZustandskontrolleZustand ist gefährlich, wenn niemand für ihn verantwortlich ist.
Frage:

„Wie gehen Sie mit Seiteneffekten und Mutable State in sauberem Java-Code um?“

Kurz erklärt:

Clean Code macht Seiteneffekte sichtbar und kontrolliert Zustand. Immutability reduziert unbeabsichtigte Änderungen.

Musterantwort:

Ich versuche, Seiteneffekte sichtbar an Use-Case-Grenzen zu halten: Datenbank speichern, Event publizieren, externe Systeme aufrufen. Fachliche Berechnungen und Value Objects halte ich möglichst unveränderlich. Mutable State ist nicht verboten, aber er muss kontrolliert sein.

Im Beispiel ist Money immutable. Account hat bewusst veränderbaren Zustand, weil es ein fachliches Aggregat ist. Änderungen passieren über Methoden wie debit , die Invarianten schützen. Der Use Case orchestriert Persistenz und Event-Publishing. Dadurch sind Seiteneffekte klar erkennbar.

Senior-Antwort:

„Mutable State ist erlaubt, aber nicht beliebig. Ein Aggregat darf Zustand ändern, wenn es dabei seine Invarianten schützt.“

Häufige Fehler:
  • Öffentliche Setter für jede Entity.
  • Mutable Collections direkt zurückgeben.
  • Value Objects veränderlich machen.
  • IO oder Datenbankzugriff in scheinbar reinen Methoden verstecken.
  • Globale statische Zustände verwenden.
  • Thread-Safety aus Immutability nicht verstehen.
Best Practices:
  • Value Objects immutable modellieren.
  • Collections defensiv kopieren.
  • Aggregatzustand über Methoden schützen.
  • Seiteneffekte an Use-Case- oder Adapter-Grenzen sichtbar machen.
  • Keine versteckten Remote Calls in Policies.
  • Mutable State fachlich begründen.
  • Hotpaths messen statt Immutability vorschnell vermeiden.
Stolperfalle:

final List<T> bedeutet nicht, dass die Liste unveränderlich ist. Es bedeutet nur, dass die Referenz nicht neu gesetzt wird.

Merksatz:

Zustand ist gefährlich, wenn niemand für ihn verantwortlich ist.

B4-K02-Q004 – Fehlerbehandlung, Exceptions und LesbarkeitFehlerbehandlung ist Teil des Designs, nicht der Restmüll des Codes.
Frage:

„Wie gestalten Sie Exception Handling sauber, ohne den Code unlesbar zu machen?“

Kurz erklärt:

Saubere Fehlerbehandlung trennt normalen Ablauf von Fehlerfällen, macht fachliche Fehler explizit und versteckt keine Fehler.

Musterantwort:

Ich unterscheide fachliche Fehler, technische Fehler und Programmierfehler. Fachliche Fehler bekommen sprechende Exceptions wie InsufficientFundsException oder AccountNotFoundException . Technische Fehler werden an Infrastrukturgrenzen gemappt oder gewrappt. Der Use Case sollte nicht überall generische Exception fangen.

Sauberer Code vermeidet Fehlercodes im normalen Rückgabewert, wenn ein Fehler den Ablauf abbricht. Gleichzeitig verwende ich Exceptions nicht für normale Alternativen in Hotpaths. Wichtig ist, Fehler dort zu behandeln, wo eine sinnvolle Entscheidung möglich ist, und sie sonst weiterzureichen.

Senior-Antwort:

„Eine Exception sollte dem nächsten Leser sagen, was fachlich oder technisch passiert ist und wo sie sinnvoll behandelt werden kann.“

Häufige Fehler:
  • catch (Exception) ohne sinnvolle Behandlung.
  • Exceptions verschlucken.
  • Fachliche Fehler als technische Fehler modellieren.
  • Technische Details an API-Clients leaken.
  • Exceptions für normale Iteration nutzen.
  • Doppelt loggen auf jeder Schicht.
Best Practices:
  • Fachliche Exceptions sprechend benennen.
  • Fehler an sinnvollen Grenzen behandeln.
  • Nicht überall loggen, sondern an klaren Boundarys.
  • Cause Chain erhalten.
  • Technische Details kapseln.
  • REST-Fehler zentral mappen.
  • Exceptions nicht als normale Verzweigung missbrauchen.
Stolperfalle:

catch (Exception e) { log.error(...); } ohne Re-Throw oder Recovery macht Fehler unsichtbar und Systeme inkonsistent.

Merksatz:

Fehlerbehandlung ist Teil des Designs, nicht der Restmüll des Codes.

B4-K02-Q005 – Null, Optional und API-VerträgeAbwesenheit ist ein Zustand, der benannt werden muss.
Frage:

„Wie gehen Sie in Java sauber mit null und Optional um?“

Kurz erklärt:

Clean Code macht Abwesenheit explizit. null sollte nicht zufällig durch das System wandern.

Musterantwort:

Ich vermeide null als impliziten Zustand. Wenn ein Wert optional ist, mache ich das im API-Vertrag sichtbar, meistens über Optional bei Rückgabetypen oder über klare DTO-Felder. Für Pflichtwerte nutze ich Konstruktorvalidierung, Objects.requireNonNull oder Bean Validation an API-Grenzen.

Optional ist besonders sinnvoll als Rückgabetyp, etwa bei Repository- oder Query-Methoden. Ich verwende es nicht als Feld in JPA-Entities und selten als Methodenparameter. Für Collections gebe ich leere Collections zurück, nicht null .

Senior-Antwort:

„ null ist kein Vertrag. Wenn etwas fehlen darf, soll der Code das sichtbar machen.“

Häufige Fehler:
  • null als „nicht gefunden“ zurückgeben.
  • Optional.get() ohne Prüfung.
  • Optional-Felder in JPA-Entities.
  • Leere Collections als null .
  • Nullprüfungen überall statt klarer Verträge.
  • Pflichtwerte erst tief im System validieren.
Best Practices:
  • Optional für möglicherweise fehlende Rückgabewerte.
  • Pflichtwerte früh validieren.
  • Leere Collections statt null .
  • Keine Optional.get() ohne Absicherung.
  • JPA-Entities nicht mit Optional-Feldern modellieren.
  • API-DTOs klar nach Pflicht und Optionalität definieren.
  • Nullability-Konventionen im Team festlegen.
Stolperfalle:

Optional löst keine schlechte Modellierung. Ein optionales Feld braucht trotzdem klare fachliche Bedeutung.

Merksatz:

Abwesenheit ist ein Zustand, der benannt werden muss.

B4-K02-Q006 – Architekturgrenzen, DTOs und MappingGrenzen brauchen Übersetzung.
Frage:

„Warum sollte man DTOs, Domain-Modelle und Entities nicht unkontrolliert vermischen?“

Kurz erklärt:

Sauberer Code respektiert Architekturgrenzen. API-DTOs, Commands, Domain-Objekte und Persistence-Entities haben unterschiedliche Rollen.

Musterantwort:

Weil sie unterschiedliche Änderungsgründe haben. Ein API-DTO ändert sich wegen Client-Verträgen. Ein Domain-Modell ändert sich wegen Fachregeln. Eine JPA-Entity ändert sich wegen Persistenzschema und ORM-Mapping. Wenn ich dasselbe Objekt für alles verwende, koppeln sich API, Domain und Datenbank.

Sauberer Code macht diese Grenzen sichtbar. Mapping ist zwar zusätzlicher Code, aber es schützt Verträge und verhindert Leaks: interne Felder, Lazy Loading, JPA-Anforderungen oder API-Kompatibilitätsprobleme.

Senior-Antwort:

„Mapping ist nicht nur Boilerplate. Es ist der Code, der Architekturgrenzen schützt.“

Häufige Fehler:
  • JPA-Entities direkt als API-Response.
  • Request-DTOs direkt persistieren.
  • Domain-Modelle mit Jackson-Annotationen überladen.
  • API-Kompatibilität durch Entity-Änderungen brechen.
  • Sensitive Felder versehentlich serialisieren.
  • Mapping ohne Tests bei kritischen Verträgen.
Best Practices:
  • DTOs an API-Verträgen ausrichten.
  • Commands an Use Cases ausrichten.
  • Domain-Modelle fachlich halten.
  • Persistence-Modelle nicht nach API formen.
  • Mapping explizit und testbar halten.
  • Sensitive Felder bewusst ausschließen.
  • Für komplexes Mapping Mapping-Komponenten nutzen.
Stolperfalle:

Ein gemeinsames Objekt für API, Domain und Datenbank spart heute Code und erzeugt morgen Kopplung.

Merksatz:

Grenzen brauchen Übersetzung.

B4-K02-Q007 – Testbarer Code und Dependency ManagementTestbarkeit ist ein Architekturmerkmal.
Frage:

„Woran erkennen Sie, dass Code schlecht testbar ist?“

Kurz erklärt:

Testbarer Code hat explizite Abhängigkeiten, kontrollierte Zeit, keine versteckten Singletons und klare Ports.

Musterantwort:

Schlecht testbarer Code hat versteckte Abhängigkeiten: statische Zugriffe, new RestClient() in Methoden, Systemzeit direkt im Code, globale Zustände, zufällige IDs ohne Abstraktion, Datenbankzugriff in Fachlogik und Framework-Abhängigkeiten überall. Tests brauchen dann Spring Context, echte Infrastruktur oder komplizierte Mocks.

Gut testbarer Code hat Constructor Injection, Ports, explizite Zeitabstraktion, kleine Verantwortlichkeiten und klare Rückgabewerte. Im Beispiel kann ich Repository, Payment, Clock und Event Publisher als Fakes einsetzen und den Use Case ohne Spring testen.

Senior-Antwort:

„Wenn ich Fachlogik nur mit einem laufenden Spring Context und drei externen Systemen testen kann, ist das kein Testproblem, sondern ein Designproblem.“

Häufige Fehler:
  • Instant.now() überall direkt verwenden.
  • Externe Clients in Methoden erzeugen.
  • Statische globale Konfiguration lesen.
  • Fachlogik nur über Spring Integration testbar machen.
  • Tests stark an Implementierungsdetails koppeln.
  • Zu viele Mocks als Symptom schlechter Verantwortlichkeit ignorieren.
Best Practices:
  • Constructor Injection nutzen.
  • Zeit über Clock oder Port abstrahieren.
  • Externe Systeme über Ports kapseln.
  • Fachlogik ohne Spring testbar halten.
  • Fakes für stabile fachliche Tests nutzen.
  • Mocks für Interaktionsgrenzen sparsam einsetzen.
  • Testbarkeit als Designkriterium in Reviews nutzen.
Stolperfalle:

Zu viele Mocks in einem Test bedeuten oft, dass die getestete Klasse zu viele Dinge koordiniert oder zu viele Details kennt.

Merksatz:

Testbarkeit ist ein Architekturmerkmal.

B4-K02-Q008 – Kommentare, Dokumentation und selbsterklärender CodeKommentare erklären das Warum; Code erklärt das Was.
Frage:

„Wann sind Kommentare in Clean Code sinnvoll, und wann sind sie ein Code Smell?“

Kurz erklärt:

Kommentare sollten erklären, warum etwas so ist, nicht wiederholen, was der Code bereits sagt.

Musterantwort:

Kommentare sind sinnvoll, wenn sie Kontext, Entscheidung, Risiko, regulatorische Gründe oder nicht offensichtliche Trade-offs erklären. Sie sind schlecht, wenn sie nur den Code paraphrasieren oder veraltete Informationen enthalten.

Wenn ich einen Kommentar brauche, um zu erklären, was eine Methode tut, sollte ich zuerst über Namen, Extraktion oder Modellierung nachdenken. Ein Kommentar wie // check amount ist wertlos. Ein Kommentar, der erklärt, warum ein regulatorischer Schwellwert gilt oder warum eine scheinbar einfache Optimierung nicht erlaubt ist, kann wertvoll sein.

Senior-Antwort:

„Ein Kommentar sollte mir nicht sagen, was der Code tut. Er sollte mir sagen, warum diese Lösung trotz Alternativen richtig ist.“

Häufige Fehler:
  • Kommentare wiederholen Code.
  • Veraltete Kommentare bleiben stehen.
  • TODOs ohne Ownership.
  • Fachliche Entscheidungen nur im Kopf des Entwicklers.
  • Öffentliche APIs ohne Vertrag.
  • Komplexe Workarounds ohne Erklärung.
Best Practices:
  • Code zuerst selbsterklärend machen.
  • Kommentare für Warum, Risiko und Kontext nutzen.
  • JavaDoc für öffentliche Verträge.
  • ADRs für Architekturentscheidungen.
  • TODOs mit Ticket oder klarer Ownership.
  • Veraltete Kommentare in Reviews entfernen.
  • Fachliche Schwellwerte dokumentieren.
Stolperfalle:

Ein guter Kommentar kann schlechten Code nicht dauerhaft retten. Erst refaktorieren, dann kommentieren, wenn noch Kontext fehlt.

Merksatz:

Kommentare erklären das Warum; Code erklärt das Was.

B4-K02-Q009 – Code Smells und RefactoringRefactoring ändert Struktur, nicht Verhalten.
Frage:

„Welche Code Smells erkennen Sie häufig in Java-/Spring-Projekten, und wie refaktorieren Sie sie?“

Kurz erklärt:

Code Smells sind Hinweise auf mögliche Designprobleme. Refactoring verbessert Struktur, ohne Verhalten zu ändern.

Musterantwort:

Häufige Smells sind God Services, lange Methoden, Primitive Obsession, Feature Envy, lange Parameterlisten, Shotgun Surgery, duplicated logic, breite Interfaces, zyklische Abhängigkeiten, anämische Domain-Modelle und technische Details in Fachlogik.

Ich refaktoriere nicht blind. Zuerst sichere ich Verhalten durch Tests. Dann extrahiere ich Value Objects, Policies, Ports, kleine Methoden oder Klassen. Bei häufigen Varianten ersetze ich switch / if -Ketten durch Strategien. Wichtig ist, kleine Schritte zu machen und Verhalten stabil zu halten.

Senior-Antwort:

„Ein Smell ist ein Hinweis, kein Urteil. Ich refaktoriere dort, wo Änderung, Fehler oder Verständnisprobleme tatsächliche Kosten erzeugen.“

Häufige Fehler:
  • Refactoring mit Rewrite verwechseln.
  • Ohne Tests große Änderungen machen.
  • Smells dogmatisch behandeln.
  • Nur Formatierung ändern und Designproblem lassen.
  • Neue Abstraktionen ohne realen Nutzen erzeugen.
  • Framework-Verhalten beim Verschieben von Code ignorieren.
Best Practices:
  • Verhalten zuerst mit Tests sichern.
  • Kleine Refactoring-Schritte.
  • Fachliche Value Objects extrahieren.
  • Varianten über Strategien modellieren.
  • Duplicates nach Bedeutung, nicht nur Textgleichheit bewerten.
  • Refactoring regelmäßig statt nur in Großprojekten.
  • Architekturtests für Abhängigkeitsregeln nutzen.
Stolperfalle:

Ein großer Rewrite fühlt sich wie Refactoring an, ist aber oft eine riskante Neuentwicklung ohne Verhaltensgarantie.

Merksatz:

Refactoring ändert Struktur, nicht Verhalten.

B4-K02-Q010 – Team-Standards, Reviews und nachhaltige CodequalitätClean Code ist Teamsport. 24h
Frage:

„Wie etablieren Sie Clean Code in einem Team oder einer Organisation?“

Kurz erklärt:

Clean Code ist nicht nur Individualleistung. Nachhaltige Codequalität braucht Team-Standards, Reviews, Automatisierung und gemeinsame Verantwortung.

Musterantwort:

Ich etabliere Clean Code nicht durch Geschmack, sondern durch gemeinsame Standards und Feedback-Schleifen: Coding Guidelines, Architekturprinzipien, Definition of Done, Code Reviews, Pairing, statische Analyse, Formatierung, Tests, Architekturtests und regelmäßiges Refactoring. Wichtig ist, Diskussionen von persönlichem Geschmack zu objektiven Kriterien zu verschieben.

Ein Senior oder Lead sollte nicht nur selbst sauberen Code schreiben, sondern die Umgebung schaffen, in der saubere Entscheidungen wahrscheinlicher werden: Templates, Beispiele, Review-Checklisten, Teststandards, Modulregeln und konstruktive Reviews.

Senior-Antwort:

„Clean Code skaliert nicht über Geschmack, sondern über gemeinsame Standards, gute Beispiele, Automatisierung und respektvolle Reviews.“

Häufige Fehler:
  • Clean Code als persönliche Meinung behandeln.
  • Reviews nur auf Stil statt Design fokussieren.
  • Keine automatisierte Formatierung.
  • Test- und Architekturregeln manuell prüfen.
  • Legacy-Code mit Neucode-Standards blockieren.
  • Quality Gates ohne Ausnahmen- und Migrationsstrategie.
Best Practices:
  • Standards gemeinsam definieren.
  • Formatierung automatisieren.
  • Reviews auf Design, Risiken und Verträge fokussieren.
  • Statische Analyse sinnvoll konfigurieren.
  • Architekturtests für wichtige Grenzen.
  • Definition of Done konkret machen.
  • Legacy-Code inkrementell verbessern.
  • Gute Beispiele im Codebase sichtbar machen.
Stolperfalle:

Ein Review, das nur Formatierung kritisiert, aber fehlende Transaktionsgrenzen, schlechte Namen oder fehlende Fehlerfälle übersieht, verbessert Codequalität kaum.

Merksatz:

Clean Code ist Teamsport.

B4-K03 – Kapitel 3: Design Patterns 10 Karten

B4-K03-Q001 – Zweck, Nutzen und Grenzen von Design PatternsPatterns sind Sprache für Designentscheidungen, keine Checkliste. 24h
Frage:

„Was sind Design Patterns, und woran erkennen Sie, ob ein Pattern sinnvoll eingesetzt wurde?“

Kurz erklärt:

Design Patterns geben bekannten Designproblemen eine gemeinsame Sprache. Sie sollen Code verständlicher, flexibler und wartbarer machen, nicht künstlich komplizierter.

Musterantwort:

Design Patterns sind bewährte Lösungsstrukturen für wiederkehrende Designprobleme. Sie helfen, Verantwortlichkeiten, Erweiterungspunkte und Abhängigkeiten sauber zu gestalten. Ein sinnvoll eingesetztes Pattern macht Code verständlicher oder änderbarer. Im Beispiel erlauben einzelne Autorisierungsregeln, neue Regeln hinzuzufügen, ohne eine zentrale if-else -Methode ständig zu ändern.

Ein Pattern ist schlecht eingesetzt, wenn es nur Komplexität erzeugt. Wenn eine einfache Berechnung plötzlich fünf Interfaces, Factories und abstrakte Basisklassen braucht, ohne reale Varianten oder Änderungsgründe, ist das Overengineering. Senior-Entwickler fragen deshalb immer: Welches Problem löst dieses Pattern konkret?

Senior-Antwort:

„Ein Pattern ist für mich kein Schmuckstück. Es ist dann gut, wenn es einen echten Änderungsdruck abfängt und den Code für das Team verständlicher macht.“

Häufige Fehler:
  • Patterns auswendig anwenden, ohne Problem zu verstehen.
  • Jede if -Anweisung durch Strategy ersetzen.
  • Pattern-Namen als Ersatz für Designbegründung verwenden.
  • Zu abstrakte Basisklassen bauen.
  • Framework-Patterns nicht erkennen.
  • Patterns dogmatisch statt pragmatisch nutzen.
Best Practices:
  • Pattern erst wählen, wenn das Problem klar ist.
  • Varianten, Änderungsgründe und Kopplung analysieren.
  • Pattern-Namen zur Kommunikation nutzen.
  • Einfachheit gegen Erweiterbarkeit abwägen.
  • Tests für Pattern-Komposition schreiben.
  • Patterns nicht in Domain-Sprache verstecken, sondern sinnvoll benennen.
  • Overengineering regelmäßig entfernen.
Stolperfalle:

Ein Pattern löst kein unklar geschnittenes Fachproblem. Schlechte Domain-Modellierung bleibt schlecht, auch wenn sie mit Strategy oder Factory implementiert wird.

Merksatz:

Patterns sind Sprache für Designentscheidungen, keine Checkliste.

B4-K03-Q002 – Strategy PatternStrategy kapselt Varianten, nicht Zufallskomplexität.
Frage:

„Wann verwenden Sie das Strategy Pattern in Java oder Spring?“

Kurz erklärt:

Strategy kapselt austauschbare Algorithmen hinter einem gemeinsamen Interface. Der Client wählt nicht den Algorithmus per großem if-else , sondern delegiert an passende Strategien.

Musterantwort:

Ich verwende Strategy, wenn ein Verhalten mehrere Varianten hat und diese Varianten unabhängig erweitert, getestet oder konfiguriert werden sollen. Typische Beispiele sind Gebührenberechnung, Rabattregeln, Exportformate, Zahlungsanbieter, Validierungsregeln, Routing-Entscheidungen oder Risk Policies.

In Spring ist Strategy besonders angenehm, weil ich alle Implementierungen eines Interfaces als List<PaymentFeeStrategy> injizieren kann. Neue Strategien können als neue Beans ergänzt werden, ohne den Calculator zu ändern. Wichtig ist aber, Auswahlregeln, Reihenfolge und Fehlverhalten klar zu definieren.

Senior-Antwort:

„Strategy ist sinnvoll, wenn Varianten fachlich stabil getrennt sind und häufig erweitert werden. Es ersetzt nicht jede Verzweigung, sondern schützt echte Erweiterungspunkte.“

Häufige Fehler:
  • Strategy für stabile Einzelfälle verwenden.
  • Auswahlreihenfolge nicht definieren.
  • Mehrere Strategien matchen unkontrolliert.
  • Strategy-Interface zu breit machen.
  • Strategien mit Infrastruktur-Details überladen.
  • Fehlerfall bei fehlender Strategie nicht behandeln.
Best Practices:
  • Strategy für reale Varianten verwenden.
  • Interface klein und fachlich halten.
  • Auswahl eindeutig machen.
  • Strategien einzeln testen.
  • Komposition zusätzlich testen.
  • Reihenfolge explizit definieren, wenn nötig.
  • Bei vielen Strategien Registry oder Map verwenden.
Stolperfalle:

Eine Strategy-Liste ohne eindeutige Auswahlregel ist nur ein verteilter if-else -Block.

Merksatz:

Strategy kapselt Varianten, nicht Zufallskomplexität.

B4-K03-Q003 – Factory und Abstract FactoryFactory kapselt gültige Erzeugung.
Frage:

„Wann verwenden Sie eine Factory statt direkt new aufzurufen?“

Kurz erklärt:

Factory kapselt komplexe Objekterzeugung. Sie ist sinnvoll, wenn Konstruktion fachliche Regeln, abhängige Werte, Validierung oder Varianten enthält.

Musterantwort:

Ich verwende eine Factory, wenn Objekterzeugung mehr ist als einfache Feldzuweisung. Wenn beim Erzeugen einer Rechnung Nummern generiert, Steuerregeln angewendet, IDs erzeugt, Defaults gesetzt und Invarianten geprüft werden, gehört diese Logik nicht verstreut in Controller oder Services.

Direktes new ist völlig okay für einfache Value Objects oder DTOs. Eine Factory ist sinnvoll, wenn Erzeugung fachlich relevant, wiederverwendbar, variantenabhängig oder schwer korrekt zu duplizieren ist.

Senior-Antwort:

„Ich nutze Factories dort, wo Erzeugung fachliche Bedeutung hat. Für ein simples Record brauche ich keine Factory, für eine gültige Rechnung oft schon.“

Häufige Fehler:
  • Factory für triviale DTO-Erzeugung.
  • Objekterzeugungslogik überall duplizieren.
  • Factory mit Persistenz mischen.
  • Factory mit Service Locator verwechseln.
  • Zu generische ObjectFactory -Klassen bauen.
  • Factory ohne fachlichen Namen.
Best Practices:
  • Factory für fachlich relevante Erzeugung verwenden.
  • Konstruktoren für einfache Objekte nutzen.
  • Invarianten in Factory oder Domain-Konstruktor sichern.
  • Factory nicht mit Repository vermischen.
  • Erzeugungsvarianten klar benennen.
  • Factory-Tests für wichtige Defaults und Regeln schreiben.
Stolperfalle:

Eine Factory, die gleichzeitig erzeugt, speichert, Events publiziert und externe Services aufruft, hat zu viele Verantwortlichkeiten.

Merksatz:

Factory kapselt gültige Erzeugung.

B4-K03-Q004 – Builder PatternBuilder macht komplexe Erzeugung lesbar, nicht automatisch korrekt.
Frage:

„Wann ist das Builder Pattern sinnvoll, und wann ist es unnötig?“

Kurz erklärt:

Builder hilft beim Erzeugen komplexer Objekte mit vielen optionalen Teilen, klaren Defaults und Validierung am Ende.

Musterantwort:

Builder ist sinnvoll, wenn ein Objekt viele optionale Felder, schrittweise Konstruktion oder komplexe Validierung hat. Ein Compliance Report mit mehreren Sections, Defaults und Pflichtfeldern passt gut.

Unnötig ist Builder für einfache Records mit wenigen Pflichtfeldern. Java Records machen viele einfache DTOs bereits gut lesbar. Ein Builder für jedes Objekt erzeugt Boilerplate und versteckt manchmal Pflichtfelder.

Senior-Antwort:

„Builder ist gut für komplexe Konstruktion. Für einfache Datenobjekte ist ein klarer Konstruktor oder Record oft sauberer.“

Häufige Fehler:
  • Builder für jedes triviale DTO.
  • Pflichtfelder nur optional im Builder setzen.
  • Validierung im Builder vergessen.
  • Mutable Collections aus fertigem Objekt zurückgeben.
  • Builder als Ersatz für Domain-Modellierung verwenden.
  • Lombok-Builder ohne Invariantenprüfung.
Best Practices:
  • Builder für komplexe Konstruktion verwenden.
  • Pflichtfelder im Builder-Einstieg erzwingen.
  • Fertiges Objekt immutable machen.
  • Im build() validieren.
  • Collections defensiv kopieren.
  • Für einfache Records Konstruktoren bevorzugen.
  • Builder-API fachlich lesbar gestalten.
Stolperfalle:

Ein Builder mit 20 optionalen Feldern kann ungültige Objekte leichter erzeugen als ein guter Konstruktor mit fachlichen Pflichtwerten.

Merksatz:

Builder macht komplexe Erzeugung lesbar, nicht automatisch korrekt.

B4-K03-Q005 – Adapter PatternAdapter schützen den Kern vor fremden Schnittstellen.
Frage:

„Wie nutzen Sie das Adapter Pattern in einer Spring-/Hexagonal-Architecture-Anwendung?“

Kurz erklärt:

Adapter übersetzt zwischen zwei Schnittstellen oder Modellen. In Hexagonal Architecture sind Adapter die Brücke zwischen Ports und externer Infrastruktur.

Musterantwort:

Ich nutze Adapter, um externe Systeme, Frameworks oder technische APIs von meiner Fachlogik zu entkoppeln. Der Use Case spricht mit einem Port wie FraudScoringPort . Der Adapter übersetzt mein internes Modell in das externe API-Modell und zurück.

Das schützt die Fachlogik vor externen Begriffen, Formaten, Fehlercodes und technischen Details. Wenn der Fraud-Anbieter sein JSON oder seine Codes ändert, sollte möglichst nur der Adapter betroffen sein.

Senior-Antwort:

„Adapter sind meine Übersetzer an den Systemgrenzen. Sie verhindern, dass externe Begriffe und technische Details meine Fachlogik kontaminieren.“

Häufige Fehler:
  • Externe DTOs in Domain-Code verwenden.
  • Provider-spezifische Fehlercodes durch die Anwendung leaken.
  • Adapter mit Fachentscheidung überladen.
  • Mapping ungetestet lassen.
  • Externe SDKs direkt in Use Cases injizieren.
  • Adapter ohne Timeouts und Observability bauen.
Best Practices:
  • Port fachlich benennen.
  • Adapter an Infrastrukturgrenzen platzieren.
  • Externe Modelle nicht in Domain leaken.
  • Fehler und Codes übersetzen.
  • Mapping testen.
  • Adapter mit Timeouts, Metrics und Correlation ausstatten.
  • Providerwechsel durch Adaptergrenze ermöglichen.
Stolperfalle:

Ein Adapter darf mappen und technische Fehler übersetzen, aber er sollte nicht heimlich zentrale Domain-Entscheidungen treffen.

Merksatz:

Adapter schützen den Kern vor fremden Schnittstellen.

B4-K03-Q006 – Decorator PatternDecorator erweitert Verhalten durch Umhüllen, nicht durch Aufblasen.
Frage:

„Wann ist Decorator sinnvoller als Vererbung oder Änderung der Originalklasse?“

Kurz erklärt:

Decorator fügt einem Objekt zusätzliche Verantwortung hinzu, ohne dessen Interface zu ändern. Mehrere Decorators können gestapelt werden.

Musterantwort:

Decorator ist sinnvoll, wenn ich Querschnittsverhalten hinzufügen möchte, ohne die Kernimplementierung zu verändern: Logging, Metriken, Retry, Caching, Tracing, Rate Limiting oder Security Checks. Die Kernklasse bleibt auf ihre eigentliche Aufgabe fokussiert, während Decorators Zusatzverhalten kapseln.

Im Beispiel sendet der eigentliche Sender Nachrichten. Ein Decorator misst Metriken, ein anderer wiederholt fehlgeschlagene Versuche. Das ist flexibler als eine Vererbungshierarchie aus RetryingObservedEmailSender .

Senior-Antwort:

„Decorator ist stark, wenn Zusatzverhalten orthogonal zur Kernaufgabe ist. Die Kernklasse bleibt einfach, und Zusatzverhalten bleibt kombinierbar.“

Häufige Fehler:
  • Decorator-Reihenfolge nicht definieren.
  • Decorator verändert Semantik unerwartet.
  • Zu viele Querschnittsthemen manuell dekorieren.
  • Retry-Decorator für nicht-idempotente Operationen verwenden.
  • Exceptions im Decorator verschlucken.
  • Decorator und Kernverantwortung vermischen.
Best Practices:
  • Decorator für Querschnittsverhalten verwenden.
  • Delegate klar benennen.
  • Reihenfolge dokumentieren und testen.
  • Semantik des Interfaces erhalten.
  • Retry nur bei geeigneter Fachsemantik.
  • AOP prüfen, wenn Verhalten wirklich querschnittlich ist.
  • Decorators einzeln und kombiniert testen.
Stolperfalle:

Retry als Decorator ist gefährlich, wenn die darunterliegende Operation nicht idempotent ist.

Merksatz:

Decorator erweitert Verhalten durch Umhüllen, nicht durch Aufblasen.

B4-K03-Q007 – Template Method PatternTemplate Method fixiert den Ablauf und variiert die Schritte.
Frage:

„Wann ist Template Method sinnvoll, und welche Risiken hat es?“

Kurz erklärt:

Template Method definiert einen festen Algorithmus in einer Basisklasse und lässt einzelne Schritte von Subklassen implementieren.

Musterantwort:

Template Method ist sinnvoll, wenn ein Ablauf stabil ist, aber einzelne Schritte variieren. Ein Importjob hat oft denselben Ablauf: Audit starten, Daten lesen, parsen, validieren, persistieren, Audit abschließen. Die Details variieren je nach Dateityp oder Quelle.

Das Risiko ist Vererbungskopplung. Subklassen hängen stark am Ablauf der Basisklasse. Wenn viele Hooks, Flags und Sonderfälle entstehen, wird Template Method schnell fragil. Dann sind Komposition oder Strategy oft besser.

Senior-Antwort:

„Template Method ist gut, wenn der Ablauf stabil ist. Wenn der Ablauf selbst variiert, wird Vererbung schnell zum Gefängnis.“

Häufige Fehler:
  • Zu viele Hook-Methoden.
  • Subklassen müssen Basisklasseninternas kennen.
  • Ablauf wird durch Flags unverständlich.
  • Template Method für instabile Prozesse verwenden.
  • Vererbung nur zur Code-Wiederverwendung nutzen.
  • Private Zustände der Basisklasse zu komplex machen.
Best Practices:
  • Template nur für wirklich stabilen Ablauf verwenden.
  • Template-Methode final machen.
  • Hooks klein und klar benennen.
  • Subklassen nicht mit Interna belasten.
  • Komposition prüfen, wenn Varianten wachsen.
  • Tests für gemeinsamen Ablauf und konkrete Varianten schreiben.
  • Vererbungshierarchie flach halten.
Stolperfalle:

Viele optionale Hooks sind ein Zeichen, dass der Algorithmus doch nicht so stabil ist.

Merksatz:

Template Method fixiert den Ablauf und variiert die Schritte.

B4-K03-Q008 – Observer, Domain Events und Application EventsEin Event sagt, was passiert ist, nicht was jemand tun soll.
Frage:

„Wie nutzen Sie Observer-ähnliche Patterns in modernen Spring-Anwendungen?“

Kurz erklärt:

Observer entkoppelt Ereigniserzeuger von Reaktionen. In Domain- und Application-Architekturen erscheint es oft als Domain Events oder Application Events.

Musterantwort:

Ich nutze Events, wenn ein fachlicher Vorgang passiert ist und mehrere Reaktionen entkoppelt werden sollen. Eine Order wird bezahlt; danach können Audit, Notification, Projection Update oder Integration Events folgen. Der Order-Code muss diese Reaktionen nicht direkt kennen.

Wichtig ist die Grenze: Ein Domain Event bedeutet „etwas ist fachlich passiert“. Es ist nicht automatisch eine Kafka-Nachricht. Für externe Kommunikation brauche ich oft Outbox, Transaktionsgrenzen, Idempotenz und Schema-Versionierung.

Senior-Antwort:

„Events entkoppeln Reaktionen, aber sie entkoppeln nicht Verantwortung. Transaktionen, Fehler, Idempotenz und Versionierung bleiben Architekturthemen.“

Häufige Fehler:
  • Events als versteckte synchrone Methodenaufrufe nutzen.
  • Fachliche Pflichtlogik in unkontrollierte Listener auslagern.
  • Transaktionsgrenzen ignorieren.
  • Domain Events direkt als Kafka-Schema verwenden.
  • Listener-Reihenfolge voraussetzen.
  • Idempotenz bei asynchronen Events vergessen.
Best Practices:
  • Events nach abgeschlossenen fachlichen Fakten benennen.
  • Domain Events von Integration Events trennen.
  • Kritische Konsistenz nicht unbedacht asynchronisieren.
  • Outbox für externe Events prüfen.
  • Listener idempotent machen.
  • Event-Verträge versionieren.
  • Event-Flows beobachtbar machen.
Stolperfalle:

Nur weil etwas technisch als Event gesendet wird, ist es noch kein gutes fachliches Event.

Merksatz:

Ein Event sagt, was passiert ist, nicht was jemand tun soll.

B4-K03-Q009 – Command PatternCommands modellieren Absicht.
Frage:

„Wann verwenden Sie das Command Pattern in Enterprise-Anwendungen?“

Kurz erklärt:

Command kapselt eine auszuführende Aktion als Objekt. Das ermöglicht Dispatching, Queuing, Logging, Retry, Validierung und Entkopplung von Sender und Handler.

Musterantwort:

Ich verwende Command, wenn Aktionen explizit modelliert werden sollen: Payment autorisieren, Capture ausführen, Refund starten, Bestellung stornieren. Ein Command enthält die Absicht und nötige Daten. Ein Handler führt sie aus.

Das Pattern ist hilfreich bei CQRS, Messaging, Workflows, Audit, Retry, Async Processing und Use-Case-Grenzen. Es macht Aktionen sichtbar und testbar. Man sollte aber nicht jede einfache Methode zwanghaft in Command und Handler zerlegen.

Senior-Antwort:

„Ein Command beschreibt eine Absicht. Ein Event beschreibt ein passiertes Faktum. Diese Unterscheidung ist für saubere Architektur entscheidend.“

Häufige Fehler:
  • Commands als anämische Kopien von API-DTOs ohne Use-Case-Semantik.
  • Ein generischer ProcessCommand für alles.
  • Handler mit zu vielen Verantwortlichkeiten.
  • Command und Event verwechseln.
  • Dispatching ohne Typsicherheit.
  • Commands ohne Idempotenz in Messaging verwenden.
Best Practices:
  • Commands nach Absicht benennen.
  • Commands immutable machen.
  • Handler pro Use Case schneiden.
  • Validierung an Boundarys und fachlich im Handler.
  • Command und Event klar trennen.
  • Idempotenz für asynchrone Commands definieren.
  • Dispatching einfach halten.
Stolperfalle:

Ein Command Handler darf nicht zum neuen God Service werden. Er sollte einen konkreten Use Case kapseln.

Merksatz:

Commands modellieren Absicht.

B4-K03-Q010 – Proxy Pattern, Spring AOP und Framework-MagieProxies machen Framework-Verhalten sichtbar, aber nur an Proxy-Grenzen. 24h
Frage:

„Wie funktioniert das Proxy Pattern in Spring, und warum ist Self-Invocation problematisch?“

Kurz erklärt:

Proxy stellt ein Ersatzobjekt vor ein Zielobjekt. Spring nutzt Proxies für AOP-Funktionen wie Transaktionen, Security, Caching und Observability.

Musterantwort:

Spring AOP arbeitet häufig über Proxies. Der Client ruft nicht direkt das Zielobjekt auf, sondern einen Proxy. Dieser kann vor und nach dem Aufruf zusätzliche Logik ausführen, etwa Transaktion starten, Security prüfen oder Cache verwenden.

Self-Invocation ist problematisch, weil ein Methodenaufruf innerhalb derselben Klasse über this nicht zwingend durch den Proxy geht. Wenn transferTwice intern transfer aufruft, kann die @Transactional -Logik auf transfer umgangen werden, je nach Proxy-Konfiguration. Deshalb gehören transaktionale Grenzen an externe Proxy-Grenzen oder in separate Beans.

Senior-Antwort:

„Spring-Annotationen wirken oft über Proxies. Wer Proxy-Grenzen nicht versteht, versteht Transaktionen, Security und Caching in Spring nur halb.“

Häufige Fehler:
  • @Transactional auf private Methoden setzen.
  • Self-Invocation ignorieren.
  • Final-Klassen oder Methoden mit AOP kombinieren.
  • Spring-Annotationen als direkte Sprachfeatures verstehen.
  • Transaktionsgrenzen auf zu kleine interne Methoden legen.
  • Proxy-Verhalten in Tests nicht berücksichtigen.
Best Practices:
  • AOP-Grenzen bewusst setzen.
  • Transaktionale Use Cases von außen über Bean-Grenzen aufrufen.
  • Private Methoden nicht mit AOP-Annotationen versehen.
  • Self-Invocation vermeiden oder refaktorieren.
  • Tests für Transaktionsverhalten schreiben.
  • Proxy-Mechanik bei Security und Caching verstehen.
  • Annotationen nicht mit Fachlogik verwechseln.
Stolperfalle:

@Transactional ist kein Keyword der Java-Sprache. Es ist Framework-Verhalten, das nur unter bestimmten Aufrufbedingungen greift.

Merksatz:

Proxies machen Framework-Verhalten sichtbar, aber nur an Proxy-Grenzen.

B4-K04 – Kapitel 4: Domain-Driven Design 10 Karten

B4-K04-Q001 – Ubiquitous Language und fachliche ModellierungDie Sprache ist das erste Modell. 24h
Frage:

„Was ist Ubiquitous Language, und warum ist sie für DDD so wichtig?“

Kurz erklärt:

Ubiquitous Language bedeutet, dass Fachbereich, Entwickler, Tests, APIs und Code dieselben Begriffe mit derselben Bedeutung verwenden.

Musterantwort:

Ubiquitous Language ist die gemeinsame Sprache eines Bounded Contexts. Sie verbindet Fachbereich und Softwaremodell. Begriffe wie Payment , Authorization , Capture , Refund , AuthorizationCode oder DeclineReason sollten im Code dieselbe Bedeutung haben wie im Fachgespräch.

Das ist wichtig, weil viele Modellierungsfehler durch unscharfe Begriffe entstehen. Wenn ein Team Customer , ein anderes Client , ein drittes Account und ein viertes Party sagt, aber alle etwas anderes meinen, entstehen falsche APIs, falsche Datenmodelle und falsche Verantwortlichkeiten. DDD beginnt deshalb nicht mit Klassen, sondern mit Sprache.

Senior-Antwort:

„DDD beginnt für mich mit Sprache. Wenn der Code andere Begriffe verwendet als das Fachgespräch, modelliert der Code wahrscheinlich nicht die Domäne, sondern nur technische Abläufe.“

Häufige Fehler:
  • Technische Namen statt fachlicher Begriffe verwenden.
  • Unterschiedliche Begriffe für dasselbe Konzept.
  • Gleiche Begriffe für unterschiedliche Konzepte.
  • Fachliche Begriffe in generischen Services verstecken.
  • API-Begriffe und Domain-Begriffe unkontrolliert vermischen.
  • Events nach technischen Aktionen statt fachlichen Fakten benennen.
Best Practices:
  • Fachbegriffe aktiv mit Domain Experts klären.
  • Glossar pro Bounded Context pflegen.
  • Code, Tests, APIs und Events an derselben Sprache ausrichten.
  • Value Objects für wichtige Begriffe nutzen.
  • Unscharfe Begriffe in Reviews diskutieren.
  • Begriffe nicht contextübergreifend erzwingen.
  • Veraltete Namen gezielt refaktorieren.
Stolperfalle:

Eine gemeinsame Unternehmenssprache ist nicht immer möglich oder sinnvoll. Derselbe Begriff kann in verschiedenen Bounded Contexts legitimerweise unterschiedliche Bedeutungen haben.

Merksatz:

Die Sprache ist das erste Modell.

B4-K04-Q002 – Bounded Contexts und strategisches DesignEin Bounded Context begrenzt Bedeutung.
Frage:

„Was ist ein Bounded Context, und warum ist er wichtiger als eine technische Service-Grenze?“

Kurz erklärt:

Ein Bounded Context ist eine fachliche Modellgrenze, innerhalb der Begriffe, Regeln und Modelle konsistent gelten.

Musterantwort:

Ein Bounded Context ist eine Grenze für Bedeutung. Innerhalb eines Contexts haben Begriffe eine eindeutige Bedeutung und ein konsistentes Modell. Ein Customer im CRM-Kontext ist möglicherweise ein gepflegtes Kundenprofil. Im Payment-Kontext ist derselbe Kunde nur eine CustomerReference mit Kreditstatus. Im Support-Kontext ist er vielleicht ein Ticket-Ersteller.

Das ist wichtiger als eine technische Service-Grenze, weil Microservices ohne klare Bounded Contexts nur verteilte technische Komponenten sind. Wenn mehrere Services dasselbe Datenmodell teilen oder denselben Begriff unterschiedlich verwenden, entsteht ein verteilter Monolith.

Senior-Antwort:

„Ein Bounded Context ist keine Deployment-Einheit, sondern eine Bedeutungsgrenze. Ein guter Microservice-Schnitt folgt meist einem guten Context-Schnitt, aber er ersetzt ihn nicht.“

Häufige Fehler:
  • Bounded Context mit REST-Service gleichsetzen.
  • Ein globales Unternehmensdatenmodell erzwingen.
  • Fremde Entities direkt verwenden.
  • Shared Database als Integrationsmodell.
  • Context-Schnitt nach technischen Schichten statt Fachlichkeit.
  • Team-Ownership ignorieren.
Best Practices:
  • Contexts nach fachlicher Bedeutung schneiden.
  • Begriffe pro Context definieren.
  • Fremde Modelle nicht direkt importieren.
  • Public APIs und Events als Verträge nutzen.
  • Context-Grenzen technisch absichern.
  • Team-Ownership mit Context-Grenzen synchronisieren.
  • Context Mapping dokumentieren.
Stolperfalle:

Ein gemeinsam genutztes Customer -Modul sieht nach Wiederverwendung aus, kann aber unterschiedliche fachliche Bedeutungen gefährlich vermischen.

Merksatz:

Ein Bounded Context begrenzt Bedeutung.

B4-K04-Q003 – Entities und IdentityEine Entity ist durch ihre Identität definiert, nicht durch ihre aktuellen Werte.
Frage:

„Was ist der Unterschied zwischen einer DDD Entity und einer JPA Entity?“

Kurz erklärt:

Eine Entity ist ein Objekt mit fachlicher Identität über Zeit. Ihr Zustand kann sich ändern, aber ihre Identität bleibt stabil.

Musterantwort:

Eine DDD Entity ist ein fachliches Konzept mit Identität und Verhalten. Sie schützt Invarianten und drückt fachliche Zustandsänderungen aus. Eine JPA Entity ist eine Persistenzklasse, die von Hibernate gemappt wird. Manchmal kann dieselbe Klasse beide Rollen erfüllen, aber das ist eine bewusste Entscheidung, kein Automatismus.

Im Beispiel ist Customer eine fachliche Entity, weil sie eine stabile CustomerId hat und Verhalten wie changeEmail und deactivate kapselt. Sie ist nicht nur ein Datencontainer mit Settern.

Senior-Antwort:

„Eine DDD Entity ist nicht nur eine Tabelle als Objekt. Sie hat Identität, Lebenszyklus und Verhalten, das fachliche Invarianten schützt.“

Häufige Fehler:
  • Entity als reinen Datencontainer modellieren.
  • Technische Datenbank-ID mit fachlicher Identität verwechseln.
  • Öffentliche Setter für jede Eigenschaft.
  • equals/hashCode auf veränderliche Felder stützen.
  • Entities zu großen Objektgraphen machen.
  • JPA-Anforderungen ungeprüft in Domain-Modell übernehmen.
Best Practices:
  • Identität explizit modellieren.
  • Verhalten statt Setter anbieten.
  • Invarianten in Entity-Methoden schützen.
  • equals/hashCode vorsichtig und identitätsbasiert gestalten.
  • JPA-Anforderungen bewusst behandeln.
  • Entities nicht als API-DTOs verwenden.
  • Große Objektgraphen vermeiden.
Stolperfalle:

@Entity bedeutet JPA Entity, nicht automatisch gutes Domain-Modell.

Merksatz:

Eine Entity ist durch ihre Identität definiert, nicht durch ihre aktuellen Werte.

B4-K04-Q004 – Value Objects und fachliche UnveränderlichkeitEin Value Object macht einen fachlichen Wert explizit und sicher.
Frage:

„Was ist ein Value Object, und warum ist es in DDD so wichtig?“

Kurz erklärt:

Value Objects beschreiben fachliche Werte ohne eigene Identität. Sie sind idealerweise immutable und validieren ihre eigenen Regeln.

Musterantwort:

Ein Value Object ist ein fachlicher Wert, der durch seine Attribute definiert ist, nicht durch eine Identität. Zwei Money -Objekte mit demselben Betrag und derselben Währung sind fachlich gleich. Value Objects sind idealerweise immutable und schützen ihre eigenen Invarianten.

Sie sind wichtig, weil sie Primitive Obsession verhindern. Statt überall BigDecimal amount und String currency herumzureichen, modelliert Money Währungsprüfung, Skalierung, Addition und Subtraktion selbst. Dadurch wandert Fachlogik aus zufälligen Services in den passenden Typ.

Senior-Antwort:

„Value Objects sind kleine Fachmodelle. Sie verhindern, dass wichtige Regeln als lose if -Checks durch die Anwendung verstreut werden.“

Häufige Fehler:
  • Primitive Obsession mit String , BigDecimal , long .
  • Value Objects mutable machen.
  • Validation außerhalb des Value Objects duplizieren.
  • Value Objects ohne fachliches Verhalten.
  • Währung und Betrag getrennt weiterreichen.
  • BigDecimal.equals ohne Skalierungsbewusstsein verwenden.
Best Practices:
  • Wichtige Fachwerte als Value Objects modellieren.
  • Value Objects immutable halten.
  • Konstruktorvalidierung nutzen.
  • Fachliche Operationen im Value Object kapseln.
  • Records für einfache Value Objects verwenden.
  • JPA-Mapping bewusst planen.
  • Keine technischen Utility-Objekte als Value Objects tarnen.
Stolperfalle:

Ein Record ist nur strukturell immutable. Wenn ein Record eine mutable Collection enthält und diese nicht kopiert, ist das Value Object nicht wirklich sicher.

Merksatz:

Ein Value Object macht einen fachlichen Wert explizit und sicher.

B4-K04-Q005 – Aggregates, Aggregate Root und InvariantenAggregates schützen Invarianten, nicht Beziehungen.
Frage:

„Was ist ein Aggregate, und wie schneiden Sie Aggregates richtig?“

Kurz erklärt:

Ein Aggregate ist eine Konsistenzgrenze. Der Aggregate Root kontrolliert Zugriff auf interne Entities und Value Objects und schützt Invarianten.

Musterantwort:

Ein Aggregate ist eine Gruppe fachlich zusammengehöriger Objekte, die gemeinsam konsistent gehalten werden müssen. Der Aggregate Root ist der einzige Einstiegspunkt. Externe Objekte ändern interne Teile nicht direkt, sondern rufen Methoden auf dem Root auf.

Ich schneide Aggregates nach Invarianten, nicht nach Datenbankrelationen. Wenn eine Regel sofort und transaktional gelten muss, gehört sie wahrscheinlich innerhalb eines Aggregates. Wenn Konsistenz eventual sein darf, sollten es eher getrennte Aggregates sein. Zu große Aggregates führen zu Locking, Performanceproblemen und unnötiger Kopplung.

Senior-Antwort:

„Ein Aggregate ist keine Datenbankstruktur, sondern eine Konsistenzgrenze. Ich frage: Welche Regeln müssen sofort gemeinsam wahr bleiben?“

Häufige Fehler:
  • Aggregates nach Datenbank-Foreign-Keys schneiden.
  • Große Objektgraphen als ein Aggregate modellieren.
  • Interne Collections modifizierbar herausgeben.
  • Andere Aggregates direkt als Objekt referenzieren.
  • Invarianten in Application Services statt im Aggregate verstreuen.
  • Aggregate Root umgehen.
Best Practices:
  • Aggregates nach Invarianten schneiden.
  • Root als einzigen Änderungszugang verwenden.
  • Interne Collections schützen.
  • Andere Aggregates über IDs referenzieren.
  • Aggregate klein halten.
  • Transaktionsgrenzen am Aggregate orientieren.
  • Eventual Consistency bewusst einsetzen.
  • JPA-Mapping an Aggregate-Schnitt ausrichten.
Stolperfalle:

Order mit allen Payments, Shipments, Customer-Daten, Invoice und Inventory als ein Aggregate ist fast immer zu groß.

Merksatz:

Aggregates schützen Invarianten, nicht Beziehungen.

B4-K04-Q006 – Repositories und PersistenzignoranzRepository lädt Aggregate, nicht beliebige Tabellenzeilen.
Frage:

„Was ist ein Repository in DDD, und wie unterscheidet es sich von Spring Data Repository?“

Kurz erklärt:

Ein DDD Repository ist eine fachliche Collection-Abstraktion für Aggregate. Es versteckt Persistenzdetails vor dem Domain- und Application-Code.

Musterantwort:

Ein DDD Repository ist eine fachliche Abstraktion zum Laden und Speichern von Aggregates. Der Use Case fragt etwa paymentRepository.findById(paymentId) und speichert paymentRepository.save(payment) . Er kennt keine SQL-Queries, EntityManager, Lazy Loading oder Spring Data Details.

Ein Spring Data Repository ist eine technische Implementierungshilfe. Es kann ein Adapter hinter einem DDD Repository sein. Ich sollte aber vermeiden, dass Application- und Domain-Code direkt von technischen Repository-Methoden, Query-Namen oder JPA-Entities abhängig wird, wenn ich eine klare DDD-Architektur anstrebe.

Senior-Antwort:

„Ein DDD Repository ist kein beliebiger DAO. Es ist die Sammlung meiner Aggregate Roots und schützt den Domain-Code vor Persistenzdetails.“

Häufige Fehler:
  • Spring Data Repositories direkt im Domain Core verwenden.
  • Repository als generischen DAO für alles nutzen.
  • Reporting-Abfragen über Aggregate Repositories lösen.
  • Fremde Aggregates über Repository-Methoden verbinden.
  • Persistenzdetails in Domain Exceptions leaken.
  • findAll() auf Aggregates unkontrolliert anbieten.
Best Practices:
  • Repository pro Aggregate Root definieren.
  • Interface fachlich im Application/Domain-Modul platzieren.
  • Technische Spring-Data-Implementierung als Adapter kapseln.
  • Read Queries getrennt modellieren.
  • Keine generischen Datenzugriffsoperationen ohne Fachbedarf.
  • Aggregate vollständig genug laden, um Invarianten zu schützen.
  • Repository-Adapter mit echter DB testen.
Stolperfalle:

Spring Data macht Repository-Erzeugung einfach, aber nicht jede automatisch mögliche Query gehört in ein DDD Repository.

Merksatz:

Repository lädt Aggregate, nicht beliebige Tabellenzeilen.

B4-K04-Q007 – Domain Services und Application ServicesApplication Services koordinieren; Domain Services modellieren fachliche Logik.
Frage:

„Was ist der Unterschied zwischen Application Service und Domain Service?“

Kurz erklärt:

Application Services orchestrieren Use Cases. Domain Services kapseln fachliche Logik, die nicht natürlich zu einer einzelnen Entity oder einem Value Object gehört.

Musterantwort:

Ein Application Service orchestriert den Use Case: laden, Transaktion koordinieren, Domain-Methoden aufrufen, speichern, Events publizieren, externe Ports nutzen. Er enthält möglichst wenig Fachentscheidung.

Ein Domain Service enthält fachliche Logik, die nicht sauber in eine einzelne Entity oder ein Value Object passt. Im Transfer-Beispiel betrifft die Regel mehrere Accounts: Quelle und Ziel dürfen nicht gleich sein, Debit und Credit gehören fachlich zusammen. Diese Logik kann in einem TransferDomainService liegen.

Senior-Antwort:

„Ein Application Service koordiniert den Anwendungsfall. Ein Domain Service trifft fachliche Entscheidungen, die nicht natürlich in ein einzelnes Aggregat passen.“

Häufige Fehler:
  • Application Service mit Domain Service verwechseln.
  • Alle Fachlogik aus Entities in Services ziehen.
  • Domain Service mit Infrastrukturzugriff überladen.
  • Transaktions- und Weblogik in Domain Service mischen.
  • Generische Services ohne fachlichen Namen.
  • Use Case nur als Weiterleitung auf Domain Service bauen.
Best Practices:
  • Application Services orchestrieren.
  • Entities und Value Objects zuerst prüfen.
  • Domain Service nur für echte fachliche Logik zwischen Objekten.
  • Fachliche Namen verwenden.
  • Technische Details am Rand halten.
  • Domain Services gut unit-testen.
  • Keine Service-Klasse als Sammelbecken.
Stolperfalle:

Ein Domain Service ist kein Ort für alles, was man nicht in eine Entity schreiben möchte. Oft gehört Verhalten doch ins Aggregate.

Merksatz:

Application Services koordinieren; Domain Services modellieren fachliche Logik.

B4-K04-Q008 – Domain Events und Integration EventsDomain Events erzählen dem Modell, was passiert ist; Integration Events erzählen es anderen…
Frage:

„Was ist der Unterschied zwischen Domain Event und Integration Event?“

Kurz erklärt:

Domain Events beschreiben fachliche Fakten innerhalb eines Bounded Contexts. Integration Events sind Verträge für andere Systeme oder Contexts.

Musterantwort:

Ein Domain Event beschreibt, dass innerhalb des Domain-Modells etwas fachlich passiert ist, zum Beispiel PaymentAuthorized . Es entsteht im Aggregate und nutzt die Sprache des Bounded Contexts.

Ein Integration Event ist ein externer Vertrag. Es wird an andere Contexts oder Systeme publiziert und muss versioniert, stabil und konsumierbar sein. Es sollte nicht automatisch dasselbe Objekt sein wie das Domain Event. Zwischen beiden kann ein Mapping liegen, oft zusammen mit Outbox, Idempotenz und Schema-Versionierung.

Senior-Antwort:

„Ein Domain Event ist interne Fachsprache. Ein Integration Event ist ein externer Vertrag. Diese beiden Dinge sollten nicht aus Bequemlichkeit vermischt werden.“

Häufige Fehler:
  • Domain Event direkt als Kafka-Vertrag verwenden.
  • Events vor erfolgreichem Commit publizieren.
  • Event-Consumer nicht idempotent bauen.
  • Commands als Events benennen.
  • Zu technische Events wie PaymentEntityUpdated .
  • Event-Schema ohne Versionierung ändern.
Best Practices:
  • Events nach fachlichen Fakten benennen.
  • Domain Events intern halten.
  • Integration Events separat versionieren.
  • Outbox für zuverlässiges Publishing prüfen.
  • Consumer idempotent gestalten.
  • Event-Schemas dokumentieren.
  • Keine sensiblen Daten unnötig in Events.
  • Event-Flows mit Correlation IDs beobachten.
Stolperfalle:

Ein Event ist kein Befehl. PaymentAuthorized beschreibt ein passiertes Faktum; AuthorizePayment wäre ein Command.

Merksatz:

Domain Events erzählen dem Modell, was passiert ist; Integration Events erzählen es anderen Systemen.

B4-K04-Q009 – Context Mapping und Integration zwischen ContextsContext Mapping macht Beziehungen zwischen Modellen explizit.
Frage:

„Was ist ein Anti-Corruption Layer, und wann setzen Sie ihn ein?“

Kurz erklärt:

Context Mapping beschreibt die Beziehung zwischen Bounded Contexts. Anti-Corruption Layer schützen ein Modell vor fremden Begriffen und Strukturen.

Musterantwort:

Ein Anti-Corruption Layer ist eine Übersetzungsschicht zwischen zwei Modellen. Ich setze ihn ein, wenn mein Bounded Context mit einem fremden oder Legacy-Modell interagieren muss, dessen Begriffe, Datenstrukturen oder Semantik ich nicht in mein Modell übernehmen möchte.

Im Beispiel liefert CRM Risikokategorien A , B , C . Der Payment-Kontext möchte aber LOW , MEDIUM , HIGH als eigene Sprache verwenden. Der Adapter übersetzt und verhindert, dass CRM-Begriffe in Payment-Fachlogik leaken.

Senior-Antwort:

„Ein Anti-Corruption Layer ist kein Luxus. Er schützt mein Modell vor fremden Annahmen, schlechten Namen und Legacy-Semantik.“

Häufige Fehler:
  • Fremde Modelle direkt übernehmen.
  • Shared Kernel zu groß machen.
  • ACL mit Fachlogik überfrachten.
  • Integration ohne Mapping dokumentieren.
  • Fremde Fehlercodes durchreichen.
  • Context-Beziehungen politisch ignorieren.
Best Practices:
  • Context-Beziehungen explizit dokumentieren.
  • Fremde Modelle am Rand übersetzen.
  • ACL für Legacy- oder fremde Semantik nutzen.
  • Shared Kernel klein und streng kontrolliert halten.
  • Fehlercodes und Begriffe mappen.
  • Integrationstests für Mapping schreiben.
  • Ownership und Änderungsrichtung klären.
Stolperfalle:

Direktes Wiederverwenden fremder DTOs spart heute Mapping und kostet morgen Modellklarheit.

Merksatz:

Context Mapping macht Beziehungen zwischen Modellen explizit.

B4-K04-Q010 – DDD mit Spring, JPA und typische FallenJPA persistiert Objekte; DDD modelliert Fachlichkeit. 24h
Frage:

„Welche typischen Fallen entstehen, wenn man DDD mit Spring Data JPA und Hibernate umsetzt?“

Kurz erklärt:

Spring und JPA können DDD unterstützen, aber auch untergraben. Man muss bewusst entscheiden, wo Domain-Modell, Persistence-Modell und Framework-Anforderungen zusammenfallen oder getrennt bleiben.

Musterantwort:

Die häufigsten Fallen sind: JPA-Entities als Domain-Modell erzwingen, öffentliche Setter für Hibernate-Bequemlichkeit einführen, Aggregates nach Tabellenrelationen schneiden, Lazy Loading in Domain-Logik leaken lassen, Repositories als technische DAOs verwenden, Domain Events vor Commit publizieren und API-DTOs direkt aus Entities serialisieren.

Spring und JPA sind Werkzeuge. Für einfache Domänen kann ein gemeinsames Domain-/JPA-Modell pragmatisch sein. Für komplexe Domänen trenne ich oft Domain Aggregate und Persistence Entity oder kapsle JPA-Anforderungen sehr bewusst.

Senior-Antwort:

„DDD mit JPA ist möglich, aber nicht automatisch. Ich muss bewusst entscheiden, ob Hibernate mein Domain-Modell tragen darf oder ob ich eine Persistenzschicht als Adapter brauche.“

Häufige Fehler:
  • DDD mit @Entity gleichsetzen.
  • JPA-Setter öffentlich machen und Invarianten umgehen.
  • Aggregates nach Foreign Keys statt Invarianten schneiden.
  • API serialisiert JPA Entities.
  • Repository-Methoden wachsen zu Query-Monstern.
  • Domain Events ohne Outbox publizieren.
  • Read- und Write-Modelle vermischen.
Best Practices:
  • Modellkomplexität ehrlich bewerten.
  • Gemeinsames Domain-/JPA-Modell nur bewusst nutzen.
  • Öffentliche Setter vermeiden.
  • Aggregates nach Invarianten schneiden.
  • Read-Modelle getrennt optimieren.
  • JPA-Details am Rand halten.
  • Lazy Loading nicht in API leaken.
  • Domain Events nach Commit oder über Outbox publizieren.
Stolperfalle:

Weniger Mapping wirkt kurzfristig sauberer, kann aber langfristig Domain und Persistenz untrennbar koppeln.

Merksatz:

JPA persistiert Objekte; DDD modelliert Fachlichkeit.

B4-K05 – Kapitel 5: Microservices 10 Karten

B4-K05-Q001 – Service-Schnitt und fachliche OwnershipEin Microservice ist eine fachliche Grenze mit Betriebsverantwortung. 24h
Frage:

„Wie schneiden Sie Microservices richtig?“

Kurz erklärt:

Ein Microservice sollte eine fachliche Verantwortung und klare Ownership haben. Er ist keine zufällige technische Komponente.

Musterantwort:

Ich schneide Microservices primär nach fachlichen Fähigkeiten und Ownership, nicht nach technischen Schichten. Ein Payment-Service ist verantwortlich für Payment-Lifecycle, Payment-Regeln, Payment-Daten und Payment-APIs. Er sollte nicht nur ein technischer Wrapper um eine Tabelle oder ein REST-Endpoint für fremde Logik sein.

Ein guter Service-Schnitt folgt oft einem Bounded Context. Das bedeutet: eigene Sprache, eigene Daten, klare Verträge und ein Team, das fachlich und operativ verantwortlich ist. Wenn ein Service bei jeder Änderung fünf andere Services synchron mitändern muss, ist er wahrscheinlich falsch geschnitten.

Senior-Antwort:

„Ich schneide Microservices nicht nach Klassen oder Tabellen, sondern nach fachlicher Verantwortung, Datenhoheit und Team-Ownership.“

Häufige Fehler:
  • Services nach technischen Schichten schneiden.
  • Datenbanktabellen eins zu eins in Services übersetzen.
  • Zu kleine Services ohne eigene Fachverantwortung.
  • Gemeinsame Datenbank als Integrationsmodell.
  • Deployment-Autonomie mit fachlicher Autonomie verwechseln.
  • Team-Ownership ignorieren.
Best Practices:
  • Services nach Business Capability schneiden.
  • Bounded Contexts als Ausgangspunkt nutzen.
  • Datenhoheit pro Service definieren.
  • Verträge explizit versionieren.
  • Team-Ownership klar machen.
  • Service-Größe an Änderungsgründen orientieren.
  • Erst modularisieren, dann verteilen.
Stolperfalle:

Ein kleiner Service ist nicht automatisch ein guter Microservice. Ohne eigene fachliche Verantwortung ist er nur verteilte Komplexität.

Merksatz:

Ein Microservice ist eine fachliche Grenze mit Betriebsverantwortung.

B4-K05-Q002 – Datenhoheit, Database per Service und Shared DatabaseWer die Daten besitzt, besitzt die fachliche Wahrheit.
Frage:

„Warum ist Shared Database zwischen Microservices problematisch?“

Kurz erklärt:

Jeder Microservice sollte seine eigenen Daten besitzen. Andere Services greifen über APIs oder Events zu, nicht direkt über Tabellen.

Musterantwort:

Eine gemeinsame Datenbank koppelt Services auf Schema-, Transaktions- und Deployment-Ebene. Wenn der Order-Service direkt Inventory-Tabellen liest oder Payment-Tabellen schreibt, gibt es keine echte Service-Autonomie. Jede Schemaänderung kann andere Services brechen, fachliche Regeln werden umgangen, und Ownership wird unklar.

Database per Service bedeutet nicht zwingend ein eigener Datenbankserver pro Service, aber eine klare Datenhoheit: Nur der owning Service schreibt und kontrolliert seine Daten. Andere Services konsumieren APIs, Events oder replizierte Read Models.

Senior-Antwort:

„Ein Microservice besitzt nicht nur Code, sondern auch Daten. Ohne Datenhoheit bleibt er abhängig und ist oft nur ein verteilter DAO.“

Häufige Fehler:
  • Fremde Tabellen direkt lesen.
  • Fremde Daten direkt schreiben.
  • Gemeinsames Schema als Integrationsvertrag.
  • Reporting-Service mit Vollzugriff auf alle Produktionsdaten.
  • Datenhoheit nicht dokumentieren.
  • Schemaänderungen ohne Consumer-Analyse.
Best Practices:
  • Datenowner pro Service definieren.
  • Schreibzugriff strikt auf owning Service begrenzen.
  • Fremdzugriff über APIs oder Events.
  • Reporting über eigene Read Models.
  • Schemaänderungen versionieren.
  • Datenbankrechte technisch einschränken.
  • Datenverträge dokumentieren.
Stolperfalle:

Eine gemeinsame Datenbank ist die schnellste Abkürzung zum verteilten Monolithen.

Merksatz:

Wer die Daten besitzt, besitzt die fachliche Wahrheit.

B4-K05-Q003 – Synchrone Kommunikation, REST und Service-to-Service CallsRemote Calls sind verteilte Systemgrenzen, keine Methodenaufrufe.
Frage:

„Wann verwenden Sie synchrone REST-Kommunikation zwischen Microservices, und welche Risiken beachten Sie?“

Kurz erklärt:

Synchrone Service-to-Service-Kommunikation ist einfach zu verstehen, aber erhöht Laufzeitkopplung. Jeder Remote Call kann langsam, fehlerhaft oder nicht verfügbar sein.

Musterantwort:

Synchrone Kommunikation ist sinnvoll, wenn der aufrufende Use Case eine unmittelbare Antwort braucht, zum Beispiel Eligibility prüfen, Preis berechnen oder Payment autorisieren. Sie ist verständlich und passt gut zu Request-Response-Interaktionen.

Die Risiken sind Laufzeitkopplung, Latenz, Fehlerausbreitung und Kettenabhängigkeiten. Jeder synchrone Call braucht Timeouts, klare Fehlerabbildung, Authentifizierung, Observability, Versionierung und eine bewusste Fallback- oder Fail-Fast-Strategie. Ein Remote Call ist kein lokaler Methodenaufruf.

Senior-Antwort:

„Ein synchroner Service Call ist eine Laufzeitabhängigkeit. Ich nutze ihn bewusst, mit Timeout, Vertrag, Fehlerstrategie und Observability.“

Häufige Fehler:
  • Keine Timeouts.
  • Remote Calls wie lokale Methoden behandeln.
  • Synchronous Chains über viele Services.
  • Retries ohne Idempotenz.
  • Fehlercodes nicht mappen.
  • Provider-Verträge nicht versionieren.
Best Practices:
  • Synchrone Calls nur bei echtem Antwortbedarf.
  • Timeouts pro Downstream definieren.
  • Fehler fachlich mappen.
  • Circuit Breaker und Bulkhead prüfen.
  • Verträge mit OpenAPI/Contract Tests absichern.
  • Correlation IDs propagieren.
  • Anzahl synchroner Calls im Request begrenzen.
Stolperfalle:

Ein Feign-Interface sieht wie ein lokales Java-Interface aus. Das macht den Call aber nicht lokal.

Merksatz:

Remote Calls sind verteilte Systemgrenzen, keine Methodenaufrufe.

B4-K05-Q004 – Asynchrone Kommunikation, Events und MessagingEin Event ist ein Fakt, kein Wunsch.
Frage:

„Wann verwenden Sie Events oder Messaging zwischen Microservices?“

Kurz erklärt:

Asynchrone Kommunikation entkoppelt Services zeitlich. Sie bringt aber Eventual Consistency, Duplikate, Ordering-Fragen und Betriebsaufwand.

Musterantwort:

Ich verwende Events, wenn ein Service ein fachliches Faktum veröffentlicht und andere Services darauf reagieren können, ohne den ursprünglichen Use Case zu blockieren. Typische Beispiele sind PaymentAuthorized , OrderSubmitted , CustomerRegistered oder ShipmentDispatched .

Events entkoppeln zeitlich, aber nicht logisch. Consumer müssen idempotent sein, Event-Schemas müssen versioniert werden, Ordering muss verstanden werden, Fehler gehören in DLQ- und Retry-Strategien, und Monitoring muss Consumer Lag und Fehlerraten zeigen. Messaging ist kein einfacherer REST-Ersatz.

Senior-Antwort:

„Events entkoppeln Zeit, aber sie entfernen keine Verantwortung. Jeder Consumer muss mit Duplikaten, Verzögerung, Versionierung und Fehlern umgehen können.“

Häufige Fehler:
  • Events als Commands missbrauchen.
  • Consumer nicht idempotent.
  • Keine Event-Versionierung.
  • Große interne Datenmodelle als Event senden.
  • Ordering global annehmen.
  • Fehlerhafte Nachrichten endlos retryen.
Best Practices:
  • Events als fachliche Fakten benennen.
  • Event-Schema stabil und minimal halten.
  • Consumer idempotent machen.
  • Event IDs und Correlation IDs verwenden.
  • DLQ und Retry definieren.
  • Schema-Evolution planen.
  • Consumer Lag beobachten.
  • Outbox für zuverlässiges Publishing prüfen.
Stolperfalle:

Ein Event CreateInvoice ist meist kein Event, sondern ein Command. Ein Event wäre InvoiceRequested oder PaymentAuthorized .

Merksatz:

Ein Event ist ein Fakt, kein Wunsch.

B4-K05-Q005 – Verteilte Transaktionen, Saga und OutboxMicroservice-Konsistenz ist Prozessdesign, nicht nur Transaktionskonfiguration. 24h
Frage:

„Wie lösen Sie Transaktionen über mehrere Microservices hinweg?“

Kurz erklärt:

Microservices vermeiden klassische verteilte ACID-Transaktionen meist. Stattdessen nutzt man Sagas, Kompensation, Outbox und Eventual Consistency.

Musterantwort:

Ich versuche zuerst, Service-Grenzen so zu schneiden, dass starke Transaktionen innerhalb eines Services bleiben. Wenn ein Geschäftsprozess mehrere Services betrifft, nutze ich meist Saga-Pattern: Eine Folge lokaler Transaktionen mit Kompensationsschritten. Für zuverlässiges Event-Publishing nutze ich Outbox, damit Datenänderung und Event-Erfassung in einer lokalen Transaktion passieren.

Ich vermeide 2PC in den meisten Microservice-Systemen, weil es Kopplung, Blockierung und Betriebsrisiken erhöht. Stattdessen akzeptiere ich Eventual Consistency dort, wo die Fachlichkeit es erlaubt, und modelliere Zwischenzustände explizit.

Senior-Antwort:

„In Microservices versuche ich nicht, die alte Datenbanktransaktion über das Netzwerk zu retten. Ich modelliere Prozesse, Zwischenzustände und Kompensation bewusst.“

Häufige Fehler:
  • @Transactional über Remote Calls erwarten.
  • Zwischenzustände nicht modellieren.
  • Kompensationen nicht idempotent.
  • Outbox und Domain-Änderung getrennt speichern.
  • Fehlerpfade nicht testen.
  • Manuelle Korrekturprozesse vergessen.
Best Practices:
  • Starke Konsistenz innerhalb eines Services halten.
  • Service-Grenzen nach Konsistenzbedarf prüfen.
  • Saga-Zustände explizit modellieren.
  • Kompensationen idempotent bauen.
  • Outbox für zuverlässige Events verwenden.
  • Retry und Timeout definieren.
  • Observability für Saga-Schritte.
  • Manuelle Recovery-Pfade planen.
Stolperfalle:

@Transactional schützt nicht vor einem erfolgreichen Remote Call, der danach lokal zurückgerollt wird.

Merksatz:

Microservice-Konsistenz ist Prozessdesign, nicht nur Transaktionskonfiguration.

B4-K05-Q006 – Resilience, Timeouts, Retries und BulkheadsResilience begrenzt Schaden, nicht Realität.
Frage:

„Wie machen Sie Microservices resilient gegen Downstream-Ausfälle?“

Kurz erklärt:

Resilience verhindert nicht alle Fehler. Sie begrenzt Ausfallausbreitung durch Timeouts, Circuit Breaker, Bulkheads, Retry-Budgets und Fallbacks.

Musterantwort:

Ich beginne immer mit Timeouts. Ohne Timeouts kann ein langsamer Downstream Threads, Connections und Request-Slots blockieren. Danach definiere ich Retries nur für geeignete, idempotente Operationen mit Backoff und Budget. Circuit Breaker verhindern wiederholte Calls an gestörte Downstreams. Bulkheads begrenzen parallele Aufrufe, damit ein kaputter Downstream nicht den ganzen Service blockiert.

Fallbacks müssen fachlich korrekt sein. Ein leerer Empfehlungsblock ist oft okay; ein gefälschter Payment-Erfolg ist gefährlich. Resilience ist deshalb nicht nur Technik, sondern Fachentscheidung.

Senior-Antwort:

„Resilience beginnt nicht mit @Retry , sondern mit Zeitbudgets, Isolation und fachlich korrektem Fehlverhalten.“

Häufige Fehler:
  • Keine Timeouts.
  • Blindes Retry auf POST.
  • Fallback erzeugt fachlich falschen Erfolg.
  • Circuit Breaker ohne Metriken.
  • Gemeinsamer Threadpool für alle Downstreams.
  • Resilience-Defaults ungeprüft übernehmen.
Best Practices:
  • Timeouts pro Downstream definieren.
  • Deadlines durch Call-Ketten propagieren.
  • Retries begrenzen und idempotent machen.
  • Bulkheads für kritische Ressourcen.
  • Circuit Breaker mit Metriken betreiben.
  • Fallbacks fachlich freigeben.
  • Chaos-/Failure-Tests durchführen.
  • p95/p99 beobachten.
Stolperfalle:

Retries können einen Ausfall verstärken, wenn viele Services gleichzeitig denselben gestörten Downstream erneut belasten.

Merksatz:

Resilience begrenzt Schaden, nicht Realität.

B4-K05-Q007 – API Contracts, Versionierung und Consumer CompatibilityEin Microservice-Vertrag ist ein Produktversprechen.
Frage:

„Wie verhindern Sie, dass ein Microservice-Release andere Services bricht?“

Kurz erklärt:

Microservices kommunizieren über Verträge. API- und Event-Verträge müssen versioniert, getestet und kompatibel weiterentwickelt werden.

Musterantwort:

Ich behandle APIs und Events als versionierte Verträge. Breaking Changes werden vermieden oder bewusst versioniert. Neue optionale Felder sind meistens kompatibel, entfernte Felder, umbenannte Felder, geänderte Semantik, neue Pflichtfelder oder geänderte Statuscodes sind riskant.

Ich nutze OpenAPI, Schema-Verträge, Consumer-driven Contract Tests, API-Diff-Prüfungen und CI-Gates. Außerdem beobachte ich, welche Consumer welche Version nutzen, bevor ich alte Verträge entferne. Kompatibilität ist kein Dokumentationsproblem, sondern ein Build- und Betriebsproblem.

Senior-Antwort:

„Unabhängige Deployments funktionieren nur mit stabilen Verträgen. Ohne Contract Tests ist Microservice-Autonomie oft nur Hoffnung.“

Häufige Fehler:
  • Verträge nur als Swagger-Doku behandeln.
  • Required-Felder entfernen.
  • Fehlerformate inkompatibel ändern.
  • Enum-Werte ohne Consumer-Abstimmung ändern.
  • Consumer-Nutzung nicht messen.
  • Keine Deprecation-Strategie.
Best Practices:
  • API-Verträge versionieren.
  • Breaking Changes automatisiert erkennen.
  • Consumer-driven Contract Tests nutzen.
  • Fehlerverträge dokumentieren.
  • Events rückwärtskompatibel entwickeln.
  • Deprecation und Sunset planen.
  • Consumer-Nutzung beobachten.
  • Beispiele und Schemas pflegen.
Stolperfalle:

Ein zusätzlicher Enum-Wert kann Consumer brechen, wenn sie nicht defensiv implementiert sind.

Merksatz:

Ein Microservice-Vertrag ist ein Produktversprechen.

B4-K05-Q008 – Observability, Logging, Metrics und TracingWas man nicht beobachten kann, kann man nicht zuverlässig betreiben.
Frage:

„Was bedeutet Observability in einer Microservice-Architektur?“

Kurz erklärt:

Observability ist Pflicht in Microservices. Ohne strukturierte Logs, Metriken, Traces und Correlation IDs sind Fehler kaum nachvollziehbar.

Musterantwort:

Observability bedeutet, dass ich den Zustand und das Verhalten des Systems aus seinen Signalen verstehen kann: Logs, Metrics und Traces. In Microservices ist das essenziell, weil ein einzelner User-Request oft mehrere Services, Queues, Datenbanken und externe Systeme durchläuft.

Ich brauche strukturierte Logs mit Correlation ID, Metriken für Latenz, Fehler, Durchsatz und Ressourcen, verteiltes Tracing für Request-Flows und Business-Metriken für fachliche Zustände. Ohne Observability ist Incident-Analyse nur Raten.

Senior-Antwort:

„In Microservices ist Observability kein Add-on. Sie ist Teil der Architektur, weil kein einzelner Stacktrace mehr den ganzen Fehler erklärt.“

Häufige Fehler:
  • Nur lokale Logs ohne Correlation ID.
  • Keine p95/p99-Metriken.
  • Hohe Cardinality durch IDs als Labels.
  • Sensitive Daten loggen.
  • Tracing nicht über Messaging propagieren.
  • Business-Metriken vergessen.
Best Practices:
  • Strukturierte Logs verwenden.
  • Correlation IDs propagieren.
  • Metrics für RED/USE erfassen.
  • Distributed Tracing aktivieren.
  • Business-KPIs messen.
  • Sensitive Daten maskieren.
  • Alerting auf Symptome und SLOs ausrichten.
  • Dashboards pro Service und End-to-End-Flow.
Stolperfalle:

Zu viele Labels in Metriken, etwa customerId oder orderId , können das Monitoring-System überlasten.

Merksatz:

Was man nicht beobachten kann, kann man nicht zuverlässig betreiben.

B4-K05-Q009 – Security, Zero Trust und Service-to-Service AuthZero Trust bedeutet: Jeder Aufruf braucht Identität, Zweck und Berechtigung.
Frage:

„Wie sichern Sie Microservice-Kommunikation ab?“

Kurz erklärt:

Microservices brauchen Security auf jeder Ebene: Edge, Service-to-Service, Daten, Secrets, Events, Observability und Betrieb.

Musterantwort:

Ich gehe von Zero Trust aus: Kein Service ist automatisch vertrauenswürdig, nur weil er im internen Netzwerk läuft. Externe Requests werden am Edge authentifiziert und autorisiert. Service-to-Service-Kommunikation nutzt mTLS, OAuth2 Client Credentials, JWTs, opaque Tokens oder Service-Mesh-Identitäten, je nach Plattform.

Wichtig sind Audience-Prüfung, Scopes, kurze Token-Lebensdauer, Secret Rotation, Least Privilege, sichere Defaults, keine sensiblen Logs und Autorisierung im Zielservice. Das Gateway kann vorprüfen, aber der Service muss kritische Entscheidungen selbst schützen.

Senior-Antwort:

„Intern ist nicht automatisch sicher. Jeder Service muss wissen, wer ihn aufruft, wofür der Aufrufer berechtigt ist und welche Daten er sehen darf.“

Häufige Fehler:
  • Internes Netzwerk als vertrauenswürdig betrachten.
  • Nur am Gateway autorisieren.
  • JWT nur decodieren, nicht validieren.
  • Audience nicht prüfen.
  • Gemeinsame Secrets nie rotieren.
  • Tokens oder personenbezogene Daten loggen.
Best Practices:
  • Zero-Trust-Prinzip verwenden.
  • Least Privilege für Service-Scopes.
  • Audience und Issuer validieren.
  • Kurze Token-Lebensdauer.
  • Secrets rotieren.
  • Actuator und Admin-Endpunkte schützen.
  • Security-Tests für 401/403 schreiben.
  • Sensitive Daten in Logs maskieren.
Stolperfalle:

Gateway-Autorisierung allein reicht nicht, wenn Services intern direkt erreichbar sind oder später neue Kommunikationspfade entstehen.

Merksatz:

Zero Trust bedeutet: Jeder Aufruf braucht Identität, Zweck und Berechtigung.

B4-K05-Q010 – Migration, modularer Monolith und Anti-PatternsVerteile nur, was du vorher modular verstanden hast. 24h
Frage:

„Wie migrieren Sie von einem Monolithen zu Microservices, ohne einen verteilten Monolithen zu bauen?“

Kurz erklärt:

Microservices sollten selten der erste Schritt sein. Ein modularer Monolith kann fachliche Grenzen klären, bevor sie über das Netzwerk verteilt werden.

Musterantwort:

Ich beginne mit fachlicher Modularisierung im Monolithen. Zuerst identifiziere ich Bounded Contexts, trenne Module, definiere Modul-APIs, entkopple Datenzugriffe, entferne direkte Abhängigkeiten und schreibe Architekturtests. Erst wenn ein Modul klare fachliche Ownership, stabile Verträge und eigene Datenhoheit hat, ist es ein Kandidat für Extraktion.

Für die eigentliche Migration nutze ich Patterns wie Strangler Fig, Anti-Corruption Layer, Parallel Run, Event-Driven Synchronization und schrittweise Datenextraktion. Ich vermeide Big-Bang-Rewrites.

Senior-Antwort:

„Ich extrahiere keine Unordnung über das Netzwerk. Erst ziehe ich fachliche und technische Grenzen im Monolithen, dann entscheide ich, ob Deployment-Autonomie den Preis wert ist.“

Häufige Fehler:
  • Erst deployen, dann Grenzen klären.
  • Lokale Methodenaufrufe eins zu eins zu REST machen.
  • Gemeinsame Datenbank behalten.
  • Big-Bang-Rewrite.
  • Module ohne Tests extrahieren.
  • Betriebskomplexität unterschätzen.
Best Practices:
  • Erst fachlich modularisieren.
  • Modul-APIs definieren.
  • Datenzugriffe entkoppeln.
  • Architekturtests einsetzen.
  • Strangler Pattern nutzen.
  • Extraktion über klare Verträge.
  • Performance vor und nach Migration messen.
  • Team-Ownership und Betrieb klären.
Stolperfalle:

Microservices lösen keinen schlecht strukturierten Monolithen. Sie machen seine Kopplung nur teurer sichtbar.

Merksatz:

Verteile nur, was du vorher modular verstanden hast.

B4-K06 – Kapitel 6: Kafka 10 Karten

B4-K06-Q001 – Kafka-Grundmodell: Topics, Partitions, Records und LogKafka skaliert über Partitionen, aber Ordering lebt nur innerhalb einer Partition. 24h
Frage:

„Erklären Sie das Grundmodell von Kafka: Topic, Partition, Record, Offset und Log.“

Kurz erklärt:

Kafka speichert Events in Topics. Topics sind in Partitionen aufgeteilt. Jede Partition ist ein append-only Log mit geordneten Records und Offsets.

Musterantwort:

Ein Kafka Topic ist ein benannter Event-Stream. Ein Topic besteht aus Partitionen. Eine Partition ist ein geordnetes, append-only Log. Jeder Record in einer Partition hat einen Offset. Kafka garantiert Ordering innerhalb einer Partition, aber nicht global über alle Partitionen eines Topics.

Ein Record besteht typischerweise aus Key, Value, Headers, Timestamp und Metadaten. Der Key ist wichtig, weil er meist bestimmt, in welche Partition ein Record geschrieben wird. Wenn ich alle Events eines Payments in Reihenfolge verarbeiten will, verwende ich paymentId als Key, damit diese Events in derselben Partition landen.

Senior-Antwort:

„Kafka ist ein partitioniertes Log. Wer Ordering, Keys und Offsets nicht bewusst modelliert, bekommt später zufällige Parallelität statt verlässliches Eventing.“

Häufige Fehler:
  • Globale Reihenfolge über ein Topic erwarten.
  • Topic-Key zufällig wählen.
  • Partitionen nachträglich ohne Ordering-Auswirkung ändern.
  • Kafka als Queue ohne Log-Semantik verstehen.
  • Events ohne stabile IDs publizieren.
  • Topics nach technischen Klassen statt fachlichen Streams benennen.
Best Practices:
  • Topics nach fachlichen Event-Streams benennen.
  • Keys bewusst nach Ordering- und Verteilungsanforderung wählen.
  • Event IDs und Correlation IDs verwenden.
  • Ordering nur innerhalb einer Partition erwarten.
  • Partitionenzahl bewusst dimensionieren.
  • Topic-Versionierung planen.
  • Event-Namen als fachliche Fakten formulieren.
Stolperfalle:

Ein Topic ist nicht automatisch eine FIFO-Queue. FIFO gilt nur innerhalb einer Partition.

Merksatz:

Kafka skaliert über Partitionen, aber Ordering lebt nur innerhalb einer Partition.

B4-K06-Q002 – Producer, Acknowledgements und IdempotenzZuverlässiges Publishing beginnt in der Anwendung, nicht erst im Broker.
Frage:

„Welche Producer-Konfigurationen sind für zuverlässiges Kafka-Publishing wichtig?“

Kurz erklärt:

Producer schreiben Records in Kafka. Acknowledgements, Retries und Idempotenz bestimmen, wie robust und duplikatsarm Publishing ist.

Musterantwort:

Für zuverlässiges Publishing achte ich auf acks=all , Idempotenz, sinnvolle Retries über das Delivery Timeout, stabile Keys, sinnvolle Batching-Parameter und Monitoring. acks=all bedeutet, dass der Producer erst nach Bestätigung durch die relevante Replikationskonfiguration Erfolg meldet. enable.idempotence=true verhindert Duplikate durch Producer-Retries innerhalb der Producer-Session.

Ich unterscheide aber Producer-Idempotenz von fachlicher Idempotenz. Wenn meine Anwendung dasselbe Event zweimal erzeugt, verhindert Kafka das nicht automatisch. Dafür brauche ich Event IDs, Outbox, deduplizierende Consumer oder fachliche Idempotenzmechanismen.

Senior-Antwort:

„Producer-Idempotenz hilft gegen Retry-Duplikate auf Producer-Ebene. Sie ersetzt aber keine fachliche Idempotenz und keine Outbox.“

Häufige Fehler:
  • Send-Future ignorieren.
  • Producer-Fehler nur loggen, aber nicht behandeln.
  • Idempotent Producer mit fachlicher Exactly-once-Garantie verwechseln.
  • Zufällige Keys verwenden.
  • Zu kleine Batches bei hohem Durchsatz.
  • Events direkt nach Datenbankcommit ohne Outbox publizieren und Crash-Lücke erzeugen.
Best Practices:
  • acks=all für kritische Events verwenden.
  • Producer-Idempotenz aktiv lassen.
  • Send-Erfolg und Fehler behandeln.
  • Event ID und Correlation ID setzen.
  • Keys fachlich wählen.
  • Batching und Compression messen.
  • Outbox für transaktionales Publishing verwenden.
  • Producer-Metriken beobachten.
Stolperfalle:

enable.idempotence=true bedeutet nicht, dass Ihr Business-Event niemals doppelt existiert. Es reduziert eine bestimmte Klasse von Producer-Duplikaten.

Merksatz:

Zuverlässiges Publishing beginnt in der Anwendung, nicht erst im Broker.

B4-K06-Q003 – Consumer Groups, Offsets und RebalancingConsumer skalieren über Partitionen und bleiben korrekt durch idempotente Verarbeitung.
Frage:

„Wie funktionieren Consumer Groups und Offset Commits in Kafka?“

Kurz erklärt:

Consumer lesen Records aus Partitionen. Consumer Groups verteilen Partitionen auf Consumer-Instanzen. Offsets speichern die Lese-/Verarbeitungsposition.

Musterantwort:

Eine Consumer Group verarbeitet ein Topic gemeinsam. Jede Partition wird innerhalb einer Consumer Group zu einem Zeitpunkt höchstens einem Consumer zugewiesen. Dadurch kann die Gruppe parallelisieren. Mehr Consumer als Partitionen bringen für dieses Topic keine zusätzliche Parallelität.

Offsets beschreiben die Position eines Consumers in einer Partition. Wichtig ist: Ich committe normalerweise den nächsten zu lesenden Offset, also record.offset() + 1 , nachdem die Verarbeitung erfolgreich war. Wenn ich vor Verarbeitung committe und dann crashte, verliere ich Daten. Wenn ich nach Verarbeitung committe und dann crashte, kann ich Duplikate bekommen. Deshalb müssen Consumer idempotent sein.

Senior-Antwort:

„Offset Commit ist kein technisches Detail. Es definiert, ob ich Datenverlust, Duplikate oder kontrollierte Wiederholung riskiere.“

Häufige Fehler:
  • Offset vor Verarbeitung committen.
  • Idempotenz vergessen.
  • Auto-Commit unbedacht aktivieren.
  • Langsame Verarbeitung löst Rebalances aus.
  • Rebalance Listener ignorieren.
  • Consumer Parallelität ohne Partitionen erwarten.
  • Fehlerhafte Records blockieren Partition dauerhaft.
Best Practices:
  • Nach erfolgreicher Verarbeitung committen.
  • Consumer idempotent machen.
  • Commit-Strategie bewusst wählen.
  • Rebalance-Verhalten testen.
  • max.poll.records an Verarbeitung anpassen.
  • Poison Messages behandeln.
  • Lag und Rebalance-Rate beobachten.
  • Partitionenzahl und Consumer-Anzahl gemeinsam planen.
Stolperfalle:

Auto-Commit kann Offsets committen, obwohl die fachliche Verarbeitung noch nicht dauerhaft abgeschlossen ist.

Merksatz:

Consumer skalieren über Partitionen und bleiben korrekt durch idempotente Verarbeitung.

B4-K06-Q004 – Partitionierung, Ordering und Key DesignDer Kafka-Key entscheidet, was zusammenbleibt.
Frage:

„Wie wählen Sie den Key für Kafka-Events?“

Kurz erklärt:

Der Kafka-Key bestimmt häufig die Partition. Dadurch beeinflusst er Ordering, Lastverteilung und parallele Verarbeitung.

Musterantwort:

Ich wähle den Key nach fachlicher Ordering-Anforderung und Lastverteilung. Wenn alle Events einer Order in Reihenfolge verarbeitet werden müssen, nutze ich orderId als Key. Dann landen alle Events derselben Order in derselben Partition. Wenn ich customerId verwende, bekomme ich Ordering pro Kunde, aber möglicherweise Hotspots bei großen Kunden.

Key Design ist ein Trade-off: stärkere Ordering-Garantien bedeuten oft weniger Verteilung. Bessere Verteilung kann Ordering schwächen. Man muss also fragen: Für welche Entität brauche ich Reihenfolge wirklich?

Senior-Antwort:

„Der Key ist keine technische Kleinigkeit. Er ist die Verbindung zwischen fachlicher Reihenfolge und technischer Skalierung.“

Häufige Fehler:
  • eventId als Key verwenden und Ordering verlieren.
  • Low-Cardinality-Key wie country oder status .
  • Hot Customers nicht berücksichtigen.
  • Partitionenzahl ändern, ohne Key-Auswirkung zu verstehen.
  • Globales Ordering verlangen.
  • Custom Partitioner ohne Betriebsgrund.
Best Practices:
  • Key nach fachlicher Ordering-Grenze wählen.
  • Cardinality prüfen.
  • Hotspots messen.
  • Partitionenzahl langfristig planen.
  • Key-Serialisierung stabil halten.
  • Ordering-Annahmen dokumentieren.
  • Consumer auf Out-of-Order über Partitionen vorbereiten.
Stolperfalle:

eventId ist gut für Deduplikation, aber oft schlecht als Kafka-Key, weil es Events derselben Entität über Partitionen verteilt.

Merksatz:

Der Kafka-Key entscheidet, was zusammenbleibt.

B4-K06-Q005 – Delivery Semantics: At-most-once, At-least-once und Exactly-onceKafka-Semantik endet nicht am Broker; Business-Korrektheit endet am Side Effect. 24h
Frage:

„Was bedeuten at-most-once, at-least-once und exactly-once in Kafka?“

Kurz erklärt:

Kafka kann unterschiedliche Delivery Semantics unterstützen, aber Ende-zu-Ende-Korrektheit hängt von Producer, Broker, Consumer und Zielsystem ab.

Musterantwort:

At-most-once bedeutet: Eine Nachricht wird höchstens einmal verarbeitet, kann aber verloren gehen. Das passiert etwa, wenn der Consumer den Offset vor der Verarbeitung committet und danach crasht.

At-least-once bedeutet: Eine Nachricht wird mindestens einmal verarbeitet, kann aber doppelt verarbeitet werden. Das ist in Kafka sehr häufig: erst verarbeiten, dann Offset committen. Wenn der Consumer nach Verarbeitung, aber vor Commit crasht, wird erneut gelesen.

Exactly-once ist komplexer. Kafka unterstützt genauere Garantien für Kafka-zu-Kafka-Pipelines über idempotente Producer und Transactions. Aber sobald externe Systeme wie Datenbanken, REST-APIs oder E-Mail beteiligt sind, brauche ich weiterhin Idempotenz, eindeutige Business Keys, Outbox/Inbox oder transaktionale Kopplung. „Exactly once“ ist kein Zauberwort für beliebige Side Effects.

Senior-Antwort:

„Exactly-once muss immer Ende-zu-Ende betrachtet werden. Kafka kann viel garantieren, aber meine Datenbank, mein REST-Call und meine E-Mail brauchen eigene Idempotenzstrategie.“

Häufige Fehler:
  • Exactly-once als globale Garantie missverstehen.
  • Consumer ohne Idempotenz bauen.
  • Offset vor Side Effect committen.
  • Side Effects wie E-Mail oder REST-Call nicht deduplizieren.
  • Producer-Idempotenz mit Consumer-Idempotenz verwechseln.
  • Transaktionen aktivieren, ohne Semantik zu verstehen.
Best Practices:
  • At-least-once plus idempotente Consumer als Standard betrachten.
  • Event IDs und Business Keys verwenden.
  • Processed-Event-Tabelle oder Inbox nutzen.
  • Unique Constraints für Deduplikation.
  • Kafka Transactions für Kafka-zu-Kafka-Pipelines prüfen.
  • Externe Side Effects bewusst absichern.
  • Delivery-Semantik dokumentieren und testen.
Stolperfalle:

Ein Kafka-Consumer mit exactly-once-Konfiguration verhindert nicht automatisch doppelte Buchungen in einer externen Datenbank.

Merksatz:

Kafka-Semantik endet nicht am Broker; Business-Korrektheit endet am Side Effect.

B4-K06-Q006 – Schema Evolution, Serialization und Event ContractsEvents leben länger als der Code, der sie zuerst erzeugt.
Frage:

„Wie gehen Sie mit Schema Evolution bei Kafka Events um?“

Kurz erklärt:

Kafka Events sind Verträge. Serialization und Schema Evolution müssen so gestaltet werden, dass alte und neue Producer/Consumer kompatibel bleiben.

Musterantwort:

Ich behandle Event-Schemas als versionierte Verträge. Neue optionale Felder sind meist kompatibel. Entfernte Felder, umbenannte Felder, geänderte Typen, geänderte Semantik oder neue Pflichtfelder sind riskant. Deshalb nutze ich klare Kompatibilitätsregeln, Schema Registry, Contract Tests und Versionsstrategie.

Wichtig ist auch, Domain Event und Integration Event zu trennen. Das Integration Event darf stabiler sein als mein internes Domain-Modell. Ich mappe bewusst und publiziere nicht einfach interne Entities oder Domain-Objekte.

Senior-Antwort:

„Ein Kafka-Event ist kein internes DTO. Es ist ein langfristiger Vertrag mit unbekannt vielen Consumern.“

Häufige Fehler:
  • Interne Entities als Kafka-Events serialisieren.
  • Event-Schema ohne Versionierung ändern.
  • Neue Pflichtfelder ohne Default.
  • Enum-Werte ohne Consumer-Kompatibilität erweitern.
  • Datumsformate uneinheitlich.
  • Sensitive Daten in Events aufnehmen.
Best Practices:
  • Integration Events als stabile Verträge behandeln.
  • Schema Registry oder vergleichbare Governance nutzen.
  • Kompatibilitätsregeln definieren.
  • Neue Felder optional/defaultfähig machen.
  • Events klein und fachlich halten.
  • Domain und Integration Event trennen.
  • Contract Tests für Producer und Consumer.
  • Event-Versionen und Deprecation dokumentieren.
Stolperfalle:

Ein Consumer kann durch einen neuen Enum-Wert brechen, obwohl das Schema formal kompatibel aussieht.

Merksatz:

Events leben länger als der Code, der sie zuerst erzeugt.

B4-K06-Q007 – Outbox Pattern und transaktionales Event PublishingOutbox schließt die Lücke zwischen lokaler Transaktion und externem Event.
Frage:

„Warum brauchen viele Kafka-basierte Microservices das Outbox Pattern?“

Kurz erklärt:

Outbox löst die Crash-Lücke zwischen Datenbankänderung und Kafka-Publishing, indem Event-Nachrichten in derselben lokalen Transaktion gespeichert werden.

Musterantwort:

Weil Datenbank-Commit und Kafka-Publish sonst zwei getrennte Operationen sind. Wenn ich zuerst die Datenbank ändere und danach Kafka publiziere, kann der Service nach dem Commit, aber vor dem Publish crashen. Dann fehlt das Event. Wenn ich zuerst publiziere und danach die Datenbanktransaktion fehlschlägt, publiziere ich ein falsches Event.

Outbox speichert die fachliche Änderung und die zu publizierende Nachricht in derselben lokalen Datenbanktransaktion. Ein separater Publisher liest Pending-Outbox-Einträge und publiziert sie nach Kafka. Dadurch wird Publishing zuverlässig, aber Consumer müssen weiterhin mit Duplikaten umgehen können.

Senior-Antwort:

„Outbox ist kein Kafka-Feature, sondern eine Anwendungsgarantie: Mein State Change und mein Event werden lokal atomar erfasst.“

Häufige Fehler:
  • Event direkt nach DB-Commit publizieren.
  • Outbox-Eintrag außerhalb der Transaktion speichern.
  • Publisher nicht idempotent bauen.
  • Outbox ohne Cleanup.
  • Keine Monitoring-Metriken für Pending/Failed.
  • Consumer nicht auf Duplikate vorbereiten.
Best Practices:
  • Domain-Änderung und Outbox in einer Transaktion.
  • Outbox-Publisher getrennt betreiben.
  • Event ID stabil speichern.
  • Publish-Retries einplanen.
  • Consumer idempotent bauen.
  • Outbox-Lag und Failed Count überwachen.
  • Cleanup/Archivierung definieren.
  • CDC prüfen, wenn Polling nicht skaliert.
Stolperfalle:

Outbox garantiert nicht, dass Consumer das Event genau einmal verarbeiten. Sie garantiert zuverlässigeres Publishing; Consumer-Idempotenz bleibt nötig.

Merksatz:

Outbox schließt die Lücke zwischen lokaler Transaktion und externem Event.

B4-K06-Q008 – Error Handling, Retry Topics und Dead Letter QueueEin Kafka-Consumer braucht einen Plan für schlechte Events.
Frage:

„Wie behandeln Sie Fehler in Kafka-Consumern professionell?“

Kurz erklärt:

Consumer-Fehler müssen klassifiziert werden: retryable, nicht retryable, poison message oder manuell zu behandeln. Sonst blockieren fehlerhafte Events ganze Partitionen.

Musterantwort:

Ich unterscheide retryable und non-retryable Fehler. Temporäre Downstream-Probleme können mit Backoff retryt werden. Validierungsfehler, unbekannte Schema-Versionen oder semantisch defekte Events gehören eher in eine Dead Letter Queue. Wichtig ist, dass ein einzelnes Poison Event nicht endlos eine Partition blockiert.

Ich speichere Fehlerkontext: Topic, Partition, Offset, Key, Event ID, Exception-Klasse, Reason, Attempt und Timestamp. Außerdem brauche ich Monitoring, Alerts und einen Reprocessing-Prozess. Eine DLQ ohne Betriebsprozess ist nur ein digitales Grab.

Senior-Antwort:

„Fehlerhandling ist bei Kafka Teil des Designs. Ohne Retry- und DLQ-Konzept wird aus einem defekten Event schnell ein blockierter Geschäftsprozess.“

Häufige Fehler:
  • Alle Fehler endlos retryen.
  • DLQ ohne Monitoring.
  • Fehlerkontext verlieren.
  • Nicht-retryable Fehler als transient behandeln.
  • Offset committen, obwohl Fehler nicht dokumentiert wurde.
  • Reprocessing ohne Idempotenz.
Best Practices:
  • Fehler klassifizieren.
  • Backoff und Max Attempts definieren.
  • DLQ mit vollständigem Kontext.
  • Monitoring auf Retry- und DLQ-Raten.
  • Reprocessing-Prozess dokumentieren.
  • Consumer idempotent halten.
  • Poison Events isolieren.
  • Alerts auf fachlich kritische Dead Letters.
Stolperfalle:

Eine DLQ löst nichts, wenn niemand sie überwacht, analysiert und reprocessen kann.

Merksatz:

Ein Kafka-Consumer braucht einen Plan für schlechte Events.

B4-K06-Q009 – Kafka Operations: Replication, KRaft, Retention und MonitoringKafka-Design endet nicht beim Producer-Code.
Frage:

„Welche operativen Kafka-Themen muss ein Senior-Entwickler verstehen?“

Kurz erklärt:

Kafka-Betrieb ist Architektur. Replication, Retention, Partitionen, KRaft, Monitoring und Topic-Konfiguration beeinflussen Zuverlässigkeit und Kosten.

Musterantwort:

Ein Senior muss verstehen, dass Topic-Design und Cluster-Betrieb zusammengehören. Wichtige Themen sind Replication Factor, Leader/Follower, In-Sync Replicas, Retention, Cleanup Policy, Partitionenzahl, Consumer Lag, Under-Replicated Partitions, Broker-Storage, Netzwerk, Controller, KRaft und Security.

Seit modernen Kafka-Versionen ist KRaft der Kafka-eigene Metadaten-Quorum-Modus. In KRaft werden bestimmte Server als Controller ausgewählt, die am Metadata Quorum teilnehmen; ein Controller ist aktiv, andere können hot standby sein.

Senior-Antwort:

„Kafka ist nicht nur eine Library im Code. Topic-Konfiguration, Retention, Replikation und Monitoring sind Teil des Architekturvertrags.“

Häufige Fehler:
  • Topic-Konfiguration nicht versionieren.
  • Retention zufällig wählen.
  • Zu große Events speichern.
  • Consumer Lag nicht überwachen.
  • Under-Replicated Partitions ignorieren.
  • Partitionenzahl ohne Plan ändern.
  • Kafka als dauerhafte Business-Datenbank missbrauchen.
Best Practices:
  • Topic-Konfiguration als Code verwalten.
  • Replication Factor passend zur Kritikalität wählen.
  • Retention fachlich und regulatorisch definieren.
  • Cleanup Policy bewusst wählen.
  • Consumer Lag pro Gruppe überwachen.
  • Broker- und Topic-Metriken alerten.
  • Event-Größe begrenzen.
  • KRaft/Controller-Health überwachen.
Stolperfalle:

Consumer Lag ist kein reines Betriebsdetail. Wachsender Lag bedeutet oft, dass fachliche Verarbeitung nicht mehr rechtzeitig passiert.

Merksatz:

Kafka-Design endet nicht beim Producer-Code.

B4-K06-Q010 – Kafka in Spring Boot: Spring Kafka, Listener und TestingSpring Kafka vereinfacht Integration, nicht Semantik. 24h
Frage:

„Wie setzen Sie Kafka in Spring Boot sauber ein?“

Kurz erklärt:

Spring Kafka vereinfacht Producer und Consumer, aber Kafka-Semantik bleibt bestehen: Idempotenz, Offsets, Errors, Transactions und Testing müssen bewusst gestaltet werden.

Musterantwort:

Ich kapsle Kafka an den Systemgrenzen. Producer publizieren fachliche Integration Events über klare Publisher-Komponenten. Consumer delegieren möglichst schnell an Application Services und enthalten Fehler-, Idempotenz- und Mapping-Logik an der Boundary.

Mit Spring Kafka nutze ich Listener-Container, Error Handler, Serializer/Deserializer, Transactions und Testing-Unterstützung. Trotzdem verlasse ich mich nicht blind auf Annotationen. Ich definiere Ack-Modus, Retry, DLQ, Idempotenz, Schema, Observability und Tests explizit.

Senior-Antwort:

„Spring Kafka nimmt Boilerplate weg, aber nicht Verantwortung. Offsets, Idempotenz, Fehlerhandling und Schema-Verträge bleiben Architekturentscheidungen.“

Häufige Fehler:
  • Business-Logik direkt in Listener stopfen.
  • Ack-/Commit-Modus nicht verstehen.
  • Listener-Concurrency höher als Partitionenzahl setzen und Parallelität erwarten.
  • Keine Idempotenztests.
  • Serialization nur im Unit-Test mit Java-Objekten testen.
  • Fehlerhandler und DLQ nicht konfigurieren.
Best Practices:
  • Kafka-spezifische Logik an Boundary halten.
  • Listener an Application Services delegieren.
  • Idempotenz fachlich testen.
  • Serialization/Deserialization integrieren testen.
  • Retry und DLQ explizit konfigurieren.
  • Concurrency an Partitionen ausrichten.
  • Consumer Lag und Fehlerquoten beobachten.
  • Testcontainers für realistische Kafka-Integration nutzen.
Stolperfalle:

Ein grüner Unit-Test, der direkt die Listener-Methode mit einem Java-Objekt aufruft, beweist nicht, dass Serialization, Topic-Konfiguration oder Consumer Group korrekt sind.

Merksatz:

Spring Kafka vereinfacht Integration, nicht Semantik.

B4-K07 – Kapitel 7: Docker 10 Karten

B4-K07-Q001 – Container, Images und LaufzeitmodellImages werden gebaut; Container werden ausgeführt. 24h
Frage:

„Was ist der Unterschied zwischen Docker Image und Container?“

Kurz erklärt:

Ein Image ist ein unveränderliches Build-Artefakt. Ein Container ist eine laufende Instanz dieses Images mit eigener Prozess-, Netzwerk- und Dateisystemisolation.

Musterantwort:

Ein Docker Image ist ein versioniertes, schichtbasiertes Artefakt. Es enthält Dateisystem-Layer, Metadaten, Entrypoint, Default-Konfiguration und Anwendungspaket. Ein Container ist ein laufender Prozess auf Basis dieses Images, isoliert durch Betriebssystemmechanismen.

Ich behandle Images als immutable Artefakte: einmal gebaut, signiert, gescannt und durch die Pipeline bewegt. Runtime-Konfiguration wie Profile, Secrets, Datenbank-URLs oder Ressourcenlimits gehören nicht hart ins Image, sondern werden beim Start über Environment, Dateien, Secrets oder Orchestrator-Konfiguration geliefert.

Senior-Antwort:

„Ein Image ist mein deploybares Artefakt. Der Container ist eine Laufzeitinstanz. Ich baue nicht pro Umgebung ein anderes Image, sondern konfiguriere pro Umgebung anders.“

Häufige Fehler:
  • Umgebungsspezifische Konfiguration ins Image backen.
  • Container als vollwertige VM behandeln.
  • Logs in Dateien im Container schreiben.
  • JDK statt JRE verwenden, obwohl Runtime reicht.
  • Keine Ressourcenlimits setzen.
  • JVM-Heap größer als Container-Limit konfigurieren.
Best Practices:
  • Images immutable behandeln.
  • Ein Image für mehrere Umgebungen verwenden.
  • Runtime-Konfiguration extern halten.
  • Logs nach stdout/stderr schreiben.
  • Ressourcenlimits definieren.
  • JVM-Container-Speicher bewusst konfigurieren.
  • Images regelmäßig aktualisieren und scannen.
Stolperfalle:

Ein Container ist kein Ort für manuelle Änderungen. Alles, was im Container per SSH gefixt wird, ist beim nächsten Start weg und nicht reproduzierbar.

Merksatz:

Images werden gebaut; Container werden ausgeführt.

B4-K07-Q002 – Dockerfile für Java 21 und Spring BootDas Runtime-Image sollte nur enthalten, was zur Ausführung nötig ist.
Frage:

„Wie schreiben Sie ein produktionsreifes Dockerfile für eine Spring-Boot-Anwendung?“

Kurz erklärt:

Ein gutes Dockerfile für Java trennt Build-Umgebung und Runtime, nutzt einen passenden Base Layer, läuft nicht als root und enthält nur notwendige Artefakte.

Musterantwort:

Ich verwende meistens Multi-Stage Builds. In der Build-Stage nutze ich ein JDK und baue das Artefakt. In der Runtime-Stage nutze ich ein kleineres JRE-Image oder ein anderes gehärtetes Runtime-Image. Dadurch landen Build-Tools, Gradle/Maven-Caches und Quellcode nicht im finalen Image.

Außerdem achte ich auf einen vertrauenswürdigen Base Image, kleine Angriffsfläche, nicht-root User, klare ENTRYPOINT -Definition, externe Konfiguration, stdout/stderr-Logging und sinnvolle JVM-Container-Optionen. Ein Dockerfile ist Produktionscode und gehört reviewed, getestet und gescannt.

Senior-Antwort:

„Mein finales Image ist eine Runtime, keine Build-Maschine. Alles, was ich zum Kompilieren brauche, hat im Produktionsimage nichts verloren.“

Häufige Fehler:
  • Build-Tools im finalen Image lassen.
  • Als root laufen.
  • latest als Base Image verwenden.
  • Unnötige Dateien kopieren.
  • Secrets beim Build ins Image schreiben.
  • Shell-Skripte als Entrypoint ohne Signal-Handling nutzen.
  • Keine JVM-Speicherstrategie definieren.
Best Practices:
  • Multi-Stage Build nutzen.
  • Base Image bewusst wählen und pinnen.
  • Nicht-root User verwenden.
  • Nur notwendige Artefakte kopieren.
  • .dockerignore pflegen.
  • JVM-Optionen containerbewusst setzen.
  • Images scannen.
  • Dockerfile wie Produktionscode reviewen.
Stolperfalle:

Ein Multi-Stage Build reduziert nur dann Angriffsfläche, wenn wirklich nur notwendige Artefakte in die finale Stage kopiert werden.

Merksatz:

Das Runtime-Image sollte nur enthalten, was zur Ausführung nötig ist.

B4-K07-Q003 – Multi-Stage Builds, Layer und Build CacheCache funktioniert nur, wenn stabile Dinge stabil bleiben.
Frage:

„Wie optimieren Sie Docker-Builds für Java-Projekte?“

Kurz erklärt:

Docker baut Images layerweise. Build Cache beschleunigt Builds, wenn stabile Schritte früh und häufig geänderte Schritte spät stehen.

Musterantwort:

Ich ordne Dockerfile-Schritte so, dass sich selten ändernde Dateien früh im Build befinden. Dependency-Dateien wie build.gradle.kts , settings.gradle.kts , pom.xml oder Wrapper-Dateien kopiere ich vor dem Source Code. Danach lade ich Dependencies oder lasse Build-Tools Caches nutzen. Source Code kommt später, weil er sich häufiger ändert.

Außerdem nutze ich Multi-Stage Builds, BuildKit, .dockerignore , Registry Cache in CI und möglichst stabile Base Images. Das Ziel ist nicht nur ein kleines Image, sondern auch ein schneller, reproduzierbarer Build.

Senior-Antwort:

„Dockerfile-Reihenfolge ist Build-Performance. Ich kopiere stabile Build-Metadaten vor häufig wechselndem Source Code.“

Häufige Fehler:
  • COPY . . als erster Schritt.
  • .git , build , target , lokale Caches und Secrets mitkopieren.
  • Dependency-Layer ständig invalidieren.
  • Cache in CI nicht nutzen.
  • Nicht reproduzierbare Builds durch wechselnde latest -Tags.
  • Build Cache mit Runtime Cache verwechseln.
Best Practices:
  • Dockerfile nach Änderungsfrequenz ordnen.
  • .dockerignore konsequent pflegen.
  • Dependencies vor Source Code kopieren.
  • Multi-Stage Builds verwenden.
  • BuildKit und Registry Cache prüfen.
  • Base Images pinnen.
  • CI-Build-Zeit messen.
  • Build und Runtime klar trennen.
Stolperfalle:

Ein .dockerignore ist kein Nice-to-have. Ohne ihn landen oft lokale Artefakte, Git-Daten oder sogar Secrets im Build Context.

Merksatz:

Cache funktioniert nur, wenn stabile Dinge stabil bleiben.

B4-K07-Q004 – Image Security, Base Images und Supply ChainSichere Container entstehen durch sichere Lieferkette, nicht durch einen letzten Scan.
Frage:

„Wie bauen Sie sichere Docker Images für Java-Anwendungen?“

Kurz erklärt:

Container-Sicherheit beginnt beim Base Image und endet nicht beim Scan. Sie umfasst Image-Herkunft, CVEs, Secrets, User, Dateisystem, SBOM, Signierung und Runtime-Policies.

Musterantwort:

Ich beginne mit einem vertrauenswürdigen, minimalen und regelmäßig aktualisierten Base Image. Ich vermeide latest , laufe nicht als root, kopiere keine Secrets ins Image, reduziere Tools im finalen Image, scanne Images auf CVEs, erzeuge SBOMs und signiere Images, wenn die Plattform das unterstützt.

Außerdem muss man CVEs bewerten. Nicht jede CVE ist im konkreten Runtime-Kontext ausnutzbar, aber unbehandelte kritische Findings in Base Images sind ein echtes Risiko. Docker empfiehlt bei Base Images, auf vertrauenswürdige Quellen und kleine Images zu achten; Docker Official Images werden als kuratierte Sammlung mit klarer Dokumentation und regelmäßigen Updates beschrieben.

Senior-Antwort:

„Ein Container Image ist ein Supply-Chain-Artefakt. Ich vertraue ihm nicht, nur weil es erfolgreich gebaut wurde.“

Häufige Fehler:
  • latest verwenden.
  • Container als root laufen lassen.
  • Secrets per ARG oder ENV ins Image schreiben.
  • Build-Tools im Runtime-Image belassen.
  • CVE-Scans ignorieren.
  • Images aus unbekannten Registries nutzen.
  • Shell und Paketmanager unnötig im Produktionsimage lassen.
Best Practices:
  • Vertrauenswürdige Base Images wählen.
  • Base Images pinnen und regelmäßig aktualisieren.
  • Nicht-root User verwenden.
  • Secrets nie ins Image bauen.
  • Images scannen.
  • SBOM erzeugen.
  • Image-Signierung prüfen.
  • Runtime-Dateisystem möglichst read-only.
Stolperfalle:

Ein Secret, das während des Builds per ARG ins Image gelangt, kann in Layer-Historie oder Build-Systemen sichtbar bleiben.

Merksatz:

Sichere Container entstehen durch sichere Lieferkette, nicht durch einen letzten Scan.

B4-K07-Q005 – JVM im Container: Speicher, CPU und StartverhaltenIm Container gehört der JVM nicht der ganze Speicher. 24h
Frage:

„Welche JVM-Themen müssen Sie bei Docker-Deployments beachten?“

Kurz erklärt:

Container-Ressourcenlimits bestimmen, wie viel CPU und Speicher die JVM sinnvoll nutzen darf. Heap ist nur ein Teil des Speicherverbrauchs.

Musterantwort:

Ich achte auf Container-Memory-Limits, Heap-Konfiguration, Non-Heap-Speicher, Metaspace, Thread Stacks, Direct Buffers, Code Cache und GC. Der Heap darf nicht das gesamte Container-Limit verbrauchen, weil die JVM und native Komponenten zusätzlichen Speicher brauchen. Deshalb nutze ich häufig MaxRAMPercentage statt fixer -Xmx -Werte.

Außerdem beachte ich CPU-Limits, available processors, GC-Auswahl, Startzeit, Warmup, JIT, Class Data Sharing, Healthchecks und graceful shutdown. Containerisierung nimmt der JVM keine Verantwortung ab.

Senior-Antwort:

„Im Container zählt der gesamte Prozessspeicher, nicht nur der Java Heap. Ein sauberer -Xmx -Wert kann trotzdem zum OOMKill führen.“

Häufige Fehler:
  • Heap zu groß setzen.
  • Non-Heap-Speicher ignorieren.
  • Keine OOM-Diagnose.
  • CPU-Limits ohne JVM-Auswirkung betrachten.
  • Zu kurze Startup-/Readiness-Zeiten.
  • Shutdown-Signale nicht korrekt behandeln.
Best Practices:
  • Container-Limits bewusst setzen.
  • Heap als Anteil des Limits konfigurieren.
  • Non-Heap berücksichtigen.
  • ExitOnOutOfMemoryError prüfen.
  • GC- und Speicher-Metriken beobachten.
  • Graceful Shutdown implementieren.
  • Readiness erst nach echter Betriebsbereitschaft melden.
  • Lasttests mit echten Limits durchführen.
Stolperfalle:

Ein Kubernetes OOMKill ist nicht dasselbe wie eine Java OutOfMemoryError im Heap. Die JVM bekommt möglicherweise keine Chance mehr, sauber zu reagieren.

Merksatz:

Im Container gehört der JVM nicht der ganze Speicher.

B4-K07-Q006 – Konfiguration, Secrets und Twelve-Factor-PrinzipienBuild once, configure at runtime.
Frage:

„Wie konfigurieren Sie containerisierte Spring-Boot-Anwendungen sauber?“

Kurz erklärt:

Container-Images sollten keine Umgebungskonfiguration und keine Secrets enthalten. Konfiguration kommt zur Runtime.

Musterantwort:

Ich baue ein unveränderliches Image und injiziere Konfiguration zur Runtime über Environment-Variablen, Config-Dateien, Secrets, Kubernetes ConfigMaps/Secrets oder Plattformmechanismen. Secrets gehören nicht ins Image, nicht in Git und nicht in normale Logs.

Für Spring Boot nutze ich Externalized Configuration und validierte @ConfigurationProperties . Wichtig ist, zwischen normaler Konfiguration, Secrets und Feature Flags zu unterscheiden. Ein fehlender kritischer Wert sollte den Start verhindern, statt mit unsicheren Defaults zu laufen.

Senior-Antwort:

„Ich baue nicht für Dev, Staging und Prod unterschiedliche Images. Ich baue ein Image und gebe ihm zur Runtime die passende Umgebung.“

Häufige Fehler:
  • Secrets ins Image kopieren.
  • Umgebungsspezifische Images bauen.
  • Unsichere Defaults.
  • Konfiguration nicht validieren.
  • Secrets in Logs ausgeben.
  • Feature Flags mit Secrets vermischen.
Best Practices:
  • Ein Image für alle Umgebungen.
  • Konfiguration extern injizieren.
  • Secrets getrennt behandeln.
  • @ConfigurationProperties validieren.
  • Kritische Werte fail-fast prüfen.
  • Keine Secrets in Logs oder Actuator.
  • Defaults bewusst dokumentieren.
  • Konfigurationsänderungen versionieren.
Stolperfalle:

Environment-Variablen sind bequem, aber für Secrets nicht immer ideal, weil sie leichter in Dumps, Diagnoseausgaben oder Prozessumgebungen sichtbar werden können.

Merksatz:

Build once, configure at runtime.

B4-K07-Q007 – Docker Compose für lokale Entwicklungs- und TestumgebungenCompose macht lokale Abhängigkeiten reproduzierbar.
Frage:

„Wofür verwenden Sie Docker Compose in Java-/Spring-Projekten?“

Kurz erklärt:

Docker Compose definiert mehrere Services, Netzwerke und Volumes in einer YAML-Datei. Es eignet sich besonders für lokale Entwicklung, Integrationstests und reproduzierbare Umgebungen.

Musterantwort:

Ich nutze Docker Compose, um lokale Entwicklungsumgebungen reproduzierbar zu starten: Anwendung, PostgreSQL, Kafka, Redis, WireMock oder andere Abhängigkeiten. Compose hilft, Onboarding zu beschleunigen und lokale Umgebung von zufällig installierter Infrastruktur zu entkoppeln.

Ich nutze Compose aber nicht als Ersatz für Kubernetes-Produktionsbetrieb. Es ist hervorragend für lokal und CI-nahe Tests, aber Produktionsorchestrierung braucht andere Konzepte: Scheduling, Rolling Updates, Secrets, Probes, Autoscaling und Policies.

Senior-Antwort:

„Compose ist mein Werkzeug für reproduzierbare lokale Infrastruktur. Für Testautomatisierung nutze ich oft Testcontainers, für Produktion den Orchestrator.“

Häufige Fehler:
  • Compose als Produktionsorchestrator missbrauchen.
  • depends_on mit echter Betriebsbereitschaft verwechseln.
  • Keine Healthchecks für Datenbanken.
  • Lokale Ports kollidieren.
  • Secrets in Compose-Dateien committen.
  • Zu große lokale Stacks bauen.
Best Practices:
  • Compose für lokale Entwicklung nutzen.
  • Healthchecks für Infrastruktur.
  • Environment klar dokumentieren.
  • Secrets nicht committen.
  • Profile für optionale Services nutzen.
  • Compose und Testcontainers sinnvoll trennen.
  • Ports bewusst vergeben.
  • Volumes für persistente lokale Daten bewusst einsetzen.
Stolperfalle:

depends_on bedeutet nicht automatisch, dass eine Datenbank migrationsbereit oder eine Anwendung fachlich startklar ist.

Merksatz:

Compose macht lokale Abhängigkeiten reproduzierbar.

B4-K07-Q008 – Volumes, Dateisystem und zustandslose ContainerContainerzustand ist flüchtig, bis Sie ihn bewusst persistent machen.
Frage:

„Wie gehen Sie mit Dateien und persistentem Zustand in Docker-Containern um?“

Kurz erklärt:

Container-Dateisysteme sind grundsätzlich vergänglich. Persistente Daten gehören in Volumes, externe Speicher oder Datenbanken.

Musterantwort:

Ich behandle Container als kurzlebig. Alles, was im writable Container Layer gespeichert wird, kann beim Recreate verloren gehen. Persistenter Zustand gehört in Volumes, Datenbanken, Object Storage oder externe Systeme.

Für Java-Services bedeutet das: Logs nach stdout/stderr, temporäre Dateien nach /tmp , Uploads oder Reports nicht dauerhaft im Container Layer speichern. Wenn Dateien lokal nötig sind, nutze ich Volumes oder Mounts bewusst und dokumentiere Lifecycle, Backup und Berechtigungen.

Senior-Antwort:

„Ein Container ist kein Fileserver. Wenn Daten den Container überleben müssen, brauchen sie einen expliziten Speicherort mit Lifecycle.“

Häufige Fehler:
  • Persistente Daten im Container Layer speichern.
  • Logs nur in lokale Dateien schreiben.
  • Read-only Filesystem aktivieren und /tmp vergessen.
  • Volumes ohne Backup-Strategie.
  • File Permissions ignorieren.
  • Heap Dumps unkontrolliert in kleine Container schreiben.
Best Practices:
  • Container zustandslos halten, wo möglich.
  • Persistenz in externe Systeme verlagern.
  • Volumes bewusst für lokale oder spezielle Fälle.
  • Logs nach stdout/stderr.
  • Read-only root filesystem prüfen.
  • Schreibbare temporäre Pfade explizit konfigurieren.
  • File Permissions testen.
  • Storage-Limits überwachen.
Stolperfalle:

Ein Service funktioniert lokal, weil er in den Container schreiben darf, fällt aber in Produktion mit read-only Filesystem oder fehlenden Permissions aus.

Merksatz:

Containerzustand ist flüchtig, bis Sie ihn bewusst persistent machen.

B4-K07-Q009 – Healthchecks, Graceful Shutdown und BetriebsreifeProduktionsreife Container beherrschen Start, Betrieb und Ende.
Frage:

„Was macht einen Docker-Container für Produktion betriebsreif?“

Kurz erklärt:

Ein produktionsreifer Container muss korrekt starten, Bereitschaft melden, sauber beendet werden und beobachtbar sein.

Musterantwort:

Ein betriebsreifer Container startet reproduzierbar, signalisiert Health und Readiness, schreibt Logs nach stdout/stderr, reagiert korrekt auf SIGTERM, beendet laufende Requests sauber, hat Ressourcenlimits, ist beobachtbar und läuft mit minimalen Rechten.

Für Spring Boot nutze ich Actuator Health Probes, graceful shutdown und Metriken. Wichtig ist, dass Readiness nicht nur „Prozess läuft“ bedeutet. Ein Service ist erst ready, wenn er Requests sinnvoll bearbeiten kann, etwa nach Migrationen, Cache-Warmup oder Verbindung zu kritischen Abhängigkeiten.

Senior-Antwort:

„Ein Container ist nicht ready, weil der Prozess läuft. Er ist ready, wenn er seine vertragliche Arbeit sicher übernehmen kann.“

Häufige Fehler:
  • Readiness mit Liveness verwechseln.
  • Healthcheck hängt von jedem Downstream ab.
  • Container ignoriert SIGTERM.
  • Shell-Entrypoint leitet Signale nicht weiter.
  • Zu kurze Shutdown-Zeit.
  • Kafka Listener stoppen nicht sauber.
Best Practices:
  • Liveness und Readiness trennen.
  • Readiness fachlich definieren.
  • Graceful Shutdown aktivieren.
  • Entrypoint signalfreundlich gestalten.
  • Logs nach stdout/stderr.
  • Healthchecks schnell und stabil halten.
  • Shutdown-Verhalten testen.
  • Metriken und Traces bereitstellen.
Stolperfalle:

Wenn Readiness von einem optionalen Downstream abhängt, kann ein fremder Ausfall Ihren Service komplett aus dem Load Balancing nehmen.

Merksatz:

Produktionsreife Container beherrschen Start, Betrieb und Ende.

B4-K07-Q010 – CI/CD, Registry und Image PromotionBuild once, scan once, promote by digest. 24h
Frage:

„Wie integrieren Sie Docker Images sauber in eine CI/CD-Pipeline?“

Kurz erklärt:

Docker-Images sind CI/CD-Artefakte. Sie sollten eindeutig getaggt, getestet, gescannt, signiert, gepusht und unverändert durch Umgebungen promoted werden.

Musterantwort:

Die Pipeline baut nach Tests ein Image mit eindeutigem, unveränderlichem Tag, typischerweise Commit SHA oder Build-ID. Danach wird das Image gescannt, optional signiert, mit SBOM versehen und in eine Registry gepusht. Deployments referenzieren genau dieses Image.

Ich vermeide, für jede Umgebung neu zu bauen. Promotion bedeutet: dasselbe Image wandert von Test nach Staging nach Produktion. Nur Konfiguration und Deployment-Parameter ändern sich. Dadurch werden Deployments reproduzierbarer und Rollbacks einfacher.

Senior-Antwort:

„Ich deploye nicht ‘irgendein neu gebautes Prod-Image’. Ich deploye genau das Artefakt, das getestet, gescannt und freigegeben wurde.“

Häufige Fehler:
  • latest deployen.
  • Images pro Umgebung neu bauen.
  • Ungescannte Images deployen.
  • Registry-Retention löscht noch benötigte Images.
  • Build-Args mit Secrets verwenden.
  • Tag mutieren und damit Reproduzierbarkeit verlieren.
Best Practices:
  • Eindeutige immutable Tags verwenden.
  • Deployments nach Digest pinnen.
  • Images einmal bauen, mehrfach deployen.
  • Scans und SBOM in Pipeline integrieren.
  • Signierung prüfen.
  • Registry-Retention bewusst konfigurieren.
  • Rollback auf bekannte Digests ermöglichen.
  • Build Cache in CI nutzen.
Stolperfalle:

Ein Tag ist veränderbar. Ein Digest identifiziert den Image-Inhalt eindeutig.

Merksatz:

Build once, scan once, promote by digest.

B4-K08 – Kapitel 8: Kubernetes 10 Karten

B4-K08-Q001 – Kubernetes-Grundmodell: Cluster, Node, Pod und ContainerKubernetes betreibt Pods, keine Haustiere. 24h
Frage:

„Was ist ein Pod, und warum deployt Kubernetes nicht einfach einzelne Container?“

Kurz erklärt:

Ein Pod ist die kleinste deploybare Einheit in Kubernetes. Ein Pod enthält einen oder mehrere Container, die Netzwerk- und Storage-Kontext teilen.

Musterantwort:

Ein Pod ist die kleinste Einheit, die Kubernetes plant und verwaltet. Er kapselt einen oder mehrere eng zusammengehörige Container. Container in einem Pod teilen sich Netzwerk-Namespace, können über localhost kommunizieren und können gemeinsame Volumes nutzen.

Für typische Spring-Boot-Services enthält ein Pod meist genau einen Applikationscontainer. Zusätzliche Container können Sidecars sein, etwa für Proxy, Log-Agent, Service-Mesh-Integration oder Hilfsprozesse. Wichtig ist: Kubernetes skaliert Pods, nicht einzelne Container innerhalb eines Pods.

Senior-Antwort:

„Ein Pod ist keine kleine VM. Er ist eine ersetzbare Laufzeiteinheit. Meine Anwendung muss damit rechnen, dass sie jederzeit neu gestartet oder verschoben wird.“

Häufige Fehler:
  • Pod als dauerhafte Maschine betrachten.
  • Lokalen Pod-Speicher als persistent annehmen.
  • Manuelle Änderungen im Pod vornehmen.
  • Einen Pod direkt statt Deployment betreiben.
  • Sidecars ohne klaren Nutzen einführen.
  • JVM-Ressourcenlimits ignorieren.
Best Practices:
  • Anwendungen pod-freundlich und stateless bauen.
  • Pods als ersetzbar behandeln.
  • Deployments statt nackter Pods verwenden.
  • Logs nach stdout/stderr schreiben.
  • Ressourcenrequests setzen.
  • Graceful Shutdown implementieren.
  • Sidecars nur bei klarem Betriebsnutzen einsetzen.
Stolperfalle:

Alles, was nur im Pod-Dateisystem liegt, ist potenziell weg, wenn der Pod ersetzt wird.

Merksatz:

Kubernetes betreibt Pods, keine Haustiere.

B4-K08-Q002 – Deployment, ReplicaSet und Rolling UpdatesDeployment rollt Pods aus; Kompatibilität muss die Anwendung liefern.
Frage:

„Wie funktioniert ein Kubernetes Deployment, und was passiert bei einem Rolling Update?“

Kurz erklärt:

Ein Deployment verwaltet ReplicaSets und Pods deklarativ. Es ermöglicht Rolling Updates und Rollbacks.

Musterantwort:

Ein Deployment beschreibt den gewünschten Zustand: Image, Replica-Anzahl, Labels, Pod Template und Update-Strategie. Kubernetes sorgt über ReplicaSets dafür, dass die gewünschte Anzahl Pods läuft.

Bei einem Rolling Update wird ein neues ReplicaSet mit dem neuen Pod Template erzeugt. Kubernetes skaliert neue Pods hoch und alte Pods herunter. Mit maxUnavailable und maxSurge steuere ich, wie viele Pods während des Updates fehlen dürfen und wie viele zusätzliche Pods temporär erlaubt sind. Kubernetes beschreibt Rolling Updates als inkrementelles Ersetzen alter Pods durch neue Pods, wobei neue Pods auf Nodes mit Ressourcen gestartet werden und Kubernetes wartet, bis neue Pods laufen, bevor alte entfernt werden.

Senior-Antwort:

„Ein Deployment macht nur dann zero-downtime Updates möglich, wenn meine Anwendung korrekt readiness-fähig ist und alte und neue Versionen kurzzeitig parallel funktionieren.“

Häufige Fehler:
  • Deployment ohne Readiness Probe.
  • maxUnavailable zu hoch für kritische Services.
  • Rollout startet, obwohl neue Pods nicht wirklich bereit sind.
  • Image-Tag latest verwenden.
  • Keine Rollback-Strategie.
  • Migrationen nicht rollout-kompatibel gestalten.
Best Practices:
  • Deployments für stateless Services verwenden.
  • Readiness korrekt konfigurieren.
  • RollingUpdate-Parameter bewusst setzen.
  • Image-Digests oder immutable Tags verwenden.
  • Rollout-Status überwachen.
  • Datenbankmigrationen backward-compatible gestalten.
  • Rollbacks regelmäßig testen.
  • Revision History begrenzen, aber nicht auf null setzen.
Stolperfalle:

Rolling Update schützt nicht vor inkompatiblen Datenbankmigrationen oder breaking API Changes zwischen alter und neuer Version.

Merksatz:

Deployment rollt Pods aus; Kompatibilität muss die Anwendung liefern.

B4-K08-Q003 – Service, DNS und IngressPods sind flüchtig; Services sind stabile Adressen.
Frage:

„Warum braucht man in Kubernetes einen Service, wenn Pods bereits IP-Adressen haben?“

Kurz erklärt:

Services geben Pods eine stabile Netzwerkadresse. Ingress veröffentlicht HTTP-Zugriff von außen oder am Plattformrand.

Musterantwort:

Pod-IPs sind dynamisch und Pods sind kurzlebig. Ein Service bietet eine stabile virtuelle Adresse und DNS-Namen für eine Gruppe von Pods, die über Labels selektiert werden. Clients rufen den Service auf, nicht einzelne Pods.

Ingress ist eine HTTP-Routing-Schicht am Rand, die externe Requests anhand von Host und Path an Services weiterleitet. Für interne Service-to-Service-Kommunikation reicht oft ein ClusterIP-Service; für externen HTTP-Zugriff nutzt man Ingress oder Gateway-Mechanismen.

Senior-Antwort:

„Clients sollten Services adressieren, nicht Pods. Pods kommen und gehen; Services geben dem System stabile Namen.“

Häufige Fehler:
  • Pod-IPs direkt konfigurieren.
  • Service-Selector passt nicht zu Pod-Labels.
  • Ingress für interne Kommunikation missbrauchen.
  • Kein Timeout bei Service-to-Service Calls.
  • TLS-Terminierung unklar.
  • DNS- und Connection-Pool-Verhalten ignorieren.
Best Practices:
  • Services für stabile interne Adressen nutzen.
  • Labels und Selectors konsistent standardisieren.
  • ClusterIP für interne Services bevorzugen.
  • Ingress/Gateway für externe HTTP-Routen.
  • Service-to-Service Timeouts setzen.
  • TLS- und AuthN/AuthZ-Grenzen klären.
  • NetworkPolicies für Zugriffsbeschränkung prüfen.
Stolperfalle:

Ein Kubernetes Service löst Adressierung, aber keine Resilience. Ein langsamer oder fehlerhafter Downstream bleibt ein Downstream-Problem.

Merksatz:

Pods sind flüchtig; Services sind stabile Adressen.

B4-K08-Q004 – ConfigMaps, Secrets und Runtime-KonfigurationKonfiguration ist Runtime-Vertrag, Secrets sind Sicherheitsobjekte.
Frage:

„Wie verwenden Sie ConfigMaps und Secrets für Spring-Boot-Services?“

Kurz erklärt:

ConfigMaps speichern nicht-sensitive Konfiguration. Secrets speichern sensible Werte, müssen aber trotzdem sorgfältig geschützt werden.

Musterantwort:

Ich halte das Container-Image unverändert und injiziere Konfiguration zur Runtime. Nicht-sensitive Werte wie Base URLs, Timeouts oder Feature-Konfigurationen kommen aus ConfigMaps. Sensitive Werte wie Passwörter, Tokens oder private Schlüssel kommen aus Secrets oder einem externen Secret Manager.

In Spring Boot mappe ich Konfiguration auf validierte @ConfigurationProperties . Kritische Werte müssen beim Start validiert werden. Außerdem behandle ich Kubernetes Secrets nicht als vollständige Sicherheitslösung: RBAC, Verschlüsselung at rest, Zugriffsbeschränkung, Rotation und Maskierung in Logs bleiben wichtig.

Senior-Antwort:

„ConfigMaps sind keine Secrets. Und Secrets sind kein Grund, Zugriffskontrolle, Rotation und Maskierung zu ignorieren.“

Häufige Fehler:
  • Secrets in ConfigMaps speichern.
  • Secrets in Logs oder Actuator ausgeben.
  • Keine Validierung von Konfiguration.
  • ConfigMap-Änderung erwarten, ohne Rollout/Reload.
  • Zu breite RBAC-Rechte auf Secrets.
  • Umgebungsspezifische Images bauen.
Best Practices:
  • Config und Secrets trennen.
  • Konfiguration validieren.
  • Secrets minimal exponieren.
  • RBAC strikt halten.
  • Secret-Rotation planen.
  • Config-Änderungen versionieren.
  • Rollout bei Config-Änderungen automatisieren.
  • Externe Secret Manager für kritische Secrets prüfen.
Stolperfalle:

Ein geänderter Secret-Wert bedeutet nicht automatisch, dass jede Anwendung sofort neue Credentials verwendet.

Merksatz:

Konfiguration ist Runtime-Vertrag, Secrets sind Sicherheitsobjekte.

B4-K08-Q005 – Liveness, Readiness und Startup ProbesNicht jeder nicht-bereite Pod ist ein toter Pod.
Frage:

„Was ist der Unterschied zwischen Liveness, Readiness und Startup Probe?“

Kurz erklärt:

Probes steuern, ob ein Container neu gestartet wird und ob ein Pod Traffic erhalten darf.

Musterantwort:

Liveness prüft, ob der Container noch lebt. Wenn Liveness fehlschlägt, kann Kubernetes den Container neu starten. Readiness prüft, ob der Pod bereit ist, Traffic zu erhalten. Wenn Readiness fehlschlägt, wird der Pod aus Service-Endpunkten entfernt, aber nicht zwingend neu gestartet. Startup Probe hilft bei langsam startenden Anwendungen; solange sie läuft, können Liveness-Prüfungen zurückgestellt werden.

Für Spring Boot ist das wichtig: Eine JVM kann leben, aber noch nicht bereit sein. Readiness sollte erst grün sein, wenn die Anwendung wirklich Requests annehmen kann. Kubernetes dokumentiert, dass Probes periodisch durch den Kubelet ausgeführt werden und je nach Ergebnis Container neu starten oder Traffic zu nicht bereiten Containern stoppen können.

Senior-Antwort:

„Liveness beantwortet: Soll Kubernetes mich neu starten? Readiness beantwortet: Soll ich Traffic bekommen? Diese Fragen dürfen nicht verwechselt werden.“

Häufige Fehler:
  • Liveness und Readiness identisch konfigurieren.
  • Liveness von Datenbank oder externen Services abhängig machen.
  • Zu kurze initiale Probe-Zeiten für Java-Apps.
  • Readiness zu früh grün.
  • Teure Healthchecks.
  • Keine Probes und trotzdem Rolling Updates erwarten.
Best Practices:
  • Startup Probe für langsam startende JVMs nutzen.
  • Liveness minimal halten.
  • Readiness fachlich korrekt definieren.
  • Probes schnell und billig machen.
  • Spring Boot Actuator Probes verwenden.
  • Probe-Zeiten unter Last testen.
  • Downstream-Abhängigkeiten bewusst in Readiness einordnen.
  • Rollout-Verhalten mit Probes testen.
Stolperfalle:

Eine Liveness Probe, die die Datenbank prüft, kann bei Datenbankausfall einen Restart-Sturm gesunder Pods auslösen.

Merksatz:

Nicht jeder nicht-bereite Pod ist ein toter Pod.

B4-K08-Q006 – Requests, Limits, QoS und JVM-RessourcenKubernetes plant Requests; die Runtime erzwingt Limits.
Frage:

„Wie setzen Sie CPU- und Memory-Requests/Limits für Java-Services in Kubernetes?“

Kurz erklärt:

Requests beeinflussen Scheduling. Limits begrenzen Ressourcenverbrauch. Für Java ist Memory-Limit besonders kritisch, weil Heap nicht der gesamte Prozessspeicher ist.

Musterantwort:

Requests sind die Ressourcen, die Kubernetes für Scheduling einplant. Limits sind Obergrenzen. Für Java setze ich Memory so, dass der Heap nicht das gesamte Limit belegt. Zusätzlich brauche ich Platz für Metaspace, Thread Stacks, Direct Memory, Code Cache und native JVM-Strukturen.

CPU-Requests sollten realistische Grundlast widerspiegeln, sonst werden Pods schlecht geplant. CPU-Limits sind vorsichtig zu setzen, weil throttling Latenz verursachen kann. Für Memory sind Limits meist wichtig, weil ein Pod sonst Node-Speicher gefährden kann. Für HPA sind CPU-/Memory-Requests ebenfalls relevant, weil resource utilization auf Requests basiert. Kubernetes dokumentiert für HPA, dass Ressourcenrequests wie CPU und Memory in der Pod-Spezifikation gesetzt werden sollten, da sie zur Berechnung der Ressourcenauslastung verwendet werden.

Senior-Antwort:

„Requests sind Scheduling-Verträge, Limits sind harte Grenzen. Bei Java muss ich immer den gesamten JVM-Prozess betrachten, nicht nur den Heap.“

Häufige Fehler:
  • Keine Requests setzen.
  • Heap gleich Memory-Limit setzen.
  • CPU-Limits ungetestet setzen.
  • HPA nutzen, aber Requests vergessen.
  • OOMKill mit Java Heap OOM verwechseln.
  • Limits aus der Luft greifen statt Lasttests nutzen.
Best Practices:
  • Requests auf gemessener Last basieren.
  • Memory-Limits mit Non-Heap-Budget setzen.
  • JVM-Container-Optionen konfigurieren.
  • CPU-Throttling beobachten.
  • Lasttests mit echten Limits durchführen.
  • HPA-Metriken und Requests gemeinsam planen.
  • OOMKill- und GC-Metriken überwachen.
  • QoS bewusst wählen.
Stolperfalle:

Ein Pod kann OOMKilled werden, obwohl der Java Heap nicht voll wirkt, weil Non-Heap oder native Speicher das Container-Limit sprengen.

Merksatz:

Kubernetes plant Requests; die Runtime erzwingt Limits.

B4-K08-Q007 – Horizontal Pod Autoscaler und SkalierungsstrategieAutoscaling skaliert Pods, nicht automatisch Kapazität des Gesamtsystems.
Frage:

„Wie funktioniert Horizontal Pod Autoscaling, und worauf achten Sie bei Java-Services?“

Kurz erklärt:

HPA skaliert Pods horizontal anhand von Metriken. Skalierung ist reaktiv und hängt stark von Requests, Metriken, Startzeit und Lastprofil ab.

Musterantwort:

Der Horizontal Pod Autoscaler passt die Anzahl der Replikas eines skalierbaren Workloads wie Deployment oder ReplicaSet anhand beobachteter Metriken an. Häufig wird CPU-Auslastung verwendet, aber auch Memory, Custom Metrics oder externe Metriken sind möglich. Kubernetes beschreibt HPA als Mechanismus, der Zielressourcen anhand beobachteter Pod-Metriken hoch- oder herunterskaliert.

Bei Java-Services achte ich auf realistische CPU-Requests, JVM-Startzeit, Warmup, Readiness, Connection Pools und Downstream-Kapazität. HPA erzeugt keine sofortige Kapazität. Wenn ein Pod 60 Sekunden bis ready braucht, kann eine Traffic-Spitze bereits p99-Probleme verursachen, bevor neue Pods helfen.

Senior-Antwort:

„Autoscaling ist kein Ersatz für Capacity Planning. HPA reagiert auf Signale, aber neue JVM-Kapazität braucht Startzeit, Warmup und Downstream-Kapazität.“

Häufige Fehler:
  • HPA ohne CPU-Requests.
  • Nur CPU skalieren, obwohl Bottleneck IO oder Queue Lag ist.
  • maxReplicas zu niedrig.
  • Downstreams bei Scale-out überlasten.
  • Readiness zu früh.
  • Autoscaling nicht unter Last testen.
Best Practices:
  • Requests realistisch setzen.
  • Min/Max Replicas bewusst definieren.
  • Startup- und Readiness-Zeit messen.
  • Custom Metrics für Queue/Kafka Lag prüfen.
  • Downstream-Kapazität berücksichtigen.
  • Scale-out und scale-in testen.
  • SLOs und Autoscaling gemeinsam betrachten.
  • Flapping durch Stabilization und passende Schwellen vermeiden.
Stolperfalle:

Mehr Pods bedeuten nicht automatisch mehr Durchsatz, wenn die Datenbank, Kafka-Partitionen oder ein externer Provider der eigentliche Bottleneck sind.

Merksatz:

Autoscaling skaliert Pods, nicht automatisch Kapazität des Gesamtsystems.

B4-K08-Q008 – StatefulSet, Volumes und persistenter ZustandStateless ist der Default; Stateful braucht Begründung.
Frage:

„Wann verwenden Sie StatefulSet statt Deployment?“

Kurz erklärt:

Deployments eignen sich für stateless Workloads. StatefulSets eignen sich für Workloads, die stabile Identität, geordnete Skalierung oder persistente Volumes pro Instanz brauchen.

Musterantwort:

Ich verwende StatefulSet, wenn Pods stabile Identitäten, stabile Netzwerknamen, geordnete Rollouts oder individuelle persistente Volumes brauchen. Typische Beispiele sind Datenbanken, Broker, bestimmte Cluster-Komponenten oder zustandsbehaftete Systeme.

Für normale Spring-Boot-REST-Services ist meistens Deployment richtig. Persistenter fachlicher Zustand gehört in Datenbanken oder externe Storage-Systeme, nicht in lokale Pod-Dateien. Wenn ein Java-Service StatefulSet braucht, sollte man genau prüfen, warum.

Senior-Antwort:

„StatefulSet ist kein besseres Deployment. Es ist ein Werkzeug für Workloads, deren Identität und Speicher pro Instanz wichtig sind.“

Häufige Fehler:
  • StatefulSet für normale REST-Services verwenden.
  • Lokale Dateien als fachlichen Zustand speichern.
  • Volumes ohne Backup-Strategie.
  • Storage Performance nicht messen.
  • Zugriffsmodes ignorieren.
  • Stateful Workloads ohne Plattformreife betreiben.
Best Practices:
  • Stateless Services als Deployment.
  • Zustand in externe Datenbanken/Storage.
  • StatefulSet nur bei echtem Bedarf.
  • PVCs, Backup und Restore planen.
  • Storage-Klasse bewusst wählen.
  • IO-Metriken beobachten.
  • Stateful Rollouts besonders testen.
  • Datenbankbetrieb in Kubernetes kritisch bewerten.
Stolperfalle:

Ein lokaler Volume Mount macht einen Service nicht automatisch sicher stateful. Backup, Restore, Rescheduling und Storage-Performance bleiben ungelöst.

Merksatz:

Stateless ist der Default; Stateful braucht Begründung.

B4-K08-Q009 – Security: RBAC, ServiceAccounts, NetworkPolicy und Pod SecurityJeder Pod braucht minimale Identität, minimale Rechte und minimale Kommunikationswege.
Frage:

„Welche Kubernetes-Security-Themen sind für Anwendungsteams relevant?“

Kurz erklärt:

Kubernetes-Security umfasst Identität, Rechte, Netzwerkzugriff, Container-Härtung, Secrets und Policy Enforcement.

Musterantwort:

Anwendungsteams müssen mindestens ServiceAccounts, RBAC, Secrets, NetworkPolicies und Pod Security verstehen. Ein Pod sollte mit eigener Identität laufen, nur notwendige API-Rechte haben, nicht als root laufen, keine Privilege Escalation erlauben und nur mit notwendigen Services kommunizieren dürfen.

Security ist nicht nur Plattformteam-Aufgabe. Wenn eine Anwendung alle Secrets lesen kann, als root läuft, jedes interne System erreichen darf und Actuator offen exponiert, ist das ein Anwendungsarchitekturproblem.

Senior-Antwort:

„Kubernetes-Security ist mehrschichtig. Spring Security schützt meine API, aber RBAC, NetworkPolicy und Pod Security schützen die Plattformgrenzen.“

Häufige Fehler:
  • Default ServiceAccount verwenden.
  • ClusterRole zu breit vergeben.
  • Alle Pods dürfen mit allen Pods sprechen.
  • Container laufen als root.
  • Privilege Escalation erlaubt.
  • Actuator ungeschützt exponieren.
  • Secrets in Logs ausgeben.
Best Practices:
  • Eigener ServiceAccount pro Workload.
  • Least Privilege RBAC.
  • NetworkPolicies für kritische Pfade.
  • Non-root Container.
  • Capabilities droppen.
  • Read-only Root Filesystem prüfen.
  • Secrets minimal mounten.
  • Actuator und Admin-Endpunkte schützen.
Stolperfalle:

Ein Pod ohne öffentliche API kann trotzdem gefährlich sein, wenn er mit breiten Kubernetes-Rechten und Zugriff auf viele Secrets läuft.

Merksatz:

Jeder Pod braucht minimale Identität, minimale Rechte und minimale Kommunikationswege.

B4-K08-Q010 – Observability, Debugging und ProduktionsbetriebKubernetes-Observability verbindet Plattformzustand mit Anwendungssignalen. 24h
Frage:

„Wie debuggen und betreiben Sie Spring-Boot-Anwendungen in Kubernetes?“

Kurz erklärt:

Kubernetes-Betrieb braucht Logs, Metriken, Events, Traces, Rollout-Status, Pod-Zustände und klare Debugging-Prozesse.

Musterantwort:

Ich beginne mit dem Kubernetes-Zustand: Deployment, ReplicaSet, Pods, Events, Container-Status, Restarts, Probes, Ressourcenverbrauch und Logs. Danach prüfe ich Anwendungssignale: Actuator Health, Metriken, Traces, Correlation IDs, Fehlerquoten, p95/p99, DB-Pool, Kafka Lag und Downstream-Latenzen.

Wichtig ist, dass Observability bereits eingebaut ist. Im Incident sollte ich nicht erst neue Logs hinzufügen müssen. Für Java brauche ich außerdem JVM-Metriken: Heap, GC, Threads, CPU, Classloading, Direct Memory und gegebenenfalls JFR oder Thread Dumps.

Senior-Antwort:

„In Kubernetes debugge ich nicht nur die Anwendung und nicht nur den Pod. Ich verbinde Kubernetes-Events, Containerstatus, JVM-Metriken, Logs, Traces und fachliche Signale.“

Häufige Fehler:
  • Nur Application Logs prüfen, Kubernetes Events ignorieren.
  • Keine JVM-Metriken.
  • Zu hohe Metric Cardinality.
  • Keine Correlation IDs.
  • Heap Dumps unkontrolliert in kleine Volumes schreiben.
  • Debugging nur durch Shell im Container planen.
  • Alerts auf einzelne Pods statt SLOs.
Best Practices:
  • Strukturierte Logs.
  • Metriken für App, JVM und Container.
  • Distributed Tracing.
  • Kubernetes Events in Diagnose einbeziehen.
  • Dashboards pro Service.
  • Alerts auf SLOs und Symptome.
  • Runbooks für häufige Fehler.
  • Sichere Diagnosepfade für Dumps.
Stolperfalle:

Ein CrashLoopBackOff ist kein Root Cause. Es ist ein Zustand, der sagt: Der Container startet wiederholt nicht stabil.

Merksatz:

Kubernetes-Observability verbindet Plattformzustand mit Anwendungssignalen.

B4-K09 – Kapitel 9: SQL 10 Karten

B4-K09-Q001 – Relationale Modellierung und NormalisierungEin gutes Schema schützt Daten auch dann, wenn Anwendungscode versagt. 24h
Frage:

„Wie modellieren Sie relationale Datenbanken für Enterprise-Anwendungen?“

Kurz erklärt:

Relationale Modellierung strukturiert Daten in Tabellen, Beziehungen und Constraints. Normalisierung reduziert Redundanz und schützt Konsistenz.

Musterantwort:

Ich beginne mit dem fachlichen Modell und den Invarianten. Tabellen sollten klare fachliche Entitäten oder Beziehungen repräsentieren. Primärschlüssel, Fremdschlüssel, Unique Constraints, Check Constraints und Not-Null-Regeln gehören bewusst ins Schema, weil sie Datenintegrität unabhängig vom Anwendungscode schützen.

Normalisierung ist wichtig, um Redundanz und Update-Anomalien zu vermeiden. Ich denormalisiere nur bewusst, wenn konkrete Query- oder Performance-Anforderungen es rechtfertigen, zum Beispiel für Reporting oder Read Models. Die Datenbank ist nicht nur ein Speicher, sondern ein Integritätsmechanismus.

Senior-Antwort:

„Ich verlasse mich nicht nur auf Java-Validierung. Kritische Datenintegrität gehört auch in die Datenbank, weil sie die letzte Konsistenzgrenze ist.“

Häufige Fehler:
  • Datenbank nur als Dump für Java-Objekte behandeln.
  • Keine Foreign Keys aus Performanceangst.
  • Fachliche Constraints nur im Service-Code prüfen.
  • Zu viele nullable Spalten ohne Bedeutung.
  • Denormalisierung ohne Konsistenzstrategie.
  • JPA-Autogeneration als Schema-Design verwenden.
Best Practices:
  • Fachliche Invarianten als Constraints ausdrücken.
  • Not Null, Unique und Check Constraints bewusst setzen.
  • Foreign Keys für echte Beziehungen verwenden.
  • Normalisieren als Default betrachten.
  • Denormalisierung begründen und testen.
  • Schema-Migrationen versionieren.
  • Datenmodell mit Query-Anforderungen abgleichen.
Stolperfalle:

Ein ORM-Modell kann kompiliert und funktionieren, obwohl das relationale Modell fachlich schwach, inkonsistent oder schwer abfragbar ist.

Merksatz:

Ein gutes Schema schützt Daten auch dann, wenn Anwendungscode versagt.

B4-K09-Q002 – Joins, Aggregationen und Query-DenkenSQL fragt Mengen ab; Java verarbeitet Objekte.
Frage:

„Wie erklären Sie Joins, Aggregationen und GROUP BY in SQL?“

Kurz erklärt:

SQL ist mengenorientiert. Gute Queries formulieren, welche Datenmenge gebraucht wird, nicht wie jedes Objekt einzeln geladen wird.

Musterantwort:

Ein Join kombiniert Zeilen aus mehreren Tabellen anhand einer Bedingung. Ein INNER JOIN liefert nur passende Zeilen auf beiden Seiten. Ein LEFT JOIN liefert alle linken Zeilen und ergänzt rechte Daten, wenn sie existieren. Aggregationen wie COUNT , SUM , MAX verdichten mehrere Zeilen zu Kennzahlen. GROUP BY definiert, pro welcher Gruppe aggregiert wird.

Wichtig ist, Filter korrekt zu platzieren. Wenn ich bei einem LEFT JOIN Bedingungen auf die rechte Tabelle in die WHERE -Klausel schreibe, kann ich versehentlich wieder einen INNER JOIN daraus machen. Deshalb steht p.status = 'CAPTURED' im Beispiel in der Join-Bedingung.

Senior-Antwort:

„SQL ist mengenorientiert. Wenn ich tausende Entities lade, um sie in Java zu zählen, sollte ich zuerst prüfen, ob die Datenbank das nicht besser kann.“

Häufige Fehler:
  • LEFT JOIN durch WHERE -Filter kaputt machen.
  • Aggregationen in Java statt SQL berechnen.
  • SELECT * in Reporting-Queries.
  • GROUP BY ohne Verständnis der Kardinalität.
  • Joins ohne passende Indizes.
  • Unbegrenzte Result Sets.
Best Practices:
  • Mengenorientiert denken.
  • Nur benötigte Spalten selektieren.
  • Joins fachlich und kardinalitätsbewusst schreiben.
  • Filter früh und korrekt setzen.
  • Aggregationen in der Datenbank nutzen.
  • Result Sets begrenzen.
  • Query-Pläne prüfen.
Stolperfalle:

Ein Filter auf die rechte Tabelle eines LEFT JOIN in der WHERE -Klausel kann die Semantik unbeabsichtigt verändern.

Merksatz:

SQL fragt Mengen ab; Java verarbeitet Objekte.

B4-K09-Q003 – Indizes und Query-PerformanceIndizes optimieren Queries, nicht Tabellen.
Frage:

„Wie funktionieren Datenbankindizes, und wie wählen Sie passende Indizes aus?“

Kurz erklärt:

Indizes beschleunigen Lesezugriffe, kosten aber Speicher und Schreibaufwand. Gute Indizes folgen konkreten Query-Mustern.

Musterantwort:

Ein Index ist eine zusätzliche Datenstruktur, meist B-Tree-artig, die Suche, Sortierung und teilweise Constraints beschleunigt. Ich wähle Indizes nicht pauschal auf jede Spalte, sondern nach wichtigen Query-Mustern: Filter, Join-Bedingungen, Sortierung und Selektivität.

Für WHERE customer_id = ? ORDER BY created_at DESC LIMIT 50 ist ein zusammengesetzter Index auf (customer_id, created_at DESC) sinnvoll, weil die Datenbank zuerst nach Customer einschränken und dann bereits sortiert lesen kann. Jeder Index kostet aber Schreibperformance, Speicher und Wartung.

Senior-Antwort:

„Ein Index ist kein Dekorator für Spalten. Er ist eine Antwort auf ein konkretes Query-Muster.“

Häufige Fehler:
  • Index auf jede Spalte setzen.
  • Composite-Index-Reihenfolge ignorieren.
  • Sortierung nicht berücksichtigen.
  • Low-Cardinality-Spalten isoliert indexieren.
  • Query-Pläne nie prüfen.
  • Indizes für seltene Queries bauen und Writes verlangsamen.
Best Practices:
  • Indizes aus Query-Mustern ableiten.
  • Composite-Indizes bewusst ordnen.
  • Filter, Join und Order By gemeinsam betrachten.
  • Ausführungspläne prüfen.
  • Index-Nutzung unter realistischen Datenmengen testen.
  • Schreibkosten berücksichtigen.
  • Unbenutzte Indizes regelmäßig entfernen.
  • Unique Indexes für fachliche Eindeutigkeit nutzen.
Stolperfalle:

Ein einzelner Index auf customer_id und ein einzelner Index auf created_at ersetzen nicht automatisch einen gut gewählten Composite Index für Filter plus Sortierung.

Merksatz:

Indizes optimieren Queries, nicht Tabellen.

B4-K09-Q004 – Transaktionen, ACID und Commit-GrenzenTransaktionen schützen lokale Daten, nicht automatisch verteilte Prozesse.
Frage:

„Was bedeutet ACID, und wie denken Sie über Transaktionsgrenzen in Spring-Anwendungen?“

Kurz erklärt:

Transaktionen gruppieren mehrere Datenbankoperationen zu einer atomaren Einheit. ACID beschreibt Atomicity, Consistency, Isolation und Durability.

Musterantwort:

ACID beschreibt zentrale Transaktionseigenschaften. Atomicity bedeutet alles oder nichts. Consistency bedeutet, dass Constraints und fachliche Regeln von einem gültigen Zustand in den nächsten führen. Isolation bedeutet, dass parallele Transaktionen sich kontrolliert beeinflussen. Durability bedeutet, dass ein Commit dauerhaft gespeichert ist.

In Spring setze ich Transaktionsgrenzen auf Use-Case-Ebene, nicht auf jede kleine Repository-Methode. Eine Überweisung muss Debit, Credit und Transfer-Record gemeinsam committen oder gemeinsam rollbacken. Remote Calls innerhalb einer DB-Transaktion vermeide ich möglichst, weil sie Locks lange halten und bei Fehlern schwer kontrollierbar sind.

Senior-Antwort:

„Eine Transaktion ist eine lokale Konsistenzgrenze. Ich halte sie so kurz wie möglich und so groß wie fachlich nötig.“

Häufige Fehler:
  • Transaktionen auf Repository-Methoden verstreuen.
  • Remote HTTP-Calls innerhalb langer Transaktionen.
  • Exceptions fangen und Rollback verhindern.
  • Self-Invocation bei @Transactional .
  • Transaktion zu groß schneiden.
  • Datenbanktransaktion mit Geschäftsprozess verwechseln.
Best Practices:
  • Transaktionen an Use-Case-Grenzen setzen.
  • Transaktionen kurz halten.
  • Remote Calls möglichst außerhalb lokaler DB-Transaktionen.
  • Rollback-Regeln bewusst kennen.
  • Flush-Verhalten verstehen.
  • Konsistenzgrenzen fachlich modellieren.
  • Outbox für Events innerhalb lokaler Transaktion nutzen.
Stolperfalle:

Ein erfolgreicher HTTP-Call wird nicht zurückgerollt, nur weil die nachfolgende Datenbanktransaktion scheitert.

Merksatz:

Transaktionen schützen lokale Daten, nicht automatisch verteilte Prozesse.

B4-K09-Q005 – Isolation Levels, Locks und AnomalienKorrekte Konkurrenz ist Design, nicht Glück.
Frage:

„Welche Isolation-Anomalien kennen Sie, und wie gehen Sie mit Locking um?“

Kurz erklärt:

Isolation Levels bestimmen, welche Nebenwirkungen paralleler Transaktionen sichtbar sind. Locks und MVCC steuern konkurrierenden Zugriff.

Musterantwort:

Typische Anomalien sind Dirty Reads, Non-repeatable Reads, Phantom Reads, Lost Updates und Write Skew. Je nach Datenbank und Isolation Level werden sie unterschiedlich verhindert. Wichtig ist, nicht nur den Namen des Isolation Levels zu kennen, sondern die konkrete Datenbankimplementierung.

Für konkurrierende Updates nutze ich oft optimistisches Locking mit Versionen, wenn Konflikte selten sind. Wenn Konflikte häufig oder kritisch sind, kann pessimistisches Locking mit SELECT FOR UPDATE sinnvoll sein. Beide Strategien müssen mit Timeouts, Retry und Fehlerbehandlung kombiniert werden.

Senior-Antwort:

„Isolation ist kein theoretisches Datenbankkapitel. Sie entscheidet, ob parallele Geschäftsprozesse korrekte Daten erzeugen.“

Häufige Fehler:
  • Isolation Level auswendig nennen, aber DB-Verhalten nicht kennen.
  • Lost Updates nicht absichern.
  • Pessimistische Locks zu lange halten.
  • Optimistische Konflikte nicht behandeln.
  • Deadlocks nicht retryen.
  • Locking und Business-Idempotenz verwechseln.
Best Practices:
  • Konfliktmuster fachlich analysieren.
  • Optimistic Locking bei seltenen Konflikten.
  • Pessimistic Locking bei kritischen Hotspots prüfen.
  • Locks kurz halten.
  • Deadlock- und Lock-Timeouts behandeln.
  • Retry-Strategien definieren.
  • Isolation Level datenbankspezifisch prüfen.
  • Lasttests mit Konkurrenz durchführen.
Stolperfalle:

Ein einzelner Unit-Test ohne echte Parallelität beweist nicht, dass Locking und Isolation korrekt funktionieren.

Merksatz:

Korrekte Konkurrenz ist Design, nicht Glück.

B4-K09-Q006 – Pagination, Sorting und große DatenmengenJe tiefer man mit OFFSET blättert, desto mehr Arbeit wirft man weg.
Frage:

„Was ist der Unterschied zwischen Offset Pagination und Keyset Pagination?“

Kurz erklärt:

Offset Pagination ist einfach, aber bei großen Offsets oft teuer und bei parallelen Änderungen instabil. Keyset Pagination skaliert für fortlaufende Listen besser.

Musterantwort:

Offset Pagination verwendet LIMIT und OFFSET . Sie ist einfach und erlaubt direkte Seitennummern, wird aber bei großen Offsets teuer, weil die Datenbank viele Zeilen überspringen muss. Außerdem können neue oder gelöschte Zeilen während der Navigation zu Duplikaten oder Lücken führen.

Keyset Pagination verwendet den letzten gesehenen Sortierschlüssel als Cursor. Sie ist effizienter für große Datenmengen und stabile Infinite-Scroll- oder Timeline-Use-Cases. Dafür kann man nicht beliebig direkt auf Seite 500 springen. Wichtig ist eine deterministische Sortierung, zum Beispiel created_at DESC, payment_id DESC .

Senior-Antwort:

„Pagination ist ein Datenbankdesign-Thema. Bei großen Tabellen ist OFFSET oft nur bequem, nicht skalierbar.“

Häufige Fehler:
  • Große Tabellen mit Offset Pagination.
  • Sortierung ohne eindeutigen Tie-Breaker.
  • Pagination ohne passenden Index.
  • Page mit Count Query für riesige Tabellen.
  • Neue Datensätze während Navigation ignorieren.
  • Cursor als unsichere interne SQL-Fragmente bauen.
Best Practices:
  • Offset für kleine, administrative Listen.
  • Keyset für große, fortlaufende Listen.
  • Deterministische Sortierung verwenden.
  • Cursor fachlich und sicher serialisieren.
  • Count Queries kritisch prüfen.
  • Passende Composite-Indizes anlegen.
  • Pagination unter Produktionsdaten testen.
Stolperfalle:

ORDER BY created_at allein ist nicht stabil, wenn mehrere Zeilen denselben Timestamp haben.

Merksatz:

Je tiefer man mit OFFSET blättert, desto mehr Arbeit wirft man weg.

B4-K09-Q007 – N+1, ORM-Fallen und SQL-SichtbarkeitORM ersetzt SQL-Schreiben, nicht SQL-Verstehen.
Frage:

„Was ist das N+1-Problem, und wie vermeiden Sie es in JPA/Hibernate-Anwendungen?“

Kurz erklärt:

N+1 entsteht, wenn eine initiale Query N Datensätze lädt und danach pro Datensatz eine weitere Query ausgeführt wird.

Musterantwort:

N+1 bedeutet: Ich lade zum Beispiel 100 Orders und greife danach für jede Order lazy auf Lines zu. Dadurch entstehen 1 Query für Orders plus 100 Queries für Lines. Das ist oft in Dev kaum sichtbar, aber in Produktion sehr teuer.

Ich vermeide N+1 durch bewusstes Fetching: Join Fetch, EntityGraph, Batch Fetching, DTO-Projections oder separate Query für Child-Daten. Wichtig ist, SQL sichtbar zu machen, Query-Zahlen zu testen und nicht blind auf Lazy Loading zu vertrauen.

Senior-Antwort:

„Hibernate versteckt SQL nicht, es erzeugt SQL. Ich muss wissen, welche Statements meine API wirklich ausführt.“

Häufige Fehler:
  • SQL-Logs nie aktivieren.
  • Lazy Loading in Serialisierung laufen lassen.
  • Open Session in View als Lösung für Architekturproblem.
  • Alles eager fetchen.
  • Join Fetch mit Pagination falsch kombinieren.
  • DTO-Projections ignorieren.
Best Practices:
  • SQL und Query Count sichtbar machen.
  • Fetching pro Use Case entscheiden.
  • DTO-Projections für Read APIs nutzen.
  • Batch Fetching für Collections prüfen.
  • EntityGraph bewusst einsetzen.
  • Lazy Loading nicht in Controller/JSON leaken.
  • Performance-Tests mit realistischen Daten.
  • ORM nicht als SQL-Ersatz betrachten.
Stolperfalle:

Open Session in View verhindert LazyInitializationException, kann aber N+1 bis in die View- oder JSON-Serialisierung verschieben.

Merksatz:

ORM ersetzt SQL-Schreiben, nicht SQL-Verstehen.

B4-K09-Q008 – Schema Migrationen, Flyway/Liquibase und Zero-DowntimeSchema-Evolution muss rollout-kompatibel sein.
Frage:

„Wie führen Sie Datenbankmigrationen in produktiven Systemen sicher durch?“

Kurz erklärt:

Schema Migrationen versionieren Datenbankänderungen. Zero-Downtime-Migrationen müssen alte und neue Anwendungsversionen parallel unterstützen.

Musterantwort:

Ich versioniere Migrationen mit Tools wie Flyway oder Liquibase und lasse sie kontrolliert in der Pipeline oder beim Deployment ausführen. Für Zero-Downtime nutze ich expand-and-contract: Zuerst kompatibel erweitern, zum Beispiel nullable Spalte hinzufügen. Dann Anwendung anpassen, Daten backfillen, Consumer migrieren und erst später Constraints verschärfen oder alte Spalten entfernen.

Wichtig ist, dass alte und neue Anwendungsversionen während eines Rolling Updates gleichzeitig funktionieren. Breaking Schema Changes wie Umbenennen, Entfernen oder Not-Null ohne Backfill sind gefährlich.

Senior-Antwort:

„Eine Datenbankmigration ist Teil des Release-Designs. Code und Schema müssen während des Rollouts kompatibel bleiben.“

Häufige Fehler:
  • Breaking Changes während Rolling Update.
  • Spalte entfernen, bevor alle Consumer migriert sind.
  • Not-Null-Spalte ohne Default/Backfill hinzufügen.
  • Große Backfills in einer Transaktion.
  • Migrationen nur gegen leere Dev-DB testen.
  • App-Start durch lange Migrationen blockieren.
Best Practices:
  • Migrationen versionieren.
  • Backward-compatible ändern.
  • Expand-and-contract nutzen.
  • Backfills batchen.
  • Große Migrationen separat planen.
  • Migrationen unter realistischen Daten testen.
  • Rollback-/Forward-Fix-Strategie definieren.
  • Schema-Owner klar benennen.
Stolperfalle:

Ein Rollback des Codes ist einfach. Ein Rollback einer destruktiven Datenbankmigration ist oft nicht einfach.

Merksatz:

Schema-Evolution muss rollout-kompatibel sein.

B4-K09-Q009 – Reporting, Read Models und analytische AbfragenRead Models sind erlaubt, wenn das Write Model die falsche Frage beantworten muss.
Frage:

„Wie trennen Sie operative Queries von Reporting- und Analytics-Abfragen?“

Kurz erklärt:

Reporting-Queries haben andere Anforderungen als transaktionale Use Cases. Read Models, Materialized Views und separate Analytics-Systeme können sinnvoll sein.

Musterantwort:

Operative Queries unterstützen konkrete Use Cases und sollten schnell, selektiv und transaktionsnah sein. Reporting-Queries aggregieren oft große Datenmengen, filtern anders und brauchen historische Sichten. Wenn Reporting dieselbe OLTP-Datenbank stark belastet, gefährdet es operative Workloads.

Ich nutze je nach Bedarf DTO-Projections, Read Models, Materialized Views, Replikas, Data Warehouse oder Event-basierte Projektionen. Wichtig ist, Konsistenzanforderungen zu klären: Muss der Report live sein, oder reicht eventual consistency?

Senior-Antwort:

„Nicht jede Abfrage gehört auf das Write Model. Manchmal ist ein eigenes Read Model die sauberste Performance- und Architekturentscheidung.“

Häufige Fehler:
  • Schwere Reports auf OLTP-Peak-Zeiten laufen lassen.
  • Entities für große Reports laden.
  • Keine Konsistenzanforderung klären.
  • Materialized Views ohne Refresh-Plan.
  • Reporting-Indizes vergessen.
  • Dashboard-Queries ohne Limits.
Best Practices:
  • Reporting-Anforderungen separat analysieren.
  • DTO-Projections statt Entities.
  • Read Models für häufige Dashboards.
  • Materialized Views bewusst refreshen.
  • Replikas oder Warehouse für schwere Analysen prüfen.
  • Konsistenzmodell dokumentieren.
  • Query-Kosten beobachten.
  • Operative DB vor Reporting-Last schützen.
Stolperfalle:

Ein Report, der heute mit 10.000 Zeilen schnell ist, kann morgen mit 100 Millionen Zeilen das operative System belasten.

Merksatz:

Read Models sind erlaubt, wenn das Write Model die falsche Frage beantworten muss.

B4-K09-Q010 – SQL in Architekturentscheidungen und Anti-PatternsDatenbankdesign ist Architekturdesign mit langer Halbwertszeit. 24h
Frage:

„Welche SQL- und Datenbank-Anti-Patterns sehen Sie häufig in Enterprise-Systemen?“

Kurz erklärt:

SQL-Design ist Architekturdesign. Schlechte Datenmodelle erzeugen langfristige Kopplung, Performanceprobleme und schwache Datenintegrität.

Musterantwort:

Häufige Anti-Patterns sind: generische Key-Value-Tabellen für zentrale Fachlichkeit, fehlende Constraints, Soft Deletes ohne klare Semantik, unkontrollierte Shared Database, SELECT * , fehlende Indizes, zu viele Indizes, N+1, lange Transaktionen, Reporting auf OLTP ohne Schutz, Migrationen ohne Zero-Downtime-Plan und ORM-generiertes Schema ohne Review.

Ich bewerte Datenbankdesign als Architekturthema. Wenn das Schema fachliche Regeln nicht ausdrückt, Queries nicht unterstützt oder Migrationen nicht sicher erlaubt, wird die Anwendung langfristig teuer.

Senior-Antwort:

„Das Datenbankschema ist kein Nebeneffekt meiner Entities. Es ist ein langlebiger Architekturvertrag.“

Häufige Fehler:
  • Flexibilität durch generische Tabellen vortäuschen.
  • Datenintegrität nur im Java-Code.
  • SQL-Reviews überspringen.
  • Migrationen nicht testen.
  • Reporting und OLTP vermischen.
  • ORM als Ersatz für Datenbankwissen betrachten.
  • Datenbankbesitz in Microservices nicht klären.
Best Practices:
  • Datenmodell als Architekturartefakt behandeln.
  • Fachliche Constraints explizit machen.
  • Query-Muster früh analysieren.
  • SQL und Ausführungspläne reviewen.
  • Migrationen in Release-Design aufnehmen.
  • ORM-generiertes SQL sichtbar machen.
  • Datenbank-Ownership definieren.
  • Anti-Patterns bewusst dokumentieren und abbauen.
Stolperfalle:

Generische Modelle wirken flexibel, verschieben aber Validierung, Typisierung und Performanceprobleme in jede einzelne Query.

Merksatz:

Datenbankdesign ist Architekturdesign mit langer Halbwertszeit.

B4-K10 – Kapitel 10: Redis 10 Karten

B4-K10-Q001 – Redis-Grundmodell: Key-Value, Datenstrukturen und EinsatzfälleRedis ist schnell, aber Speicher und Semantik sind endlich. 24h
Frage:

„Was ist Redis, und für welche Einsatzfälle würden Sie es in einer Java-Enterprise-Architektur verwenden?“

Kurz erklärt:

Redis speichert Daten unter Keys und bietet spezialisierte Datenstrukturen. Gute Redis-Nutzung beginnt mit bewusstem Key-Design, TTL-Strategie und passender Datenstruktur.

Musterantwort:

Redis ist ein schneller In-Memory-Datenspeicher mit mehreren Datenstrukturen. Ich verwende Redis typischerweise für kurzlebige, schnell zugreifbare oder koordinierende Daten: Caches, Rate Limits, Session-Daten, Idempotenzmarker, verteilte Zähler, Token-Blacklists, Leaderboards oder deduplizierende Marker.

Ich würde Redis nicht automatisch als primäre System-of-Record-Datenbank für kritische Fachtransaktionen verwenden. Dafür muss man Persistenz, Konsistenz, Recovery und Datenverlusttoleranz sehr genau prüfen. Redis ist stark, wenn die Datenstruktur und Lebensdauer klar sind.

Senior-Antwort:

„Redis ist für mich kein schneller Mülleimer. Jeder Key braucht Zweck, Datenstruktur, Lebensdauer, Konsistenzannahme und Betriebsstrategie.“

Häufige Fehler:
  • Redis als unendlichen Cache betrachten.
  • Keine TTL setzen.
  • Riesige JSON-Objekte unter einem Key speichern.
  • Key-Namen ohne Namespace-Konvention.
  • Redis als Ersatz für relationale Integrität nutzen.
  • Java-native Serialisierung verwenden.
  • Fachliche Lebensdauer von Daten nicht klären.
Best Practices:
  • Use Case vor Datenstruktur wählen.
  • Keys konsistent namespacen.
  • TTLs fachlich definieren.
  • Values klein halten.
  • Serializer bewusst wählen.
  • Redis nicht als versteckte Hauptdatenbank verwenden.
  • Speicherverbrauch und Key-Anzahl überwachen.
  • Fallback-Verhalten bei Redis-Ausfall definieren.
Stolperfalle:

Ein Cache ohne TTL und Eviction-Verständnis wird schnell zu einer zweiten, schlechter kontrollierten Datenbank.

Merksatz:

Redis ist schnell, aber Speicher und Semantik sind endlich.

B4-K10-Q002 – Caching-Strategien, TTL und Cache-InvalidierungEin Cache ist eine Kopie mit Ablaufdatum.
Frage:

„Welche Redis-Caching-Strategien kennen Sie, und wie vermeiden Sie stale data?“

Kurz erklärt:

Caching reduziert Latenz und Last, erzeugt aber Konsistenzprobleme. TTL und Invalidierung sind fachliche Architekturentscheidungen.

Musterantwort:

Die häufigste Strategie ist Cache-Aside: Die Anwendung prüft den Cache, lädt bei Miss aus der Datenbank und schreibt danach in Redis. Bei Änderungen wird der Cache invalidiert oder aktualisiert. Daneben gibt es Write-Through, Write-Behind, Refresh-Ahead und lokale Near-Caches.

Stale Data vermeide ich nicht absolut, sondern durch ein passendes Konsistenzmodell. Für Produktkatalogdaten sind 15 Minuten TTL oft akzeptabel. Für Kontostände oder Berechtigungen möglicherweise nicht. Ich definiere TTLs fachlich, invalidiere bei Writes, schütze vor Cache Stampede und überwache Hit Rate, Miss Rate und Latenz.

Senior-Antwort:

„Caching ist kontrollierte Inkonsistenz. Ich muss wissen, welche Inkonsistenz fachlich akzeptabel ist und wie lange.“

Häufige Fehler:
  • Keine TTL.
  • TTL rein technisch statt fachlich wählen.
  • Cache bei Write nicht invalidieren.
  • Stale Data bei Berechtigungen unterschätzen.
  • Cache Stampede ignorieren.
  • Cache-Ausfall als Total-Ausfall der Anwendung.
  • @Cacheable ohne Key- und Serializer-Konzept.
Best Practices:
  • Cache nur für passende Use Cases.
  • TTL fachlich definieren.
  • Invalidierung auf Write-Pfaden.
  • Jitter für viele gleichzeitige Expirations.
  • Stampede-Schutz für Hot Keys.
  • Hit/Miss und Latenz überwachen.
  • Fallback bei Redis-Ausfall definieren.
  • Cache-Inhalte versionieren, wenn Schema sich ändert.
Stolperfalle:

Ein schneller Cache kann falsche Daten sehr schnell und sehr zuverlässig ausliefern.

Merksatz:

Ein Cache ist eine Kopie mit Ablaufdatum.

B4-K10-Q003 – Eviction Policies, Memory Management und Key-LebensdauerNicht jeder Redis-Key darf bei Speicherdruck verschwinden.
Frage:

„Was passiert, wenn Redis sein Memory Limit erreicht, und wie planen Sie TTLs und Eviction Policies?“

Kurz erklärt:

Redis läuft primär im Speicher. Eviction Policies bestimmen, was passiert, wenn Redis sein Speicherlimit erreicht.

Musterantwort:

Wenn Redis sein Memory Limit erreicht und neue Daten geschrieben werden, entscheidet die Eviction Policy, ob und welche Keys entfernt werden. Redis dokumentiert, dass bei Überschreiten des Speicherlimits Keys entsprechend der gewählten Policy evicted werden; bei noeviction werden Schreibbefehle mit Fehler beantwortet, statt Keys zu entfernen.

Ich plane TTLs fachlich: Idempotenzmarker brauchen andere Lebensdauer als Produktdetails oder Rate-Limit-Buckets. Außerdem wähle ich Eviction Policies passend zum Use Case. Für reinen Cache kann allkeys-lru oder allkeys-lfu sinnvoll sein. Für kritische Marker ist automatische Eviction gefährlich, weil das fachliche Garantien brechen kann.

Senior-Antwort:

„TTL ist fachliche Lebensdauer. Eviction ist Speicherdruckverhalten. Diese beiden Dinge darf man nicht verwechseln.“

Häufige Fehler:
  • Keine maxmemory -Strategie.
  • Kritische Daten in evictable Cache legen.
  • TTLs vergessen.
  • Alle Daten mit gleicher TTL behandeln.
  • Eviction als normale fachliche Löschung missverstehen.
  • Big Keys nicht überwachen.
  • Memory-Fragmentation ignorieren.
Best Practices:
  • Redis-Daten nach Kritikalität trennen.
  • TTLs fachlich definieren.
  • Eviction Policy pro Use Case wählen.
  • noeviction für kritische Nicht-Cache-Daten prüfen.
  • Cache-Daten von Koordinationsdaten trennen.
  • Big Keys und Memory Usage überwachen.
  • Jitter für massenhaft gleiche TTLs.
  • Capacity Planning für Redis-Speicher.
Stolperfalle:

Wenn ein Idempotenz-Key evicted wird, bevor die fachliche Duplikatfrist vorbei ist, kann die Anwendung doppelte Verarbeitung erlauben.

Merksatz:

Nicht jeder Redis-Key darf bei Speicherdruck verschwinden.

B4-K10-Q004 – Rate Limiting mit RedisEin Rate Limiter schützt nur, wenn seine eigenen Zustandsoperationen korrekt sind.
Frage:

„Wie implementieren Sie Rate Limiting mit Redis?“

Kurz erklärt:

Redis eignet sich gut für Rate Limiting, weil Zähler und TTLs schnell und atomar verwaltet werden können.

Musterantwort:

Eine einfache Variante ist Fixed Window: Pro Kunde und Zeitfenster gibt es einen Key. Jeder Request erhöht den Counter, der Key bekommt eine TTL. Wenn der Wert über dem Limit liegt, wird abgelehnt. Wichtig ist, Increment und Expiry atomar zu setzen, sonst kann ein Crash zwischen INCR und EXPIRE einen Key ohne Ablauf erzeugen.

Für präzisere Limits nutze ich Sliding Window, Token Bucket oder Leaky Bucket. Redis Sorted Sets eignen sich gut für Sliding-Window-Rate-Limits, weil sie Requests mit Timestamp als Score speichern und alte Einträge entfernen können. Redis dokumentiert Sorted Sets als eindeutige Strings mit Score, optimiert für Range Queries nach Score oder Rank.

Senior-Antwort:

„Rate Limiting ist nicht nur ein Counter. Es ist eine fachliche Policy mit Atomizität, TTL, Ausfallverhalten und Missbrauchsszenarien.“

Häufige Fehler:
  • INCR und EXPIRE nicht atomar.
  • Kein TTL-Jitter oder schlechte Key-Cardinality.
  • Redis im Hot Path ohne Timeout.
  • Rate Limit pro Instanz statt global.
  • Sliding Window ohne Speicherbegrenzung.
  • Fail-open/fail-closed nicht fachlich entscheiden.
Best Practices:
  • Rate-Limit-Algorithmus bewusst wählen.
  • Atomare Redis-Operationen verwenden.
  • TTL immer setzen.
  • Redis-Time-outs kurz halten.
  • Fail-open oder fail-closed fachlich entscheiden.
  • Metriken für erlaubt/abgelehnt.
  • Abuse- und Hot-Key-Szenarien testen.
  • Sorted Sets für präzisere Sliding Windows prüfen.
Stolperfalle:

Ein Crash zwischen INCR und EXPIRE kann ohne Atomizität dauerhaft wachsende Rate-Limit-Keys erzeugen.

Merksatz:

Ein Rate Limiter schützt nur, wenn seine eigenen Zustandsoperationen korrekt sind.

B4-K10-Q005 – Verteilte Locks, SET NX PX und RedlockEin Lock ist eine Koordinationshilfe, keine Entschuldigung für fehlende Idempotenz.
Frage:

„Wie implementieren Sie einen verteilten Lock mit Redis, und welche Risiken hat das?“

Kurz erklärt:

Redis kann für verteilte Locks genutzt werden, aber Locks sind fehleranfällig. TTL, eindeutige Tokens, atomare Freigabe und Ausfallannahmen sind entscheidend.

Musterantwort:

Die einfache Basis ist SET key token NX PX ttl : NX setzt nur, wenn der Key noch nicht existiert, PX gibt eine Lease-Zeit. Der Token ist wichtig, damit ein Client nicht versehentlich den Lock eines anderen Clients löscht. Freigabe muss atomar sein: nur löschen, wenn der gespeicherte Token dem eigenen Token entspricht.

Risiken sind Clock/Timing, zu kurze TTL, lange GC-Pausen, Netzwerkpartitionen, Redis-Failover und fachlich falsch verstandene Exklusivität. Für kritische Konsistenz verlasse ich mich lieber auf Datenbankconstraints, Optimistic Locking oder echte transaktionale Mechanismen. Redis Locks sind für Koordination geeignet, aber keine universelle Konsistenzgarantie.

Senior-Antwort:

„Ein Redis Lock kann Koordination helfen, aber er ist nicht meine einzige fachliche Sicherheitsbarriere. Kritische Invarianten sichere ich zusätzlich im System of Record.“

Häufige Fehler:
  • Lock ohne TTL.
  • Lock ohne eindeutigen Token.
  • Release mit einfachem DEL .
  • Operation läuft länger als TTL.
  • Lock als einzige Konsistenzgarantie.
  • Redis-Failover-Semantik ignorieren.
  • Hot Locks nicht messen.
Best Practices:
  • SET NX PX für Acquire verwenden.
  • Zufälligen Token speichern.
  • Release atomar per Lua.
  • Lease-Zeit realistisch setzen.
  • Operation idempotent halten.
  • Datenbankconstraints als letzte Absicherung nutzen.
  • Lock-Contention überwachen.
  • Kritische Konsistenz nicht blind auf Redis Lock stützen.
Stolperfalle:

Wenn die Operation länger läuft als die Lock-TTL, kann ein zweiter Client den Lock erwerben, während der erste noch arbeitet.

Merksatz:

Ein Lock ist eine Koordinationshilfe, keine Entschuldigung für fehlende Idempotenz.

B4-K10-Q006 – Redis Transactions, Pipelines und Lua-SkriptePerformance-Batching ersetzt keine Atomizität.
Frage:

„Was ist der Unterschied zwischen Redis Pipeline, Transaction und Lua-Skript?“

Kurz erklärt:

Pipelines reduzieren Roundtrips. Transactions gruppieren Befehle. Lua-Skripte ermöglichen atomare serverseitige Logik.

Musterantwort:

Pipeline bedeutet: Mehrere Befehle werden gebündelt an Redis gesendet, um Netzwerk-Roundtrips zu reduzieren. Das ist primär Performance, keine Atomizitätsgarantie.

Redis Transactions mit MULTI , EXEC , DISCARD und WATCH erlauben, mehrere Befehle in einem Schritt auszuführen. Redis dokumentiert Transactions als Mechanismus, bei dem Befehle in einer Gruppe ausgeführt werden und sich um MULTI , EXEC , DISCARD und WATCH drehen.

Lua-Skripte führen serverseitige Logik atomar aus. Für komplexe Bedingungen wie „setze nur, wenn nicht existiert, und setze TTL im selben Schritt“ sind Lua-Skripte oft sauberer und robuster.

Senior-Antwort:

„Pipeline ist schneller, aber nicht automatisch sicherer. Für Race Conditions brauche ich atomare Semantik, nicht nur weniger Netzwerk-Roundtrips.“

Häufige Fehler:
  • Pipeline mit Transaction verwechseln.
  • Mehrere Redis-Befehle ohne Atomizität bei Race Conditions.
  • Lange Lua-Skripte schreiben.
  • Redis-Transaction als relationale Transaktion missverstehen.
  • Serialization in Scripts nicht berücksichtigen.
  • Fehler in EXEC -Resultaten ignorieren.
Best Practices:
  • Pipeline für Bulk/Performance.
  • Transaction für gruppierte Befehle.
  • Lua für atomare bedingte Logik.
  • Skripte kurz halten.
  • Redis-Atomizität und DB-Transaktionen trennen.
  • Race Conditions explizit testen.
  • Script-Versionierung und Monitoring beachten.
  • Keine lang laufenden Operationen in Lua.
Stolperfalle:

Ein GET , danach SET , danach EXPIRE sind drei getrennte Schritte und können ohne Kapselung Race Conditions erzeugen.

Merksatz:

Performance-Batching ersetzt keine Atomizität.

B4-K10-Q007 – Persistenz, Replikation und HochverfügbarkeitRedis-Durability muss zum fachlichen Schaden eines verlorenen Keys passen.
Frage:

„Wie funktionieren Redis Persistenz und Hochverfügbarkeit, und worauf achten Sie bei kritischen Daten?“

Kurz erklärt:

Redis kann persistieren und replizieren, aber Datenverlust- und Failover-Verhalten müssen zum Use Case passen.

Musterantwort:

Redis kann Daten über RDB-Snapshots und AOF persistieren. RDB speichert point-in-time Snapshots; AOF protokolliert Schreiboperationen und kann bessere Durability bieten, hat aber eigene IO- und Rewrite-Kosten. Redis dokumentiert Persistenzoptionen und die Interaktion zwischen RDB und AOF.

Für reine Caches ist Datenverlust oft akzeptabel, solange die Daten aus dem System of Record neu geladen werden können. Für Sessions, Idempotenzmarker oder Koordinationsdaten muss ich Datenverlust, Replikationslag, Failover und Recovery genau bewerten. Redis-Replikation und Persistenz sind keine automatische Garantie für relationale ACID-Semantik.

Senior-Antwort:

„Redis kann persistent sein, aber ich frage zuerst: Was passiert fachlich, wenn dieser Key verloren geht?“

Häufige Fehler:
  • Redis-Persistenz mit relationaler Durability verwechseln.
  • Kritische Idempotenz nur in volatilem Redis speichern.
  • Replikationslag ignorieren.
  • Redis-Ausfallverhalten nicht definieren.
  • AOF-Kosten nicht berücksichtigen.
  • Restore-Zeit nicht testen.
Best Practices:
  • Use Case nach Datenverlusttoleranz klassifizieren.
  • Cache-Daten von kritischen Daten trennen.
  • RDB/AOF bewusst konfigurieren.
  • Failover-Verhalten testen.
  • Reconnect- und Timeout-Strategie definieren.
  • Kritische Garantien im System of Record absichern.
  • Persistence-Metriken überwachen.
  • Restore regelmäßig testen.
Stolperfalle:

Nur weil Redis repliziert ist, heißt das nicht, dass jeder bestätigte Write nach Failover garantiert erhalten bleibt.

Merksatz:

Redis-Durability muss zum fachlichen Schaden eines verlorenen Keys passen.

B4-K10-Q008 – Redis Streams, Pub/Sub und Messaging-GrenzenPub/Sub signalisiert; Streams speichern; Kafka skaliert Event-Streaming als Plattform.
Frage:

„Wann verwenden Sie Redis Streams oder Pub/Sub statt Kafka?“

Kurz erklärt:

Redis Pub/Sub ist flüchtig. Redis Streams sind persistierte append-only Strukturen mit Consumer-Gruppen-ähnlichen Mechanismen. Kafka bleibt für viele Event-Streaming-Plattformfälle stärker.

Musterantwort:

Redis Pub/Sub eignet sich für flüchtige, einfache Benachrichtigungen, bei denen Nachrichtenverlust akzeptabel ist, etwa Cache-Invalidierung in einer lokalen Umgebung. Redis Streams eignen sich für leichtere Event-Queues oder interne Workflows, weil sie als append-only Log arbeiten und Consumer-Gruppen-Mechanismen bieten. Redis dokumentiert Streams als append-only Log-Struktur, die Events in Auftretensreihenfolge aufnimmt und zur Verarbeitung syndizieren kann.

Für unternehmensweite Event-Streaming-Plattformen mit hoher Retention, Partitionierung, Replay, Schema Governance, vielen Consumer-Teams und starkem Betriebskonzept ist Kafka oft geeigneter. Redis Streams sind nützlich, aber nicht automatisch Kafka-Ersatz.

Senior-Antwort:

„Redis Pub/Sub ist kein zuverlässiges Event Log. Redis Streams sind stärker, aber ich prüfe trotzdem Retention, Replay, Consumer-Zahl und Betriebsanforderungen, bevor ich Kafka ersetze.“

Häufige Fehler:
  • Pub/Sub für zuverlässige Business-Events verwenden.
  • Streams ohne Trimming wachsen lassen.
  • Pending Entries ignorieren.
  • Redis-Speicher mit Event-Historie füllen.
  • Keine DLQ-/Retry-Strategie.
  • Kafka und Redis Streams ohne klare Kriterien austauschen.
Best Practices:
  • Pub/Sub nur für flüchtige Signale.
  • Streams für überschaubare interne Workflows.
  • Stream-Länge begrenzen oder trimmen.
  • ACK/Pending-Handling implementieren.
  • Redis Memory überwachen.
  • Für langfristige Event-Plattform Kafka prüfen.
  • Messaging-Semantik dokumentieren.
  • Consumer idempotent bauen.
Stolperfalle:

Ein verlorenes Pub/Sub-Signal ist kein Bug, wenn man Pub/Sub gewählt hat. Es ist Teil der Semantik.

Merksatz:

Pub/Sub signalisiert; Streams speichern; Kafka skaliert Event-Streaming als Plattform.

B4-K10-Q009 – Spring Boot, Spring Cache und Redis-IntegrationSpring macht Redis bequem, nicht automatisch korrekt.
Frage:

„Welche Fallstricke gibt es bei Spring Cache mit Redis?“

Kurz erklärt:

Spring abstrahiert Redis-Zugriff, aber Key-Design, TTL, Serializer, Fehlerverhalten und Cache-Semantik bleiben Architekturentscheidungen.

Musterantwort:

Spring Cache ist bequem, aber gefährlich, wenn man die Semantik nicht explizit macht. Ich muss Cache-Namen, Keys, TTLs, Null-Caching, Serializer, Fehlerverhalten und Invalidierung definieren. Außerdem muss ich Spring-AOP-Grenzen verstehen: Self-Invocation kann @Cacheable oder @CacheEvict umgehen, weil die Annotation proxy-basiert wirkt.

Ich vermeide generische Default-TTLs für alles. Produktdetails, Sessions, Token-Blacklists und Idempotenzmarker haben unterschiedliche Lebensdauern und Risiken. Spring Cache ist gut für einfache Cache-Aside-Fälle, aber nicht für jede Redis-Nutzung.

Senior-Antwort:

„ @Cacheable ist kein Architekturkonzept. Es ist eine bequeme Implementierung für eine vorher definierte Cache-Strategie.“

Häufige Fehler:
  • Eine Default-TTL für alle Caches.
  • Unklare Key-Generierung.
  • Self-Invocation bei Cache-Annotationen.
  • Java-native Serialization ohne Versionierungsstrategie.
  • Null-Werte unbedacht cachen.
  • Cache-Eviction beim Write vergessen.
  • Redis-Timeouts zu lang.
Best Practices:
  • Cache-Namen und Keys standardisieren.
  • TTL pro Cache definieren.
  • Serializer bewusst wählen.
  • Null-Caching explizit entscheiden.
  • Cache-Annotationen an Proxy-Grenzen beachten.
  • Invalidierung testen.
  • Redis-Ausfallverhalten definieren.
  • Cache-Metriken beobachten.
Stolperfalle:

Ein interner Methodenaufruf auf eine @Cacheable -Methode kann den Spring-Proxy umgehen und den Cache nicht nutzen.

Merksatz:

Spring macht Redis bequem, nicht automatisch korrekt.

B4-K10-Q010 – Monitoring, Betrieb und typische ProduktionsproblemeEin schneller Cache braucht langsames, gründliches Monitoring. 24h
Frage:

„Welche Redis-Metriken und Produktionsprobleme überwachen Sie?“

Kurz erklärt:

Redis-Betrieb braucht Monitoring für Speicher, Latenz, Evictions, Hit Rate, Verbindungen, Replikation, Persistenz und große/hot Keys.

Musterantwort:

Ich überwache Speicherverbrauch, Maxmemory-Nähe, Evicted Keys, Expired Keys, Hit/Miss-Rate, Latenz, Ops/sec, verbundene Clients, blockierte Clients, Rejected Connections, Big Keys, Hot Keys, Replikationslag und Persistenzstatus. Redis INFO liefert viele dieser Kategorien, darunter Memory, Persistence, Stats, Replication, Commandstats und Latencystats.

Typische Probleme sind Memory-Wachstum, Eviction-Stürme, zu große Keys, Hot Keys, langsame Lua-Skripte, blockierende Commands, schlechte Serializer, Connection-Pool-Erschöpfung, fehlende Timeouts und Cache-Stampede. Redis muss wie eine kritische Infrastrukturkomponente betrieben werden.

Senior-Antwort:

„Redis ist oft der schnellste Teil des Systems, bis es zum zentralen Hotspot wird. Deshalb überwache ich Speicher, Latenz und Evictions so ernst wie Datenbankmetriken.“

Häufige Fehler:
  • Redis ohne Memory-Alerts betreiben.
  • Evictions ignorieren.
  • KEYS * in Produktion nutzen.
  • Große Values cachen.
  • Keine Client-Timeouts.
  • Connection Pools zu klein oder zu groß.
  • Slowlog und Latency nicht beobachten.
  • Redis-Ausfall nicht testen.
Best Practices:
  • Memory und Evictions alerten.
  • Hit/Miss-Rate pro Use Case messen.
  • Latenzpercentiles überwachen.
  • Big Keys und Hot Keys analysieren.
  • SCAN statt KEYS für Diagnose nutzen.
  • Client-Timeouts kurz und bewusst setzen.
  • Circuit Breaker oder Degradation definieren.
  • Redis-Failover regelmäßig testen.
Stolperfalle:

KEYS * wirkt harmlos in Dev, kann aber in Produktion Redis blockieren und Latenzspitzen verursachen.

Merksatz:

Ein schneller Cache braucht langsames, gründliches Monitoring.

B4-K11 – Kapitel 11: Elasticsearch 10 Karten

B4-K11-Q001 – Index, Dokument, Shard und ReplicaEin Index ist logisch; Shards sind die physische Realität. 24h
Frage:

„Erklären Sie Index, Dokument, Shard und Replica in Elasticsearch.“

Kurz erklärt:

Ein Elasticsearch-Index enthält Dokumente. Ein Index wird physisch in Shards aufgeteilt. Replicas sind Kopien primärer Shards für Ausfallsicherheit und Leseskalierung.

Musterantwort:

Ein Index ist eine logische Sammlung ähnlicher Dokumente, zum Beispiel products-v1 . Ein Dokument ist ein JSON-artiger Datensatz, der indexiert und gesucht werden kann. Ein Index wird physisch in Primary Shards verteilt. Jeder Shard ist eine eigenständige Lucene-Indexeinheit. Replicas sind Kopien primärer Shards. Sie erhöhen Ausfallsicherheit und können Suchlast verteilen.

Wichtig ist: Die Shard-Anzahl ist eine Architekturentscheidung. Zu wenige Shards begrenzen Skalierung; zu viele Shards erzeugen Overhead. Replicas schützen vor Node-Ausfall und erhöhen Read-Kapazität, kosten aber Speicher und Schreibaufwand. Elasticsearch dokumentiert Primary Shards und Replica Shards als zentrale Verteilungseinheiten; jedes Dokument gehört zu einem Primary Shard, Replicas sind Kopien davon.

Senior-Antwort:

„Elasticsearch skaliert über Shards, aber Shards sind nicht kostenlos. Ich dimensioniere sie aus Datenvolumen, Query-Last und Betriebsmodell, nicht aus Bauchgefühl.“

Häufige Fehler:
  • Zu viele kleine Shards.
  • Shard-Anzahl ohne Datenvolumen planen.
  • Replicas mit Backups verwechseln.
  • Ein Index pro Kunde ohne Notwendigkeit.
  • Dokumente relational statt suchorientiert modellieren.
  • Mapping automatisch entstehen lassen und später bereuen.
Best Practices:
  • Indexdesign aus Query- und Datenvolumen ableiten.
  • Shard-Anzahl bewusst dimensionieren.
  • Replicas nach Verfügbarkeit und Read-Last wählen.
  • Indexnamen versionieren.
  • Mapping explizit definieren.
  • Suchdokumente als Read Model betrachten.
  • Elasticsearch nicht als primäres System of Record für transaktionale Daten missbrauchen.
Stolperfalle:

Replicas sind keine Backups. Sie schützen gegen Ausfall einzelner Shard-Kopien, aber nicht gegen versehentliches Löschen oder falsches Reindexing.

Merksatz:

Ein Index ist logisch; Shards sind die physische Realität.

B4-K11-Q002 – Mapping, Field Types und AnalyzerMapping entscheidet, was überhaupt sinnvoll gesucht werden kann.
Frage:

„Was ist der Unterschied zwischen text und keyword in Elasticsearch?“

Kurz erklärt:

Mappings definieren, wie Felder indexiert werden. Analyzer bestimmen, wie Text tokenisiert und normalisiert wird. text und keyword haben unterschiedliche Suchsemantik.

Musterantwort:

text ist für Volltextsuche gedacht. Es wird analysiert: Text wird tokenisiert, normalisiert und abhängig vom Analyzer verarbeitet. Dadurch kann man nach einzelnen Wörtern, Phrasen oder Relevanz suchen. keyword ist für exakte Werte gedacht: IDs, Status, Kategorien, Tags, Währungen oder Sortier-/Aggregationsfelder.

Ein häufiger Fehler ist, exakte Filter auf text -Feldern oder Volltextsuche auf keyword -Feldern zu machen. Deshalb mappe ich häufig Multi-Fields: title als text für Suche und title.keyword als keyword für exakte Sortierung oder Aggregation.

Senior-Antwort:

„Mapping ist Sucharchitektur. Wenn ich es Elasticsearch automatisch überlasse, überlasse ich Relevanz, Performance und Reindexing-Risiko dem Zufall.“

Häufige Fehler:
  • IDs als text mappen.
  • Statuswerte als text statt keyword .
  • Automatisches Mapping unkontrolliert zulassen.
  • Analyzer nach Produktivstart ohne Reindex ändern wollen.
  • Aggregationen auf analysiertem Text.
  • Datumsfelder als Strings speichern.
Best Practices:
  • Mapping explizit definieren.
  • text für Volltext.
  • keyword für Filter, IDs, Status und Aggregationen.
  • Multi-Fields für kombinierte Anforderungen.
  • Analyzer fachlich testen.
  • Dynamic Mapping begrenzen.
  • Mappings versionieren.
  • Reindexing als Änderungsstrategie einplanen.
Stolperfalle:

Ein Feldtyp lässt sich nicht beliebig ändern, ohne Daten neu zu indexieren. Falsches Mapping ist oft eine Reindexing-Schuld.

Merksatz:

Mapping entscheidet, was überhaupt sinnvoll gesucht werden kann.

B4-K11-Q003 – Query DSL: Match, Term, Bool und Filter ContextSuche bewertet; Filter grenzen ein.
Frage:

„Was ist der Unterschied zwischen match , term , bool.must und bool.filter ?“

Kurz erklärt:

Query DSL unterscheidet zwischen Query Context und Filter Context. Query Context beeinflusst Relevanzscore; Filter Context filtert ohne Score-Berechnung.

Musterantwort:

match ist typischerweise für Volltextsuche. Der Suchtext wird analysiert und gegen analysierte Felder gesucht. Elastic beschreibt die match Query als Standardquery für Volltextsuche; der bereitgestellte Text wird vor dem Matching analysiert.

term ist für exakte Werte wie category , status , id oder Boolean-Felder. bool.must wird im Query Context ausgeführt und beeinflusst den Score. bool.filter wird im Filter Context ausgeführt, filtert Dokumente, beeinflusst aber den Score nicht. Elastic dokumentiert, dass Query Context zur Bewertung der Relevanz genutzt wird, während Filter Context Dokumente ausschließt, ohne den Score zu beeinflussen.

Senior-Antwort:

„Alles, was Relevanz beeinflussen soll, gehört in den Query Context. Alles, was nur einschränkt, gehört in den Filter Context.“

Häufige Fehler:
  • term auf analysiertem text -Feld verwenden.
  • Strukturierte Filter in must statt filter .
  • Volltextsuche auf keyword .
  • Relevanzscore ignorieren.
  • Zu komplexe Bool Queries ohne Tests.
  • Große Pagination mit from .
Best Practices:
  • Volltext mit match .
  • Exakte Werte mit term auf keyword .
  • Strukturierte Bedingungen in filter .
  • Query-JSON testen und versionieren.
  • Relevanz mit echten Daten validieren.
  • Query Performance messen.
  • Suchverhalten fachlich abnehmen lassen.
Stolperfalle:

term sucht nicht „ungefähr“. Auf einem analysierten Textfeld sucht es nach exakt dem Term, der im Index liegt.

Merksatz:

Suche bewertet; Filter grenzen ein.

B4-K11-Q004 – Near Real-Time, Refresh und KonsistenzElasticsearch ist ein Suchindex, kein synchroner Transaktionsspiegel.
Frage:

„Warum findet eine Elasticsearch-Suche ein gerade indexiertes Dokument manchmal nicht sofort?“

Kurz erklärt:

Elasticsearch ist near real-time. Ein indexiertes Dokument ist nicht zwingend sofort über Suche sichtbar.

Musterantwort:

Elasticsearch ist near real-time. Nach dem Indexieren ist ein Dokument nicht zwingend sofort suchbar, weil ein Refresh nötig ist, damit Änderungen für Search sichtbar werden. Elastic dokumentiert, dass Elasticsearch Indizes standardmäßig periodisch refresht, typischerweise jede Sekunde für aktiv gesuchte Indizes, und dass Änderungen deshalb nicht sofort, sondern innerhalb dieses Zeitfensters sichtbar werden.

Das bedeutet: Elasticsearch ist für Suche und Read Models hervorragend, aber ich sollte es nicht als stark konsistentes System of Record behandeln. Wenn ein Use Case unmittelbar nach einem Write garantiert den neuen Zustand braucht, lese ich aus dem primären Datenspeicher oder gestalte das Konsistenzmodell bewusst.

Senior-Antwort:

„Elasticsearch ist schnell, aber Suche ist near real-time. Für starke Konsistenz lese ich aus dem System of Record, nicht aus dem Suchindex.“

Häufige Fehler:
  • Elasticsearch als sofort konsistentes System behandeln.
  • Nach Indexing sofortige Suche erwarten.
  • Explizites Refresh nach jedem Write.
  • Tests ohne Refresh-/Wait-Strategie.
  • Primäre Daten nur aus Elasticsearch lesen.
  • Refresh-Intervalle ohne Lasttest ändern.
Best Practices:
  • Konsistenzmodell dokumentieren.
  • System of Record separat halten.
  • Suche als eventual consistent Read Model betrachten.
  • Explizite Refreshes sparsam nutzen.
  • Tests mit kontrolliertem Refresh schreiben.
  • Bulk-Indexing und Refresh bewusst abstimmen.
  • Nutzererwartung für Verzögerung gestalten.
Stolperfalle:

index() erfolgreich heißt nicht automatisch, dass eine nachfolgende Search Query das Dokument sofort findet.

Merksatz:

Elasticsearch ist ein Suchindex, kein synchroner Transaktionsspiegel.

B4-K11-Q005 – Indexing, Bulk API und ReindexingIndexversionen und Aliases machen Suchänderungen releasefähig.
Frage:

„Wie führen Sie Reindexing in Elasticsearch ohne Downtime durch?“

Kurz erklärt:

Bulk Indexing ist für hohe Schreibmengen effizienter als Einzelwrites. Reindexing wird nötig, wenn Mapping, Analyzer oder Dokumentstruktur geändert werden müssen.

Musterantwort:

Ich verwende versionierte Indizes und Aliases. Statt products direkt als physischen Index zu nutzen, zeigt Alias products auf products-v1 . Für eine Mapping-Änderung erstelle ich products-v2 , indexiere Daten aus dem System of Record oder über Reindexing neu und schalte danach den Alias atomar auf products-v2 um. So müssen Clients nur den Alias kennen.

Bulk API nutze ich für effizientes Indexing in Batches. Wichtig ist, Bulk-Antworten itemweise auf Fehler zu prüfen. Ein HTTP-200 für den Bulk Request bedeutet nicht, dass jedes Dokument erfolgreich indexiert wurde.

Senior-Antwort:

„Ich deploye Suchindex-Änderungen wie API-Änderungen: versioniert, validiert, umschaltbar und rollback-fähig.“

Häufige Fehler:
  • Anwendung direkt auf physischen Index koppeln.
  • Mapping im bestehenden Index ändern wollen.
  • Bulk-Fehler ignorieren.
  • Reindexing ohne Clusterkapazitätsplanung.
  • Alias-Switch ohne Validierung.
  • System of Record nicht als Quelle verwenden.
  • Reindexing und Live-Writes nicht synchronisieren.
Best Practices:
  • Alias vor physischen Index schalten.
  • Indizes versionieren.
  • Bulk-Responses itemweise prüfen.
  • Reindexing aus System of Record bevorzugen.
  • Dokumentanzahl und Suchqualität validieren.
  • Alias-Switch atomar durchführen.
  • Rollback-Plan vorbereiten.
  • Clusterlast während Reindexing überwachen.
Stolperfalle:

Bulk API kann teilweise fehlschlagen. Wer nur den HTTP-Status prüft, übersieht fehlerhafte Dokumente.

Merksatz:

Indexversionen und Aliases machen Suchänderungen releasefähig.

B4-K11-Q006 – Aggregations, Facetten und analytische SucheAggregations verdichten Suchergebnisse, aber sie verdichten auch Clusterressourcen.
Frage:

„Wie funktionieren Aggregations in Elasticsearch, und wofür verwenden Sie sie?“

Kurz erklärt:

Aggregations berechnen Zusammenfassungen über Suchergebnisse. Sie sind die Grundlage für Facetten, Dashboards und analytische Suche.

Musterantwort:

Aggregations berechnen Metriken oder Buckets über Dokumente, die zu einer Query passen. Für Produktfacetten nutze ich terms -Aggregations auf keyword -Feldern wie Brand oder Category, range -Aggregations für Preisbereiche und metrische Aggregations wie avg oder sum .

Wichtig ist, dass Aggregations auf passenden Feldtypen laufen. Eine Brand-Facette gehört auf ein keyword -Feld, nicht auf analysierten Text. Außerdem können Aggregations teuer werden, wenn sehr viele Buckets entstehen oder große Datenmengen ohne Filter aggregiert werden.

Senior-Antwort:

„Aggregations sind stark für Suchfacetten und explorative Analyse, aber ich kontrolliere Bucket-Anzahl, Feldtypen und Clusterlast sehr bewusst.“

Häufige Fehler:
  • Facetten auf text -Feldern.
  • Zu hohe Bucket-Anzahl.
  • Aggregations ohne Filter auf riesige Datenmengen.
  • Rohes Elasticsearch-Response-Modell an API-Clients leaken.
  • Aggregations als Ersatz für Data Warehouse missbrauchen.
  • Speicherverbrauch nicht überwachen.
Best Practices:
  • Aggregations auf passenden Feldtypen.
  • Filter vor Aggregation anwenden.
  • Bucket-Anzahl begrenzen.
  • size: 0 nutzen, wenn keine Hits nötig sind.
  • API-DTOs für Facetten definieren.
  • Hochkardinale Felder vorsichtig aggregieren.
  • Query- und Aggregation-Latenz messen.
  • Analytics-Workloads vom Suchpfad entkoppeln, wenn nötig.
Stolperfalle:

Ein Feld, das für Volltextsuche gut gemappt ist, ist nicht automatisch für Aggregationen geeignet.

Merksatz:

Aggregations verdichten Suchergebnisse, aber sie verdichten auch Clusterressourcen.

B4-K11-Q007 – Pagination, Sortierung und Deep PagingTiefe Seiten sind oft ein Symptom schlechter Such-UX.
Frage:

„Warum ist Deep Paging in Elasticsearch problematisch, und wie lösen Sie es?“

Kurz erklärt:

Elasticsearch kann mit from und size paginieren, aber Deep Paging ist teuer. Für tiefe Navigation nutzt man search_after mit stabiler Sortierung.

Musterantwort:

Bei from und size müssen Shards potenziell viele Treffer sammeln und sortieren, nur um spätere Seiten zurückzugeben. Je tiefer man paginiert, desto mehr Arbeit wird verworfen. Deshalb ist Deep Paging teuer.

Für tiefe oder fortlaufende Navigation nutze ich search_after mit stabiler Sortierung und Cursor. Die Sortierung muss eindeutig sein, zum Beispiel updatedAt desc plus productId asc als Tie-Breaker. Für konsistente Snapshots über mehrere Requests kann Point-in-Time relevant sein. Für normale Suchseiten begrenze ich tiefe Seitennavigation und nutze UX-Muster wie Infinite Scroll oder Filterverfeinerung.

Senior-Antwort:

„Deep Paging ist in verteilten Suchsystemen teuer. Ich entwerfe API und UX so, dass Nutzer bessere Filter und Cursor statt Seite 10000 verwenden.“

Häufige Fehler:
  • Elasticsearch wie SQL mit tiefem Offset nutzen.
  • Sortierung ohne eindeutigen Tie-Breaker.
  • search_after ohne stabile Sortfelder.
  • Sortieren auf analysiertem Text.
  • Unbegrenzte Suchseiten erlauben.
  • Cursor unsicher oder unversioniert gestalten.
Best Practices:
  • Tiefe Pagination vermeiden.
  • search_after für fortlaufende Navigation.
  • Eindeutige Sortierung verwenden.
  • Tie-Breaker-Feld ergänzen.
  • Sortfelder korrekt mappen.
  • Cursor als API-Konzept kapseln.
  • Nutzer zur Filterverfeinerung führen.
  • Performancegrenzen dokumentieren.
Stolperfalle:

search_after ohne eindeutige Sortierung kann Dokumente überspringen oder doppelt liefern.

Merksatz:

Tiefe Seiten sind oft ein Symptom schlechter Such-UX.

B4-K11-Q008 – Elasticsearch als Read Model: Synchronisation mit System of RecordEin Suchindex ist abgeleitete Wahrheit, nicht automatisch primäre Wahrheit.
Frage:

„Wie synchronisieren Sie Elasticsearch mit dem System of Record?“

Kurz erklärt:

Elasticsearch ist in vielen Architekturen ein Read Model. Das System of Record bleibt meist relationale Datenbank, Event Store oder ein anderer primärer Speicher.

Musterantwort:

Ich behandle Elasticsearch als abgeleiteten Suchindex. Die primäre Wahrheit liegt im System of Record, zum Beispiel PostgreSQL. Synchronisation erfolgt über Events, Outbox, CDC, Batch-Reindexing oder direkte Indexierung im Write-Pfad. Für robuste Systeme bevorzuge ich Outbox oder CDC, weil sie die Lücke zwischen Datenbank-Commit und Indexing besser kontrollieren.

Der Indexer muss idempotent sein, mit Out-of-Order-Events umgehen können und Indexing-Lag überwachen. Wenn der Suchindex kaputt ist, muss ich ihn aus dem System of Record neu aufbauen können.

Senior-Antwort:

„Elasticsearch ist für mich meistens ein Such-Read-Model. Wenn es verloren geht, muss ich es aus der Wahrheit wieder aufbauen können.“

Häufige Fehler:
  • Elasticsearch als einzige Wahrheit für kritische Daten.
  • Keine Rebuild-Strategie.
  • Nicht-idempotente Indexer.
  • Alte Events überschreiben neue Daten.
  • Synchrones Indexing ohne Fallback.
  • Indexing-Fehler nur loggen.
  • Denormalisierung ohne Ownership klären.
Best Practices:
  • System of Record klar definieren.
  • Elasticsearch als abgeleitetes Read Model behandeln.
  • Outbox oder CDC für robuste Synchronisation.
  • Indexer idempotent bauen.
  • Dokumentversion oder updatedAt prüfen.
  • Indexing-Lag überwachen.
  • Rebuild-Prozess automatisieren.
  • Fehler und Dead Letters behandeln.
Stolperfalle:

Direkte Dual Writes von Datenbank und Elasticsearch ohne Outbox erzeugen Crash-Lücken und schwer nachvollziehbare Inkonsistenzen.

Merksatz:

Ein Suchindex ist abgeleitete Wahrheit, nicht automatisch primäre Wahrheit.

B4-K11-Q009 – Betrieb, Cluster Health und Performance-DiagnoseElasticsearch-Betrieb ist Sucharchitektur unter Last.
Frage:

„Welche Elasticsearch-Betriebsmetriken beobachten Sie in Produktion?“

Kurz erklärt:

Elasticsearch-Betrieb umfasst Cluster Health, Shard Allocation, JVM-Heap, Disk, Thread Pools, Query-Latenz, Indexing-Latenz und Datenwachstum.

Musterantwort:

Ich beobachte Cluster Health, unassigned Shards, Node-Zustand, JVM Heap, GC, Disk Usage, Disk Watermarks, Shard Count, Search Latency, Indexing Latency, Thread-Pool-Rejections, Refresh/Merge Pressure, Query Rate und Indexing Rate. Wenn Cluster Health yellow ist, fehlen typischerweise Replicas; red bedeutet, dass mindestens ein Primary Shard nicht verfügbar ist und Daten nicht vollständig verfügbar sind.

Außerdem prüfe ich Query-Profile, Slow Logs und Mapping-/Shard-Design. Viele Performanceprobleme entstehen nicht durch „Elasticsearch ist langsam“, sondern durch zu viele Shards, falsches Mapping, teure Aggregations, Deep Paging oder unkontrollierte Indexing-Last. Elasticsearch dokumentiert Shard Allocation als Prozess, bei dem Shard-Kopien Nodes zugewiesen und bei Änderungen im Cluster rebalanciert werden.

Senior-Antwort:

„Elasticsearch-Performance ist selten nur eine Clusterfrage. Query, Mapping, Shards, Refresh, Aggregations und Client-Verhalten gehören zusammen.“

Häufige Fehler:
  • Cluster Health ignorieren.
  • Zu viele Shards pro Node.
  • Deep Paging erlauben.
  • Teure Aggregations ohne Limits.
  • Bulk Indexing ohne Backpressure.
  • Client-Retries verstärken Überlast.
  • Disk-Watermarks erst bei Incident beachten.
Best Practices:
  • Cluster Health und Shard Allocation überwachen.
  • JVM Heap und GC beobachten.
  • Search- und Indexing-Latenzen messen.
  • Slow Logs aktiv nutzen.
  • Shard Count kontrollieren.
  • Disk-Watermarks ernst nehmen.
  • Client-Time-outs und Backpressure konfigurieren.
  • Performance mit produktionsnahen Daten testen.
Stolperfalle:

Mehr Shards bedeuten nicht automatisch bessere Performance. Zu viele Shards können Performance und Stabilität verschlechtern.

Merksatz:

Elasticsearch-Betrieb ist Sucharchitektur unter Last.

B4-K11-Q010 – Java/Spring Integration, Clients und typische Anti-PatternsElasticsearch ist ein Suchadapter, kein Domain-Layer. 24h
Frage:

„Wie integrieren Sie Elasticsearch sauber in eine Spring-Boot-Architektur?“

Kurz erklärt:

Java- und Spring-Integration sollte Elasticsearch an der Infrastrukturgrenze kapseln. Domain und Application sollten nicht von Query-JSON, Indexnamen oder Elasticsearch-Response-Strukturen abhängig sein.

Musterantwort:

Ich behandle Elasticsearch als Infrastrukturadapter für ein Such-Read-Model. Application Services sprechen mit einem fachlichen Port wie ProductSearchClient . Der Adapter baut Query DSL, setzt Timeouts, mapt Ergebnisse, behandelt Fehler und kapselt Indexnamen, Aliases und Clientdetails.

Ich vermeide, Elasticsearch-Dokumente direkt als Domain-Entities zu verwenden. Suchdokumente sind denormalisierte Read-Model-Dokumente. Außerdem begrenze ich size , verhindere Deep Paging, setze Timeouts, beobachte Latenz und definiere Fallbacks. Der offizielle Java API Client bietet typisierte Requests und Responses; trotzdem sollte die Fachschicht nicht von Elasticsearch-API-Typen durchdrungen werden.

Senior-Antwort:

„Elasticsearch gehört für mich an die Infrastrukturgrenze. Meine Fachschicht fragt nach Produktsuche, nicht nach Bool Queries und Shard Responses.“

Häufige Fehler:
  • Elasticsearch-Typen in Domain-Code leaken.
  • Indexnamen hart in Fachlogik verwenden.
  • Unbegrenzte Suchgrößen erlauben.
  • Rohe Elasticsearch-Responses an Frontend geben.
  • Keine Timeouts.
  • Keine Relevanztests.
  • Suchdokumente als System of Record nutzen.
Best Practices:
  • Elasticsearch hinter Port/Adapter kapseln.
  • Indexalias statt physische Indexnamen in Clients.
  • Suchdokumente als Read Models behandeln.
  • Query DSL explizit testen.
  • Timeouts und Limits setzen.
  • Fehler fachlich mappen.
  • Relevanztests mit realistischen Daten.
  • Integrationstests gegen echten Elasticsearch-Container.
Stolperfalle:

Spring Data Elasticsearch Repositories sehen vertraut aus, aber Suchsysteme haben andere Semantik als relationale Repositories.

Merksatz:

Elasticsearch ist ein Suchadapter, kein Domain-Layer.

B4-K12 – Kapitel 12: System Design 10 Karten

B4-K12-Q001 – Requirements, Constraints und ArchitekturzieleGutes System Design beginnt mit präzisen Fragen. 24h
Frage:

„Wie beginnen Sie eine System-Design-Aufgabe im Interview?“

Kurz erklärt:

System Design beginnt mit Anforderungen und Constraints. Ohne klare Ziele ist jede Architekturentscheidung zufällig.

Musterantwort:

Ich beginne nicht mit Technologie, sondern mit Klärungsfragen. Ich frage nach funktionalen Anforderungen, Nutzergruppen, Last, Latenz, Verfügbarkeit, Konsistenz, Datenvolumen, Sicherheitsanforderungen, Compliance, Kostenrahmen und Evolutionszielen.

Danach grenze ich bewusst ab, was nicht Teil des Designs ist. Erst dann entwerfe ich High-Level-Komponenten, Datenmodell, APIs, Skalierung, Konsistenzmodell und Betriebsstrategie. Gute System-Design-Antworten zeigen Trade-offs, nicht nur Zielarchitektur.

Senior-Antwort:

„Ich beginne System Design mit Fragen, nicht mit Komponenten. Architektur ist die Antwort auf Anforderungen, nicht auf Technologiewünsche.“

Häufige Fehler:
  • Sofort mit Kafka, Redis oder Kubernetes starten.
  • Anforderungen nicht klären.
  • Keine Annahmen explizit machen.
  • Funktionale und nichtfunktionale Anforderungen vermischen.
  • Konsistenzanforderungen ignorieren.
  • Architekturziele nicht messbar formulieren.
Best Practices:
  • Erst Anforderungen klären.
  • Annahmen explizit nennen.
  • Out-of-Scope definieren.
  • NFRs messbar machen.
  • Trade-offs dokumentieren.
  • Technologie erst nach Architekturtreibern wählen.
  • Risiken früh benennen.
  • Design inkrementell aufbauen.
Stolperfalle:

Ein Interviewer erwartet oft nicht die perfekte Architektur, sondern sichtbares Denken: Annahmen, Abwägungen, Risiken und Priorisierung.

Merksatz:

Gutes System Design beginnt mit präzisen Fragen.

B4-K12-Q002 – Capacity Estimation und GrößenordnungGrößenordnungen schlagen Bauchgefühl.
Frage:

„Wie schätzen Sie Kapazität und Skalierungsbedarf für ein neues System?“

Kurz erklärt:

Capacity Estimation liefert Größenordnungen für Traffic, Storage, Bandbreite, Partitionierung, Caching und Skalierung.

Musterantwort:

Ich mache grobe, nachvollziehbare Abschätzungen. Ich schätze Nutzer, Requests pro Nutzer, Read/Write-Verhältnis, Peak-Faktor, Payload-Größen, Datenwachstum, Retention, Cache-Hit-Rate und externe Abhängigkeiten. Ziel ist nicht exakte Mathematik, sondern Architekturtreiber zu erkennen.

Wenn ich zum Beispiel 10.000 Writes pro Sekunde und ein Jahr Retention habe, ist das ein anderes Design als 10 Writes pro Sekunde. Capacity Estimation beeinflusst Datenbankwahl, Partitionierung, Kafka-Topics, Redis-Nutzung, Elasticsearch-Shards, Kubernetes-Replicas und Kosten.

Senior-Antwort:

„Ich brauche keine Scheingenauigkeit. Ich brauche Größenordnungen, die Architekturentscheidungen sichtbar machen.“

Häufige Fehler:
  • Nur Durchschnittslast betrachten.
  • Payload-Größen ignorieren.
  • Retention vergessen.
  • Hotspots nicht berücksichtigen.
  • Externe Limits ignorieren.
  • Abschätzungen als exakte Werte verkaufen.
  • Lasttests auslassen.
Best Practices:
  • Mit Größenordnungen arbeiten.
  • Annahmen transparent machen.
  • Peak und Wachstum berücksichtigen.
  • Read und Write getrennt schätzen.
  • Storage inklusive Retention planen.
  • Bandbreite und Payloads einbeziehen.
  • Hotspots und Skew bedenken.
  • Abschätzungen durch Messungen validieren.
Stolperfalle:

Durchschnittswerte sind beruhigend, aber Systeme fallen unter Peaks, Hotspots und Wachstum aus.

Merksatz:

Größenordnungen schlagen Bauchgefühl.

B4-K12-Q003 – API Design, Contracts und Service BoundariesAPIs sind Architekturverträge, nicht Controller-Methoden.
Frage:

„Wie entwerfen Sie APIs in einem System Design?“

Kurz erklärt:

API Design definiert Verträge zwischen Clients, Services und Teams. Gute APIs sind fachlich klar, versionierbar, idempotent und evolvierbar.

Musterantwort:

Ich beginne mit fachlichen Ressourcen und Use Cases. Eine API sollte nicht interne Tabellen oder Entity-Strukturen spiegeln, sondern stabile fachliche Operationen anbieten. Ich definiere Request-/Response-DTOs, Statuscodes, Fehlerformat, Idempotenz, Pagination, Versionierung, Authentifizierung und Backward Compatibility.

Bei Service-to-Service-APIs frage ich zusätzlich: Ist synchrone Antwort wirklich nötig? Muss der Aufrufer warten? Gibt es Timeouts? Welche Fehler sind retrybar? Welche Operationen müssen idempotent sein? Ein guter API-Vertrag ist Teil der Architektur, nicht nur Controller-Code.

Senior-Antwort:

„Eine API ist ein langfristiger Vertrag. Ich lasse dort keine JPA-Entities, internen Statusnamen oder zufällige Implementierungsdetails auslaufen.“

Häufige Fehler:
  • Datenbanktabellen als REST-Ressourcen spiegeln.
  • JPA-Entities direkt serialisieren.
  • Keine Idempotenz bei kritischen POSTs.
  • Fehlende Pagination.
  • Fehlerformate inkonsistent.
  • Breaking Changes ohne Versionierung.
  • Zu viele synchrone Calls im User-Pfad.
Best Practices:
  • APIs fachlich schneiden.
  • DTOs für Verträge verwenden.
  • Idempotenz für kritische Writes.
  • Einheitliches Fehlerformat.
  • Pagination und Limits.
  • Backward Compatibility beachten.
  • Contract Tests einsetzen.
  • Timeouts und Retry-Semantik dokumentieren.
Stolperfalle:

Ein REST-Endpunkt, der intern alles synchron orchestriert, kann schnell zum Single Point of Latency und Failure werden.

Merksatz:

APIs sind Architekturverträge, nicht Controller-Methoden.

B4-K12-Q004 – Datenmodell, Storage Choice und System of RecordDer Speicher folgt dem Zugriffsmuster, nicht dem Hype.
Frage:

„Wie entscheiden Sie, welche Daten in SQL, Redis, Kafka, Elasticsearch oder Object Storage gehören?“

Kurz erklärt:

Storage Choice hängt von Zugriffsmustern, Konsistenz, Datenvolumen, Retention, Abfrageform und Betriebsanforderungen ab.

Musterantwort:

Ich entscheide nach Datencharakter und Zugriffsmuster. Relationale Datenbanken sind starke Defaults für transaktionale Business-Daten, Constraints und Konsistenz. Redis eignet sich für kurzlebige, schnelle, oft ableitbare Daten. Kafka ist ein Event Log und Integrationsstrom, keine Query-Datenbank. Elasticsearch ist ein Such-Read-Model für Volltext, Facetten und Relevanz. Object Storage ist gut für große unveränderliche Dateien oder Blobs.

Wichtig ist die Frage: Was ist das System of Record? Für kritische Fachzustände muss klar sein, wo die Wahrheit liegt. Abgeleitete Systeme wie Redis Cache oder Elasticsearch Index müssen rekonstruierbar sein.

Senior-Antwort:

„Ich wähle Speicher nach Zugriffsmuster und Konsistenzbedarf. Nicht jeder schnelle Speicher darf Wahrheit werden.“

Häufige Fehler:
  • Technologie nach Popularität wählen.
  • Redis als primäre Datenbank für kritische Daten.
  • Elasticsearch als einzige Wahrheit.
  • Kafka für Abfragen missbrauchen.
  • Daten in mehreren Systemen ohne Ownership.
  • Rebuild-Strategien vergessen.
  • Backup/Restore nicht pro Speicher betrachten.
Best Practices:
  • System of Record klar definieren.
  • Zugriffsmuster vor Storage wählen.
  • Abgeleitete Stores rekonstruierbar halten.
  • Konsistenzmodell dokumentieren.
  • Speicheranzahl begrenzen.
  • Betriebsaufwand einpreisen.
  • Backup/Restore und Migrationen planen.
  • Ownership pro Datenart definieren.
Stolperfalle:

Polyglot Persistence klingt modern, erhöht aber jedes Mal Konsistenz-, Betriebs- und Debugging-Kosten.

Merksatz:

Der Speicher folgt dem Zugriffsmuster, nicht dem Hype.

B4-K12-Q005 – Skalierung, Load Balancing und CachingSkalierung beginnt mit Bottleneck-Verständnis.
Frage:

„Wie skalieren Sie ein System, das unter hoher Leselast steht?“

Kurz erklärt:

Skalierung kann vertikal, horizontal, durch Caching, Partitionierung, asynchrone Verarbeitung oder bessere Datenmodelle erfolgen.

Musterantwort:

Ich analysiere zuerst den Bottleneck. Wenn die Anwendung CPU-limitiert ist, kann horizontales Skalieren helfen. Wenn die Datenbank der Engpass ist, helfen mehr App-Instanzen nicht unbedingt. Für hohe Leselast prüfe ich Caching, Read Replicas, Materialized Views, CDN, Query-Optimierung, Pagination, Denormalisierung oder Elasticsearch-Read-Models.

Load Balancing verteilt Traffic auf Instanzen, löst aber keine Zustands-, Datenbank- oder Hot-Key-Probleme. Caching reduziert Last, erzeugt aber Konsistenzfragen. Skalierung ist immer Bottleneck-spezifisch.

Senior-Antwort:

„Ich skaliere nicht blind Pods. Ich suche den Bottleneck und wähle die Maßnahme, die genau dort wirkt.“

Häufige Fehler:
  • Mehr App-Instanzen starten, obwohl DB limitiert.
  • Stateful Services horizontal skalieren wollen.
  • Cache ohne Invalidierung.
  • Keine Connection-Pool-Koordination.
  • Hot Keys ignorieren.
  • Skalierung ohne Metriken.
  • Caching für stark konsistente Daten.
Best Practices:
  • Bottleneck messen.
  • Stateless Services bevorzugen.
  • Horizontal skalieren, wo sinnvoll.
  • Caching fachlich begründen.
  • Cache Stampede verhindern.
  • Read und Write getrennt optimieren.
  • Connection Pools gesamtsystemisch planen.
  • Loadtests mit realistischen Daten.
Stolperfalle:

Horizontal skalierte Anwendungen können eine Datenbank schneller überlasten, wenn jeder Pod eigene Connection Pools und Caches mitbringt.

Merksatz:

Skalierung beginnt mit Bottleneck-Verständnis.

B4-K12-Q006 – Konsistenz, verteilte Transaktionen und IdempotenzIn verteilten Systemen ist Wiederholung normal; Korrektheit entsteht durch Idempotenz.
Frage:

„Wie gehen Sie mit Konsistenz in verteilten Systemen um?“

Kurz erklärt:

Verteilte Systeme brauchen bewusstes Konsistenzdesign. Idempotenz, Sagas, Outbox und Kompensation ersetzen häufig globale Transaktionen.

Musterantwort:

Ich versuche zuerst, starke Konsistenz innerhalb klarer Grenzen zu halten: innerhalb eines Aggregates, einer Datenbanktransaktion oder eines Services. Über Servicegrenzen hinweg akzeptiere ich oft Eventual Consistency und modelliere Geschäftsprozesse explizit, zum Beispiel mit Sagas, Zuständen, Kompensation, Outbox und idempotenten Consumer.

Idempotenz ist zentral, weil Retries, Timeouts und doppelte Events normal sind. Ein Payment-Create-Command braucht einen Idempotency-Key. Ein Kafka-Consumer braucht Event-Deduplikation. Eine externe Provider-Operation braucht fachliche Referenzen, damit Wiederholungen sicher sind.

Senior-Antwort:

„Konsistenz ist eine fachliche Entscheidung. Ich sichere sie lokal stark und verteile Prozesse bewusst mit Idempotenz, Outbox und Kompensation.“

Häufige Fehler:
  • @Transactional über Remote Calls erwarten.
  • Keine Idempotenz bei Retries.
  • Zwischenzustände nicht modellieren.
  • Kompensation vergessen.
  • Eventual Consistency dem Fachbereich nicht erklären.
  • Idempotency-Key nur im Cache ohne Garantie.
  • Doppelte Events nicht behandeln.
Best Practices:
  • Konsistenzbedarf pro Use Case klären.
  • Starke Konsistenz lokal halten.
  • Verteilte Prozesse als Saga modellieren.
  • Outbox für zuverlässige Events.
  • Idempotenz überall bei Retries.
  • Zustände und Kompensation explizit machen.
  • Fehlerpfade testen.
  • Fachliche Kommunikation über Eventual Consistency.
Stolperfalle:

Ein Timeout bedeutet nicht, dass die Operation fehlgeschlagen ist. Sie kann erfolgreich gewesen sein, während die Antwort verloren ging.

Merksatz:

In verteilten Systemen ist Wiederholung normal; Korrektheit entsteht durch Idempotenz.

B4-K12-Q007 – Asynchrone Verarbeitung, Queues und Event-driven DesignEvents entkoppeln Zeit, nicht Verantwortung.
Frage:

„Wann verwenden Sie asynchrone Verarbeitung oder Events in einem System Design?“

Kurz erklärt:

Asynchrone Verarbeitung entkoppelt Latenz und Verfügbarkeit, erzeugt aber Eventual Consistency, Retry-Logik und Observability-Anforderungen.

Musterantwort:

Ich nutze asynchrone Verarbeitung, wenn ein Schritt nicht im synchronen User-Pfad abgeschlossen werden muss, wenn Arbeit lange dauert, wenn Last geglättet werden soll oder wenn mehrere Consumer unabhängig reagieren sollen. Beispiele sind E-Mail-Versand, Reporting, Suchindex-Aktualisierung, Audit, Notifications oder nachgelagerte Integrationen.

Ich unterscheide zwischen Command, Event und Job. Ein Event beschreibt, was passiert ist. Ein Command fordert eine Aktion an. Ein Job ist eine auszuführende Arbeitseinheit. Für robuste Asynchronität brauche ich Idempotenz, Retry, DLQ, Backpressure, Lag-Monitoring und klare Ordering-Annahmen.

Senior-Antwort:

„Asynchronität reduziert Latenzkopplung, aber sie entfernt keine Verantwortung. Sie verschiebt Verantwortung in Idempotenz, Retry, Lag und Monitoring.“

Häufige Fehler:
  • Asynchronität nutzen, um Fachfehler zu verstecken.
  • Keine Idempotenz.
  • Keine DLQ.
  • Consumer Lag nicht überwachen.
  • Ordering global annehmen.
  • @Async als zuverlässige Queue missbrauchen.
  • Poison Messages nicht behandeln.
Best Practices:
  • Asynchronität fachlich begründen.
  • Event, Command und Job trennen.
  • Outbox für zuverlässiges Publishing.
  • Consumer idempotent bauen.
  • Retry und DLQ definieren.
  • Lag und Fehlerquoten überwachen.
  • Backpressure ernst nehmen.
  • Ordering-Annahmen dokumentieren.
Stolperfalle:

Eine Queue macht ein überlastetes Downstream-System nicht schneller. Sie macht Überlast sichtbarer und zeitlich entkoppelt.

Merksatz:

Events entkoppeln Zeit, nicht Verantwortung.

B4-K12-Q008 – Reliability, Resilience und ObservabilityReliability ist geplantes Verhalten im Fehlerfall.
Frage:

„Wie entwerfen Sie ein zuverlässiges System?“

Kurz erklärt:

Reliability entsteht durch Fehlerannahmen, Resilience Patterns, Observability, SLOs, Runbooks und getestete Recovery.

Musterantwort:

Ich entwerfe für Fehler, nicht für den Happy Path. Ich identifiziere kritische Abhängigkeiten, definiere SLOs, Timeouts, Retries mit Budget, Circuit Breaker, Bulkheads, Fallbacks, Backpressure, Graceful Degradation und Recovery-Prozesse. Außerdem baue ich Observability: Logs, Metrics, Traces, Correlation IDs, Business KPIs und Alerts auf Symptome.

Reliability ist nicht nur Code. Es geht auch um Rollouts, Monitoring, Incident Response, Runbooks, Chaos Tests, Capacity Planning und Postmortems. Ein System ist erst zuverlässig, wenn es unter Fehlerbedingungen verständlich und steuerbar bleibt.

Senior-Antwort:

„Zuverlässigkeit entsteht nicht dadurch, dass nichts kaputtgeht. Sie entsteht dadurch, dass Fehler begrenzt, sichtbar und wiederherstellbar sind.“

Häufige Fehler:
  • Keine Timeouts.
  • Retries ohne Idempotenz.
  • Fallbacks ohne fachliche Freigabe.
  • Alerts auf technische Einzelereignisse statt Symptome.
  • Keine Correlation IDs.
  • Incident Runbooks fehlen.
  • Resilience nicht testen.
Best Practices:
  • Fehlerfälle explizit modellieren.
  • Timeouts überall setzen.
  • Retries begrenzen und idempotent machen.
  • Bulkheads für kritische Ressourcen.
  • Circuit Breaker für instabile Downstreams.
  • Graceful Degradation definieren.
  • SLO-basierte Alerts.
  • Logs, Metrics und Traces korrelieren.
Stolperfalle:

Retries ohne Budget und Idempotenz sind kein Resilience Pattern, sondern ein Lastverstärker.

Merksatz:

Reliability ist geplantes Verhalten im Fehlerfall.

B4-K12-Q009 – Security, Privacy und Compliance by DesignSecurity by Design bedeutet: Schutz ist Architektur, nicht Nachrüstung.
Frage:

„Wie berücksichtigen Sie Security und Datenschutz in einem System Design?“

Kurz erklärt:

Security und Privacy müssen in System Design von Anfang an berücksichtigt werden: Identität, Autorisierung, Datenklassifikation, Secrets, Verschlüsselung, Auditing und Least Privilege.

Musterantwort:

Ich beginne mit Datenklassifikation und Bedrohungsmodell: Welche Daten sind öffentlich, intern, personenbezogen, finanziell oder reguliert? Danach entwerfe ich Authentifizierung, Autorisierung, Mandantentrennung, Least Privilege, Secret Management, Verschlüsselung in Transit und at Rest, Audit Logs, Zugriffskontrollen, Datenminimierung, Retention und Löschkonzepte.

Security darf nicht nur am API Gateway sitzen. Jeder Service muss kritische Autorisierung selbst prüfen. Logs dürfen keine sensiblen Daten enthalten. Backups, Events, Caches und Suchindizes müssen ebenfalls Datenschutzregeln einhalten.

Senior-Antwort:

„Security ist kein Randfilter. Sie betrifft Identität, Daten, Logs, Events, Caches, Suchindizes, Backups und Betrieb.“

Häufige Fehler:
  • Nur am Gateway autorisieren.
  • Tenant-ID aus Request ungeprüft vertrauen.
  • Sensitive Daten loggen.
  • Secrets in ConfigMaps oder Images.
  • Events mit unnötigen personenbezogenen Daten.
  • Keine Retention-Strategie.
  • Admin-Endpunkte ungeschützt.
Best Practices:
  • Datenklassifikation früh durchführen.
  • Threat Modeling nutzen.
  • AuthN und AuthZ trennen.
  • Least Privilege konsequent anwenden.
  • Mandantentrennung technisch absichern.
  • Sensitive Daten maskieren.
  • Secrets sicher verwalten und rotieren.
  • Audit Logs manipulationsarm gestalten.
Stolperfalle:

Ein Event kann genauso sensible Daten leaken wie eine REST API, nur oft schwerer sichtbar.

Merksatz:

Security by Design bedeutet: Schutz ist Architektur, nicht Nachrüstung.

B4-K12-Q010 – Evolution, Trade-offs und ArchitekturentscheidungenArchitektur ist eine Folge bewusster Entscheidungen unter Unsicherheit. 24h
Frage:

„Wie gehen Sie mit Trade-offs und zukünftiger Evolution im System Design um?“

Kurz erklärt:

System Design ist evolutionär. Gute Architekturen dokumentieren Entscheidungen, Trade-offs und Bedingungen, unter denen sie geändert werden.

Musterantwort:

Ich versuche nicht, die perfekte Endarchitektur zu bauen. Ich identifiziere Architekturtreiber, wähle eine angemessene Lösung für den aktuellen Kontext und dokumentiere Trade-offs. Ich frage: Was ist heute teuer? Was könnte morgen teuer werden? Welche Entscheidung ist reversibel? Welche Entscheidung ist schwer änderbar?

Architekturentscheidungen dokumentiere ich als ADRs: Kontext, Entscheidung, Alternativen, Konsequenzen. Außerdem definiere ich Evolutionspfade: Wann extrahieren wir einen Service? Wann führen wir Kafka ein? Wann brauchen wir Elasticsearch? Wann reicht SQL? Das verhindert Hype-driven Design.

Senior-Antwort:

„Ich entwerfe nicht für jede hypothetische Zukunft, aber ich vermeide Entscheidungen, die wahrscheinliche Zukunft teuer oder unmöglich machen.“

Häufige Fehler:
  • Zukunftsarchitektur für hypothetische Last bauen.
  • Alle Entscheidungen als endgültig behandeln.
  • Trade-offs nicht dokumentieren.
  • Komplexität ohne Trigger einführen.
  • Architektur nicht mit Teamstruktur abstimmen.
  • Keine technischen Grenzen im Code.
  • Keine Migrationspfade planen.
Best Practices:
  • Entscheidungen dokumentieren.
  • Trade-offs explizit machen.
  • Reversible und irreversible Entscheidungen unterscheiden.
  • Einfach starten, aber Grenzen sauber setzen.
  • Evolutions-Trigger definieren.
  • Architekturtests nutzen.
  • Technische Schulden sichtbar machen.
  • Regelmäßige Architekturreviews.
Stolperfalle:

„Wir bauen es gleich richtig skalierbar“ bedeutet oft: Wir bauen es gleich unnötig komplex.

Merksatz:

Architektur ist eine Folge bewusster Entscheidungen unter Unsicherheit.

B5 – Band 5: Jakarta EE Professional

B5-K01 – Kapitel 1: CDI 10 Karten

B5-K01-Q001 – Bean-Modell, Dependency Injection und typesafe resolutionCDI injiziert nicht nach Namen, sondern primär nach Typ und Qualifier. 24h
Frage:

„Was ist eine CDI Bean, und wie funktioniert Dependency Injection in CDI?“

Kurz erklärt:

CDI verwaltet Beans, löst Abhängigkeiten typesicher auf und injiziert passende Instanzen anhand von Typ und Qualifier.

Musterantwort:

Eine CDI Bean ist ein vom Container verwaltetes Objekt mit Bean Types, Qualifiers, Scope, optionalem Namen, optionalen Interceptor Bindings und einer Implementierung. Der CDI-Container erzeugt, injiziert, scoped und zerstört Beans nach definierten Regeln. Die CDI-Spezifikation beschreibt Bean-Attribute explizit als Bean Types, Qualifiers, Scope, optionalen Bean-Namen, Interceptor Bindings und Bean-Implementierung.

Injection funktioniert typesicher: Der Container betrachtet den benötigten Typ und die Qualifiers des Injection Points und sucht eine passende Bean. Wenn keine Bean passt, entsteht eine unsatisfied dependency. Wenn mehrere Beans gleich gut passen, entsteht eine ambiguous dependency. Diese Fehler werden normalerweise beim Deployment erkannt, nicht erst im ersten Request.

Senior-Antwort:

„CDI ist für mich nicht nur @Inject . Es ist ein typesicherer Container, der Lebenszyklus, Scope, Auswahl und Cross-Cutting Concerns kontrolliert.“

Häufige Fehler:
  • Feldinjektion als Default verwenden.
  • Zu viele konkrete Implementierungen ohne Qualifier.
  • @Named für technische Auswahl missbrauchen.
  • Containerfehler erst im Runtime-Pfad erwarten.
  • Domain-Objekte unnötig zu CDI Beans machen.
  • new verwenden und damit Injection umgehen.
Best Practices:
  • Konstruktorinjektion bevorzugen.
  • Ports über Interfaces modellieren.
  • Qualifier für echte Varianten verwenden.
  • Domain-Objekte von CDI Lifecycle trennen.
  • Deploymentfehler ernst nehmen.
  • Abhängigkeiten klein und explizit halten.
  • Keine Service-Locator-Patterns in CDI-Code verstecken.
Stolperfalle:

Eine Klasse, die man mit new erzeugt, ist kein vom CDI-Container verwaltetes Objekt. Injection, Interceptors und Lifecycle Callbacks greifen dann nicht.

Merksatz:

CDI injiziert nicht nach Namen, sondern primär nach Typ und Qualifier.

B5-K01-Q002 – Scopes, Contexts und Bean-LifecycleJe länger der Scope, desto gefährlicher unkontrollierter Zustand.
Frage:

„Was ist der Unterschied zwischen @ApplicationScoped , @RequestScoped und @Dependent ?“

Kurz erklärt:

Scopes bestimmen, wie lange eine Bean-Instanz lebt und in welchem Context sie gültig ist.

Musterantwort:

@ApplicationScoped bedeutet: eine kontextuelle Instanz pro Anwendungskontext. Sie eignet sich für zustandslose Services, Adapter, Policies oder Caches mit kontrolliertem Thread-Safety-Design. @RequestScoped bedeutet: eine Instanz pro Request-Kontext. Sie eignet sich für requestbezogene Daten, Correlation, User-Request-Zustand oder requestlokale Verarbeitung. @Dependent ist ein Pseudo-Scope. Eine dependent Bean wird nicht als normaler Context mit Client Proxy verwaltet, sondern an den Lebenszyklus des Objekts gebunden, in das sie injiziert wird. Die CDI-Spezifikation sagt ausdrücklich, dass @Dependent ein Pseudo-Scope ist und dass eine injizierte dependent Instanz nicht zwischen mehreren Injection Points geteilt wird.

Senior-relevant ist: Scope ist eine Architekturentscheidung. Ein mutable @ApplicationScoped Bean kann Race Conditions erzeugen. Ein schweres @RequestScoped Objekt kann pro Request Kosten erzeugen. Ein @Dependent Objekt in einem langlebigen Bean lebt ebenfalls lange.

Senior-Antwort:

„Scope ist nicht Dekoration. Scope definiert Lebensdauer, Sharing, Thread-Safety und Speicherverhalten einer Bean.“

Häufige Fehler:
  • Mutable State in @ApplicationScoped .
  • Requestdaten in Application Beans speichern.
  • @Dependent für ressourcenintensive Objekte ohne Lifecycle-Kontrolle.
  • Scope mit Thread-Safety verwechseln.
  • Session- oder Conversation-Scope ohne Passivation-Verständnis.
  • Asynchrone Verarbeitung mit Request Scope erwarten.
Best Practices:
  • Application Beans zustandslos halten.
  • Requestdaten in Request Scope oder explizite DTOs legen.
  • @Dependent bewusst einsetzen.
  • Ressourcen mit Producer/Disposer verwalten.
  • Scope-Lebensdauer dokumentieren.
  • Thread-Safety bei langlebigen Beans prüfen.
  • Async-Grenzen explizit modellieren.
Stolperfalle:

@ApplicationScoped bedeutet nicht automatisch thread-safe. Es bedeutet nur, dass die Instanz anwendungsweit geteilt wird.

Merksatz:

Je länger der Scope, desto gefährlicher unkontrollierter Zustand.

B5-K01-Q003 – Qualifier, Alternatives und ImplementierungsauswahlQualifier lösen Varianten; Alternatives lösen Austauschbarkeit.
Frage:

„Wann verwenden Sie CDI Qualifier statt @Named oder Alternatives?“

Kurz erklärt:

Qualifier unterscheiden mehrere Beans desselben Typs. Alternatives erlauben gezielte Austauschbarkeit, zum Beispiel für Tests oder Deployment-Profile.

Musterantwort:

Qualifier verwende ich, wenn mehrere Implementierungen desselben Typs fachlich oder technisch unterscheidbar sind, zum Beispiel @PrimaryProvider , @FallbackProvider , @Audit , @Internal oder @External . Qualifier sind typesicher und können Annotation-Member besitzen. CDI sagt, dass Qualifiers durch Annotationen an Bean-Klassen, Producer-Methoden oder Producer-Feldern deklariert werden und dass eine Bean mehrere Qualifier haben kann.

@Named verwende ich primär für Namensintegration, etwa mit EL oder Legacy-Fällen, nicht als Standard für technische Auswahl. Alternatives verwende ich, wenn eine Bean nur explizit aktiviert werden soll, etwa Mock, Testadapter oder alternative Provider-Implementierung.

Senior-Antwort:

„Qualifier sind meine typesichere Sprache für Varianten. Namen sind Strings; Qualifier sind Architektur.“

Häufige Fehler:
  • @Named("stripe") statt typesicherem Qualifier.
  • Mehrere Default-Implementierungen desselben Interfaces.
  • Qualifier ohne fachliche Bedeutung.
  • Alternatives versehentlich global aktivieren.
  • Test-Alternatives in Produktion verfügbar machen.
  • Qualifier-Member ohne @Nonbinding falsch verwenden.
Best Practices:
  • Qualifier fachlich benennen.
  • @Named nicht als Implementierungsauswahl missbrauchen.
  • Alternatives für Test- oder Deployment-Varianten.
  • @Priority bewusst und sparsam einsetzen.
  • Default-Implementierungen eindeutig halten.
  • Qualifier-Konventionen im Team standardisieren.
  • Deploymentfehler durch CDI-Resolution als Schutz verstehen.
Stolperfalle:

Wenn zwei Beans denselben Typ und denselben Qualifier erfüllen, ist die Dependency ambiguous. CDI entscheidet dann nicht magisch „irgendeine“.

Merksatz:

Qualifier lösen Varianten; Alternatives lösen Austauschbarkeit.

B5-K01-Q004 – Producer, Disposer und RessourcenintegrationProducer bauen Ressourcen; CDI verwaltet ihren Vertrag.
Frage:

„Wann verwenden Sie CDI Producer Methods oder Producer Fields?“

Kurz erklärt:

Producer machen Objekte zu CDI Beans, die nicht direkt vom Container konstruiert werden können oder sollen. Disposer räumen Producer-Produkte kontrolliert auf.

Musterantwort:

Producer verwende ich, wenn die Erzeugung einer Bean nicht durch einfachen Konstruktor möglich oder sinnvoll ist: HTTP Clients, Konfigurationsobjekte, externe SDK-Clients, EntityManager-ähnliche Ressourcen, tenantabhängige Adapter, Formatter oder technische Factories. Eine Producer Method wird mit @Produces annotiert und kann selbst Injection nutzen. Die CDI-Spezifikation beschreibt Producer Methods als Methoden mit @Produces ; sie können Scope, Bean Name, Stereotypes und Qualifiers deklarieren, und ihre Parameter sind Injection Points.

Disposer verwende ich, wenn das produzierte Objekt Cleanup benötigt. Wichtig ist, Producer nicht direkt aufzurufen, wenn man CDI-Lifecycle erwartet. Ein direkter Methodenaufruf liefert nur ein normales Java-Objekt und bindet es nicht an den CDI-Context.

Senior-Antwort:

„Producer sind meine kontrollierte Brücke zwischen CDI und Objekten, die der Container nicht trivial selbst bauen sollte.“

Häufige Fehler:
  • Producer direkt als Factory-Methode aufrufen.
  • Teure Clients als @Dependent produzieren.
  • Qualifier am Producer vergessen.
  • Disposer für Closeable-Ressourcen vergessen.
  • Producer mit zu viel Business-Logik überladen.
  • Null-Produkte mit normalem Scope riskieren.
Best Practices:
  • Producer für technische Ressourcen verwenden.
  • Scope des Producer-Produkts bewusst wählen.
  • Qualifier für mehrere Clients.
  • Cleanup über Disposer modellieren.
  • Producer schlank halten.
  • Fachcode von Ressourcenaufbau trennen.
  • Producer in Integrationstests prüfen.
Stolperfalle:

Der Scope des Producer-Beans ist nicht automatisch der Scope des produzierten Objekts. Das muss explizit verstanden werden.

Merksatz:

Producer bauen Ressourcen; CDI verwaltet ihren Vertrag.

B5-K01-Q005 – CDI Events, Observer und lose KopplungCDI Events entkoppeln Beans, nicht Systeme. 24h
Frage:

„Wann verwenden Sie CDI Events, und wann lieber Kafka oder JMS?“

Kurz erklärt:

CDI Events ermöglichen lose Kopplung innerhalb des CDI-Containers. Observer reagieren auf Events anhand von Typ und Qualifier.

Musterantwort:

CDI Events eignen sich für lose gekoppelte Kommunikation innerhalb derselben Anwendung und desselben CDI-Containers: lokale Domain-Benachrichtigungen, interne Erweiterungspunkte, Cache-Invalidierung im selben Deployment oder technische Reaktionen. Die Spezifikation beschreibt CDI Events als Möglichkeit, dass Beans Events produzieren und konsumieren, ohne Compile-Time-Abhängigkeit zwischen den interagierenden Beans; ein Event besteht aus einem Java-Objekt und Qualifier-Instanzen.

Für verlässliche serviceübergreifende Integration, Replay, Retention, Consumer-Gruppen, Backpressure oder unabhängige Deployments nutze ich Kafka, JMS oder eine Outbox-basierte Messaging-Lösung. CDI Events sind kein Ersatz für einen persistenten Broker.

Senior-Antwort:

„CDI Events sind gut für lokale lose Kopplung. Für verteilte, persistente Integration brauche ich einen Broker oder eine Outbox.“

Häufige Fehler:
  • CDI Events als Kafka-Ersatz verwenden.
  • Teure Logik in synchronen Observern.
  • Observer-Reihenfolge fachlich voraussetzen.
  • Fehler in asynchronen Observern nicht überwachen.
  • Transaktionsgrenzen ignorieren.
  • Domain Events und Integration Events vermischen.
Best Practices:
  • CDI Events für lokale Entkopplung nutzen.
  • Observer klein und fokussiert halten.
  • Durable Integration über Outbox/Broker.
  • Qualifier für Event-Kategorien verwenden.
  • Fehlerverhalten dokumentieren.
  • Asynchrone Observer überwachen.
  • Events fachlich benennen.
  • Keine globale Geschäftsorchestrierung über CDI Events verstecken.
Stolperfalle:

Ein CDI Event ist nicht automatisch persistent, replaybar oder serviceübergreifend zustellbar.

Merksatz:

CDI Events entkoppeln Beans, nicht Systeme.

B5-K01-Q006 – Interceptors, Decorators und Cross-Cutting ConcernsInterceptors schneiden quer; Decorators erweitern entlang eines Interfaces.
Frage:

„Was ist der Unterschied zwischen CDI Interceptor und Decorator?“

Kurz erklärt:

Interceptors kapseln technische Cross-Cutting Concerns. Decorators erweitern fachliche Interfaces durch delegierendes Verhalten.

Musterantwort:

Ein Interceptor hängt an Interceptor Bindings und kapselt technische Querschnittsthemen wie Auditing, Logging, Security, Timing oder Transaktionsähnliches. Er interceptet Methodenaufrufe generisch. Ein Decorator ist fachnäher: Er implementiert dasselbe Interface wie die dekorierte Bean und delegiert über @Delegate . Dadurch kann er fachliche Erweiterungen oder Policy-Verhalten um eine bestimmte Schnittstelle legen.

CDI Full stellt zusätzlich zu CDI Lite auch Decorators bereit; die CDI-Spezifikation nennt Decoration ausdrücklich als Fähigkeit im CDI-Full-Umfeld.

Senior-Antwort:

„Interceptors sind generische Querschnittsmechanik. Decorators sind fachlich typisierte Erweiterung eines Vertrags.“

Häufige Fehler:
  • Businesslogik in generische Interceptors legen.
  • Self-Invocation erwarten.
  • Interceptors auf nicht-containerverwalteten Objekten erwarten.
  • Zu viele Interceptors auf Hot Paths.
  • Decorator ohne klares Interface.
  • Interceptor-Reihenfolge nicht kontrollieren.
Best Practices:
  • Interceptors für technische Querschnittsthemen.
  • Decorators für fachnahe Erweiterung eines Interfaces.
  • Binding-Annotationen klar benennen.
  • Aufruf über CDI-Proxy sicherstellen.
  • Interceptor-Logik kurz halten.
  • Reihenfolge über Priorität bewusst steuern.
  • Cross-Cutting Concerns testbar halten.
Stolperfalle:

Ein Methodenaufruf innerhalb derselben Bean läuft nicht zwangsläufig durch den CDI-Proxy. Interceptors können dadurch ausbleiben.

Merksatz:

Interceptors schneiden quer; Decorators erweitern entlang eines Interfaces.

B5-K01-Q007 – Programmatic Lookup mit Instance und dynamische AuswahlDynamische Auswahl braucht Instance ; klare Abhängigkeiten brauchen Konstruktoren.
Frage:

„Wann verwenden Sie Instance<T> statt normaler Injection?“

Kurz erklärt:

Instance<T> ermöglicht programmatic lookup, lazy retrieval und dynamische Auswahl von Beans zur Laufzeit.

Musterantwort:

Normale Injection ist der Default. Instance<T> nutze ich, wenn ich Beans lazy, optional, mehrfach oder dynamisch auswählen muss. Beispiele sind Plugin-Mechanismen, Provider-Routing, optionale Integrationen, Strategie-Auswahl oder Laufzeitselektion anhand von Tenant, Region oder Feature.

Die CDI-Spezifikation beschreibt Instance als built-in Bean, die für Injection Points vom Typ Instance<X> oder Provider<X> verfügbar ist, jeden Qualifier akzeptieren kann, @Dependent scope hat und automatisch vom Container bereitgestellt wird.

Senior-Antwort:

„ Instance<T> ist mein Werkzeug für echte Laufzeitdynamik. Für normale Abhängigkeiten bleibt Constructor Injection klarer.“

Häufige Fehler:
  • Instance als globale Bean Registry missbrauchen.
  • Fachlogik durch versteckte Lookup-Logik ersetzen.
  • Dependent-Objekte beim Lookup nicht zerstören.
  • Dynamische Auswahl ohne Tests.
  • Qualifier-Resolution durch manuelle Strings umgehen.
  • Normale Injection vermeiden, obwohl sie reicht.
Best Practices:
  • Direkte Injection bevorzugen.
  • Instance für echte Dynamik verwenden.
  • Auswahlregeln kapseln.
  • Unsatisfied/Ambiguous explizit behandeln.
  • Dependent-Lifecycle beachten.
  • Qualifier statt Strings verwenden, wo möglich.
  • Plugin-Mechanismen dokumentieren.
Stolperfalle:

Programmatic lookup kann Abhängigkeiten verstecken. Was im Konstruktor explizit wäre, wird dann zur Laufzeitmagie.

Merksatz:

Dynamische Auswahl braucht Instance ; klare Abhängigkeiten brauchen Konstruktoren.

B5-K01-Q008 – Bean Discovery, beans.xml, CDI Lite und CDI FullWas eine Bean sein soll, sollte als Bean erkennbar sein.
Frage:

„Was bedeutet Bean Discovery in CDI, und was ist der Unterschied zwischen CDI Lite und CDI Full?“

Kurz erklärt:

Bean Discovery bestimmt, welche Klassen zu CDI Beans werden. CDI 4.x unterscheidet CDI Lite und CDI Full.

Musterantwort:

Bean Discovery ist der Prozess, in dem der Container erkennt, welche Klassen, Producer, Observer, Interceptors und Erweiterungen Teil des CDI-Bean-Archives sind. Seit CDI 4.0 ist der Default für beans.xml stärker auf annotated ausgerichtet: Standardmäßig werden nur Typen mit bean-defining annotations berücksichtigt; Deployments, die alle Typen entdecken wollen, müssen bean-discovery-mode="all" setzen oder passende bean-defining annotations hinzufügen. Die CDI-4.1-Spezifikation dokumentiert diese CDI-4.0-Änderung und erklärt den annotated -Default.

CDI Lite ist ein Kernsubset für eingeschränktere Umgebungen und Build-Time-orientierte Integrationen. CDI Full enthält CDI Lite plus zusätzliche Features, darunter klassische Portable Extensions, Decorators und weitere Full-Features. CDI 4.0 hat den Core in Lite und Full aufgeteilt.

Senior-Antwort:

„Bean Discovery ist Teil der Architektur. Ich will wissen, welche Klassen Beans sind, statt dem Container zufällige Klassen anzubieten.“

Häufige Fehler:
  • Verlassen auf zufällige Bean Discovery.
  • beans.xml aus alten Java-EE-Versionen unverändert übernehmen.
  • Plain Klassen als Beans erwarten.
  • CDI Lite und Full Features verwechseln.
  • Decorators in Lite-Umgebung erwarten.
  • Deploymentfehler durch fehlende bean-defining annotations ignorieren.
Best Practices:
  • Beans explizit annotieren.
  • bean-discovery-mode="annotated" bewusst nutzen.
  • Domain-Objekte nicht unnötig zu Beans machen.
  • CDI Lite vs Full im Zielruntime prüfen.
  • beans.xml versioniert pflegen.
  • Stereotypes für wiederkehrende Bean-Muster nutzen.
  • Startup- und Discovery-Verhalten testen.
Stolperfalle:

Nach Migration auf CDI 4.x können Klassen ohne bean-defining annotation plötzlich nicht mehr entdeckt werden, wenn vorher implizite Discovery genutzt wurde.

Merksatz:

Was eine Bean sein soll, sollte als Bean erkennbar sein.

B5-K01-Q009 – Portable Extensions, Build Compatible Extensions und Container-ErweiterungCDI Extensions erweitern den Container, nicht den Use Case.
Frage:

„Was sind CDI Extensions, und wann würden Sie sie verwenden?“

Kurz erklärt:

CDI Extensions erlauben Frameworks und Plattformteams, das CDI-Modell zu erweitern, Beans zu verändern, zu validieren oder zusätzliche Integrationsmechanismen bereitzustellen.

Musterantwort:

CDI Extensions sind SPI-Mechanismen, mit denen Frameworks oder Plattformen den Container-Bootstrap beeinflussen können: Beans analysieren, vetoen, synthetische Beans hinzufügen, Metadaten ändern oder Integrationsregeln erzwingen. Ich verwende sie nicht für normale Businesslogik, sondern für Framework-, Plattform- oder Infrastrukturintegration.

CDI 4.x unterscheidet klassische Portable Extensions und Build Compatible Extensions. CDI 4.0 hat CDI Lite eingeführt und Build Compatible Extensions als neue Extension-API für eingeschränktere, build-time-freundliche Umgebungen relevant gemacht; CDI Full enthält die klassischen Full-Features.

Senior-Antwort:

„Extensions sind Container-Programmierung. Ich nutze sie für Plattformmechanik, nicht für Geschäftslogik.“

Häufige Fehler:
  • Businessregeln in Extensions verstecken.
  • Extensions ohne Tests.
  • Container-Bootstrap stark verlangsamen.
  • Portable Extensions in CDI-Lite-Umgebung erwarten.
  • Veto/Metadata-Änderungen schwer nachvollziehbar machen.
  • Anwendung an proprietäre Containerdetails koppeln.
Best Practices:
  • Extensions für Framework- und Plattformlogik.
  • Businesslogik in normalen Beans lassen.
  • Bootstrap-Auswirkungen messen.
  • Extension-Verhalten dokumentieren.
  • Deploymentfehler klar formulieren.
  • CDI Lite/Full-Kompatibilität prüfen.
  • Extensions isoliert testen.
  • Build Compatible Extensions für moderne Build-Time-Szenarien prüfen.
Stolperfalle:

Eine Extension kann das Bean-Modell verändern. Wenn das nicht gut dokumentiert ist, wird Injection-Verhalten schwer nachvollziehbar.

Merksatz:

CDI Extensions erweitern den Container, nicht den Use Case.

B5-K01-Q010 – CDI in Jakarta EE: Integration, Architektur und typische Anti-PatternsCDI verdrahtet Architektur; es ersetzt sie nicht. 24h
Frage:

„Wie setzen Sie CDI in einer Jakarta-EE-Anwendung architektonisch sauber ein?“

Kurz erklärt:

CDI integriert Jakarta-EE-Komponenten, sollte aber nicht dazu führen, dass Architekturgrenzen verschwimmen.

Musterantwort:

Ich nutze CDI als Composition- und Lifecycle-Mechanismus. REST Resources, Application Services, Repositories, Adapter, Producers, Interceptors und Events werden containerverwaltet, aber sauber geschichtet. JAX-RS Resources sind Boundary-Code. Use Cases enthalten Application Logic. Repositories kapseln Persistenz. Producer kapseln Infrastruktur. Interceptors kapseln technische Querschnittsthemen.

Jakarta EE Platform 11 definiert die Plattform für Jakarta-EE-Anwendungen; CDI selbst hat in 4.1 die Jakarta-EE-Integration aus der CDI-Spezifikation in die Platform-, Web-Profile- und Core-Profile-Spezifikationen verlagert. Das ist für Nutzer meist wenig sichtbar, aber architektonisch wichtig: CDI ist Kernmechanismus, die Plattform definiert die Integration der Komponenten.

Senior-Antwort:

„CDI ist mein Wiring- und Lifecycle-Modell. Die Architektur entsteht trotzdem durch klare Grenzen zwischen Boundary, Use Case, Domain und Infrastruktur.“

Häufige Fehler:
  • REST Resource enthält Businesslogik, Persistenz und Integration.
  • Transaktionen um externe HTTP-Calls.
  • CDI Beans als globale Utility-Klassen.
  • Alles @ApplicationScoped , auch requestbezogener Zustand.
  • Events als versteckte Prozesssteuerung.
  • Producer mit Businesslogik.
  • Fehlende Architekturtests.
Best Practices:
  • CDI für Composition nutzen.
  • Boundary, Application, Domain und Infrastructure trennen.
  • Transaktionen an Use-Case-Grenzen.
  • REST DTOs von Domain trennen.
  • Producers für Infrastruktur.
  • Interceptors für technische Cross-Cutting Concerns.
  • Events lokal und bewusst einsetzen.
  • Outbox für durable Integration.
Stolperfalle:

Nur weil eine Klasse leicht injizierbar ist, sollte sie nicht jede Verantwortung bekommen.

Merksatz:

CDI verdrahtet Architektur; es ersetzt sie nicht.

B5-K02 – Kapitel 2: JPA 10 Karten

B5-K02-Q001 – EntityManager, Persistence Context und Identity MapDer Persistence Context ist die Brücke zwischen Objektwelt und relationaler Transaktion. 24h
Frage:

„Was ist der Persistence Context in Jakarta Persistence, und warum ist er so wichtig?“

Kurz erklärt:

Der EntityManager verwaltet einen Persistence Context. Der Persistence Context enthält managed Entities und stellt pro Entity-Identität typischerweise genau eine Java-Instanz bereit.

Musterantwort:

Der Persistence Context ist die Arbeitseinheit, in der Entities managed sind. Innerhalb eines Persistence Contexts gibt es für eine bestimmte Entity-Klasse und Primary-Key-Identität höchstens eine managed Java-Instanz. Dadurch funktionieren Identity Map, Dirty Checking, Change Tracking und konsistente Objektidentität innerhalb einer Transaktion. Die EntityManager -API ist laut Jakarta Persistence API dafür zuständig, Operationen auszuführen, die den Persistence Context und den Lifecycle einzelner Entity-Instanzen betreffen.

Wenn ich entityManager.find(PaymentEntity.class, id) zweimal innerhalb desselben Persistence Contexts aufrufe, bekomme ich semantisch dieselbe managed Entity-Instanz. Ändere ich diese Instanz, muss ich nicht zwingend save() aufrufen. Der Provider erkennt Änderungen und synchronisiert sie beim Flush mit der Datenbank.

Senior-Antwort:

„Der Persistence Context ist die Unit of Work von JPA. Wer ihn nicht versteht, versteht weder Dirty Checking noch Lazy Loading noch Flush-Verhalten.“

Häufige Fehler:
  • JPA wie ein reines DAO-Framework behandeln.
  • save() -Denken aus jedem ORM-Kontext übernehmen.
  • Zu viele Entities in einem Persistence Context halten.
  • Managed und detached Entities verwechseln.
  • EntityManager als Thread-safe Singleton verwenden.
  • SQL-Ausführung mit Methodenaufruf verwechseln.
Best Practices:
  • Transaktionsgrenzen bewusst schneiden.
  • Persistence Context klein halten.
  • Managed Entities nur innerhalb klarer Use Cases ändern.
  • DTOs für Read APIs nutzen.
  • Batch-Jobs mit Flush/Clear-Strategie bauen.
  • EntityManager nicht zwischen Threads teilen.
  • SQL-Logging und Query Count in Tests sichtbar machen.
Stolperfalle:

Ein EntityManager ist nicht einfach ein Repository. Er verwaltet Identität, Zustand und Synchronisation einer ganzen Objektarbeitsmenge.

Merksatz:

Der Persistence Context ist die Brücke zwischen Objektwelt und relationaler Transaktion.

B5-K02-Q002 – Entity Lifecycle: new, managed, detached, removedNur managed Entities werden automatisch synchronisiert.
Frage:

„Welche Entity-Zustände gibt es in Jakarta Persistence, und was ist der Unterschied zwischen persist und merge ?“

Kurz erklärt:

Entities haben Lifecycle-Zustände: new/transient, managed, detached und removed. Diese Zustände bestimmen, ob Änderungen automatisch persistiert werden.

Musterantwort:

Eine neue Entity ist noch nicht persistent und nicht im Persistence Context. Nach persist() wird sie managed und beim Flush eingefügt. Eine managed Entity ist Teil des Persistence Contexts; Änderungen werden durch Dirty Checking erkannt. Eine detached Entity war einmal persistent, ist aber nicht mehr managed. Änderungen an detached Entities werden nicht automatisch synchronisiert. Eine removed Entity ist zur Löschung markiert.

persist() macht eine neue Entity managed. merge() kopiert den Zustand einer detached Entity in eine managed Instanz und gibt diese managed Instanz zurück. Der übergebene detached Parameter bleibt detached. Genau das wird oft falsch verstanden.

Senior-Antwort:

„ merge() macht nicht das übergebene Objekt managed. Es kopiert Zustand in eine managed Instanz. Genau deshalb ist blindes Merge gefährlich.“

Häufige Fehler:
  • Rückgabewert von merge() ignorieren.
  • Detached Entities wie managed behandeln.
  • REST direkt mit Entities modellieren.
  • Große Objektgraphen unkontrolliert mergen.
  • persist() und merge() als synonym ansehen.
  • Delete ohne Beziehungskaskaden verstehen zu wollen.
Best Practices:
  • DTOs an API-Grenzen verwenden.
  • Managed Entity laden und gezielt ändern.
  • merge() sparsam und bewusst einsetzen.
  • Entity Lifecycle in Reviews prüfen.
  • Cascades bei merge besonders vorsichtig verwenden.
  • Business-Methoden statt Setter-Flut verwenden.
  • Tests für detached/update-Fälle schreiben.
Stolperfalle:

Ein detached Entity-Graph kann veraltete Werte enthalten und beim Merge neuere Daten überschreiben.

Merksatz:

Nur managed Entities werden automatisch synchronisiert.

B5-K02-Q003 – Mapping: Entities, Embeddables, Records und IdentityEntities haben Identität; Embeddables haben Bedeutung ohne eigene Identität.
Frage:

„Wann verwenden Sie Entity, Embeddable und Value Object in Jakarta Persistence?“

Kurz erklärt:

Entities besitzen Identität und Lifecycle. Embeddables modellieren wertartige, identitätslose Komponenten. Jakarta Persistence 3.2 erlaubt Java Records als @Embeddable und @IdClass .

Musterantwort:

Eine Entity hat eine persistente Identität und einen Lifecycle. Ein Customer oder Payment ist typischerweise eine Entity. Ein Embeddable hat keine eigene Identität und gehört zu einer Entity, zum Beispiel EmailAddress , Money , Address oder CustomerProfile .

Jakarta Persistence 3.2 unterstützt Java Records als @Embeddable und @IdClass , was für immutable Value-Object-artige Modellierung sehr hilfreich ist. Außerdem werden java.time.Instant und java.time.Year in Persistence 3.2 als eingebaute Typen unterstützt, während ältere Date -/ Calendar -/ @Temporal -Ansätze zugunsten von java.time weiter zurückgedrängt werden.

Senior-Antwort:

„Nicht jedes fachliche Objekt braucht eine eigene Tabelle und ID. Embeddables sind ideal, wenn ein Wert nur im Kontext seines Owners existiert.“

Häufige Fehler:
  • Alles als Entity modellieren.
  • Value Objects als primitive Strings verstreuen.
  • Entitäten als Records erzwingen.
  • IDs mit fachlicher Gleichheit verwechseln.
  • Mutable Embeddables ohne Kontrolle.
  • Alte Date/Calendar-Mappings in neuem Code verwenden.
Best Practices:
  • Entity für identitätsbehaftete Fachobjekte.
  • Embeddable für wertartige Komponenten.
  • Records für immutable Embeddables prüfen.
  • java.time für Zeitmodellierung nutzen.
  • Fachliche Constraints im Value Object ausdrücken.
  • Mapping explizit und lesbar halten.
  • IDs und Equality bewusst definieren.
Stolperfalle:

Entity-Identität und Value-Object-Gleichheit sind unterschiedliche Konzepte. Wer sie vermischt, bekommt fehlerhafte Equals-, Cache- und Mapping-Semantik.

Merksatz:

Entities haben Identität; Embeddables haben Bedeutung ohne eigene Identität.

B5-K02-Q004 – Beziehungen, Fetching, Cascades und orphanRemovalCascades folgen Ownership, nicht Bequemlichkeit.
Frage:

„Wie gehen Sie mit @OneToMany , @ManyToOne , FetchType, Cascades und orphanRemoval um?“

Kurz erklärt:

JPA-Beziehungen modellieren Objektassoziationen, nicht automatisch effiziente SQL-Zugriffe. Fetching, Cascades und orphanRemoval müssen pro Aggregate bewusst gewählt werden.

Musterantwort:

Ich mappe Beziehungen nur dort, wo sie fachlich und transaktional sinnvoll sind. @ManyToOne ist häufig sinnvoll, sollte aber oft lazy sein. @OneToMany ist im Objektmodell bequem, kann aber Performanceprobleme und große Collections erzeugen. Cascades nutze ich innerhalb klarer Aggregate-Grenzen. Wenn Order Lines nur durch Order existieren, sind cascade = ALL und orphanRemoval = true sinnvoll. Bei unabhängigen Aggregates wären Cascades gefährlich.

Fetching ist keine Kleinigkeit. Lazy Loading verhindert initial zu große Loads, kann aber N+1 erzeugen. Eager Loading vermeidet LazyInitializationException nicht sauber, sondern erzeugt oft größere Queries und unnötige Datenlast.

Senior-Antwort:

„JPA-Beziehungen sind keine kostenlosen Objektzeiger. Jede Association ist auch eine SQL-, Fetching- und Lifecycle-Entscheidung.“

Häufige Fehler:
  • Alles bidirektional mappen.
  • FetchType.EAGER als Standardlösung.
  • Cascades über Aggregate-Grenzen.
  • Große @OneToMany Collections laden.
  • Entities direkt als JSON serialisieren.
  • Owning side nicht pflegen.
  • orphanRemoval mit Soft Delete verwechseln.
Best Practices:
  • Beziehungen nach Aggregate-Grenzen modellieren.
  • Cascades nur innerhalb Ownership-Grenzen.
  • Lazy als Default kritisch verstehen, nicht blind.
  • Read APIs mit DTO-Projections bauen.
  • Fetching pro Use Case definieren.
  • Bidirektionale Beziehungen vermeiden, wenn nicht nötig.
  • SQL und Query Count testen.
  • Datenbank-Foreign-Keys zusätzlich nutzen.
Stolperfalle:

FetchType.EAGER löst Lazy-Probleme selten sauber. Es verschiebt sie oft in größere, langsamere und schwer kontrollierbare Queries.

Merksatz:

Cascades folgen Ownership, nicht Bequemlichkeit.

B5-K02-Q005 – JPQL, TypedQuery, Criteria API und moderne Query-FunktionenFür Commands Entities, für Reads oft Projections.
Frage:

„Wann verwenden Sie JPQL, Criteria API, native SQL oder DTO-Projections?“

Kurz erklärt:

JPQL fragt Entity-Modelle ab, nicht Tabellen. TypedQuery liefert typsichere Resultate. Criteria API baut Queries programmatisch.

Musterantwort:

JPQL verwende ich für gut lesbare, statische Queries über das Entity-Modell. TypedQuery ist dabei besser als rohe Query , weil der Resulttyp explizit ist. Criteria API nutze ich, wenn Queries dynamisch zusammengesetzt werden müssen, zum Beispiel bei vielen optionalen Filtern. Native SQL verwende ich, wenn datenbankspezifische Features, komplexe Reporting-Queries oder Performanceoptimierung es rechtfertigen. DTO-Projections verwende ich für Read APIs, damit ich nicht unnötig Entity-Graphen lade.

Jakarta Persistence 3.2 erweitert JPQL und Criteria unter anderem um Mengenoperationen wie union , intersect , except sowie Funktionen wie cast , left , right , replace , den String-Konkatenationsoperator || und id -/ version -Funktionen.

Senior-Antwort:

„Ich lade keine Entities, wenn ich nur ein Read DTO brauche. Query-Design ist Teil der Architektur, nicht nur Repository-Code.“

Häufige Fehler:
  • Entity Queries für reine Read APIs.
  • Native SQL ohne Mapping-Strategie.
  • Dynamische JPQL-Strings unsicher zusammensetzen.
  • SELECT * -Denken in JPA.
  • Query-Pläne nie prüfen.
  • Criteria API für einfache Queries übertreiben.
Best Practices:
  • JPQL für lesbare Standardqueries.
  • Criteria für echte Dynamik.
  • DTO-Projections für Read APIs.
  • Native SQL bewusst und isoliert.
  • Parameterbindung immer nutzen.
  • Query-Limits setzen.
  • SQL-Output und Ausführungspläne prüfen.
  • Query-Methoden nach Use Case benennen.
Stolperfalle:

JPQL ist nicht SQL. Es fragt Entities und Attribute ab; das generierte SQL muss trotzdem verstanden und geprüft werden.

Merksatz:

Für Commands Entities, für Reads oft Projections.

B5-K02-Q006 – Transaktionen, Flush, Dirty Checking und JTAFlush schreibt; Commit entscheidet.
Frage:

„Was ist der Unterschied zwischen Flush und Commit in Jakarta Persistence?“

Kurz erklärt:

Transaktionen definieren Commit- und Rollback-Grenzen. Flush synchronisiert den Persistence Context mit der Datenbank, ist aber nicht dasselbe wie Commit.

Musterantwort:

Flush schreibt ausstehende Änderungen aus dem Persistence Context in die Datenbank, damit SQL ausgeführt wird. Commit beendet die Transaktion dauerhaft. Ein Flush kann vor dem Commit stattfinden, zum Beispiel vor einer Query oder explizit durch entityManager.flush() . Nach Flush kann die Transaktion immer noch rollbacken.

In Jakarta EE wird JPA häufig mit JTA genutzt. Ein Persistence Unit hat laut Jakarta Persistence API entweder Transaktionstyp JTA oder RESOURCE_LOCAL ; resource-local Transaktionen werden programmatisch über EntityTransaction verwaltet. In Jakarta-EE-Anwendungen ist JTA für containerverwaltete Transaktionen typischerweise der Standardansatz.

Senior-Antwort:

„Flush ist SQL-Synchronisation. Commit ist Transaktionsabschluss. Der Unterschied ist entscheidend für Fehleranalyse und Konsistenz.“

Häufige Fehler:
  • Flush mit Commit verwechseln.
  • Remote Calls innerhalb langer Transaktionen.
  • @Transactional bei Self-Invocation erwarten.
  • Exceptions fangen und Rollback verhindern.
  • Persistence Context im Batch unendlich wachsen lassen.
  • Transaktionale Grenzen zu fein oder zu breit schneiden.
Best Practices:
  • Transaktionen an Use-Case-Grenzen.
  • Transaktionen kurz halten.
  • Remote Calls außerhalb kritischer DB-Transaktionen.
  • Flush-Verhalten verstehen.
  • Batch-Verarbeitung mit Flush/Clear.
  • Fehlerpfade und Rollback testen.
  • JTA und RESOURCE_LOCAL nicht vermischen.
  • Outbox innerhalb lokaler Transaktion schreiben.
Stolperfalle:

Ein erfolgreiches Flush bedeutet nicht, dass die Transaktion dauerhaft abgeschlossen ist.

Merksatz:

Flush schreibt; Commit entscheidet.

B5-K02-Q007 – Optimistic Locking, Pessimistic Locking und konkurrierende UpdatesKonkurrenz kontrolliert man mit Versionen, Locks oder besserem Modell.
Frage:

„Wie verhindern Sie Lost Updates mit Jakarta Persistence?“

Kurz erklärt:

Optimistic Locking nutzt Versionen und erkennt Konflikte spät. Pessimistic Locking sperrt Datenbankzeilen früher und verhindert parallele Änderungen direkter.

Musterantwort:

Für viele Business-Systeme ist optimistisches Locking mit @Version der beste Standard. Jede Entity hat eine Version. Wenn zwei Transaktionen dieselbe Entity lesen und beide schreiben wollen, gewinnt eine; die andere schlägt beim Flush oder Commit mit einem Optimistic-Lock-Konflikt fehl. Danach kann man retryen oder dem Nutzer einen Konflikt melden.

Pessimistisches Locking nutze ich, wenn Konflikte häufig sind oder eine kritische Sequenz wirklich exklusiv sein muss. Dann kann ich LockModeType.PESSIMISTIC_WRITE verwenden. Queries können ebenfalls Lock Modes setzen; die Query -API bietet dafür setLockMode(...) .

Senior-Antwort:

„Optimistic Locking ist mein Default bei seltenen Konflikten. Pessimistic Locking ist ein bewusstes Werkzeug, wenn ich frühe Exklusivität brauche.“

Häufige Fehler:
  • Kein @Version bei konkurrierenden Updates.
  • OptimisticLockException ignorieren.
  • Pessimistic Locks zu lange halten.
  • Locking ohne Timeout-Strategie.
  • Retry ohne Idempotenz.
  • Hot Row durch Locking statt Modelländerung lösen wollen.
Best Practices:
  • @Version für mutable Aggregate verwenden.
  • Konflikte fachlich behandeln.
  • Retry nur bei idempotenten Operationen.
  • Pessimistic Locking gezielt einsetzen.
  • Locks kurz halten.
  • Lock-Timeouts konfigurieren.
  • Lasttests mit Parallelität.
  • Hotspots architektonisch analysieren.
Stolperfalle:

Ohne fachliche Retry- und Konfliktstrategie ist @Version nur ein Fehlergenerator, keine Lösung.

Merksatz:

Konkurrenz kontrolliert man mit Versionen, Locks oder besserem Modell.

B5-K02-Q008 – Performance: N+1, Fetch Joins, EntityGraph, Pagination und Batch ProcessingJPA abstrahiert SQL, aber Performance bleibt relational.
Frage:

„Wie analysieren und vermeiden Sie typische JPA-Performanceprobleme?“

Kurz erklärt:

JPA-Performance hängt stark von Fetching, Query-Form, Persistence-Context-Größe, Pagination, Batch-Verarbeitung und SQL-Ausführungsplänen ab.

Musterantwort:

Ich mache SQL sichtbar. Ich prüfe Query Count, generiertes SQL, Ausführungspläne, geladene Spalten, geladene Entities und Persistence-Context-Größe. Das häufigste Problem ist N+1: Eine Query lädt N Entities, danach werden pro Entity Lazy Associations nachgeladen.

Gegenmaßnahmen sind DTO-Projections, gezielte Fetch Joins, EntityGraphs, Batch Fetching, passende Indizes und klare API-spezifische Queries. Für Read-Listen nutze ich oft DTO-Projections. Für Use Cases, die Entities wirklich ändern, lade ich gezielt die benötigten Aggregates.

Senior-Antwort:

„JPA-Performance beginnt damit, das SQL zu sehen. Ohne Query Count und Ausführungsplan optimiert man blind.“

Häufige Fehler:
  • Entities für jede Read API laden.
  • SQL-Logs in Entwicklung deaktiviert lassen.
  • FetchType.EAGER als Allheilmittel.
  • Collection Fetch Join mit Pagination.
  • Große Batch-Jobs ohne clear() .
  • Query-Plan-Analyse ignorieren.
  • Cache statt Query-Fix verwenden.
Best Practices:
  • SQL und Query Count messen.
  • DTO-Projections für Read-Modelle.
  • Fetching pro Use Case definieren.
  • EntityGraph und Fetch Join gezielt nutzen.
  • Pagination nicht mit großen Collections vermischen.
  • Batch-Jobs chunkweise verarbeiten.
  • Flush/Clear bei großen Imports.
  • Indizes und Datenbankpläne prüfen.
Stolperfalle:

Ein Fetch Join kann N+1 lösen und gleichzeitig durch Duplikation, Pagination-Probleme oder große Result Sets ein neues Problem erzeugen.

Merksatz:

JPA abstrahiert SQL, aber Performance bleibt relational.

B5-K02-Q009 – Second-Level Cache, Cache Modes und KonsistenzrisikenCaching beschleunigt alte Wahrheit genauso gut wie neue.
Frage:

„Was ist der Unterschied zwischen First-Level Cache und Second-Level Cache in JPA?“

Kurz erklärt:

JPA unterscheidet den Persistence Context als First-Level Cache von optionalen Second-Level-Cache-Mechanismen. Caching verbessert Reads, erzeugt aber Konsistenzrisiken.

Musterantwort:

Der First-Level Cache ist der Persistence Context. Er ist immer zentral für JPA und gilt innerhalb des EntityManager-/Transaktionskontexts. Der Second-Level Cache ist optional, provider- und konfigurationsabhängig und kann Entity-Daten über Persistence Contexts hinweg wiederverwenden.

Caching ist nur sinnvoll, wenn Daten relativ stabil sind und Staleness akzeptabel oder kontrolliert ist. Referenzdaten wie Währungen oder Länder können gute Kandidaten sein. Kontostände, Berechtigungen oder stark konkurrierende Daten sind riskanter.

Jakarta Persistence 3.2 ergänzt unter anderem Methoden zum Setzen von Cache Store Mode und Cache Retrieve Mode an EntityManager und Query , wodurch Cache-Verhalten programmatisch expliziter kontrolliert werden kann.

Senior-Antwort:

„Der First-Level Cache ist JPA-Grundmechanik. Der Second-Level Cache ist eine bewusste Performanceentscheidung mit Konsistenzpreis.“

Häufige Fehler:
  • First-Level Cache mit Second-Level Cache verwechseln.
  • Second-Level Cache für stark mutable Daten.
  • Query Cache ohne Invalidierungsverständnis.
  • Cache als Ersatz für Indizes nutzen.
  • Stale Data bei Berechtigungen ignorieren.
  • Provider-spezifische Details nicht testen.
Best Practices:
  • First-Level Cache als Persistence Context verstehen.
  • Second-Level Cache nur für geeignete Daten.
  • Cache-Konfiguration pro Entity bewusst setzen.
  • Staleness fachlich bewerten.
  • Cache-Hit-Rate messen.
  • Cluster-Invalidierung testen.
  • Query Cache sehr vorsichtig einsetzen.
  • Cache nicht zur Fehlerkaschierung nutzen.
Stolperfalle:

Ein Cache-Hit ist nur gut, wenn die Daten fachlich noch gültig sind.

Merksatz:

Caching beschleunigt alte Wahrheit genauso gut wie neue.

B5-K02-Q010 – Jakarta Persistence in Architektur: Repository, Outbox, Migration und Anti-PatternsJPA verwaltet Entities; Architektur verwaltet Grenzen. 24h
Frage:

„Welche JPA-Anti-Patterns sehen Sie häufig in Enterprise-Systemen?“

Kurz erklärt:

Jakarta Persistence sollte Architekturgrenzen unterstützen: Repository-Ports, klare Transaktionen, Outbox, Migrationen, DTOs und bewusstes SQL-Verständnis.

Musterantwort:

Typische Anti-Patterns sind: Entities direkt über REST exponieren, Open-Persistence-Context bis in die View, blindes merge , übergroße Aggregate, unkontrollierte Cascades, FetchType.EAGER als Lösung, N+1 ignorieren, Datenbankconstraints vernachlässigen, Migrationen vom ORM generieren lassen, Transaktionen um Remote Calls, JPA als Ersatz für SQL-Verständnis und Repositories als anämische CRUD-Fassade ohne Use-Case-Bewusstsein.

Architektonisch nutze ich JPA für lokale Persistenz und Konsistenz. Für Integration schreibe ich Outbox-Nachrichten in derselben lokalen Transaktion. Für externe Events, Suche, Cache oder Reporting verwende ich abgeleitete Stores, aber die relationale Datenbank bleibt für kritische Zustände oft System of Record.

Senior-Antwort:

„JPA ist ein starkes Persistenzmodell, aber keine Architektur. Gute Architektur entscheidet, wo Entities leben, wo Transaktionen beginnen und wo abgeleitete Daten enden.“

Häufige Fehler:
  • Entities als API-Vertrag.
  • Repository nur als generisches CRUD ohne Use-Case-Queries.
  • ORM-DDL in Produktion als Migration.
  • Keine Datenbankconstraints.
  • Outbox außerhalb der Transaktion.
  • JPA für komplexes Reporting erzwingen.
  • Provider-spezifische Features ohne Isolation.
Best Practices:
  • Entities nicht direkt extern exponieren.
  • Repositories als Ports verstehen.
  • Use Cases als Transaktionsgrenzen.
  • Outbox für zuverlässige Integration.
  • Migrationen mit Flyway/Liquibase oder kontrolliertem Prozess.
  • SQL und Indizes reviewen.
  • DTO-Projections für Reads.
  • Native SQL isoliert und bewusst nutzen.
Stolperfalle:

Automatisch generiertes Schema ist bequem für Tests, aber kein Ersatz für produktionsreife, versionierte Migrationen.

Merksatz:

JPA verwaltet Entities; Architektur verwaltet Grenzen.

B5-K03 – Kapitel 3: JTA 10 Karten

B5-K03-Q001 – Transaktionsmodell, ACID und lokale KonsistenzgrenzenJTA schützt lokale Atomizität; verteilte Prozesse brauchen eigenes Design. 24h
Frage:

„Was ist JTA beziehungsweise Jakarta Transactions, und welches Problem löst es?“

Kurz erklärt:

JTA stellt eine standardisierte Transaktionsgrenze bereit, innerhalb der mehrere Operationen atomar committen oder rollbacken können.

Musterantwort:

Jakarta Transactions definiert ein Standardmodell für Transaktionsdemarkation und die Koordination transaktionaler Ressourcen in Jakarta-EE-Anwendungen. Es erlaubt Anwendungscode, lokale Konsistenzgrenzen zu definieren, während der Container und Transaction Manager die technischen Details von Begin, Commit, Rollback und Ressourcenkoordination übernehmen.

In einem Ledger-Use-Case müssen Kontostände, Ledger-Einträge und eine Outbox-Nachricht gemeinsam atomar gespeichert werden. Entweder sind alle Änderungen sichtbar oder keine. Genau dafür ist eine lokale JTA-Transaktion ideal. JTA löst aber nicht automatisch Geschäftsprozesse über entfernte Microservices hinweg. Dort braucht man Sagas, Outbox, Idempotenz und Kompensation.

Senior-Antwort:

„JTA ist meine lokale Konsistenzgrenze. Ich nutze es, um Datenbankzustand und Outbox atomar zu speichern, aber ich erwarte nicht, dass es verteilte Geschäftsprozesse über Microservices magisch korrekt macht.“

Häufige Fehler:
  • JTA als Microservice-weite Transaktionslösung missverstehen.
  • Transaktion zu groß schneiden.
  • Remote HTTP-Aufrufe innerhalb der Transaktion.
  • Outbox außerhalb der Datenbanktransaktion schreiben.
  • Transaktionsgrenzen in Repository-Methoden verstreuen.
  • Commit-Zeitpunkt nicht verstehen.
Best Practices:
  • Transaktionen an Use-Case-Grenzen setzen.
  • Lokale fachliche Invarianten in einer Transaktion schützen.
  • Transaktionen kurz halten.
  • Outbox in derselben Transaktion schreiben.
  • Remote Calls außerhalb kritischer DB-Transaktionen halten.
  • Idempotenz für Wiederholungen einplanen.
  • Rollback-Pfade testen.
Stolperfalle:

Eine lokale JTA-Transaktion schützt keine externe HTTP-Operation. Ein Payment Provider kann erfolgreich autorisiert haben, auch wenn die lokale Transaktion danach rollbackt.

Merksatz:

JTA schützt lokale Atomizität; verteilte Prozesse brauchen eigenes Design.

B5-K03-Q002 – @Transactional , TxType und PropagationPropagation definiert, wer mit wem committen oder rollbacken muss.
Frage:

„Welche Transactional.TxType -Werte kennen Sie, und wann verwenden Sie welche?“

Kurz erklärt:

@Transactional definiert deklarative Transaktionsgrenzen. TxType bestimmt, ob eine Methode eine bestehende Transaktion nutzt, eine neue startet, eine Transaktion verlangt oder eine Transaktion suspendiert.

Musterantwort:

Der Standard ist REQUIRED : Wenn bereits eine Transaktion existiert, wird sie genutzt; sonst wird eine neue gestartet. REQUIRES_NEW suspendiert eine bestehende Transaktion und startet eine neue. MANDATORY verlangt eine bestehende Transaktion. SUPPORTS läuft mit Transaktion, wenn eine existiert, sonst ohne. NOT_SUPPORTED suspendiert eine bestehende Transaktion und läuft ohne Transaktion. NEVER schlägt fehl, wenn eine Transaktion aktiv ist. Die Jakarta-Transactions-API dokumentiert diese Semantik für Transactional.TxType .

Ich nutze REQUIRED für normale Use Cases, MANDATORY für interne Methoden, die nie ohne äußere Transaktion laufen dürfen, REQUIRES_NEW sehr vorsichtig für technisch unabhängige Audit- oder Log-Einträge, und NOT_SUPPORTED für Operationen, die bewusst keine Transaktion halten sollen, etwa externe Calls oder lange Read-Operationen.

Senior-Antwort:

„TxType ist Commit-Semantik. Ich wähle nicht REQUIRES_NEW , weil es praktisch ist, sondern weil ein unabhängiger Commit fachlich gewollt ist.“

Häufige Fehler:
  • REQUIRES_NEW als Logging-Shortcut ohne Fachentscheidung.
  • Private Methoden mit @Transactional annotieren und Wirkung erwarten.
  • Self-Invocation übersehen.
  • SUPPORTS für Schreiboperationen verwenden.
  • NOT_SUPPORTED verwenden und danach managed Entity-Verhalten erwarten.
  • Transaktionspropagation nicht in Tests abdecken.
Best Practices:
  • REQUIRED als Standard.
  • MANDATORY für interne transaktionale Bausteine.
  • REQUIRES_NEW nur bei bewusst unabhängiger Commit-Semantik.
  • NOT_SUPPORTED für externe oder lange nichttransaktionale Arbeit.
  • Transaktionsgrenzen auf öffentliche CDI/EJB-Methoden legen.
  • Propagation in Architekturregeln dokumentieren.
  • Tests für Rollback- und Commit-Kopplung schreiben.
Stolperfalle:

REQUIRES_NEW kann Daten dauerhaft committen, obwohl der Haupt-Use-Case später rollbackt. Das ist manchmal richtig, oft aber überraschend.

Merksatz:

Propagation definiert, wer mit wem committen oder rollbacken muss.

B5-K03-Q003 – Rollback-Regeln, Exceptions und rollback-onlyRollback-Regeln sind Konsistenzregeln in Exception-Form.
Frage:

„Wann wird eine JTA-Transaktion rollback-only, und wie steuern Sie Rollback-Regeln?“

Kurz erklärt:

Exceptions bestimmen häufig, ob eine Transaktion rollbackt. @Transactional erlaubt mit rollbackOn und dontRollbackOn , Rollback-Regeln explizit zu steuern.

Musterantwort:

Bei deklarativen Transaktionen setzt der Container eine Transaktion auf Rollback, wenn eine passende Exception aus der Methode herausläuft. Mit @Transactional kann ich über rollbackOn und dontRollbackOn steuern, welche Exception-Typen Rollback auslösen oder nicht. Fachliche Fehler, die einen inkonsistenten Zustand verhindern sollen, gehören typischerweise in rollbackOn . Technische Folgefehler, die den Haupt-Use-Case nicht ungültig machen, können bewusst nicht rollbacken.

Zusätzlich kann Anwendungscode programmatisch rollback-only setzen, zum Beispiel über UserTransaction.setRollbackOnly() oder über TransactionSynchronizationRegistry.setRollbackOnly() . Die UserTransaction -API dokumentiert setRollbackOnly() als Operation, die die aktuelle Transaktion so markiert, dass nur noch Rollback möglich ist.

Senior-Antwort:

„Rollback ist keine technische Reflexhandlung. Rollback bedeutet: Der aktuelle Zustand darf fachlich nicht committed werden.“

Häufige Fehler:
  • Exceptions fangen und trotzdem committen.
  • Alle RuntimeExceptions unreflektiert rollbacken lassen.
  • Fachliche Fehler nicht als Rollback-Kriterium modellieren.
  • Notification- oder Audit-Fehler Haupttransaktion rollbacken lassen.
  • Rollback-only erst am Commit bemerken.
  • Kompensierbare Fehler und Konsistenzfehler vermischen.
Best Practices:
  • Rollback-Regeln pro Use Case bewusst definieren.
  • Fachliche Invarianten rollbacken lassen.
  • Nichtkritische Folgeaktionen entkoppeln.
  • Exceptions nicht blind fangen.
  • rollback-only explizit setzen, wenn Weiterlaufen nötig ist.
  • Rollback-Pfade testen.
  • Fehlerlogs am Ursprung erfassen.
  • Exceptions fachlich benennen.
Stolperfalle:

Eine gefangene Exception führt nicht automatisch zu Rollback. Wenn der Use Case fachlich gescheitert ist, muss das sichtbar bleiben oder rollback-only gesetzt werden.

Merksatz:

Rollback-Regeln sind Konsistenzregeln in Exception-Form.

B5-K03-Q004 – Programmatic Transactions mit UserTransactionDeclarative by default, programmatic by exception.
Frage:

„Wann würden Sie UserTransaction statt @Transactional verwenden?“

Kurz erklärt:

UserTransaction erlaubt manuelle Transaktionsdemarkation mit begin , commit , rollback , setRollbackOnly , getStatus und Timeout-Steuerung.

Musterantwort:

@Transactional ist der Standard, weil es deklarativ, lesbar und containerfreundlich ist. UserTransaction nutze ich nur, wenn ich manuelle Kontrolle brauche, etwa in Batch-Jobs mit Chunk-Transaktionen, in Legacy-Integration oder bei Spezialfällen, in denen mehrere kleine Transaktionen innerhalb eines Methodenaufrufs gesteuert werden müssen.

Die UserTransaction -API definiert Methoden, mit denen Anwendungscode Transaktionsgrenzen explizit verwalten kann; begin() erstellt eine neue Transaktion und verbindet sie mit dem aktuellen Thread, commit() beendet die aktuelle Transaktion, rollback() rollt sie zurück, und getStatus() liefert den aktuellen Status.

Senior-Antwort:

„ UserTransaction ist kein besseres @Transactional . Es ist ein Spezialwerkzeug, wenn ich Transaktionen innerhalb eines Ablaufs bewusst selbst schneiden muss.“

Häufige Fehler:
  • begin() ohne robustes rollback() im Fehlerfall.
  • Deklarative und programmatische Transaktionen unklar mischen.
  • Eine riesige Batch-Transaktion.
  • Rollback-Fehler verschlucken.
  • UserTransaction in normalen Use Cases verwenden, obwohl @Transactional reicht.
  • Thread-Bindung ignorieren.
Best Practices:
  • @Transactional als Standard.
  • UserTransaction nur bei echter Kontrollnotwendigkeit.
  • Try/catch/finally sehr sorgfältig schreiben.
  • Chunk-Größe messen.
  • Status vor Rollback prüfen.
  • Fehlerhafte Chunks separat protokollieren.
  • Programmatic Transactions isoliert testen.
  • Keine unnötige Transaktionsmagie in Fachcode.
Stolperfalle:

Programmatic Transactions geben Freiheit, aber auch die Pflicht, jeden Fehlerpfad sauber mit Rollback und Statusprüfung abzudecken.

Merksatz:

Declarative by default, programmatic by exception.

B5-K03-Q005 – JTA, JPA und Persistence ContextJTA verwaltet die Transaktion; JPA verwaltet die Entity-Arbeit darin. 24h
Frage:

„Wie hängen JTA-Transaktion und JPA Persistence Context zusammen?“

Kurz erklärt:

In Jakarta EE arbeitet JTA eng mit JPA zusammen. Ein transaction-scoped Persistence Context nimmt an der aktuellen JTA-Transaktion teil.

Musterantwort:

In einer typischen Jakarta-EE-Anwendung ist der Persistence Context transaktionsgebunden. Wenn eine @Transactional -Methode läuft, nimmt der injizierte EntityManager an der aktuellen JTA-Transaktion teil. Managed Entities werden im Persistence Context gehalten, Änderungen werden per Dirty Checking erkannt und beim Flush innerhalb derselben Transaktion mit der Datenbank synchronisiert.

Jakarta Persistence unterscheidet Persistence Units mit Transaktionstyp JTA und RESOURCE_LOCAL ; Resource-local Transaktionen werden programmatisch über EntityTransaction gesteuert, während JTA-Transaktionen containerverwaltet koordiniert werden können.

Senior-Antwort:

„JTA entscheidet über Commit und Rollback. JPA verwaltet den Objektzustand innerhalb dieser Entscheidung.“

Häufige Fehler:
  • EntityManager und Transaktion gleichsetzen.
  • RESOURCE_LOCAL in Jakarta EE unbewusst verwenden.
  • Nach Rollback mit Entity-Zustand weiterarbeiten.
  • Große Entity-Graphen in einer Transaktion halten.
  • JPA Flush mit JTA Commit verwechseln.
  • EntityManager über Threads teilen.
Best Practices:
  • JTA für Jakarta-EE-Use-Cases nutzen.
  • Persistence Context an Use-Case-Grenzen halten.
  • Flush und Commit unterscheiden.
  • Nach Rollback Zustand verwerfen oder neu laden.
  • DTOs für reine Reads verwenden.
  • Outbox in derselben JTA-Transaktion speichern.
  • Batch-Jobs chunkweise verarbeiten.
  • Provider-SQL sichtbar machen.
Stolperfalle:

Nach einem Rollback sind Java-Objekte im Speicher nicht automatisch wieder in einem fachlich sauberen Zustand. Für weitere Arbeit sollte man neu laden oder den Ablauf beenden.

Merksatz:

JTA verwaltet die Transaktion; JPA verwaltet die Entity-Arbeit darin.

B5-K03-Q006 – XA, Two-Phase Commit und mehrere Resource ManagerXA ist starke Koordination, aber starke Koordination ist starke Kopplung.
Frage:

„Was ist XA beziehungsweise Two-Phase Commit, und wann würden Sie es verwenden?“

Kurz erklärt:

XA und Two-Phase Commit ermöglichen die Koordination mehrerer transaktionaler Resource Manager, erhöhen aber Komplexität, Latenz und Betriebsrisiko.

Musterantwort:

XA ist ein Standardmodell, mit dem ein Transaction Manager mehrere XA-fähige Resource Manager koordinieren kann. Two-Phase Commit läuft grob in zwei Phasen: Prepare und Commit. Erst fragt der Transaction Manager alle beteiligten Ressourcen, ob sie commitbereit sind. Wenn alle zustimmen, wird commit ausgeführt; wenn nicht, wird rollback ausgelöst.

Jakarta Transactions definiert die Koordination XA-fähiger Resource Manager auf Basis der X/Open-XA-Spezifikation und deren Abbildung auf Java-SE- XAResource . Ich verwende XA nur, wenn echte atomare Konsistenz über mehrere transaktionale Ressourcen zwingend nötig ist und die Betriebsumgebung das zuverlässig unterstützt. In vielen modernen Systemen bevorzuge ich Outbox, Idempotenz und Saga, weil sie einfacher skalieren und besser zu Microservices passen.

Senior-Antwort:

„XA kauft Atomizität über Ressourcen hinweg, aber bezahlt mit Koordination, Latenz, Recovery-Komplexität und enger Kopplung.“

Häufige Fehler:
  • XA als einfache Lösung für Microservices betrachten.
  • Recovery und Monitoring ignorieren.
  • Nicht-XA-Ressourcen unbewusst einbeziehen.
  • Lange Transaktionen mit XA.
  • Prepared Transactions nicht überwachen.
  • XA für Event-Publishing nutzen, obwohl Outbox besser passt.
Best Practices:
  • XA nur bei echtem Bedarf.
  • Ressourcenfähigkeit prüfen.
  • Recovery-Prozesse testen.
  • Transaktionen kurz halten.
  • Outbox für DB-plus-Event häufig bevorzugen.
  • Idempotenz trotz XA einplanen.
  • Prepared-/In-doubt-Transaktionen überwachen.
  • Betriebsaufwand in Architekturentscheidung einpreisen.
Stolperfalle:

Ein System kann @Transactional verwenden und trotzdem nicht automatisch XA über alle beteiligten Ressourcen bekommen. Die Ressourcen müssen XA-fähig und korrekt enlisted sein.

Merksatz:

XA ist starke Koordination, aber starke Koordination ist starke Kopplung.

B5-K03-Q007 – TransactionSynchronizationRegistry, Synchronization und After-Commit-LogikAfter-Commit darf beschleunigen, aber nicht die einzige Wahrheit sein.
Frage:

„Wofür verwenden Sie TransactionSynchronizationRegistry oder Synchronization Callbacks?“

Kurz erklärt:

TransactionSynchronizationRegistry erlaubt systemnahen Komponenten und Anwendungscode, transaktionsbezogene Ressourcen zu verwalten, Status zu prüfen, rollback-only zu setzen und Synchronizations zu registrieren.

Musterantwort:

Ich verwende Synchronization Callbacks, wenn ich etwas bewusst an den Transaktionsabschluss koppeln muss, zum Beispiel lokale Benachrichtigung nach erfolgreichem Commit, Cleanup oder transaktionsbezogene Ressourcenverwaltung. Wichtig ist: Kritische Integration sollte nicht nur als In-Memory-After-Commit-Callback passieren. Für zuverlässige externe Integration speichere ich eine Outbox-Nachricht in der Transaktion und nutze After-Commit höchstens als Signal, dass ein Publisher geweckt werden kann.

Die TransactionSynchronizationRegistry -API ist laut Jakarta-Transactions-Javadoc für systemnahe Komponenten, Enterprise Beans und Web-Komponenten gedacht und bietet unter anderem Registrierung von Synchronization-Objekten, Zugriff auf den aktuellen Transaktionsstatus und Markierung einer Transaktion für Rollback.

Senior-Antwort:

„After-Commit ist ein gutes Signal, aber keine dauerhafte Integration. Die Wahrheit liegt in der Outbox-Tabelle.“

Häufige Fehler:
  • Externe Events nur im After-Commit-Callback senden.
  • Teure Arbeit in beforeCompletion() .
  • Callback-Fehler nicht beobachten.
  • Outbox durch volatile Callback ersetzen.
  • Status in afterCompletion ignorieren.
  • Synchronization für Business-Orchestrierung missbrauchen.
Best Practices:
  • Outbox dauerhaft in der Transaktion speichern.
  • After-Commit höchstens als Optimierungssignal verwenden.
  • Callback-Logik klein halten.
  • Status immer prüfen.
  • Fehler in Callbacks sichtbar machen.
  • Periodischen Outbox-Poller als Sicherheitsnetz nutzen.
  • TransactionSynchronizationRegistry nicht als allgemeine Event-Bus-API missbrauchen.
Stolperfalle:

Wenn ein Event nur in afterCompletion gesendet wird und der Prozess nach Commit abstürzt, kann das Event verloren gehen.

Merksatz:

After-Commit darf beschleunigen, aber nicht die einzige Wahrheit sein.

B5-K03-Q008 – Timeouts, lange Transaktionen, Locks und DeadlocksLange Transaktionen sind langsame Entscheidungen unter Lock.
Frage:

„Wie gehen Sie mit Transaktions-Timeouts, Deadlocks und langen Transaktionen um?“

Kurz erklärt:

Lange Transaktionen erhöhen Lock-Zeit, Deadlock-Risiko, Connection-Nutzung, Rollback-Kosten und p99-Latenz.

Musterantwort:

Ich halte Transaktionen kurz und fachlich fokussiert. Eine Transaktion sollte nicht auf Benutzerinteraktion, Remote HTTP, langsame Dateien, große Batch-Schleifen oder unkontrollierte externe Systeme warten. Für Batch-Verarbeitung nutze ich Chunks. Für externe Kommunikation nutze ich Outbox oder asynchrone Prozesse. Für Deadlocks implementiere ich kontrollierte Retries, aber nur bei idempotenten Operationen.

UserTransaction bietet setTransactionTimeout , um ein Timeout für spätere Transaktionen des aktuellen Threads zu setzen; die API dokumentiert außerdem Statusabfrage und Rollback-Operationen für die aktuelle Thread-Transaktion. In Containerumgebungen werden Timeouts oft zusätzlich über Server- oder Deployment-Konfiguration gesteuert.

Senior-Antwort:

„Eine Transaktion ist ein kritischer Abschnitt. Je länger sie offen ist, desto mehr Systemteile warten auf meine Entscheidung.“

Häufige Fehler:
  • Große Batch-Jobs in einer Transaktion.
  • Remote Calls innerhalb offener DB-Transaktion.
  • Keine konsistente Lock-Reihenfolge.
  • Deadlocks ohne Retry-Strategie.
  • Retries ohne Idempotenz.
  • Timeouts nur global, nicht use-case-spezifisch betrachten.
  • Lock-Timeout und Transaction-Timeout verwechseln.
Best Practices:
  • Transaktionen kurz halten.
  • Batch-Jobs in Chunks schneiden.
  • Remote Calls vor oder nach Transaktion.
  • Lock-Reihenfolge standardisieren.
  • Deadlocks kontrolliert retryen.
  • Idempotenz sicherstellen.
  • Timeouts messen und konfigurieren.
  • Lange Queries mit Query- und DB-Plänen analysieren.
Stolperfalle:

Ein Retry nach Deadlock ist nur sicher, wenn die Operation idempotent oder fachlich wiederholbar ist.

Merksatz:

Lange Transaktionen sind langsame Entscheidungen unter Lock.

B5-K03-Q009 – Threads, Async, Virtual Threads und TransaktionskontextTransaktionen überschreiten Async-Grenzen nicht ohne explizites Design.
Frage:

„Was passiert mit einer JTA-Transaktion bei asynchroner Verarbeitung oder neuen Threads?“

Kurz erklärt:

JTA-Transaktionskontext ist an den Ausführungskontext gebunden und wird nicht automatisch beliebig über Threads, Async-Aufgaben, Remote Calls oder Event-Systeme propagiert.

Musterantwort:

Ich darf nicht annehmen, dass eine JTA-Transaktion automatisch in beliebige Threads oder Async-Aufgaben propagiert wird. Wenn ich innerhalb einer Transaktion einen Executor starte, läuft diese Aufgabe typischerweise nicht zuverlässig in derselben Transaktion. Sie kann vor Commit, nach Rollback oder ohne erwarteten Context laufen. Die CDI-Spezifikation sagt für built-in normal scopes ausdrücklich, dass Contexts über lokale synchrone Java-Methodenaufrufe propagiert werden, aber nicht automatisch über Remote- oder asynchrone Aufrufe. Für Transaktionskontext gilt dieselbe architektonische Vorsicht: Async-Grenzen brauchen eigenes Design.

Mit Java 21 und Virtual Threads wird Blocking günstiger, aber Transaktionssemantik wird nicht magisch anders. Eine Transaktion sollte trotzdem kurz bleiben und nicht über unkontrollierte asynchrone Grenzen gezogen werden.

Senior-Antwort:

„Async ist eine Kontextgrenze. Ich gebe keine managed Entities und keine unausgesprochenen Transaktionsannahmen über diese Grenze.“

Häufige Fehler:
  • Async-Task innerhalb einer Transaktion starten und Commit annehmen.
  • Persistence Context in anderen Thread weiterreichen.
  • Entity-Instanzen in Async-Tasks verwenden.
  • Remote Calls als Teil derselben Transaktion betrachten.
  • Virtual Threads als Transaktionslösung verstehen.
  • ThreadLocal-Kontext unkontrolliert propagieren.
Best Practices:
  • Transaktionsgrenzen synchron und klar halten.
  • Async-Arbeit über Outbox oder Messaging.
  • Keine managed Entities über Thread-Grenzen reichen.
  • Nur IDs und immutable DTOs an Async-Prozesse geben.
  • Context Propagation bewusst konfigurieren.
  • Virtual Threads für Threadkosten nutzen, nicht für längere Transaktionen.
  • Nach Commit arbeiten, nicht während unklarem Transaktionsstatus.
  • Async-Fehlerpfade überwachen.
Stolperfalle:

Ein Async-Task kann Daten lesen, bevor die ursprüngliche Transaktion committed hat, oder er kann laufen, obwohl die ursprüngliche Transaktion später rollbackt.

Merksatz:

Transaktionen überschreiten Async-Grenzen nicht ohne explizites Design.

B5-K03-Q010 – JTA in Architektur: Outbox, Saga, Microservices und Anti-PatternsJTA für lokale Atomizität, Saga für verteilte Geschäftsprozesse. 24h
Frage:

„Welche JTA-Anti-Patterns sehen Sie in modernen Enterprise- und Microservice-Systemen?“

Kurz erklärt:

JTA ist stark für lokale Transaktionen. In verteilten Architekturen muss JTA mit Outbox, Idempotenz, Saga und kompensierenden Aktionen kombiniert werden.

Musterantwort:

Häufige Anti-Patterns sind: globale Transaktionen über Servicegrenzen erzwingen, Remote Calls innerhalb offener DB-Transaktionen, XA nutzen, um fehlende Servicegrenzen zu kaschieren, Outbox durch direkte Event-Publishes ersetzen, REQUIRES_NEW als Fehlerversteck verwenden, große Batch-Jobs in einer Transaktion ausführen, Transaktionen in Repositories statt Use Cases schneiden und Rollback-Regeln nicht fachlich definieren.

In Microservices nutze ich JTA lokal: innerhalb eines Services, einer Datenbank, eines Aggregates oder einer Outbox-Grenze. Für serviceübergreifende Prozesse nutze ich Sagas, Events, Idempotenz, Kompensation und klare Zustandsmodelle. Jakarta Transactions ist ein lokaler Standardmechanismus für Transaction Manager und Ressourcenkoordination, aber keine automatische Lösung für verteilte Geschäftsprozesse.

Senior-Antwort:

„Ich verwende JTA dort, wo ich lokale Wahrheit atomar schützen kann. Über Servicegrenzen hinweg modelliere ich Prozesse, nicht globale Transaktionen.“

Häufige Fehler:
  • JTA über Remote-Servicegrenzen erwarten.
  • Direkter Kafka-Publish vor DB-Commit.
  • Direkter Kafka-Publish nach DB-Commit ohne Outbox und Crash-Schutz.
  • Keine Idempotenz bei Consumer.
  • Saga-Zustände nicht persistieren.
  • Kompensation vergessen.
  • Transaktionale und fachliche Konsistenz verwechseln.
Best Practices:
  • JTA lokal und klar nutzen.
  • Servicegrenzen respektieren.
  • Outbox für zuverlässige Integration.
  • Consumer idempotent bauen.
  • Sagas als fachliche Zustandsmaschine modellieren.
  • Kompensation explizit definieren.
  • Remote Calls außerhalb lokaler Transaktionen.
  • Eventual Consistency fachlich kommunizieren.
Stolperfalle:

Ein Event nach Commit direkt zu senden ist besser als vor Commit, aber immer noch nicht robust gegen Crash zwischen Commit und Send. Dafür braucht man Outbox oder eine gleichwertige Strategie.

Merksatz:

JTA für lokale Atomizität, Saga für verteilte Geschäftsprozesse.

B5-K04 – Kapitel 4: JAX-RS / Jakarta RESTful Web Services 10 Karten

B5-K04-Q001 – Resource-Klassen, @Path und HTTP-MethodenJAX-RS Resources adaptieren HTTP; Use Cases führen Fachlogik aus. 24h
Frage:

„Wie modellieren Sie JAX-RS Resource-Klassen professionell?“

Kurz erklärt:

JAX-RS Resource-Klassen bilden HTTP-Ressourcen und Operationen ab. @Path definiert URI-Pfade, HTTP-Methodenannotationen wie @GET und @POST definieren die Operation.

Musterantwort:

Ich behandle JAX-RS Resources als HTTP-Boundary, nicht als Business-Service. Die Resource übersetzt HTTP-Konzepte wie Path, Headers, Statuscodes, Request Body und Response Body in Application Commands oder Query-DTOs. Die eigentliche Fachlogik liegt in Use Cases.

Eine Resource sollte also Validierung, Mapping, Statuscode-Auswahl und HTTP-spezifische Details behandeln, aber keine JPA-Transaktionslogik, keine Provider-Orchestrierung und keine Domain-Regeln verstecken. Jakarta RESTful Web Services definiert APIs für Web Services nach REST; die Ressource ist dabei die Web-Schicht, nicht das gesamte System.

Senior-Antwort:

„Eine JAX-RS Resource ist mein HTTP-Adapter. Sie spricht HTTP, aber sie entscheidet nicht die Fachlogik.“

Häufige Fehler:
  • Businesslogik direkt in Resource-Klassen.
  • JPA Entities als Request-/Response-Body.
  • Keine sauberen Statuscodes.
  • Header wie Idempotency-Key ignorieren.
  • Resource-Methoden mit zu vielen Verantwortungen.
  • REST-Pfade nach Tabellen statt fachlichen Ressourcen schneiden.
Best Practices:
  • Resources als HTTP-Boundary behandeln.
  • DTOs für API-Verträge verwenden.
  • Use Cases injizieren und delegieren.
  • HTTP-Statuscodes bewusst setzen.
  • Location bei Creation nutzen.
  • Headers und Path-Parameter explizit binden.
  • Bean Validation an Boundary verwenden.
  • Kein Persistence Context in Response-Serialisierung leaken.
Stolperfalle:

Nur weil eine Resource leicht CDI-Beans injizieren kann, sollte sie nicht zur God Class für Validierung, Transaktion, Persistenz und Integration werden.

Merksatz:

JAX-RS Resources adaptieren HTTP; Use Cases führen Fachlogik aus.

B5-K04-Q002 – Parameter Binding, Conversion und Request-KontextHTTP-Parameter sind Eingabeformat, nicht Fachmodell.
Frage:

„Wie funktioniert Parameter Binding in JAX-RS, und welche Fehler sehen Sie häufig?“

Kurz erklärt:

JAX-RS kann Pfad-, Query-, Header-, Cookie-, Matrix- und Form-Parameter binden. @BeanParam bündelt mehrere Parameter in einem Objekt.

Musterantwort:

JAX-RS bindet Request-Daten über Annotationen wie @PathParam , @QueryParam , @HeaderParam , @CookieParam , @MatrixParam , @FormParam , @BeanParam und @Context . Für komplexere Suchparameter ist @BeanParam hilfreich, weil es Parameter bündelt und Validierung zentralisiert.

Häufige Fehler sind: Query-Parameter ohne Limits, keine Default-Werte, keine Validierung, falsche Typkonvertierung, zu viele einzelne Method-Parameter und das Verwechseln von Query-Parametern mit fachlichem Suchmodell. Parameter Binding sollte nur die HTTP-Schicht betreffen; danach wird in ein fachliches Command übersetzt.

Senior-Antwort:

„Parameter Binding ist HTTP-Adapter-Logik. Danach arbeite ich mit fachlichen Commands, nicht mit @QueryParam -Werten.“

Häufige Fehler:
  • Unbegrenzte size -Parameter.
  • Zu viele HTTP-Details im Use Case.
  • Keine Default-Werte.
  • Keine Validierung auf Query-Parametern.
  • Parameterkonvertierung in Fachlogik verstecken.
  • @Context -Objekte tief in Domain-Code weiterreichen.
Best Practices:
  • HTTP-Parameter an der Resource-Grenze bündeln.
  • @BeanParam für komplexe Suche nutzen.
  • Defaults und Limits setzen.
  • Bean Validation verwenden.
  • In Commands oder Query-DTOs mappen.
  • Context-Objekte nicht in Domain-Schichten leaken.
  • Parametersemantik dokumentieren.
  • API-Tests für Grenzwerte schreiben.
Stolperfalle:

Ein unlimitierter Query-Parameter wie size=100000 ist kein harmloses API-Detail, sondern ein möglicher Produktionsincident.

Merksatz:

HTTP-Parameter sind Eingabeformat, nicht Fachmodell.

B5-K04-Q003 – Content Negotiation, @Consumes , @Produces und Media TypesEine Ressource kann mehrere Repräsentationen haben.
Frage:

„Wie funktioniert Content Negotiation in JAX-RS?“

Kurz erklärt:

Content Negotiation bestimmt anhand von Accept , Content-Type , @Produces und @Consumes , welches Repräsentationsformat gelesen oder geschrieben wird.

Musterantwort:

@Consumes beschreibt, welche Request-Media-Types eine Resource-Methode akzeptiert. @Produces beschreibt, welche Response-Media-Types sie liefern kann. Der Client sendet Content-Type für den Request Body und Accept für gewünschte Response-Formate. Die JAX-RS Runtime wählt anhand von Pfad, Methode und Media-Type-Matching eine passende Resource-Methode und einen passenden MessageBodyReader oder MessageBodyWriter.

Content Negotiation ist wichtig, weil REST nicht nur JSON bedeutet. Eine API kann JSON, CSV, PDF oder Problem Details liefern, aber das muss explizit und testbar sein. Jakarta RESTful Web Services 4.0 entfernt die JAXB-Abhängigkeit, was im modernen API-Design zusätzlich verdeutlicht, dass JSON-B, JSON-P oder andere Provider bewusst gewählt werden sollten.

Senior-Antwort:

„Content Negotiation ist Teil des API-Vertrags. JSON ist ein Format, nicht die REST-Architektur.“

Häufige Fehler:
  • @Produces vergessen.
  • Content-Type und Accept verwechseln.
  • Immer JSON erzwingen.
  • Große Exporte als komplettes JSON im Speicher bauen.
  • Fehlerantworten ohne eigenen Media Type.
  • Vendor Media Types ohne Governance.
Best Practices:
  • Media Types explizit definieren.
  • @Consumes und @Produces bewusst setzen.
  • Fehlerformate konsistent halten.
  • Große Reports streamen.
  • Content Negotiation testen.
  • 406 und 415 korrekt behandeln.
  • JSON-B/JSON-P Provider bewusst konfigurieren.
  • API-Versionierung nicht zufällig gestalten.
Stolperfalle:

Ein Client kann Accept: text/csv senden. Wenn die API nur JSON zurückgibt, ignoriert sie den ausgehandelten HTTP-Vertrag.

Merksatz:

Eine Ressource kann mehrere Repräsentationen haben.

B5-K04-Q004 – MessageBodyReader, MessageBodyWriter, JSON-B und StreamingBodies sind Verträge in Bytes.
Frage:

„Wie verarbeitet JAX-RS Request- und Response-Bodies?“

Kurz erklärt:

MessageBodyReader und MessageBodyWriter konvertieren zwischen HTTP-Body und Java-Objekten. Streaming vermeidet, große Responses vollständig im Speicher aufzubauen.

Musterantwort:

JAX-RS verwendet MessageBodyReader, um Request-Bodies in Java-Typen zu lesen, und MessageBodyWriter, um Java-Typen in Response-Bodies zu schreiben. Die Auswahl erfolgt anhand von Java-Typ, Generic Type, Annotationen und Media Type. Standardmäßig werden in Jakarta-EE-Runtimes häufig JSON-B oder JSON-P für JSON genutzt, während eigene Provider für spezielle Formate wie CSV, Avro oder domain-spezifische Payloads ergänzt werden können.

Für große Exporte verwende ich Streaming, zum Beispiel StreamingOutput , damit die Anwendung nicht zuerst alle Daten in eine Liste lädt. Das reduziert Speicherverbrauch und verbessert Time-to-first-byte.

Senior-Antwort:

„Message Body Mapping ist nicht nur Technik. Es definiert das externe Datenformat meiner API.“

Häufige Fehler:
  • Große Exporte als List im Speicher bauen.
  • Serialization-Format nicht versionieren.
  • Domain Entities direkt serialisieren.
  • Custom Writer ohne Media-Type-Einschränkung.
  • Ressourcen-Streams nicht schließen.
  • JSON-B-Defaults unbewusst als API-Vertrag verwenden.
Best Practices:
  • DTOs für Body-Verträge nutzen.
  • JSON-Format explizit testen.
  • Custom Provider für spezielle Media Types.
  • Streaming für große Downloads.
  • Pagination für große JSON-Collections.
  • Domain Entities nicht serialisieren.
  • Date/Time-Formate standardisieren.
  • Backward Compatibility bei DTO-Änderungen beachten.
Stolperfalle:

Eine JPA Entity direkt zu serialisieren kann Lazy Loading, N+1, Endlosschleifen und unbeabsichtigte Datenlecks auslösen.

Merksatz:

Bodies sind Verträge in Bytes.

B5-K04-Q005 – Response, Statuscodes, Headers und Problem DetailsHTTP ist Teil der Architektur, nicht nur Transport. 24h
Frage:

„Wie wählen Sie HTTP-Statuscodes und Response-Strukturen in JAX-RS?“

Kurz erklärt:

JAX-RS Response erlaubt explizite Kontrolle über Statuscode, Headers, Entity, Cache-Informationen, ETags und Metadaten.

Musterantwort:

Ich wähle Statuscodes nach HTTP-Semantik. 200 OK für erfolgreiche Reads oder Commands mit Response Body, 201 Created mit Location für neu erzeugte Ressourcen, 202 Accepted für asynchrone Annahme, 204 No Content für erfolgreiche Löschung ohne Body, 400 für syntaktisch oder semantisch ungültige Requests, 401 für fehlende Authentifizierung, 403 für fehlende Berechtigung, 404 für nicht gefundene Ressourcen, 409 für Konflikte und 422 für nicht erfüllbare fachliche Verarbeitung, wenn im API-Standard vorgesehen.

Für Fehler nutze ich ein konsistentes Problem-Format mit Status, Titel, Detail, Instance und Correlation-ID. Die Resource sollte nicht überall ad-hoc Strings zurückgeben.

Senior-Antwort:

„Statuscodes sind nicht Kosmetik. Sie sind Teil des maschinenlesbaren API-Vertrags.“

Häufige Fehler:
  • Immer 200 OK .
  • Fehler als Plain Text.
  • Keine Correlation-ID.
  • 404 und 403 unbedacht leaken.
  • 201 Created ohne Location .
  • DELETE mit unnötigem Body.
  • Businessfehler und technische Fehler vermischen.
Best Practices:
  • Statuscodes semantisch wählen.
  • Response für explizite HTTP-Verträge nutzen.
  • Fehlerformat standardisieren.
  • Correlation-ID einbinden.
  • Location bei Creation.
  • ETag und Cache-Header prüfen.
  • Keine Stacktraces an Clients.
  • Fehler maschinenlesbar machen.
Stolperfalle:

200 OK für jeden Fehler zwingt Clients, fachliche Fehler aus beliebigen Body-Strukturen zu erraten.

Merksatz:

HTTP ist Teil der Architektur, nicht nur Transport.

B5-K04-Q006 – ExceptionMapper und zentrale FehlerbehandlungFehlerbehandlung ist API-Design.
Frage:

„Wie implementieren Sie zentrale Fehlerbehandlung in JAX-RS?“

Kurz erklärt:

ExceptionMapper<T> übersetzt Exceptions zentral in HTTP Responses. Dadurch bleiben Resource-Methoden sauber und Fehlerformate konsistent.

Musterantwort:

Ich verwende ExceptionMapper<T> für fachliche Exceptions, technische Exceptions und Fallbacks. Fachliche Exceptions werden auf passende Statuscodes und Problem-Details gemappt. NotFoundException , Validation-Fehler und Security-Fehler bekommen eigene Mapper. Ein Fallback-Mapper für unerwartete Fehler liefert eine generische 500-Antwort und loggt intern Details.

Wichtig ist, keine Stacktraces oder sensitiven Details an Clients zu geben. Fehlerantworten sollten konsistent, maschinenlesbar und mit Correlation-ID nachvollziehbar sein.

Senior-Antwort:

„Exceptions sind intern. HTTP-Fehler sind externe Verträge. Ein Mapper ist die Übersetzungsschicht dazwischen.“

Häufige Fehler:
  • try/catch in jeder Resource.
  • Unterschiedliche Fehlerformate pro Endpoint.
  • Stacktraces im Response Body.
  • Fachliche Fehler als 500 .
  • Alle Exceptions auf 400 mappen.
  • Keine Correlation-ID.
  • Fallback-Mapper verschluckt wichtige Logs.
Best Practices:
  • Exception Mapping zentralisieren.
  • Fachliche Exceptions typisieren.
  • Problem-Format standardisieren.
  • Unerwartete Fehler intern loggen.
  • Sensitive Details nicht ausgeben.
  • Validation-Fehler strukturiert mappen.
  • Security-Fehler bewusst behandeln.
  • Mapper mit API-Tests abdecken.
Stolperfalle:

Ein generischer ExceptionMapper<Throwable> kann spezifische Fehler verschleiern, wenn Logging, Priorität und Mapping nicht sauber gesteuert sind.

Merksatz:

Fehlerbehandlung ist API-Design.

B5-K04-Q007 – Filter, Interceptors und Cross-Cutting ConcernsFilter rahmen HTTP ein; Interceptors rahmen Bodies ein.
Frage:

„Was ist der Unterschied zwischen JAX-RS Filter und Interceptor?“

Kurz erklärt:

JAX-RS Filter arbeiten auf Request-/Response-Metadaten. Reader/Writer Interceptors arbeiten um Entity-Body-Reader/-Writer herum.

Musterantwort:

Container Request und Response Filter arbeiten auf Request- und Response-Kontexten: Headers, URI, Method, Security Context, Abort, Correlation-ID, CORS, Security Headers oder Logging-Metadaten. Reader und Writer Interceptors arbeiten näher am Entity Body und können das Lesen oder Schreiben des Bodys beeinflussen, etwa für Kompression, Signierung, Verschlüsselung oder Metrik-Erfassung.

Ich nutze Filter für HTTP-Cross-Cutting Concerns und Interceptors für Body-nahe Verarbeitung. Businesslogik gehört weder in Filter noch in Interceptors.

Senior-Antwort:

„Filter sind für HTTP-Querschnitt, nicht für Fachprozesse. Was jeder Request durchläuft, muss klein, deterministisch und beobachtbar sein.“

Häufige Fehler:
  • Businessregeln in Request-Filtern.
  • Body in Filtern konsumieren und nicht wiederherstellen.
  • Teures Logging auf jedem Request.
  • Filterreihenfolge nicht kontrollieren.
  • Security Header vergessen.
  • Correlation-ID nur loggen, aber nicht zurückgeben.
Best Practices:
  • Filter für HTTP-Metadaten.
  • Interceptors für Body-nahe Querschnittsthemen.
  • Logik klein und schnell halten.
  • Reihenfolge über @Priority steuern.
  • Correlation-ID konsequent setzen.
  • Security Header zentral ergänzen.
  • Body-Interception sparsam nutzen.
  • Name Binding für selektive Anwendung prüfen.
Stolperfalle:

Ein Request-Filter, der den Entity Stream liest, kann verhindern, dass die Resource später den Body noch lesen kann.

Merksatz:

Filter rahmen HTTP ein; Interceptors rahmen Bodies ein.

B5-K04-Q008 – Client API, Timeouts, Resilience und Service-to-Service-KommunikationJeder Remote Call braucht Timeout, Semantik und Plan B.
Frage:

„Wie verwenden Sie die JAX-RS Client API sicher in Service-to-Service-Kommunikation?“

Kurz erklärt:

Die JAX-RS Client API ermöglicht HTTP-Client-Aufrufe. In produktiven Systemen sind Timeouts, Response-Handling, Ressourcenfreigabe, Retries, Circuit Breaker und Idempotenz entscheidend.

Musterantwort:

Ich erstelle Clients kontrolliert, typischerweise als langlebige CDI-Bean oder über Producer, nicht pro Request neu. Ich setze Connect- und Read-Timeouts, schließe Responses, mappe Statuscodes bewusst, leite Correlation-IDs weiter und unterscheide retrybare von nicht retrybaren Fehlern. Retries brauchen Idempotenz und ein Budget. Zusätzlich nutze ich Circuit Breaker, Bulkheads und Metriken.

Die JAX-RS Client API ist bequem, aber ein Remote Call bleibt ein unsicherer Netzwerkaufruf. Er sollte nicht in einer offenen Datenbanktransaktion blockieren, wenn das vermeidbar ist.

Senior-Antwort:

„Ein JAX-RS Client ist nicht nur ein HTTP-Helfer. Er ist eine Resilience-Grenze zu einem unzuverlässigen Netzwerk.“

Häufige Fehler:
  • Kein Timeout.
  • Client pro Request bauen.
  • Response nicht schließen.
  • Alle Fehler retryen.
  • Keine Correlation-ID.
  • Remote Call in langer JTA-Transaktion.
  • Downstream-Fehler als 500 ohne fachliche Semantik weiterreichen.
Best Practices:
  • Client lifecycle zentral verwalten.
  • Timeouts immer setzen.
  • Responses schließen.
  • Statuscodes bewusst mappen.
  • Retries nur mit Idempotenz.
  • Circuit Breaker und Bulkheads nutzen.
  • Correlation-ID propagieren.
  • Remote Calls außerhalb kritischer Transaktionen halten.
Stolperfalle:

Ein fehlender Read Timeout kann aus einem Downstream-Problem schnell ein Thread- und Connection-Pool-Problem im aufrufenden Service machen.

Merksatz:

Jeder Remote Call braucht Timeout, Semantik und Plan B.

B5-K04-Q009 – Asynchrone Verarbeitung, SSE und Long-running OperationsFür Langläufer: starte Job, gib Status, streame Fortschritt.
Frage:

„Wie modellieren Sie lange laufende Operationen in JAX-RS?“

Kurz erklärt:

JAX-RS unterstützt asynchrone Responses und Server-Sent Events. Long-running Operations sollten häufig als Job-Ressourcen modelliert werden.

Musterantwort:

Lange Operationen sollten selten den HTTP-Request blockieren. Ich modelliere sie oft als Job-Ressource: POST /exports startet den Job und liefert 202 Accepted mit Location . GET /exports/{id} liefert Status. Optional liefert SSE Fortschrittsereignisse. So bleibt die API robust gegen Timeouts, Retries und Client-Abbrüche.

Asynchrone JAX-RS Responses können sinnvoll sein, wenn der Serverthread nicht blockieren soll. Aber sie ersetzen keine durable Job-Verarbeitung. Für echte Langläufer braucht man persistierten Job-Zustand, Wiederaufnahme, Timeout, Cancellation und Monitoring.

Senior-Antwort:

„Lange Arbeit ist ein Prozess, keine blockierende HTTP-Methode. Ich mache den Prozess als Ressource sichtbar.“

Häufige Fehler:
  • Minutenlange Requests blockieren.
  • Keine Job-ID zurückgeben.
  • Job-Zustand nur im Speicher halten.
  • Client-Disconnect ignorieren.
  • Async als Ersatz für Queue missverstehen.
  • Keine Backpressure für Job-Erzeugung.
  • SSE-Verbindungen unbegrenzt erlauben.
Best Practices:
  • Long-running Work als Job modellieren.
  • 202 Accepted mit Location .
  • Job-Zustand persistieren.
  • Polling oder SSE anbieten.
  • Cancellation und Expiry definieren.
  • Backpressure und Quotas setzen.
  • Client-Disconnect behandeln.
  • Job-Metriken überwachen.
Stolperfalle:

AsyncResponse hält die HTTP-Interaktion offen, macht die eigentliche Arbeit aber nicht automatisch durable oder wiederaufsetzbar.

Merksatz:

Für Langläufer: starte Job, gib Status, streame Fortschritt.

B5-K04-Q010 – JAX-RS in Architektur: Security, Versionierung, Tests und Anti-PatternsJAX-RS baut APIs; Architektur macht sie sicher, stabil und evolvierbar. 24h
Frage:

„Welche JAX-RS-Anti-Patterns sehen Sie in Enterprise-Systemen?“

Kurz erklärt:

Professionelle JAX-RS-Architektur umfasst Security, Versionierung, DTO-Grenzen, Validation, Contract Tests, Observability und klare Anti-Pattern-Vermeidung.

Musterantwort:

Häufige Anti-Patterns sind: JPA Entities direkt serialisieren, Businesslogik in Resources, keine zentrale Fehlerstrategie, fehlende Pagination, keine Timeouts im Client, globale Filter mit Businesslogik, Security nur am Gateway, uneinheitliche Statuscodes, unversionierte Breaking Changes, fehlende Contract Tests und APIs, die interne Datenbankstrukturen spiegeln.

Architektonisch sollte JAX-RS die Boundary-Schicht sein. Sie validiert, autorisiert, mappt und delegiert. Security muss sowohl technisch als auch fachlich geprüft werden: Rollen am Endpunkt reichen oft nicht, wenn Ressourcenzugriff zusätzlich tenant- oder eigentumsbezogen ist.

Senior-Antwort:

„JAX-RS ist meine API-Grenze. Dort entscheide ich bewusst über Vertrag, Sicherheit, Limits und Mapping — nicht über Datenbankdetails.“

Häufige Fehler:
  • API-Versionierung erst nach Breaking Change planen.
  • Security nur am Gateway.
  • Role Check ohne Resource Ownership Check.
  • Entities als externe Verträge.
  • Fehlende Pagination und Limits.
  • Keine Contract Tests.
  • JAX-RS Client ohne Timeouts.
  • Filter mit versteckter Businesslogik.
Best Practices:
  • Resource-Schicht schlank halten.
  • DTOs als API-Verträge.
  • Zentrale ExceptionMapper.
  • Bean Validation an Boundary.
  • Fachliche Autorisierung zusätzlich zu Rollen.
  • Pagination und Limits erzwingen.
  • Versionierung und Deprecation planen.
  • Contract Tests und Integrationstests.
Stolperfalle:

Eine Rolle wie invoice:read bedeutet nicht automatisch, dass der Nutzer jede Rechnung jedes Tenants lesen darf.

Merksatz:

JAX-RS baut APIs; Architektur macht sie sicher, stabil und evolvierbar.

B5-K05 – Kapitel 5: JSON-B / Jakarta JSON Binding 10 Karten

B5-K05-Q001 – Grundmodell: Jsonb , JsonbBuilder und Object MappingJSON-B macht Java zu JSON; Architektur macht daraus einen stabilen Vertrag. 24h
Frage:

„Was ist JSON-B, und wie verwenden Sie Jsonb und JsonbBuilder ?“

Kurz erklärt:

Jsonb ist der zentrale Einstiegspunkt für Serialisierung und Deserialisierung. JsonbBuilder erzeugt konfigurierte Jsonb -Instanzen.

Musterantwort:

JSON-B ist der Jakarta-Standard, um Java-Objekte zu JSON zu serialisieren und JSON in Java-Objekte zu deserialisieren. Der zentrale API-Typ ist Jsonb . Er bietet Operationen wie toJson und fromJson . Die offizielle API-Dokumentation beschreibt Jsonb als Abstraktion über JSON-B-Operationen und nennt fromJson zum Lesen von JSON in Java-Objektbäume sowie toJson zum Serialisieren von Java-Objektbäumen zu JSON.

JsonbBuilder ist der Einstiegspunkt, um eine Jsonb -Instanz zu erzeugen. Für produktive Anwendungen konfiguriere ich Naming Strategy, Date/Time-Format, Null-Verhalten, Adapter und Serializer bewusst, statt mich blind auf Defaults zu verlassen. Die API-Dokumentation beschreibt JsonbBuilder als Client-Einstiegspunkt, der Jsonb -Instanzen anhand der bereitgestellten Konfiguration erzeugt; für die meisten Use Cases reicht eine Builder-Instanz innerhalb der Anwendung.

Senior-Antwort:

„JSON-B ist nicht nur Serialization. JSON-B definiert externe Verträge. Deshalb konfiguriere und teste ich es wie eine API-Oberfläche.“

Häufige Fehler:
  • JSON-B-Defaults als stabilen API-Vertrag betrachten.
  • Jsonb unnötig oft erzeugen.
  • Domain Entities direkt serialisieren.
  • Naming-Strategie nicht definieren.
  • Datum-/Zeitformate nicht standardisieren.
  • Interne Felder versehentlich veröffentlichen.
  • Deserialisierung unvalidierter externer Payloads direkt in Domain-Objekte.
Best Practices:
  • DTOs und Event-Records als JSON-Verträge nutzen.
  • JsonbConfig zentral definieren.
  • Naming Strategy explizit wählen.
  • Date/Time-Format standardisieren.
  • Jsonb -Instanzen wiederverwenden und sauber schließen.
  • JSON-B nicht direkt auf JPA Entities anwenden.
  • API-Kompatibilität mit Tests absichern.
  • Payloads nach Deserialisierung validieren.
Stolperfalle:

Ein implizites Feldnaming kann durch Refactoring von Java-Properties unabsichtlich den JSON-Vertrag brechen.

Merksatz:

JSON-B macht Java zu JSON; Architektur macht daraus einen stabilen Vertrag.

B5-K05-Q002 – Annotationen: @JsonbProperty , @JsonbTransient , @JsonbDateFormatAnnotationen machen Ausnahmen sichtbar; globale Config macht Standards stabil.
Frage:

„Wann verwenden Sie JSON-B-Annotationen statt globaler JsonbConfig ?“

Kurz erklärt:

JSON-B-Annotationen steuern Property-Namen, Ausschlüsse, Datum-/Zeitformat, Reihenfolge, Visibility, Adapter und Serializer.

Musterantwort:

Globale JsonbConfig nutze ich für Querschnittsregeln wie Naming Strategy, Null-Verhalten oder Default-Date-Format. Annotationen nutze ich, wenn ein konkreter Vertrag bewusst abweicht oder besonders stabil sein muss: externe Feldnamen, @JsonbTransient für interne Felder, @JsonbDateFormat für API-spezifische Zeitformate oder @JsonbProperty für explizite Namen.

Wichtig ist, Annotationen nicht als zufällige Reparatur einzusetzen. Wenn jede Klasse andere Regeln hat, wird der API-Vertrag schwer wartbar. Wenn ein Feld aber öffentlich customer_id heißen muss, sollte das explizit sichtbar sein.

Senior-Antwort:

„Globale Config gibt Konsistenz. Annotationen verwende ich dort, wo ein konkreter Vertrag sichtbar und stabil sein muss.“

Häufige Fehler:
  • Interne Felder versehentlich serialisieren.
  • Date/Time-Format nicht explizit testen.
  • Annotationen wild und inkonsistent verteilen.
  • API-Feldnamen an Java-Refactoring koppeln.
  • @JsonbTransient mit Security-Strategie verwechseln.
  • Sensible Daten nur über DTO-Annotationen schützen, aber anderswo loggen.
Best Practices:
  • Globale Regeln zentral konfigurieren.
  • Vertraglich wichtige Felder explizit annotieren.
  • Interne Felder aus DTOs entfernen oder @JsonbTransient nutzen.
  • Date/Time-Formate standardisieren.
  • JSON-Snapshots in Tests prüfen.
  • DTOs statt Entities annotieren.
  • API-Feldnamen dokumentieren.
  • Sensible Daten bereits im Modell begrenzen.
Stolperfalle:

@JsonbTransient verhindert JSON-Ausgabe, ersetzt aber keine Berechtigungsprüfung und keine Datenminimierung im Use Case.

Merksatz:

Annotationen machen Ausnahmen sichtbar; globale Config macht Standards stabil.

B5-K05-Q003 – Records, Immutability und DTO-DesignRecords machen JSON-Verträge kompakt, aber nicht automatisch kompatibel.
Frage:

„Sind Java Records gut für JSON-B DTOs geeignet?“

Kurz erklärt:

Records eignen sich hervorragend für immutable JSON-DTOs und Event-Payloads, wenn Konstruktoren, Feldnamen und Kompatibilität bewusst gestaltet werden.

Musterantwort:

Ja, Records sind sehr gut für JSON-DTOs geeignet, weil sie immutable, kompakt und wertorientiert sind. Für API-Responses, Commands und Events sind Records oft lesbarer als mutable POJOs. Wichtig ist aber: Ein Record ist ein Vertrag. Komponentenname, JSON-Property, Konstruktor, Default-Werte und optionale Felder müssen bewusst modelliert werden.

Für Deserialisierung muss die JSON-B-Implementierung den passenden Konstruktor und die Property-Zuordnung verstehen. Bei kritischen Verträgen verwende ich @JsonbCreator und @JsonbProperty , damit die Zuordnung eindeutig bleibt. Danach validiere ich den deserialisierten Record mit Bean Validation oder expliziter Fachvalidierung.

Senior-Antwort:

„Records sind ideal für JSON-Verträge, solange ich sie als Verträge behandle und nicht als zufällige Java-Struktur.“

Häufige Fehler:
  • Record-Komponentennamen als impliziten API-Vertrag verwenden.
  • Fehlende Defaults bei neuen Feldern.
  • Mutable Listen in Records nicht kopieren.
  • Domain Entities durch Records ersetzen wollen.
  • Keine Validierung nach Deserialisierung.
  • Optionale Felder unklar modellieren.
Best Practices:
  • Records für DTOs und Events bevorzugen.
  • JSON-Feldnamen explizit annotieren, wenn Vertrag stabil sein muss.
  • Collections defensiv kopieren.
  • Schema-Version bei Events ergänzen.
  • Bean Validation nutzen.
  • Optionale Felder bewusst modellieren.
  • Backward Compatibility testen.
  • Domain Entities von JSON-DTOs trennen.
Stolperfalle:

Ein neues Pflichtfeld in einem Record kann alte Producer oder Consumer brechen, wenn keine Kompatibilitätsstrategie existiert.

Merksatz:

Records machen JSON-Verträge kompakt, aber nicht automatisch kompatibel.

B5-K05-Q004 – Custom Adapter, Serializer und DeserializerCustom Mapping gehört an eine zentrale Adaptergrenze, nicht in jeden Use Case.
Frage:

„Wann verwenden Sie JsonbAdapter , Custom Serializer oder Custom Deserializer?“

Kurz erklärt:

Adapter übersetzen zwischen Domain-nahen Java-Typen und JSON-freundlichen Repräsentationen. Serializer/Deserializer geben noch tiefere Kontrolle über JSON-Ausgabe und -Eingabe.

Musterantwort:

JsonbAdapter verwende ich, wenn ein Java-Typ fachlich sinnvoll ist, aber eine andere JSON-Repräsentation braucht. Ein gutes Beispiel ist Money : Im Code möchte ich BigDecimal plus Währung, im JSON vielleicht { "amount": "12.34", "currency": "EUR" } . Der Adapter hält diese Übersetzung zentral und testbar.

Custom Serializer oder Deserializer verwende ich, wenn die Repräsentation sehr speziell ist oder ich direkte Kontrolle über JSON-P-Strukturen brauche. JSON-B 3.0 unterstützt @JsonbTypeDeserializer und @JsonbTypeAdapter auch als Parameter- oder Type-Annotationen, wodurch Anpassungen feiner platziert werden können.

Senior-Antwort:

„Adapter sind meine Vertragsübersetzer. Sie halten Domain-Typen stark und JSON-Repräsentationen kontrolliert.“

Häufige Fehler:
  • Geld als double serialisieren.
  • Adapter mit Businesslogik überladen.
  • Adapter nicht roundtrip-testen.
  • Unterschiedliche Money-Formate in verschiedenen APIs.
  • Mutable Adapter mit internem Zustand.
  • Fehlerhafte Scale-/Rounding-Regeln.
Best Practices:
  • Adapter für fachliche Value Types verwenden.
  • JSON-Repräsentation explizit definieren.
  • Roundtrip-Tests schreiben.
  • Adapter stateless halten.
  • Geldbeträge nicht als Floating Point.
  • Serializer/Deserializer nur bei Bedarf.
  • Formate zentral dokumentieren.
  • Kompatibilität bei Formatänderungen prüfen.
Stolperfalle:

Ein Adapter darf nicht stillschweigend runden, wenn das fachlich nicht erlaubt ist. Geldformate sind Vertrags- und Compliance-Themen.

Merksatz:

Custom Mapping gehört an eine zentrale Adaptergrenze, nicht in jeden Use Case.

B5-K05-Q005 – Null-Semantik, optionale Felder und API-Kompatibilitätnull ist kein Detail, sondern eine Entscheidung.
Frage:

„Wie behandeln Sie null , fehlende Felder und optionale Felder in JSON-B APIs?“

Kurz erklärt:

Null in JSON kann „unbekannt“, „nicht gesendet“, „explizit löschen“ oder „nicht verfügbar“ bedeuten. Diese Semantik muss pro API bewusst definiert werden.

Musterantwort:

Ich unterscheide klar zwischen fehlendem Feld und explizitem null . Bei einem vollständigen PUT kann null bedeuten, dass ein Feld leer gesetzt wird. Bei einem PATCH ist es oft entscheidend, ob ein Feld fehlt oder explizit auf null gesetzt wird. JSON-B allein löst diese fachliche Semantik nicht automatisch.

Für Responses entscheide ich, ob Null-Werte ausgegeben werden. Für externe APIs ist Konsistenz wichtig: Entweder Null-Felder werden bewusst geliefert oder bewusst weggelassen. JSON-B 3.0 hat @JsonbProperty.nillable() deprecated, was ein Hinweis ist, Null-Semantik nicht über dieses alte Property-Flag zu steuern.

Senior-Antwort:

„Null ist kein Wert, den man nebenbei behandelt. In APIs ist Null Semantik — und Semantik gehört dokumentiert und getestet.“

Häufige Fehler:
  • null ohne API-Semantik.
  • PATCH mit normalem DTO modellieren.
  • Fehlende Felder als Löschen interpretieren.
  • Neue Pflichtfelder ohne Versionierung.
  • Null-Felder inkonsistent ausgeben.
  • Leere Strings und Null vermischen.
  • Optional überall in JSON-DTOs verwenden.
Best Practices:
  • Null-Semantik pro API dokumentieren.
  • PATCH-Modelle gesondert modellieren.
  • Fehlende Felder von explizitem Null unterscheiden, wenn nötig.
  • Neue Felder zunächst optional einführen.
  • Null-Ausgabe konsistent konfigurieren.
  • Collections eher leer als null zurückgeben.
  • Bean Validation bewusst einsetzen.
  • Kompatibilitätstests für alte Payloads schreiben.
Stolperfalle:

Bei PATCH kann ein normales DTO nicht zuverlässig ausdrücken, ob ein Feld fehlt oder explizit auf Null gesetzt wurde.

Merksatz:

null ist kein Detail, sondern eine Entscheidung.

B5-K05-Q006 – Polymorphie, Type Information und SicherheitsgrenzenPolymorphie braucht Discriminator und Grenzen.
Frage:

„Wie gehen Sie mit polymorphen JSON-B-Payloads um?“

Kurz erklärt:

Polymorphe JSON-Payloads benötigen kontrollierte Type Discriminator und strenge Whitelists. Unkontrollierte Typinformationen sind ein Sicherheitsrisiko.

Musterantwort:

Polymorphe Payloads brauchen einen expliziten, stabilen Discriminator, zum Beispiel "type": "email" oder "type": "sms" . Ich mappe diesen Discriminator auf eine feste Whitelist erlaubter Java-Typen. Ich lasse niemals beliebige Java-Klassennamen aus JSON bestimmen, welche Klasse instanziiert wird. Das wäre ein Sicherheits- und Wartbarkeitsrisiko.

JSON-B 3.0 nennt Support für polymorphe Typen als Feature. Trotzdem bleibt die Architekturfrage: Welche Subtypen sind Teil des öffentlichen Vertrags, wie werden sie versioniert, validiert und abgesichert?

Senior-Antwort:

„Polymorphie im JSON-Vertrag braucht eine Whitelist, keinen freien Klassenlader.“

Häufige Fehler:
  • Java-Klassennamen als Type-Feld.
  • Beliebige Subtypen erlauben.
  • Unbekannte Typen still ignorieren.
  • Polymorphie ohne Versionierung.
  • Businesslogik in Deserializer legen.
  • Fehlende Validation pro Subtyp.
Best Practices:
  • Expliziten Discriminator verwenden.
  • Erlaubte Typen whitelisten.
  • Sealed Interfaces für erlaubte Subtypen nutzen.
  • Unbekannte Typen kontrolliert ablehnen.
  • Subtypen separat validieren.
  • Vertragsversionierung einplanen.
  • Keine Reflection-basierte freie Typinstanziierung.
  • Deserializer klein halten.
Stolperfalle:

Wenn ein JSON-Feld direkt einen Java-Klassennamen steuert, öffnet man eine gefährliche Kopplungs- und potenziell Sicherheitsfläche.

Merksatz:

Polymorphie braucht Discriminator und Grenzen.

B5-K05-Q007 – JSON-B und JSON-P: Binding vs ProcessingBinding für Modelle, Processing für Dokumente.
Frage:

„Wann verwenden Sie JSON-B und wann JSON-P?“

Kurz erklärt:

JSON-B bindet Java-Objekte an JSON. JSON-P verarbeitet JSON-Strukturen, Parser, Generatoren und Objektmodelle direkt.

Musterantwort:

JSON-B verwende ich, wenn ich einen stabilen Java-Typ habe: DTO, Command, Response, Event oder Konfigurationsmodell. Es ist ideal für objektorientiertes Binding. JSON-P verwende ich, wenn ich JSON strukturell bearbeiten muss, ohne unbedingt ein vollständiges Java-Modell zu haben: dynamische Payloads, Teiltransformationen, generische Enrichment-Pipelines, Streaming Parser oder sehr flexible Dokumente.

Die Jakarta-EE-Tutorial-Seite beschreibt JSON-B als Standard-Binding-Layer zwischen Java-Klassen und JSON-Dokumenten. Daraus folgt: Wenn der Java-Klassentyp Teil des Vertrags ist, passt JSON-B. Wenn die JSON-Struktur selbst im Vordergrund steht, passt JSON-P häufig besser.

Senior-Antwort:

„JSON-B ist für Verträge. JSON-P ist für JSON-Strukturarbeit. Ich wähle nach Stabilität des Modells.“

Häufige Fehler:
  • Dynamische JSON-Dokumente in starre DTOs pressen.
  • Stabile API-Verträge mit generischen Maps modellieren.
  • Riesige Payloads komplett materialisieren.
  • JSON-P verwenden, obwohl ein DTO klar wäre.
  • JSON-B verwenden, obwohl nur ein Feld gelesen werden muss.
  • Validierung bei dynamischen JSON-Strukturen vergessen.
Best Practices:
  • JSON-B für bekannte Verträge.
  • JSON-P für dynamische oder strukturelle Verarbeitung.
  • Streaming bei großen Payloads prüfen.
  • Hybrid-Ansätze bewusst einsetzen.
  • JSON-P-Transformationen klein halten.
  • DTOs nicht durch Map<String,Object> ersetzen, wenn Vertrag stabil ist.
  • Dynamische Payloads schema-validieren.
  • Performance mit echten Payloadgrößen messen.
Stolperfalle:

Map<String,Object> wirkt flexibel, ersetzt aber weder ein sauberes Schema noch eine klare Validierungsstrategie.

Merksatz:

Binding für Modelle, Processing für Dokumente.

B5-K05-Q008 – JAX-RS-Integration, DTO-Grenzen und API-VerträgeJSON-B serialisiert DTOs; APIs veröffentlichen Verträge.
Frage:

„Wie integrieren Sie JSON-B sauber in JAX-RS APIs?“

Kurz erklärt:

In JAX-RS wird JSON-B häufig automatisch als Body-Binding für JSON-DTOs genutzt. DTOs bilden den externen Vertrag, Use-Case-Commands das interne Modell.

Musterantwort:

Ich lasse JAX-RS JSON-Request- und Response-Bodies in DTOs binden und mappe diese DTOs anschließend auf Use-Case-Commands oder Response-Modelle. JSON-B-Annotationen gehören auf API-DTOs, nicht auf Domain Entities. Bean Validation prüft Eingaben an der Boundary. ExceptionMapper übersetzen Fehler in konsistente Problem-Responses.

Die Jakarta-EE-Tutorial-Seite beschreibt JSON-B als Standard-Binding-Schicht zwischen Java-Klassen und JSON-Dokumenten und nennt Yasson als eine Referenzimplementierung, die im GlassFish-Umfeld enthalten ist. In JAX-RS ist diese Binding-Schicht genau der Übergang zwischen HTTP-Body und Java-DTO.

Senior-Antwort:

„JSON-B sitzt an der API-Grenze. Meine Domain muss nicht wissen, wie ein Client ein Feld nennt.“

Häufige Fehler:
  • JPA Entities als JSON Bodies.
  • Domain-Objekte mit API-spezifischen JSON-Annotationen verschmutzen.
  • Header und Body-Semantik vermischen.
  • Keine Boundary-Validation.
  • Response-DTOs zu groß machen.
  • JSON-B-Konfiguration nicht in Integrationstests prüfen.
Best Practices:
  • DTOs als JSON-Verträge.
  • JSON-B-Annotationen auf DTOs.
  • Bean Validation an Request-DTOs.
  • Mapping zu Use-Case-Commands.
  • Entities niemals direkt als Response.
  • Contract Tests mit echten JSON-Beispielen.
  • Zentrale JSON-B-Konfiguration.
  • API-Versionen und DTO-Versionen bewusst verwalten.
Stolperfalle:

Wenn JPA Entities direkt serialisiert werden, wird jede Entity-Änderung potenziell zum API-Breaking-Change.

Merksatz:

JSON-B serialisiert DTOs; APIs veröffentlichen Verträge.

B5-K05-Q009 – Versionierung, Schema-Evolution und Event-KompatibilitätJSON-Verträge leben länger als Java-Refactorings.
Frage:

„Wie versionieren Sie JSON-B-basierte API- oder Event-Verträge?“

Kurz erklärt:

JSON-Verträge entwickeln sich. Versionierung und kompatible Änderungen müssen bei APIs und Events bewusst geplant werden.

Musterantwort:

Ich unterscheide API-Versionierung und Event-Schema-Evolution. Bei APIs kann Versionierung über Pfad, Header oder Media Type erfolgen. Bei Events nutze ich oft event_type und schema_version . Änderungen sollten möglichst backward- und forward-kompatibel sein: neue optionale Felder hinzufügen, alte Felder nicht sofort entfernen, Bedeutung bestehender Felder nicht ändern, Enums vorsichtig erweitern und Defaults für neue Felder definieren.

JSON-B ist nur das Binding-Werkzeug. Kompatibilität entsteht durch Vertragsdesign, Tests, Consumer-Kommunikation und Schema-Governance. JSON-B-DTOs oder Records sollten deshalb nicht nur technisch, sondern als langlebige öffentliche Dokumente behandelt werden.

Senior-Antwort:

„JSON ist tolerant, aber Verträge sind es nicht automatisch. Ich plane Evolution, statt auf Glück beim Deserialisieren zu hoffen.“

Häufige Fehler:
  • Neue Pflichtfelder ohne Default.
  • Bestehende Feldsemantik ändern.
  • Felder sofort entfernen.
  • Schema-Version vergessen.
  • Consumer-Kompatibilität nicht testen.
  • Java-Refactoring als JSON-Vertragsänderung übersehen.
  • Events wie interne DTOs behandeln.
Best Practices:
  • Verträge versionieren.
  • Neue Felder optional einführen.
  • Defaults definieren.
  • Alte Felder deprecaten, nicht sofort löschen.
  • Consumer-driven Contract Tests nutzen.
  • Event-Versionen dokumentieren.
  • Schema-Version im Event führen.
  • Legacy-Mapping zentralisieren.
Stolperfalle:

Ein neues Pflichtfeld in einem Record ist für alte JSON-Dokumente ein Breaking Change, auch wenn der Java-Code sauber kompiliert.

Merksatz:

JSON-Verträge leben länger als Java-Refactorings.

B5-K05-Q010 – Security, Performance und Anti-PatternsJSON-B veröffentlicht, was Ihr Modell preisgibt. 24h
Frage:

„Welche Sicherheits- und Performance-Fallen gibt es bei JSON-B?“

Kurz erklärt:

JSON-B muss sicher und bewusst eingesetzt werden: keine sensiblen Felder serialisieren, keine unvalidierten Payloads vertrauen, keine riesigen Objektgraphen, keine Entities direkt und keine unkontrollierte Polymorphie.

Musterantwort:

Sicherheitsfallen sind: sensible Felder versehentlich serialisieren, interne Entities veröffentlichen, unvalidierte JSON-Payloads direkt in Fachlogik geben, polymorphe Typen unkontrolliert deserialisieren, Fehlerdetails oder Secrets ausgeben und Logs mit vollständigen Payloads schreiben. Performance-Fallen sind: riesige Objektgraphen, zyklische Strukturen, Entity-Lazy-Loading während Serialization, große Arrays im Speicher, teure Adapter und pro Request erzeugte Mapper.

JSON-B bietet Binding, aber es prüft nicht automatisch fachliche Berechtigungen, Datenminimierung oder API-Kompatibilität. Diese Verantwortung liegt in Architektur, DTO-Design, Validation und Tests.

Senior-Antwort:

„Serialization ist eine Veröffentlichungsgrenze. Was JSON-B sieht, kann ein Client sehen. Deshalb muss das DTO bewusst minimal sein.“

Häufige Fehler:
  • Secrets in DTOs.
  • @JsonbTransient als alleiniger Schutz.
  • Vollständige Request-/Response-Payloads mit PII loggen.
  • Unbegrenzte JSON-Arrays akzeptieren.
  • JPA Lazy Loading während JSON-Ausgabe.
  • Keine Bean Validation nach fromJson .
  • Provider-Defaults ungetestet lassen.
Best Practices:
  • DTOs minimal halten.
  • Sensitive Daten gar nicht ins Response-DTO aufnehmen.
  • Bean Validation nach Deserialisierung.
  • Payload-Größen begrenzen.
  • Pagination und Streaming nutzen.
  • Keine Entities direkt serialisieren.
  • Polymorphie whitelisten.
  • JSON-Verträge mit Snapshot-/Contract-Tests prüfen.
Stolperfalle:

@JsonbTransient ist eine gute Absicherung, aber die bessere Lösung ist, sensible Daten gar nicht in ein externes DTO aufzunehmen.

Merksatz:

JSON-B veröffentlicht, was Ihr Modell preisgibt.

B5-K06 – Kapitel 6: JSON-P / Jakarta JSON Processing 10 Karten

B5-K06-Q001 – Grundmodell: Object Model API vs Streaming APIObject Model materialisiert JSON; Streaming liest JSON als Ereignisfluss. 24h
Frage:

„Was ist der Unterschied zwischen JSON-P Object Model API und Streaming API?“

Kurz erklärt:

JSON-P bietet zwei Hauptmodelle: Object Model API für baumartige JSON-Strukturen und Streaming API für ereignisbasiertes, speicherschonendes Lesen oder Schreiben.

Musterantwort:

Die Object Model API lädt ein JSON-Dokument als Baumstruktur, zum Beispiel JsonObject , JsonArray , JsonString , JsonNumber und JsonValue . Das ist bequem, wenn das Dokument klein bis mittelgroß ist und man flexibel auf Felder zugreifen oder Strukturen transformieren möchte.

Die Streaming API arbeitet ereignisbasiert. Ein JsonParser liefert Events wie START_OBJECT , KEY_NAME , VALUE_STRING oder END_ARRAY . Dadurch muss nicht das gesamte Dokument im Speicher gehalten werden. Das ist besser für große Payloads, Logs, Exporte oder partielle Analyse. Die Jakarta-EE-Tutorial-Seite beschreibt JSON-P ausdrücklich als API zum Parsen, Transformieren und Abfragen von JSON-Daten mit Object Model oder Streaming Model.

Senior-Antwort:

„JSON-B bindet Verträge an Java-Typen. JSON-P verarbeitet JSON als Dokument. Ich wähle nach Stabilität, Größe und Dynamik des Payloads.“

Häufige Fehler:
  • JSON-P verwenden, obwohl ein stabiles DTO besser wäre.
  • Große JSON-Dateien komplett als JsonObject laden.
  • Streaming Parser ohne saubere Zustandslogik verwenden.
  • JSON-P-Strukturen in Domain-Code leaken.
  • JSON-B und JSON-P semantisch verwechseln.
  • Dynamische Payloads ohne Validierung akzeptieren.
Best Practices:
  • JSON-B für stabile Java-Verträge.
  • JSON-P für strukturelle oder dynamische JSON-Verarbeitung.
  • Object Model für kleine bis mittlere Dokumente.
  • Streaming für große oder partielle Verarbeitung.
  • JSON-P an Infrastrukturgrenzen kapseln.
  • Dynamische Payloads validieren.
  • Parser-Logik gut testen.
  • Speicherverbrauch mit echten Payloads messen.
Stolperfalle:

Ein JsonObject ist bequem, aber bei großen Payloads kann es denselben Speicherfehler verursachen wie ein zu großes DTO.

Merksatz:

Object Model materialisiert JSON; Streaming liest JSON als Ereignisfluss.

B5-K06-Q002 – JsonObject , JsonArray , JsonValue und TypmodellJSON-P modelliert JSON-Typen, nicht Java-Domänen.
Frage:

„Wie funktioniert das JSON-P-Typmodell?“

Kurz erklärt:

JSON-P modelliert JSON über JsonValue und konkrete Subtypen wie JsonObject , JsonArray , JsonString , JsonNumber , JsonValue.TRUE , JsonValue.FALSE und JsonValue.NULL .

Musterantwort:

Das zentrale Interface ist JsonValue . Es repräsentiert jeden JSON-Wert. Konkrete Strukturen sind JsonObject für Objekte, JsonArray für Arrays, JsonString für Strings und JsonNumber für Zahlen. Booleans und Null werden über spezielle JsonValue -Instanzen beziehungsweise Value Types repräsentiert. Das Typmodell entspricht direkt JSON, nicht einem Java-Domain-Modell.

Die API-Dokumentation beschreibt JSON-P als portable APIs für JSON über Streaming API oder Object Model API. Das Object Model ist deshalb besonders geeignet, wenn man JSON-Strukturen bewusst als Dokumente analysiert oder transformiert.

Senior-Antwort:

„JSON-P zwingt mich, die JSON-Struktur explizit zu behandeln. Das ist mehr Code, aber auch mehr Kontrolle.“

Häufige Fehler:
  • getString() ohne Existenzprüfung.
  • JSON-Null und fehlendes Feld verwechseln.
  • Zahlen ohne Range-/Precision-Prüfung lesen.
  • JsonObject wie mutable Map behandeln.
  • Dynamisches JSON ohne Schema validieren.
  • JSON-P-Strukturen direkt als Domain-Modell nutzen.
Best Practices:
  • Typen explizit prüfen.
  • Pflichtfelder zentral validieren.
  • Fehlende Felder und JSON-Null unterscheiden.
  • Zahlen präzise lesen.
  • JSON-P-Objekte an Infrastrukturgrenzen halten.
  • In fachliche DTOs oder Commands übersetzen.
  • Builder für neue Strukturen nutzen.
  • Tests für fehlerhafte Typen schreiben.
Stolperfalle:

Ein fehlendes Feld und ein vorhandenes Feld mit JSON- null sind nicht automatisch dasselbe.

Merksatz:

JSON-P modelliert JSON-Typen, nicht Java-Domänen.

B5-K06-Q003 – Builder API: JsonObjectBuilder , JsonArrayBuilder und TransformationJSON-P Builder erzeugen neue Dokumente, keine mutierten alten.
Frage:

„Wie transformieren Sie JSON-Dokumente mit JSON-P?“

Kurz erklärt:

JSON-P Builder erzeugen neue immutable JSON-Strukturen. Sie sind ideal für Enrichment, Masking, Wrapping und strukturelle Transformationen.

Musterantwort:

Da JsonObject und JsonArray immutable sind, transformiere ich JSON-P-Dokumente, indem ich neue Strukturen mit JsonObjectBuilder und JsonArrayBuilder aufbaue. Ich kopiere erlaubte Felder, füge Metadaten hinzu, entferne sensible Felder oder transformiere verschachtelte Strukturen rekursiv.

Das ist besonders nützlich in Integrationsplattformen: Man kennt das vollständige fachliche Modell nicht, muss aber Metadaten wie correlation_id , source_system oder received_at hinzufügen und sensible Felder entfernen.

Senior-Antwort:

„Mit JSON-P transformiere ich Dokumente, ohne so zu tun, als hätte jedes Dokument schon ein stabiles Domain-Modell.“

Häufige Fehler:
  • Bestehende JsonObject -Instanzen mutieren wollen.
  • Sensitive Felder nur auf oberster Ebene entfernen.
  • Vollständige Transformation riesiger Dokumente.
  • Masking-Regeln im Code verstreuen.
  • Null und fehlende Felder unsauber behandeln.
  • Transformation und fachliche Validierung vermischen.
Best Practices:
  • Neue Strukturen mit Buildern erzeugen.
  • Transformationen klein und deterministisch halten.
  • Sensitive-Feld-Regeln zentralisieren.
  • Verschachtelte Objekte und Arrays berücksichtigen.
  • Für große Dokumente Streaming prüfen.
  • Transformation und Fachlogik trennen.
  • Tests mit verschachtelten Payloads schreiben.
  • Auditierbarkeit von Masking-Regeln sicherstellen.
Stolperfalle:

Masking nur auf Top-Level-Felder anzuwenden reicht nicht. Secrets verstecken sich oft tief in verschachtelten Objekten oder Arrays.

Merksatz:

JSON-P Builder erzeugen neue Dokumente, keine mutierten alten.

B5-K06-Q004 – Streaming Parser: große Payloads und partielle ExtraktionStreaming verarbeitet JSON, während es gelesen wird.
Frage:

„Wann verwenden Sie JsonParser statt JsonReader ?“

Kurz erklärt:

Der Streaming Parser liest JSON ereignisbasiert und erlaubt partielle Verarbeitung ohne vollständige Materialisierung des Dokuments.

Musterantwort:

Ich verwende JsonParser , wenn das Dokument groß ist, wenn ich nur bestimmte Felder brauche oder wenn ich zeilen-/elementweise verarbeiten möchte. JsonReader liest eine vollständige Objekt- oder Arraystruktur. Die API-Dokumentation beschreibt JsonReader als Interface, das eine JSON-Objekt- oder Arraystruktur aus einer Input-Quelle liest. Für große Importe ist das oft zu speicherintensiv.

Mit JsonParser kann ich Events lesen und Rows an einen Consumer weitergeben. Das ist ideal für Imports, Exports, Logs, Integrationsdateien oder Validierung großer JSON-Arrays.

Senior-Antwort:

„Streaming ist kein Optimierungstrick. Es ist ein anderes Verarbeitungsmodell mit eigener Zustandslogik und Backpressure-Verantwortung.“

Häufige Fehler:
  • Parser-State zu simpel modellieren.
  • Verschachtelte Objekte falsch behandeln.
  • Große Daten trotz Streaming in Liste sammeln.
  • Consumer ohne Backpressure.
  • Reader/Parser nicht schließen.
  • Fehlerhafte Payloads ohne Kontextmeldung abbrechen.
Best Practices:
  • Streaming für große Payloads.
  • Parser-State explizit modellieren.
  • Rows direkt weiterverarbeiten oder batchen.
  • Downstream-Backpressure einplanen.
  • Ressourcen schließen.
  • Fehlermeldungen mit Pfad/Position ergänzen, wenn möglich.
  • Tests mit verschachtelten und fehlerhaften Dokumenten.
  • Object Model nur für überschaubare Dokumente nutzen.
Stolperfalle:

Streaming spart nur Speicher, wenn man die gelesenen Elemente nicht danach doch wieder vollständig in einer Liste sammelt.

Merksatz:

Streaming verarbeitet JSON, während es gelesen wird.

B5-K06-Q005 – Streaming Generator: große JSON-Ausgaben und kontrolliertes SchreibenJsonGenerator schreibt JSON, ohne JSON vollständig zu besitzen.
Frage:

„Wann verwenden Sie JsonGenerator ?“

Kurz erklärt:

JsonGenerator schreibt JSON tokenweise in einen OutputStream oder Writer. Dadurch können große JSON-Ausgaben ohne vollständigen Objektgraph erzeugt werden.

Musterantwort:

Ich verwende JsonGenerator , wenn ich große JSON-Ausgaben kontrolliert und speicherschonend schreiben will. Statt eine riesige Liste von DTOs zu bauen und anschließend zu serialisieren, schreibe ich Objekte sequentiell in den OutputStream. Das passt für Exporte, Reports, Datenfeeds oder Gateway-Streaming.

Die API-Dokumentation beschreibt JSON-P als portable APIs zum Parsen, Generieren, Transformieren und Abfragen von JSON über Streaming API oder Object Model API. JsonGenerator ist der Schreibteil der Streaming API.

Senior-Antwort:

„Ein Generator ist mein Werkzeug, wenn JSON-Ausgabe ein Stream ist und kein Objektgraph.“

Häufige Fehler:
  • writeEnd() vergessen.
  • Große Daten vorher trotzdem in Liste laden.
  • Fehlerbody senden wollen, nachdem Response schon teilweise geschrieben wurde.
  • OutputStream nicht schließen oder flushen.
  • Pretty Printing für große Exporte unnötig aktivieren.
  • Domain-Objekte direkt im Generator verstreut mappen.
Best Practices:
  • Generator für große Outputs.
  • Struktur mit Tests prüfen.
  • Mapping-Code kapseln.
  • Ressourcen schließen.
  • Fehler vor Start validieren, soweit möglich.
  • Downstream-Abbrüche behandeln.
  • Pretty Printing nur für Debug oder kleine Dokumente.
  • JAX-RS StreamingOutput für HTTP integrieren.
Stolperfalle:

Sobald Teile der Response geschrieben sind, kann man viele HTTP-Fehler nicht mehr sauber als strukturierten Fehlerbody zurückgeben.

Merksatz:

JsonGenerator schreibt JSON, ohne JSON vollständig zu besitzen.

B5-K06-Q006 – JSON Patch und JSON Merge PatchPATCH ändert Dokumente; die Domain entscheidet, ob die Änderung erlaubt ist.
Frage:

„Was ist der Unterschied zwischen JSON Patch und JSON Merge Patch?“

Kurz erklärt:

JSON Patch beschreibt konkrete Operationen. JSON Merge Patch beschreibt ein Ziel-Dokumentfragment, bei dem null typischerweise Entfernung bedeutet.

Musterantwort:

JSON Patch ist eine operationsbasierte Änderungsliste. Operationen wie add , remove , replace , move , copy und test beschreiben präzise Änderungen an JSON-Pointern. JSON Merge Patch ist dokumentorientierter: Ein Patch-Dokument wird mit dem Ziel gemerged; Felder mit null bedeuten typischerweise Entfernen.

JSON-P bietet APIs für Patch und Merge Patch. Das ist nützlich für HTTP-PATCH-Endpunkte, dynamische Dokumente und partielle Updates. Wichtig ist aber, Patch nicht blind auf Domain-Zustand anzuwenden. Nach dem Patch müssen Validierung, Berechtigungen und fachliche Regeln greifen.

Senior-Antwort:

„Patch ist Dokumentänderung. Fachliche Änderung entsteht erst nach Validierung, Whitelist und Domain-Regeln.“

Häufige Fehler:
  • Patch direkt auf Entity anwenden.
  • Patch ohne Feld-Whitelist.
  • null -Semantik nicht dokumentieren.
  • Patch-Ergebnis nicht validieren.
  • Array-Operationen unterschätzen.
  • Beliebig große Patch-Requests erlauben.
  • Berechtigungen pro Feld ignorieren.
Best Practices:
  • Patch-Format bewusst wählen.
  • Patch-Größe und Operationen limitieren.
  • Patchbare Felder whitelisten.
  • Ergebnis validieren.
  • Fachliche Regeln nach Patch anwenden.
  • Berechtigungen pro sensiblen Feldern prüfen.
  • Patch-Fehler strukturiert zurückgeben.
  • Tests für remove, replace, null und Arrays.
Stolperfalle:

Ein JSON Merge Patch mit "field": null bedeutet oft Entfernen, nicht einfach „Wert ist null“. Das muss API-weit klar sein.

Merksatz:

PATCH ändert Dokumente; die Domain entscheidet, ob die Änderung erlaubt ist.

B5-K06-Q007 – JSON Pointer, Navigation und gezielte ExtraktionJSON Pointer findet Pfade, nicht Bedeutung.
Frage:

„Wofür verwenden Sie JSON Pointer in JSON-P?“

Kurz erklärt:

JSON Pointer adressiert Werte innerhalb eines JSON-Dokuments über Pfadausdrücke wie /customer/id oder /items/0/sku .

Musterantwort:

JSON Pointer verwende ich, wenn ich gezielt Werte aus dynamischen JSON-Dokumenten extrahieren oder ändern möchte, ohne ein komplettes Java-Modell zu definieren. Typische Beispiele sind Routing Keys, Mandanten-IDs, Correlation-Felder, Audit-Metadaten oder Validierungsregeln.

JSON Pointer ist besonders nützlich in Integrations- und Gateway-Komponenten. Man kann konfigurieren: „Nimm den Wert unter /tenant_id als Routing Key.“ Wichtig ist, Pointer-Ausdrücke zu validieren und fehlende oder falsch typisierte Werte kontrolliert zu behandeln.

Senior-Antwort:

„JSON Pointer ist ein präzises Skalpell für bekannte Pfade in dynamischen Dokumenten. Es ist keine allgemeine Suchmaschine.“

Häufige Fehler:
  • Pointer als komplexe Query-Sprache missbrauchen.
  • Fehlende Pfade als Fehler statt optional behandeln, obwohl optional.
  • Typen nicht prüfen.
  • Pointer-Ausdrücke ungeprüft aus externen Quellen akzeptieren.
  • Pointer auf riesigen Dokumenten nach vollständiger Materialisierung nutzen.
  • Stringwerte über toString() statt sauberem JsonString lesen.
Best Practices:
  • Pointer für gezielte, stabile Pfade.
  • Fehlende Werte kontrolliert behandeln.
  • Typen explizit prüfen.
  • Konfigurierte Pointer validieren.
  • Routing-/Audit-Regeln dokumentieren.
  • Für große Dokumente Streaming prüfen.
  • Keine freien Pointer von untrusted Clients erlauben.
  • Tests für Arrays und escaped Pointer-Segmente.
Stolperfalle:

JsonValue.toString() liefert JSON-Repräsentation, nicht immer den nackten Java-String. Für Strings sollte man gezielt JsonString verwenden.

Merksatz:

JSON Pointer findet Pfade, nicht Bedeutung.

B5-K06-Q008 – Validierung, Fehlerbehandlung und robuste DokumentgrenzenParsing ist Lesen; Validierung ist Vertrauen verdienen.
Frage:

„Wie validieren Sie JSON-P-Dokumente robust?“

Kurz erklärt:

Dynamische JSON-P-Dokumente brauchen explizite Syntax-, Struktur-, Typ- und Fachvalidierung. Parsing allein bedeutet nicht Gültigkeit.

Musterantwort:

Ich validiere in Schichten. Zuerst Syntax: Ist es gültiges JSON? Dann Struktur: Ist es ein Objekt oder Array, wie erwartet? Dann Typen: Sind Pflichtfelder Strings, Zahlen, Booleans oder Objekte? Danach fachliche Regeln: erlaubte Eventtypen, Feldlängen, Wertebereiche, Tenant-Regeln und Berechtigungen.

JSON-P gibt mir Kontrolle, aber keine automatische Fachvalidierung. Ein erfolgreich geparstes JsonObject ist nur syntaktisch und strukturell JSON, noch kein gültiges Business-Dokument.

Senior-Antwort:

„Ein geparstes JSON-Dokument ist nur syntaktisch gültig. Fachlich gültig wird es erst durch Validierung.“

Häufige Fehler:
  • Erfolgreiches Parsing als Validierung betrachten.
  • Pflichtfelder nicht prüfen.
  • Typen implizit casten.
  • Fehlerdetails unstrukturiert zurückgeben.
  • Payload-Limits vergessen.
  • Dynamische Events ohne Schema-Governance.
  • Validierung erst nach Persistenz oder Publishing.
Best Practices:
  • Syntax, Struktur, Typen und Fachregeln trennen.
  • Pflichtfelder explizit prüfen.
  • Payload-Größe und Tiefe limitieren.
  • Validierungsfehler strukturiert sammeln.
  • Fehler nicht als Stacktrace ausgeben.
  • JSON Schema für externe Verträge prüfen.
  • Vor Persistenz und Publishing validieren.
  • Tests mit ungültigen Dokumenten schreiben.
Stolperfalle:

Dynamische JSON-Payloads direkt in Kafka zu publizieren, verschiebt fehlerhafte Daten an alle Consumer.

Merksatz:

Parsing ist Lesen; Validierung ist Vertrauen verdienen.

B5-K06-Q009 – JSON-P in JAX-RS, JSON-B und Event-PipelinesJSON-P ist stark an dynamischen Systemgrenzen.
Frage:

„Wie kombinieren Sie JSON-P mit JAX-RS und JSON-B?“

Kurz erklärt:

JSON-P integriert sich gut in JAX-RS für dynamische JSON-Payloads und in Event-Pipelines für Enrichment, Routing, Masking und Validierung.

Musterantwort:

In JAX-RS verwende ich JSON-B für stabile Request- und Response-DTOs. JSON-P verwende ich, wenn der Body dynamisch ist, zum Beispiel Webhooks, generische Event-Endpunkte oder Payload-Enrichment. Eine Resource kann rohes JSON lesen, als JsonObject parsen, validieren, mit Metadaten anreichern und dann an eine Event-Pipeline übergeben.

JSON-P und JSON-B schließen sich nicht aus. Ein typischer Hybrid ist: JSON-P liest einen dynamischen Envelope oder extrahiert Metadaten, JSON-B bindet einen bekannten Teil des Payloads an ein DTO.

Senior-Antwort:

„JSON-P ist ideal am Rand dynamischer Payloads. Sobald ein stabiler Vertrag entsteht, mappe ich in typisierte Modelle.“

Häufige Fehler:
  • Dynamische Webhooks in starre DTOs zwingen.
  • Große Bodies als String materialisieren.
  • Enrichment ohne Validierung.
  • JSON-P-Dokumente direkt als Domain-Objekte behandeln.
  • Keine Payload-Limits.
  • Source-System und Correlation-ID nicht prüfen.
  • Raw Payloads mit PII unmaskiert loggen.
Best Practices:
  • JSON-B für stabile Verträge.
  • JSON-P für dynamische Bodies.
  • Reader/InputStream für große Payloads.
  • Envelope-Metadaten standardisieren.
  • Validierung vor Publishing.
  • Routing Key kontrolliert extrahieren.
  • Payload-Logging maskieren.
  • Hybrid-Ansätze gezielt einsetzen.
Stolperfalle:

Ein generischer Webhook-Endpunkt ohne Validierung wird schnell zur Müllschleuse für alle nachgelagerten Systeme.

Merksatz:

JSON-P ist stark an dynamischen Systemgrenzen.

B5-K06-Q010 – Security, Performance und Anti-PatternsDynamisches JSON braucht klare Grenzen. 24h
Frage:

„Welche Security- und Performance-Anti-Patterns gibt es bei JSON-P?“

Kurz erklärt:

JSON-P gibt hohe Kontrolle, aber auch Verantwortung: Payload-Limits, Tiefe, Typprüfung, Masking, Validierung, Streaming und sichere Fehlerbehandlung müssen explizit geplant werden.

Musterantwort:

Security-Anti-Patterns sind: beliebige große Payloads akzeptieren, dynamische JSON-Dokumente ohne Validierung weiterleiten, sensitive Daten unmaskiert loggen, JSON Pointer oder Patch-Ausdrücke ungeprüft von Clients übernehmen, Felder ohne Typprüfung lesen und Raw Payloads langfristig unverschlüsselt speichern.

Performance-Anti-Patterns sind: riesige Dokumente als Object Model materialisieren, Streaming verwenden und danach doch alles sammeln, rekursive Transformationen ohne Tiefenlimit, keine Payload-Größenlimits und Parser/Reader nicht schließen. JSON-P ist mächtig, aber es schützt nicht automatisch vor bösartigen oder fehlerhaften Dokumenten.

Senior-Antwort:

„JSON-P gibt mir Kontrolle über Dokumente. Diese Kontrolle muss ich in Limits, Validierung, Masking und Streaming übersetzen.“

Häufige Fehler:
  • Keine Request-Size-Limits.
  • Raw JSON vollständig loggen.
  • Untrusted JSON Pointer erlauben.
  • Patch ohne Whitelist.
  • Object Model für riesige Dokumente.
  • Parser nicht schließen.
  • Validierung nach Publishing statt davor.
  • Sensitive Daten in Dead-Letter-Stores unverschlüsselt halten.
Best Practices:
  • Payload-Größe limitieren.
  • Tiefe und Array-Größe kontrollieren.
  • Streaming für große Payloads.
  • Raw Payloads maskieren oder verschlüsseln.
  • Sensitive Felder rekursiv entfernen.
  • JSON Pointer und Patch whitelisten.
  • Validierung vor Persistenz und Publishing.
  • Fehler strukturiert zurückgeben.
Stolperfalle:

Dynamische Payloads sind nicht automatisch harmlos, nur weil sie gültiges JSON sind.

Merksatz:

Dynamisches JSON braucht klare Grenzen.

B5-K07 – Kapitel 7: JMS / Jakarta Messaging 10 Karten

B5-K07-Q001 – Messaging-Grundmodell: Provider, ConnectionFactory, Destination, MessageMessaging ist Entkopplung mit Lieferverantwortung. 24h
Frage:

„Was sind die wichtigsten Konzepte in Jakarta Messaging?“

Kurz erklärt:

Jakarta Messaging abstrahiert Messaging Provider über zentrale Begriffe: ConnectionFactory , JMSContext , Destination , Queue , Topic , Message , JMSProducer und JMSConsumer .

Musterantwort:

Die wichtigsten Konzepte sind Messaging Provider, ConnectionFactory, Destination, Producer, Consumer, Message und Session beziehungsweise JMSContext . Der Provider ist das Messaging-System. Die ConnectionFactory erzeugt Verbindungen beziehungsweise JMSContext . Eine Destination ist das Ziel einer Nachricht, typischerweise Queue oder Topic. Producer senden Nachrichten, Consumer empfangen Nachrichten.

Eine Queue ist für Point-to-Point-Kommunikation gedacht: Eine Nachricht wird typischerweise von einem Consumer verarbeitet. Ein Topic ist für Publish/Subscribe gedacht: Mehrere Subscriber können dieselbe Nachricht erhalten. Jakarta Messaging beschreibt genau diesen Mechanismus für lose gekoppelte, zuverlässige, asynchrone Kommunikation.

Senior-Antwort:

„Jakarta Messaging entkoppelt Zeit und Sender/Empfänger. Es entkoppelt aber nicht Verantwortung für Vertrag, Idempotenz und Betrieb.“

Häufige Fehler:
  • Messaging als Ersatz für klares Domain-Design verwenden.
  • Queue und Topic verwechseln.
  • Nachrichten ohne Eventtyp oder Version senden.
  • Payload und Message Properties unstrukturiert mischen.
  • Keine Dead-Letter-Strategie.
  • Keine Idempotenz bei Consumer-Verarbeitung.
  • Broker-Backlog nicht überwachen.
Best Practices:
  • Queue für Work Distribution.
  • Topic für Publish/Subscribe.
  • Message Payload versionieren.
  • Wichtige Metadaten als Properties setzen.
  • Consumer idempotent bauen.
  • Dead-Letter-Queue definieren.
  • Backlog und Redelivery überwachen.
  • Nachrichtengröße begrenzen.
Stolperfalle:

Eine Queue macht eine langsame Verarbeitung nicht schneller. Sie macht sie nur pufferbar und messbar.

Merksatz:

Messaging ist Entkopplung mit Lieferverantwortung.

B5-K07-Q002 – Queue, Topic, Durable Subscription und Publish/SubscribeQueue ist Auftrag; Topic ist Ereignis.
Frage:

„Was ist der Unterschied zwischen Queue und Topic, und wann brauchen Sie Durable Subscriptions?“

Kurz erklärt:

Queues verteilen Arbeit typischerweise an einen Consumer. Topics veröffentlichen Nachrichten an Subscriber. Durable Subscriptions erlauben, Topic-Nachrichten auch bei temporär inaktiven Subscriber-Instanzen zu erhalten.

Musterantwort:

Eine Queue nutze ich, wenn eine Arbeitseinheit genau einmal von einem Consumer aus einer Consumer-Gruppe verarbeitet werden soll, zum Beispiel „versende diese E-Mail“ oder „verarbeite diesen Export“. Ein Topic nutze ich, wenn mehrere unabhängige Interessenten dasselbe Ereignis erhalten sollen, etwa Notification, CRM und Analytics bei CustomerRegistered .

Durable Subscriptions sind relevant, wenn ein Subscriber auf einem Topic Nachrichten nicht verlieren soll, während er nicht aktiv verbunden ist. Ohne Durable Subscription erhält ein temporär abwesender Subscriber die Nachricht nicht. Mit Durable Subscription verwaltet der Provider den Zustand des Abonnements.

Senior-Antwort:

„Queues verteilen Arbeit. Topics verteilen Wissen über etwas, das passiert ist.“

Häufige Fehler:
  • Events über Queue an nur einen Consumer schicken.
  • Commands über Topic broadcasten.
  • Durable Subscriptions ohne Lifecycle-Governance.
  • Subscriber-Namen nicht stabil halten.
  • Topic als globale Event-Müllhalde verwenden.
  • Keine Versionierung bei Topic-Events.
  • Consumer-Abwesenheit nicht berücksichtigen.
Best Practices:
  • Queue für Jobs und Commands.
  • Topic für Domain Events.
  • Durable Subscription für zuverlässige Topic-Zustellung.
  • Subscription-Namen stabil und dokumentiert halten.
  • Eventtypen und Versionen setzen.
  • Consumer unabhängig und idempotent bauen.
  • Topic-Governance etablieren.
  • Backlog pro Subscriber überwachen.
Stolperfalle:

Ein Topic ohne durable Subscription kann für temporär inaktive Subscriber wie ein flüchtiger Broadcast wirken.

Merksatz:

Queue ist Auftrag; Topic ist Ereignis.

B5-K07-Q003 – Message Types, Properties, Headers und Payload DesignEine Message ist mehr als Payload: Headers und Properties sind Betriebsvertrag.
Frage:

„Wie gestalten Sie Message Payloads und Properties in Jakarta Messaging?“

Kurz erklärt:

Jakarta Messaging Messages bestehen aus Headers, Properties und Body. Für Enterprise-Payloads sind TextMessage mit JSON, Properties für Routing/Filtering und klare Versionierung meist sinnvoll.

Musterantwort:

Ich trenne Payload und Metadaten. Der Body enthält das fachliche Ereignis oder Command, häufig als JSON-Text. Properties enthalten technische oder routingrelevante Metadaten wie event_type , schema_version , aggregate_id , correlation_id oder tenant_id . Headers wie Message ID, Correlation ID, Delivery Mode, Expiration und Priority gehören zur Messaging-Infrastruktur.

Ich vermeide ObjectMessage für externe oder langfristige Verträge, weil es Java-Serialisierung, Klassenkopplung und Sicherheitsprobleme begünstigt. TextMessage mit JSON oder BytesMessage mit einem klaren Format ist meist portabler.

Senior-Antwort:

„Der Body trägt Fachlichkeit. Properties tragen Routing- und Betriebsmetadaten. Beides ist Vertrag.“

Häufige Fehler:
  • Java-Objekte per ObjectMessage über Servicegrenzen senden.
  • Routinginformationen nur im Payload verstecken.
  • Große Daten in Message Properties legen.
  • Keine Schema-Version.
  • Correlation-ID vergessen.
  • Payload nicht validieren.
  • Message Body als zufälligen String ohne Vertrag behandeln.
Best Practices:
  • JSON-Text oder klar definiertes Binärformat verwenden.
  • Properties für Routing und Filter.
  • Payload versionieren.
  • Correlation-ID setzen.
  • Message-Größe begrenzen.
  • ObjectMessage vermeiden.
  • Delivery Mode bewusst wählen.
  • TTL für veraltbare Nachrichten setzen.
Stolperfalle:

ObjectMessage wirkt bequem, koppelt Producer und Consumer aber an Java-Klassen und Serialisierung. Das ist für langlebige Integration meist ungeeignet.

Merksatz:

Eine Message ist mehr als Payload: Headers und Properties sind Betriebsvertrag.

B5-K07-Q004 – Producer, Consumer, Acknowledgement und RedeliveryZustellung ist Brokerarbeit; Wirkungskorrektheit ist Anwendungssache.
Frage:

„Wie funktionieren Acknowledgement und Redelivery in Jakarta Messaging?“

Kurz erklärt:

Acknowledgement bestimmt, wann eine Message als erfolgreich konsumiert gilt. Fehler, fehlende Acknowledgements oder Rollbacks können Redelivery auslösen.

Musterantwort:

Eine Message gilt erst dann als erfolgreich verarbeitet, wenn sie bestätigt wurde. In containerverwalteten transaktionalen Szenarien geschieht das typischerweise mit dem Transaktionscommit. In nichttransaktionalen Szenarien können Acknowledgement-Modi wie Auto Acknowledge oder Client Acknowledge relevant sein.

Redelivery bedeutet, dass der Provider eine Nachricht erneut zustellt, wenn Verarbeitung nicht bestätigt wurde oder eine Transaktion rollbackt. Deshalb müssen Consumer idempotent sein. Ein Consumer darf nicht davon ausgehen, dass jede Message genau einmal im Code ankommt. „Exactly once“ erreicht man in der Praxis meist über idempotente Verarbeitung, dedizierte Message IDs und transaktionale Speicherung von Verarbeitungsergebnissen.

Senior-Antwort:

„Ich plane Redelivery ein. Ein Consumer ist erst robust, wenn dieselbe Message mehrfach ankommen darf.“

Häufige Fehler:
  • Consumer nicht idempotent.
  • Acknowledge vor erfolgreicher Verarbeitung.
  • Redelivery als Fehler statt Normalfall betrachten.
  • Keine maximale Redelivery-Strategie.
  • Keine DLQ.
  • Seiteneffekte außerhalb Transaktion.
  • Poison Messages blockieren Queue.
Best Practices:
  • Consumer idempotent implementieren.
  • Acknowledge nach erfolgreicher Verarbeitung.
  • Transaktionale Verarbeitung bevorzugen.
  • Redelivery-Zähler überwachen.
  • Backoff und Max Redelivery konfigurieren.
  • Poison Messages in DLQ verschieben.
  • Deduplikationsschlüssel speichern.
  • Fachliche Operationen wiederholbar machen.
Stolperfalle:

Ein erfolgreich ausgeführter Seiteneffekt plus fehlendes Acknowledgement ist der Klassiker für doppelte Verarbeitung.

Merksatz:

Zustellung ist Brokerarbeit; Wirkungskorrektheit ist Anwendungssache.

B5-K07-Q005 – Message-Driven Beans und Container-Managed ConsumptionMDBs konsumieren Nachrichten; Use Cases verarbeiten Bedeutung.
Frage:

„Was ist eine Message-Driven Bean, und wann verwenden Sie sie?“

Kurz erklärt:

Message-Driven Beans sind containerverwaltete asynchrone Message Consumer. Der Container übernimmt Instanzverwaltung, Listener-Aufruf, Transaktion, Security und Integration mit dem Messaging Provider.

Musterantwort:

Eine Message-Driven Bean ist eine Jakarta-EE-Komponente, die asynchron Nachrichten konsumiert. Sie implementiert typischerweise MessageListener und wird mit @MessageDriven und Activation Config konfiguriert. Ich verwende MDBs, wenn ich in einer Jakarta-EE-Runtime containerverwaltete JMS-Consumption möchte: Transaktionen, Pooling, Redelivery, Security, Resource-Management und Deployment-Konfiguration.

Die Jakarta-Messaging-Spezifikation weist darauf hin, dass Jakarta EE zusätzliche Messaging-Funktionen bereitstellt, insbesondere Message-Driven Beans und Jakarta Transactions. Das ist der große Vorteil in einer Full-Platform-Umgebung: Empfang und Transaktion werden vom Container integriert.

Senior-Antwort:

„Eine MDB ist mein Container-Managed Messaging-Adapter. Die eigentliche Fachverarbeitung bleibt in einem testbaren Use Case.“

Häufige Fehler:
  • Businesslogik direkt in onMessage überladen.
  • MDB-Parallelität erhöhen, ohne DB-Limits zu prüfen.
  • Keine Idempotenz im Processor.
  • Provider-spezifische Activation Config ungetestet lassen.
  • Message Payload nicht validieren.
  • Exceptions verschlucken und dadurch falsches Ack erzeugen.
  • Keine DLQ-Konfiguration.
Best Practices:
  • MDB als Messaging-Adapter behandeln.
  • Payload früh validieren.
  • Verarbeitung an Use Case delegieren.
  • Idempotenz im Processor sicherstellen.
  • Transaktionsverhalten testen.
  • Poolgröße und Downstream-Limits abstimmen.
  • Redelivery und DLQ konfigurieren.
  • Metriken pro MDB erfassen.
Stolperfalle:

Exceptions im MDB zu schlucken kann dazu führen, dass eine fehlerhafte Nachricht als erfolgreich verarbeitet gilt.

Merksatz:

MDBs konsumieren Nachrichten; Use Cases verarbeiten Bedeutung.

B5-K07-Q006 – Transaktionen, JTA, XA und OutboxDB-Commit und Message-Send brauchen eine Zuverlässigkeitsbrücke.
Frage:

„Wie kombinieren Sie Jakarta Messaging mit JTA und Datenbanktransaktionen?“

Kurz erklärt:

Jakarta Messaging kann mit JTA koordiniert werden. In modernen Architekturen ist Outbox häufig die robustere Alternative zu direktem DB-plus-Broker-XA.

Musterantwort:

Es gibt zwei wichtige Muster. Erstens: Der Messaging Provider nimmt als XA-fähige Ressource an derselben JTA-Transaktion teil wie die Datenbank. Dann koordiniert der Transaction Manager Commit oder Rollback über beide Ressourcen. Das liefert starke Atomizität, kostet aber XA-Komplexität, Recovery und Kopplung.

Zweitens: Das Outbox Pattern. Der Use Case schreibt fachliche Daten und eine Outbox-Nachricht in dieselbe lokale Datenbanktransaktion. Ein separater Publisher sendet danach an JMS. Diese Variante ist oft robuster, einfacher zu betreiben und passt besser zu serviceorientierten Architekturen. Jakarta EE ergänzt Messaging insbesondere durch Jakarta Transactions, wodurch beide Muster möglich sind.

Senior-Antwort:

„Ich sende nicht einfach nach dem Commit und hoffe. Entweder koordiniere ich Ressourcen bewusst über XA oder ich nutze eine persistente Outbox.“

Häufige Fehler:
  • JMS direkt nach DB-Commit senden ohne Outbox.
  • JMS vor DB-Commit senden.
  • XA annehmen, obwohl Ressourcen nicht XA-fähig sind.
  • Outbox ohne idempotentes Publishing.
  • Outbox ohne Retention.
  • Publisher-Fehler nicht überwachen.
  • Consumer nicht idempotent.
Best Practices:
  • Lokale Transaktion plus Outbox bevorzugt prüfen.
  • XA nur bei echtem Bedarf.
  • Outbox-Nachrichten versionieren.
  • Publisher idempotent bauen.
  • Published-Status und Retry-Zähler speichern.
  • DLQ für Publishing-Fehler definieren.
  • Consumer-Deduplikation ergänzen.
  • Transaktionsgrenzen dokumentieren.
Stolperfalle:

Ein erfolgreicher Datenbank-Commit plus Prozessabsturz vor Message-Send erzeugt verlorene Events, wenn keine Outbox existiert.

Merksatz:

DB-Commit und Message-Send brauchen eine Zuverlässigkeitsbrücke.

B5-K07-Q007 – Selectors, Filtering, Correlation und Request/ReplySelectors filtern Nachrichten; Correlation verbindet Gespräche.
Frage:

„Wie funktionieren JMS Selectors und Request/Reply?“

Kurz erklärt:

Selectors filtern Messages anhand von Properties. Correlation IDs verbinden Request und Reply. Request/Reply über Messaging ist möglich, sollte aber bewusst eingesetzt werden.

Musterantwort:

Selectors erlauben Consumer-seitiges Filtern anhand von Message Properties. Ein Consumer kann zum Beispiel nur Nachrichten mit event_type = 'PaymentCaptured' empfangen. Request/Reply verwendet typischerweise JMSReplyTo und JMSCorrelationID : Der Client sendet eine Anfrage, gibt eine Reply Destination an und wartet auf eine Antwort mit passender Correlation ID.

Ich setze Request/Reply über Messaging sparsam ein, weil es asynchrone Infrastruktur wieder synchron macht. Wenn der Aufrufer blockiert und auf Antwort wartet, brauche ich Timeouts, Korrelation, Fehlersemantik, Ressourcenlimits und Alternativen. Für echte asynchrone Prozesse ist ein Job- oder Eventmodell oft besser.

Senior-Antwort:

„Request/Reply über Messaging ist möglich, aber es macht aus asynchroner Infrastruktur wieder eine synchrone Abhängigkeit. Das muss begründet sein.“

Häufige Fehler:
  • Request/Reply ohne Timeout.
  • Correlation ID nicht setzen.
  • Komplexe Selectors ohne Monitoring.
  • Fachliche Langläufer als Request/Reply modellieren.
  • Temporary Queues leaken.
  • Selectors auf Payload-Felder erwarten.
  • Message Properties nicht konsistent typisieren.
Best Practices:
  • Selectors nur für stabile Properties.
  • Correlation ID konsequent setzen.
  • Request/Reply nur bei klarem Bedarf.
  • Timeouts immer definieren.
  • Fehlerantworten modellieren.
  • Ressourcen schließen.
  • Selector-Nutzung überwachen.
  • Lange Prozesse als Job/Event modellieren.
Stolperfalle:

Ein fehlender Timeout bei Request/Reply kann genauso gefährlich sein wie ein HTTP-Client ohne Read Timeout.

Merksatz:

Selectors filtern Nachrichten; Correlation verbindet Gespräche.

B5-K07-Q008 – Error Handling, Poison Messages, DLQ und Retry-StrategienRetry heilt temporäre Fehler, nicht falsche Nachrichten.
Frage:

„Wie behandeln Sie Fehler in JMS-Consumern professionell?“

Kurz erklärt:

Robustes Messaging braucht Fehlerklassifikation, Redelivery-Regeln, Dead-Letter-Queues, Poison-Message-Behandlung, Backoff und Observability.

Musterantwort:

Ich unterscheide retrybare und nicht retrybare Fehler. Retrybar sind temporäre Fehler wie Datenbank-Lock, Downstream-Timeout oder kurzzeitige Ressourcenprobleme. Nicht retrybar sind ungültige Payloads, unbekannte Schema-Versionen, fehlende Pflichtfelder oder fachlich unmögliche Commands. Retrybare Fehler dürfen Redelivery auslösen. Nicht retrybare Poison Messages sollten kontrolliert in eine DLQ oder Invalid-Message-Store gehen.

Wichtig ist, nicht unendlich dieselbe Nachricht zu redelivern. Es braucht maximale Zustellversuche, Backoff, DLQ, Alerting und Tools zur Analyse oder Reprocessing. Consumer müssen idempotent bleiben, weil Redelivery normal ist.

Senior-Antwort:

„Nicht jeder Fehler verdient einen Retry. Eine ungültige Nachricht wird durch Wiederholung nicht gültiger.“

Häufige Fehler:
  • Alle Fehler retryen.
  • Ungültige Payloads endlos redelivern.
  • Keine DLQ.
  • Keine Fehlerklassifikation.
  • Kein Backoff.
  • DLQ ohne Monitoring.
  • Reprocessing ohne Idempotenz.
  • Exceptions schlucken und falsches Ack erzeugen.
Best Practices:
  • Fehler früh klassifizieren.
  • Payload validieren.
  • Retrybare Fehler gezielt rollbacken.
  • Nicht retrybare Fehler isolieren.
  • Max Redelivery konfigurieren.
  • DLQ überwachen.
  • Backoff verwenden.
  • Reprocessing-Prozess definieren.
Stolperfalle:

Eine Poison Message ohne DLQ kann eine ganze Queue effektiv blockieren oder den Consumer dauerhaft belasten.

Merksatz:

Retry heilt temporäre Fehler, nicht falsche Nachrichten.

B5-K07-Q009 – Ordering, Parallelität, Throughput und BackpressureParallelität ist gut, bis sie fachliche Ordnung bricht.
Frage:

„Wie behandeln Sie Ordering und Skalierung bei JMS-Consumern?“

Kurz erklärt:

Messaging-Parallelität erhöht Durchsatz, kann aber Ordering-Annahmen brechen. Backpressure und Consumer-Limits verhindern, dass Downstreams überlastet werden.

Musterantwort:

Ich frage zuerst: Braucht das System globale Reihenfolge, Reihenfolge pro Aggregate oder gar keine Reihenfolge? Globale Reihenfolge ist teuer und reduziert Parallelität. Häufig reicht Reihenfolge pro Schlüssel, zum Beispiel pro account_id oder order_id . Wenn mehrere Consumer parallel arbeiten, kann Ordering verloren gehen, sofern der Provider oder die Architektur keine Schlüsselreihenfolge garantiert.

Backpressure ist genauso wichtig. Mehr Consumer erhöhen nicht automatisch Stabilität. Wenn die Datenbank, ein externer Provider oder ein Lock-Hotspot limitiert, erzeugt mehr Parallelität nur mehr Fehler, Redelivery und Deadlocks.

Senior-Antwort:

„Ordering ist teuer. Ich kaufe es nur dort, wo die Fachlichkeit es wirklich braucht — meist pro Aggregate, nicht global.“

Häufige Fehler:
  • Globale Reihenfolge annehmen.
  • Consumer-Pool erhöhen und Ordering brechen.
  • Hot Aggregates ignorieren.
  • Keine Backpressure.
  • Downstream-Limits nicht berücksichtigen.
  • Redelivery und Ordering nicht zusammen denken.
  • Queue-Länge ohne Verarbeitungszeit betrachten.
Best Practices:
  • Ordering-Anforderung fachlich klären.
  • Pro-Aggregate-Reihenfolge bevorzugen, wenn nötig.
  • Parallelität bewusst begrenzen.
  • Downstream-Limits beachten.
  • Backlog und Verarbeitungszeit messen.
  • Hotspots analysieren.
  • Redelivery-Effekte berücksichtigen.
  • Provider-spezifische Ordering-Features isolieren.
Stolperfalle:

Mehr Consumer können Durchsatz erhöhen, aber auch fachliche Reihenfolge, Locks und Datenbankstabilität zerstören.

Merksatz:

Parallelität ist gut, bis sie fachliche Ordnung bricht.

B5-K07-Q010 – Jakarta Messaging in Architektur: JMS vs Kafka, Integration und Anti-PatternsWähle Messaging nach Kommunikationsmodell, nicht nach Trend. 24h
Frage:

„Wann verwenden Sie Jakarta Messaging/JMS, und wann eher Kafka?“

Kurz erklärt:

Jakarta Messaging eignet sich stark für klassische Enterprise-Messaging-Integration, Queues, MDBs und Jakarta-EE-Transaktionen. Kafka eignet sich stärker für verteilte Event Logs, Replay, Stream Processing und hohe Event-Plattform-Skalierung.

Musterantwort:

Jakarta Messaging ist sinnvoll, wenn ich klassische Enterprise-Messaging-Semantik brauche: Queues, Topics, request/reply, JMS Provider, MDBs, Jakarta-EE-Integration, JTA-Koordination und standardisierte Java-API. Es passt gut für Work Queues, interne Commands, legacy-orientierte Integration und containerverwaltete Consumer.

Kafka ist eher ein verteiltes Commit Log und eine Event-Streaming-Plattform. Es ist stark bei Replay, langfristiger Retention, Consumer Groups, Partitionierung, Stream Processing und Event-Plattformen mit vielen unabhängigen Konsumenten. Jakarta Messaging und Kafka können beide asynchron sein, aber sie haben unterschiedliche Betriebs- und Architekturmodelle.

Senior-Antwort:

„JMS und Kafka lösen überlappende, aber nicht identische Probleme. Ich wähle nicht nach Hype, sondern nach Retention, Replay, Consumer-Modell und Betriebsgrenzen.“

Häufige Fehler:
  • Kafka nur verwenden, weil modern.
  • JMS nur verwenden, weil vorhanden.
  • Queue als Event-Historie missbrauchen.
  • Kafka als einfache Job Queue missverstehen.
  • Keine klare Retention-/Replay-Anforderung.
  • Message-Verträge technologieabhängig modellieren.
  • Broker-Operations ignorieren.
Best Practices:
  • Messaging-Technologie aus Anforderungen wählen.
  • Commands und Events unterscheiden.
  • Queue, Topic und Event Log klar abgrenzen.
  • Outbox für zuverlässige Veröffentlichung.
  • Message-Verträge technologiearm halten.
  • Idempotenz unabhängig von Technologie.
  • Monitoring und DLQ/Replay-Prozesse planen.
  • Provider-spezifische Features kapseln.
Stolperfalle:

Eine Queue ist keine Event-Historie. Wenn Consumer später replayen müssen, ist ein Queue-Modell oft die falsche Abstraktion.

Merksatz:

Wähle Messaging nach Kommunikationsmodell, nicht nach Trend.

B5-K08 – Kapitel 8: Bean Validation / Jakarta Validation 10 Karten

B5-K08-Q001 – Grundmodell: Constraints, Validator und ConstraintViolationConstraints prüfen Verträge; Domain-Code schützt Wahrheit. 24h
Frage:

„Was ist Jakarta Validation, und wie funktioniert das Grundmodell?“

Kurz erklärt:

Jakarta Validation prüft Constraints auf Objekten, Properties, Methodenparametern und Rückgabewerten. Das Ergebnis sind ConstraintViolation -Objekte mit Pfad, fehlerhaftem Wert, Message und Constraint-Metadaten.

Musterantwort:

Jakarta Validation definiert ein Standardmodell für deklarative Validierung. Constraints werden über Annotationen wie @NotNull , @NotBlank , @Email , @Size , @Min oder eigene Constraint-Annotationen an Properties, Feldern, Parametern, Return Values oder Typverwendungen angebracht. Ein Validator prüft ein Objekt und liefert eine Menge von ConstraintViolation .

Senior-relevant ist: Validation ist nicht automatisch Businesslogik. Jakarta Validation eignet sich hervorragend für strukturelle, syntaktische und lokale Invarianten. Komplexe fachliche Entscheidungen, die Datenbankzustand, externe Systeme oder Prozesszustand brauchen, gehören meist in Use Cases oder Domain Services. Die Spezifikation beschreibt Jakarta Validation als Metadatenmodell und API für JavaBean- und Method-Validation.

Senior-Antwort:

„Jakarta Validation ist meine deklarative Vertragsprüfung. Fachliche Wahrheit sichere ich zusätzlich dort ab, wo der Zustand wirklich geändert wird.“

Häufige Fehler:
  • Validation als Ersatz für Domain-Invarianten verwenden.
  • Custom Validatoren mit Datenbank- oder HTTP-Zugriff.
  • Constraint Violations in generische Fehlermeldungen verlieren.
  • @NotNull überall ohne fachliche Semantik.
  • DTOs und Entities mit denselben Constraints überladen.
  • Validation erst nach Persistenz ausführen.
Best Practices:
  • Strukturvalidierung an Systemgrenzen.
  • Domain-Invarianten zusätzlich im Domain-Modell schützen.
  • Constraint Violations strukturiert zurückgeben.
  • Custom Validators zustandslos halten.
  • Keine langsamen IO-Aufrufe in Constraints.
  • Constraint-Messages standardisieren.
  • Validierung früh im Request- oder Message-Pfad.
  • Tests für negative Fälle schreiben.
Stolperfalle:

Ein Constraint auf einem Request-DTO verhindert nicht, dass ein anderer Codepfad die Domain-Invariante verletzt.

Merksatz:

Constraints prüfen Verträge; Domain-Code schützt Wahrheit.

B5-K08-Q002 – Standard Constraints und semantische PräzisionWähle Constraints nach Bedeutung, nicht nach Bequemlichkeit.
Frage:

„Wie wählen Sie passende Standard Constraints aus?“

Kurz erklärt:

Standard Constraints decken viele technische und strukturelle Prüfungen ab. Entscheidend ist, die semantisch passende Annotation zu wählen.

Musterantwort:

Ich wähle Constraints nach fachlicher und technischer Semantik. @NotNull bedeutet nur: Der Wert darf nicht null sein. @NotBlank bedeutet bei Strings zusätzlich: nicht leer und nicht nur Whitespace. @Size prüft Länge oder Collection-Größe. @Positive , @Min , @Max , @DecimalMin , @Digits prüfen Zahlen. @Pattern sollte sparsam verwendet werden, weil Regex schnell zu versteckter Fachlogik wird.

Bei Geld verwende ich kein double , sondern BigDecimal oder Minor Units. Für BigDecimal sind @Digits und @DecimalMin häufig sinnvoll. Für Währung ist ein Value Object oft besser als nur @Pattern .

Senior-Antwort:

„Ich benutze Standard Constraints für Form und lokale Semantik. Sobald Kontext nötig ist, gehört die Regel in den Use Case oder die Domain.“

Häufige Fehler:
  • @NotNull auf Strings, obwohl blank ungültig ist.
  • Regex für komplexe Fachregeln.
  • Keine Längenlimits auf externen Strings.
  • Geld als double .
  • @Email als vollständige Identitätsprüfung missverstehen.
  • Standard Constraints auf Domain Entities überfrachten.
Best Practices:
  • Semantisch passende Constraint wählen.
  • Strings mit Länge begrenzen.
  • Regex einfach halten.
  • Geld präzise modellieren.
  • Formprüfung und Fachprüfung trennen.
  • Value Objects für wichtige Konzepte verwenden.
  • Negative Tests für Grenzwerte.
  • Validierungsmeldungen fachlich verständlich formulieren.
Stolperfalle:

@Pattern("[A-Z]{3}") sagt nur, dass ein String wie eine Währung aussieht. Es sagt nicht, dass die Währung im System erlaubt ist.

Merksatz:

Wähle Constraints nach Bedeutung, nicht nach Bequemlichkeit.

B5-K08-Q003 – Cascaded Validation und Objektgraphen@Valid öffnet den Objektgraphen; Limits halten ihn beherrschbar.
Frage:

„Wie validieren Sie verschachtelte Request-Objekte und Collections?“

Kurz erklärt:

@Valid löst Validierung verschachtelter Objekte aus. Container Element Constraints prüfen Elemente in Collections, Maps oder anderen Containern.

Musterantwort:

Für verschachtelte Objekte nutze ich @Valid . Dadurch wird nicht nur das äußere DTO validiert, sondern auch die enthaltenen Objekte. Bei Collections kombiniere ich Constraints auf die Collection selbst und auf die Elemente. Beispiel: @NotEmpty @Size(max = 200) List<@Valid OrderLineRequest> lines . So prüfe ich, dass die Liste existiert, nicht leer ist, nicht zu groß wird und jedes Element valide ist.

Wichtig ist, Objektgraphen nicht unkontrolliert tief oder groß zu validieren. Cascaded Validation ist mächtig, kann aber bei großen Graphen teuer werden.

Senior-Antwort:

„ @Valid ist der Schalter für Objektgraphen. Ohne ihn bleiben verschachtelte Constraints stumm.“

Häufige Fehler:
  • @Valid auf verschachtelten Objekten vergessen.
  • Collection selbst validieren, aber Elemente nicht.
  • Elemente validieren, aber Collection-Größe nicht begrenzen.
  • Riesige Objektgraphen unlimitiert validieren.
  • Violation Paths nicht sauber in API-Fehler mappen.
  • Entity-Graphen statt DTOs validieren.
Best Practices:
  • @Valid explizit an verschachtelten Objekten.
  • Collection-Größe begrenzen.
  • Element Constraints nutzen.
  • Fehlerpfade präzise zurückgeben.
  • DTO-Graphen kleiner halten als Domain-Graphen.
  • Batch-Validation für große Daten.
  • Negative Tests für verschachtelte Fehler.
  • Domain-Graph-Traversal bewusst vermeiden.
Stolperfalle:

List<@Valid Item> validiert Elemente, aber ohne @Size kann ein Client trotzdem eine millionengroße Liste senden.

Merksatz:

@Valid öffnet den Objektgraphen; Limits halten ihn beherrschbar.

B5-K08-Q004 – Validation Groups, Group Sequences und Use-Case-spezifische RegelnGroups variieren Regeln; getrennte DTOs variieren Modelle.
Frage:

„Wann verwenden Sie Validation Groups?“

Kurz erklärt:

Validation Groups erlauben, unterschiedliche Constraint-Sets für unterschiedliche Use Cases zu verwenden. Group Sequences definieren Prüf-Reihenfolgen.

Musterantwort:

Validation Groups nutze ich, wenn dasselbe Modell in unterschiedlichen Kontexten unterschiedliche Regeln hat, zum Beispiel Create vs Update. Bei Create muss accountId vielleicht fehlen, weil sie serverseitig erzeugt wird. Bei Update muss sie vorhanden sein. Groups ermöglichen, diese Regeln getrennt auszuführen.

Ich setze Groups sparsam ein. Wenn zu viele Use Cases in ein DTO gepresst werden, wird das Modell schwer verständlich. Manchmal sind getrennte DTOs für Create und Update besser als komplexe Groups.

Senior-Antwort:

„Groups sind nützlich, wenn sich Validierungsregeln leicht unterscheiden. Wenn das Modell selbst anders ist, baue ich lieber getrennte DTOs.“

Häufige Fehler:
  • Ein DTO mit zehn Groups überladen.
  • Groups als Business-Workflow missbrauchen.
  • Default Group vergessen.
  • Create/Update-Regeln schwer nachvollziehbar machen.
  • Group Sequence mit teurer Logik überfrachten.
  • Tests nur für Default Group schreiben.
Best Practices:
  • Groups sparsam verwenden.
  • Für stark unterschiedliche Use Cases getrennte DTOs bevorzugen.
  • Marker-Interfaces klar benennen.
  • Group-Sequenzen nur für echte Prüfphasen.
  • Negative Tests pro Group.
  • Gruppen nicht als Autorisierungsmechanismus nutzen.
  • Komplexität regelmäßig refaktorieren.
  • Dokumentieren, welcher Endpoint welche Group verwendet.
Stolperfalle:

Validation Groups können ein DTO so komplex machen, dass niemand mehr weiß, welche Regel wann gilt.

Merksatz:

Groups variieren Regeln; getrennte DTOs variieren Modelle.

B5-K08-Q005 – Method Validation: Parameter, Return Values und Cross-Parameter ConstraintsMethod Validation macht Methodenverträge ausführbar.
Frage:

„Was ist Method Validation, und wann ist sie sinnvoll?“

Kurz erklärt:

Method Validation prüft Parameter und Rückgabewerte von Methoden oder Konstruktoren. Sie macht Service-Verträge explizit.

Musterantwort:

Method Validation erlaubt Constraints auf Methodenparametern, Konstruktorparametern und Rückgabewerten. Sie ist sinnvoll an Service- oder Boundary-Grenzen, wo man Verträge explizit machen will: Parameter dürfen nicht null sein, Zahlen müssen positiv sein, Rückgabewerte müssen valide sein.

Cross-Parameter-Regeln, etwa checkOut > checkIn , können als Custom Cross-Parameter Constraint modelliert werden. Ich prüfe aber, ob eine explizite Business-Methode verständlicher ist. Nicht jede fachliche Regel muss in eine Annotation.

Senior-Antwort:

„Method Validation ist ein Vertragsmechanismus. Sie ist stark an Grenzen, aber schwach als versteckte Businesslogik.“

Häufige Fehler:
  • Method Constraints auf private Methoden erwarten.
  • Self-Invocation übersehen.
  • Komplexe Businessregeln in Annotationen verstecken.
  • Return Value Validation mit großen Graphen.
  • Exceptions aus Validation nicht sauber mappen.
  • Method Validation ohne Containerintegration testen.
Best Practices:
  • Method Validation an Servicegrenzen.
  • Parameter-Constraints für einfache Verträge.
  • Return Value Validation sparsam.
  • Cross-Parameter Constraints nur bei klarer Wiederverwendbarkeit.
  • Businessregeln lesbar halten.
  • Aufruf über CDI/EJB-Proxy beachten.
  • Tests mit Container oder MethodValidator.
  • Validation Exceptions zentral mappen.
Stolperfalle:

Wenn eine validierte Methode innerhalb derselben Klasse direkt aufgerufen wird, läuft möglicherweise kein Interceptor und damit keine Method Validation.

Merksatz:

Method Validation macht Methodenverträge ausführbar.

B5-K08-Q006 – Custom Constraints und ConstraintValidatorCustom Constraints prüfen lokale Regeln, nicht verteilte Wahrheit.
Frage:

„Wie implementieren Sie einen eigenen Constraint, und worauf achten Sie dabei?“

Kurz erklärt:

Custom Constraints bestehen aus einer Constraint-Annotation und einem ConstraintValidator , der die eigentliche Prüfung ausführt.

Musterantwort:

Ein eigener Constraint besteht aus einer Annotation mit message , groups und payload sowie @Constraint(validatedBy = ...) . Der Validator implementiert ConstraintValidator<A, T> und prüft einen bestimmten Zieltyp. Wichtig ist: null sollte meist als valide gelten, wenn der Constraint nicht selbst @NotNull ersetzen soll. Dadurch kann man @NotNull separat kombinieren.

Custom Constraints sollten lokal, schnell und deterministisch sein. Ich vermeide Datenbank- oder Remote-Zugriffe im Validator. Wenn die Regel Zustand braucht, ist sie meist eine Use-Case- oder Domain-Regel.

Senior-Antwort:

„Ein Custom Constraint ist gut für lokale, wiederverwendbare Formregeln. Sobald Zustand oder Kontext nötig ist, verlasse ich die Annotation.“

Häufige Fehler:
  • null automatisch invalid machen und @NotNull duplizieren.
  • Datenbankzugriff im Validator.
  • Mutable Validator-Zustände.
  • Fehlende TYPE_USE -Unterstützung.
  • Schlechte Fehlermeldungen.
  • Custom Constraints für einmalige Regeln.
  • Business-Kontext in Annotationen pressen.
Best Practices:
  • Custom Constraints für wiederverwendbare lokale Regeln.
  • @NotNull getrennt kombinieren.
  • Validatoren stateless oder thread-safe halten.
  • Keine IO-Aufrufe.
  • Regex vorkompilieren.
  • Annotation Targets bewusst setzen.
  • Gute Messages definieren.
  • Negative und positive Tests schreiben.
Stolperfalle:

Ein Validator mit Datenbankzugriff sieht elegant aus, kann aber API-Latenz, Transaktionsverhalten und Testbarkeit massiv verschlechtern.

Merksatz:

Custom Constraints prüfen lokale Regeln, nicht verteilte Wahrheit.

B5-K08-Q007 – Class-Level und Cross-Field ValidationCross-Field Validation kennt das Objekt, aber nicht die Welt.
Frage:

„Wie validieren Sie Regeln, die mehrere Felder betreffen?“

Kurz erklärt:

Class-Level Constraints validieren Zusammenhänge zwischen mehreren Feldern eines Objekts. Sie eignen sich für lokale Cross-Field-Regeln.

Musterantwort:

Für lokale Cross-Field-Regeln nutze ich Class-Level Constraints. Beispiel: endDate muss nach startDate liegen. Die Annotation steht auf dem Typ, und der Validator bekommt das ganze Objekt. Wenn ein Fehler einem bestimmten Feld zugeordnet werden soll, kann man über ConstraintValidatorContext eine Property Node wie endDate setzen.

Ich nutze Class-Level Constraints nur für Regeln, die ohne Datenbank, Authentifizierung oder externen Zustand prüfbar sind. Wenn die Regel lautet „Kunde darf diesen Vertrag abschließen“, gehört sie in den Use Case.

Senior-Antwort:

„Cross-Field Validation ist gut, wenn alle Informationen im Objekt liegen. Sobald ich Zustand laden muss, ist es keine reine Validation mehr.“

Häufige Fehler:
  • Cross-Field-Regeln mit Datenbankzugriff.
  • Fehler nur am Objekt statt am Feld melden.
  • Zu viele unsichtbare Typregeln.
  • Null-Prüfung und Cross-Field-Prüfung vermischen.
  • Business-Autorisierung als Validator.
  • Unverständliche Fehlermeldungen.
Best Practices:
  • Cross-Field nur für lokale Objektregeln.
  • Einzelne Feldconstraints separat halten.
  • Null separat validieren.
  • Property Nodes für gute API-Fehler setzen.
  • Validator klein halten.
  • Fachkontext im Use Case prüfen.
  • Tests für alle Kombinationen.
  • Regel sprechend benennen.
Stolperfalle:

Ein Class-Level Constraint ohne Property Node erzeugt oft schlechte API-Fehler, weil der Client nicht sieht, welches Feld korrigiert werden muss.

Merksatz:

Cross-Field Validation kennt das Objekt, aber nicht die Welt.

B5-K08-Q008 – Records, Constructor Validation und moderne DTOsRecords halten Daten stabil; Validation hält Verträge prüfbar.
Frage:

„Wie funktioniert Jakarta Validation mit Java Records?“

Kurz erklärt:

Jakarta Validation 3.1 klärt die Unterstützung für Java Records. Records eignen sich hervorragend für immutable DTOs und Commands.

Musterantwort:

Records passen sehr gut zu Jakarta Validation, weil Record-Komponenten klare, immutable Datenverträge ausdrücken. Constraints können auf Record-Komponenten gesetzt werden. Jakarta Validation 3.1 hat die Unterstützung von Java Records ausdrücklich klargestellt.

Wichtig ist: Record-Konstruktorlogik und Validation sind nicht dasselbe. Der kompakte Konstruktor kann harte Java-Invarianten und defensive Kopien erzwingen. Jakarta Validation liefert strukturierte Constraint Violations für API- oder Service-Grenzen. Beide ergänzen sich.

Senior-Antwort:

„Records geben mir immutable Verträge. Validation macht diese Verträge ausführbar und clientfreundlich erklärbar.“

Häufige Fehler:
  • Validation mit Konstruktorinvarianten verwechseln.
  • Mutable Collections in Records nicht kopieren.
  • Records als JPA Entities erzwingen.
  • Neue Pflichtfelder ohne API-Kompatibilität.
  • Constraints nur auf Accessors erwarten und Record-Komponenten ignorieren.
  • Domain-Regeln vollständig in DTO-Records verlagern.
Best Practices:
  • Records für DTOs, Commands und Events.
  • Constraints auf Record-Komponenten.
  • Defensive Kopien für Collections.
  • Konstruktor für harte Invarianten.
  • Validation für strukturierte Boundary-Fehler.
  • Kompatibilität bei neuen Record-Komponenten beachten.
  • Cascaded validation bewusst einsetzen.
  • Domain Entities getrennt halten.
Stolperfalle:

Ein Record-Konstruktor kann eine Exception werfen, bevor Validation strukturierte Fehler erzeugt. Für API-Eingaben sollte man diese Reihenfolge bewusst gestalten.

Merksatz:

Records halten Daten stabil; Validation hält Verträge prüfbar.

B5-K08-Q009 – Integration mit JAX-RS, CDI, JPA und JSON-BValidation ist Schichtenverantwortung, nicht eine einzelne Annotation.
Frage:

„Wie integrieren Sie Jakarta Validation in einer Jakarta-EE-Anwendung sauber mit JAX-RS, CDI, JPA und JSON-B?“

Kurz erklärt:

Jakarta Validation integriert sich mit JAX-RS, CDI, JPA und JSON-B. Die Kunst liegt darin, die richtige Validierung an der richtigen Schicht zu platzieren.

Musterantwort:

An der JAX-RS-Grenze validiere ich Request-DTOs mit Bean Validation und mappe Verletzungen auf strukturierte API-Fehler. JSON-B bindet JSON in DTOs; Jakarta Validation prüft diese DTOs. Im Application Service kann Method Validation Service-Verträge absichern. Im Domain-Modell sichere ich harte Invarianten weiterhin durch Konstruktoren oder Methoden. JPA-Spaltenconstraints sind zusätzliche Datenbank- und Persistenzsicherheit, aber kein Ersatz für API-Validation.

Jakarta Validation stellt ein objektbasiertes Constraint-Modell sowie eine Constraint-Metadaten- und Query-API bereit. In Jakarta EE wirkt diese Spezifikation deshalb als verbindender Mechanismus zwischen API, Komponentenmodell und Persistenz.

Senior-Antwort:

„Jede Schicht validiert ihre Verantwortung. API prüft Vertrag, Domain prüft Wahrheit, Datenbank schützt Persistenz.“

Häufige Fehler:
  • Nur JPA-Constraints und keine API-Validation.
  • JPA Entities direkt als Request-DTO.
  • Domain-Invarianten ausschließlich per Annotation.
  • Lazy Loading durch Entity-Validation.
  • Fehlerhafte Validierungsmeldungen an Clients.
  • Doppelte Regeln ohne klare Verantwortung.
  • Validation Exceptions nicht zentral mappen.
Best Practices:
  • DTOs an API-Grenzen validieren.
  • JSON-B und Validation über DTOs kombinieren.
  • Use-Case-Commands sauber mappen.
  • Domain-Invarianten im Domain-Code schützen.
  • JPA/DB-Constraints als letzte Sicherheit.
  • Validation Exceptions zentral in Problem Details mappen.
  • Entity-Validation mit Lazy Graphen vorsichtig einsetzen.
  • Regeln pro Schicht dokumentieren.
Stolperfalle:

Nur weil ein DTO validiert wurde, darf die Domain nicht blind vertrauen, dass alle Invarianten für alle Codepfade geschützt sind.

Merksatz:

Validation ist Schichtenverantwortung, nicht eine einzelne Annotation.

B5-K08-Q010 – Fehlerkommunikation, Internationalisierung, Payload und Anti-PatternsValidation-Fehler müssen für Menschen verständlich und für Maschinen stabil sein. 24h
Frage:

„Wie geben Sie Validation-Fehler professionell an Clients zurück?“

Kurz erklärt:

Validation-Fehler müssen strukturiert, stabil, sicher und clientfreundlich kommuniziert werden. Messages, Pfade und Fehlercodes sind Teil des API-Vertrags.

Musterantwort:

Ich mappe ConstraintViolationException zentral auf ein strukturiertes Fehlerformat, meist Problem Details mit einer violations -Liste. Jede Violation enthält Pfad, Message, Constraint-Code und optional einen sicheren rejected value. Sensitive Werte wie Passwörter, Tokens oder PII dürfen nicht ungefiltert zurückgegeben oder geloggt werden.

Internationalisierung löse ich über Message Bundles und stabile Fehlercodes. Clients sollten nicht ausschließlich auf freie Texte parsen müssen. Freitext ist für Menschen, Code ist für Maschinen.

Senior-Antwort:

„Validation-Fehler sind kein Nebenprodukt. Sie sind Teil der Developer Experience für jeden API-Client.“

Häufige Fehler:
  • Nur „Bad Request“ ohne Details.
  • Stacktraces oder interne Klassen ausgeben.
  • Sensitive invalid values zurückgeben.
  • Clients auf Message-Text koppeln.
  • Zu viele Violations unlimitiert senden.
  • Keine stabilen Fehlercodes.
  • Unterschiedliche Fehlerformate pro Endpoint.
Best Practices:
  • Zentrales Exception Mapping.
  • Problem-Format verwenden.
  • Violations mit Pfad und Code.
  • Sensitive Werte maskieren oder weglassen.
  • Stabile Fehlercodes zusätzlich zu Messages.
  • Locale bewusst behandeln.
  • Maximalzahl von Violations begrenzen.
  • Messages testen.
Stolperfalle:

invalidValue direkt zurückzugeben kann Passwörter, Tokens, IBANs oder personenbezogene Daten leaken.

Merksatz:

Validation-Fehler müssen für Menschen verständlich und für Maschinen stabil sein.

B5-K09 – Kapitel 9: Servlet / Jakarta Servlet 10 Karten

B5-K09-Q001 – Servlet-Container, Lifecycle und HttpServletEin Servlet lebt lange; ein Request lebt kurz. 24h
Frage:

„Was ist ein Servlet-Container, und wie funktioniert der Lifecycle eines HttpServlet ?“

Kurz erklärt:

Ein Servlet ist eine containerverwaltete Komponente, die Requests verarbeitet und Responses erzeugt. HttpServlet ist die HTTP-spezifische Basisklasse.

Musterantwort:

Ein Servlet-Container ist Teil eines Web- oder Application-Servers. Er nimmt Netzwerkrequests entgegen, verarbeitet HTTP-/MIME-Details, verwaltet Servlet-Instanzen und deren Lifecycle und ruft passende Servlet-Methoden auf. Die Servlet-Spezifikation beschreibt den Servlet-Container als Teil des Web- oder Application-Servers, der Netzwerkdienste bereitstellt, MIME-basierte Requests decodiert, Responses formatiert und Servlets über ihren Lifecycle verwaltet.

Ein HttpServlet wird typischerweise einmal instanziiert, initialisiert und danach für viele Requests verwendet. Pro Request ruft der Container service() auf, das HTTP-spezifisch an doGet , doPost , doPut , doDelete und andere Methoden delegiert. HttpServlet ist laut API eine abstrakte Basisklasse für HTTP-Servlets; Subklassen überschreiben typischerweise mindestens eine Methode wie doGet , doPost , doPut , doDelete , init oder destroy .

Senior-Antwort:

„Servlets sind das HTTP-Fundament. Ich nutze sie direkt, wenn ich niedrige Kontrolle brauche — aber ich halte sie klein, zustandslos und bewusst thread-safe.“

Häufige Fehler:
  • Request-spezifische Daten in Instanzvariablen speichern.
  • Containerverwaltete Ressourcen im destroy() schließen.
  • Encoding erst nach getWriter() setzen.
  • doGet() für verändernde Operationen verwenden.
  • Lange blockierende Arbeit synchron im Servlet.
  • Statuscode erst nach Body-Schreibbeginn setzen.
Best Practices:
  • Servlets zustandslos oder thread-safe halten.
  • Request-Zustand nur in lokalen Variablen.
  • HTTP-Methode semantisch korrekt verwenden.
  • Content-Type und Encoding vor Body schreiben.
  • Containerressourcen nicht manuell schließen.
  • Lange Arbeit auslagern oder async modellieren.
  • Servlet-Code dünn halten.
  • Frameworks wie JAX-RS für höhere API-Abstraktion nutzen.
Stolperfalle:

Ein Servlet ist nicht pro Request neu. Eine Instanz kann parallel von vielen Threads benutzt werden.

Merksatz:

Ein Servlet lebt lange; ein Request lebt kurz.

B5-K09-Q002 – HttpServletRequest , Parameter, Attribute, Body und KontextRequest-Daten haben Herkunft; Attributes haben Kontext.
Frage:

„Was ist der Unterschied zwischen Request-Parametern, Headers, Attributes und Body im Servlet API?“

Kurz erklärt:

HttpServletRequest enthält HTTP-Request-Daten: Methode, URI, Query, Parameter, Headers, Cookies, Body, Session, Attributes und Kontextinformationen.

Musterantwort:

Request-Parameter kommen typischerweise aus Query String oder Form-Daten. Headers sind HTTP-Metadaten wie Authorization , Content-Type , Accept-Language oder X-Correlation-ID . Der Body enthält die eigentliche Nutzlast, zum Beispiel JSON. Request-Attributes sind serverseitige Werte, die während der Request-Verarbeitung gesetzt werden, etwa von Filtern an Servlets oder Dispatcher-Targets.

HttpServletRequest erweitert ServletRequest , um HTTP-spezifische Request-Informationen bereitzustellen. Der Container erzeugt ein HttpServletRequest -Objekt und übergibt es an Servlet-Methoden wie doGet oder doPost .

Senior-Antwort:

„Request-Parameter kommen vom Client. Request-Attributes sind serverinterne Notizen für denselben Request. Diese Trennung ist wichtig.“

Häufige Fehler:
  • Request Attributes als Clientdaten missverstehen.
  • Body mehrfach lesen wollen.
  • Große Bodies unlimitiert lesen.
  • Header nicht validieren.
  • Query-Parameter für sensitive Daten nutzen.
  • Encoding zu spät setzen.
  • getReader() und getInputStream() mischen.
Best Practices:
  • Headers für Metadaten.
  • Body für Nutzlast.
  • Attributes für serverinterne Weitergabe.
  • Body-Größe begrenzen.
  • JSON validieren.
  • Correlation-ID früh setzen.
  • Request-Daten nicht in Instanzfelder kopieren.
  • Body-Wrapping nur bewusst einsetzen.
Stolperfalle:

Der Request Body ist kein beliebig oft lesbares Objekt. Wer ihn in einem Filter konsumiert, kann nachgelagerte Komponenten brechen.

Merksatz:

Request-Daten haben Herkunft; Attributes haben Kontext.

B5-K09-Q003 – HttpServletResponse , Statuscodes, Headers, Buffering und CommitHeaders zuerst, Body danach — nach Commit ist es zu spät.
Frage:

„Was bedeutet es, dass eine Servlet Response committed ist?“

Kurz erklärt:

HttpServletResponse steuert HTTP-Status, Headers, Cookies, Content-Type, Encoding, Buffering und Body-Ausgabe.

Musterantwort:

Eine Response ist committed, wenn Status und Headers an den Client gesendet wurden. Danach kann man Statuscode und Headers nicht mehr zuverlässig ändern. Das passiert typischerweise, wenn der Buffer geflusht wird, voll ist oder die Ausgabe abgeschlossen wird. Deshalb müssen Status, Content-Type, Encoding, Cookies und wichtige Headers gesetzt werden, bevor Body-Daten geschrieben und geflusht werden.

HttpServletResponse erweitert ServletResponse , um HTTP-spezifische Funktionalität bereitzustellen, darunter Zugriff auf HTTP-Headers und Cookies.

Senior-Antwort:

„Sobald die Response committed ist, ist HTTP nicht mehr verhandelbar. Deshalb entscheide ich Status und Headers, bevor ich Body schreibe.“

Häufige Fehler:
  • Headers nach Body schreiben.
  • getWriter() und getOutputStream() mischen.
  • Error senden, nachdem Response committed ist.
  • Content-Type vergessen.
  • Encoding nach getWriter() setzen.
  • Große Dateien vollständig im Heap bauen.
  • Cache-Header nicht bewusst setzen.
Best Practices:
  • Status und Headers zuerst setzen.
  • Content-Type explizit.
  • Encoding vor Writer setzen.
  • Für Binärdaten OutputStream nutzen.
  • Große Daten streamen.
  • Cache-Header bewusst definieren.
  • Commit-Zustand bei Fehlern beachten.
  • Response-Wrapper nur gezielt einsetzen.
Stolperfalle:

Ein Fehler nach dem ersten Flush kann nicht mehr sauber in eine JSON-Problem-Response umgewandelt werden.

Merksatz:

Headers zuerst, Body danach — nach Commit ist es zu spät.

B5-K09-Q004 – Filter, FilterChain und Cross-Cutting ConcernsFilter sind Request-Rahmen, nicht Fachprozesse.
Frage:

„Wie funktionieren Servlet Filter und die FilterChain ?“

Kurz erklärt:

Servlet Filter umrahmen Request- und Response-Verarbeitung. Sie eignen sich für Cross-Cutting Concerns wie Logging, Security, CORS, Compression, Correlation-ID und Metrics.

Musterantwort:

Ein Servlet Filter wird vom Container vor oder nach einer Zielressource aufgerufen. In doFilter kann der Filter Request und Response prüfen, verändern, wrappen oder die Verarbeitung abbrechen. Mit chain.doFilter(request, response) gibt er an den nächsten Filter oder die Zielressource weiter. Code vor chain.doFilter läuft vor der Zielverarbeitung; Code danach oder im finally läuft danach.

Die Jakarta-Servlet-API-Dokumentation beschreibt, dass doFilter jedes Mal vom Container aufgerufen wird, wenn ein Request/Response-Paar durch die Chain zu einer Ressource geführt wird.

Senior-Antwort:

„Ein Filter umrahmt den Webrequest. Was dort passiert, betrifft potenziell jeden Endpoint — deshalb muss es klein, robust und fachlogikfrei sein.“

Häufige Fehler:
  • chain.doFilter vergessen.
  • Businesslogik in globalen Filtern.
  • Body im Filter lesen und nicht wiederherstellen.
  • Teure Remote Calls im Filter.
  • Filterreihenfolge nicht kontrollieren.
  • Response committen und danach weiterleiten.
  • Exceptions im Filter verschlucken.
Best Practices:
  • Filter klein und schnell halten.
  • Cross-Cutting Concerns zentralisieren.
  • finally für Metriken nutzen.
  • Correlation-ID früh setzen.
  • Security Header zentral ergänzen.
  • Body nur mit Wrapper lesen.
  • Reihenfolge dokumentieren.
  • Fachlogik in Use Cases lassen.
Stolperfalle:

Ein Filter, der chain.doFilter bei Erfolgsfällen nicht aufruft, blockiert die gesamte Anwendung.

Merksatz:

Filter sind Request-Rahmen, nicht Fachprozesse.

B5-K09-Q005 – Sessions, Cookies und State ManagementSession State ist Architekturzustand. 24h
Frage:

„Wie funktionieren HttpSession und Cookies im Servlet API, und welche Risiken gibt es?“

Kurz erklärt:

Servlet Sessions speichern serverseitigen Zustand pro Client. Cookies speichern clientseitige Werte oder Session-IDs. State Management beeinflusst Security, Skalierung und Architektur massiv.

Musterantwort:

HttpSession ist serverseitiger Zustand, der einem Client über eine Session-ID zugeordnet wird, häufig über ein Cookie. Cookies werden vom Server über die Response gesetzt und vom Browser in späteren Requests wieder mitgeschickt. Die Cookie-API-Dokumentation beschreibt, dass Servlets Cookies über HttpServletResponse senden und Cookies aus Requests über HttpServletRequest.getCookies() lesen können.

Risiken sind Session Fixation, Session Hijacking, zu langer Session-Lifetime, nicht serialisierbarer Session State, Cluster-Replikationskosten und Vermischung von Authentifizierung, UI-Präferenz und fachlichem Zustand. Nach erfolgreichem Login sollte die Session-ID gewechselt werden. Cookies sollten HttpOnly , Secure und passende SameSite-Strategien haben, soweit vom Container beziehungsweise Header-Management unterstützt.

Senior-Antwort:

„Sessions sind nicht schlecht. Unbewusster Server-State ist schlecht.“

Häufige Fehler:
  • Session-ID nach Login nicht wechseln.
  • Große Objekte in Session speichern.
  • Sensitive Daten in Cookies.
  • Cookies ohne Secure/HttpOnly.
  • Sessions in REST APIs unbewusst erzeugen.
  • Session State nicht clusterfähig.
  • Logout ohne Session Invalidierung.
Best Practices:
  • Session State minimal halten.
  • Session-ID nach Login wechseln.
  • Session Timeout bewusst setzen.
  • Cookies sicher konfigurieren.
  • Sensitive Daten nicht in Cookies.
  • Session-Erzeugung vermeiden, wenn nicht nötig.
  • Clusterverhalten testen.
  • Logout invalidiert Session.
Stolperfalle:

Ein Session-Cookie ist kein sicherer Speicherort für fachliche oder sensitive Daten. Es ist primär ein Verweis auf serverseitigen Zustand.

Merksatz:

Session State ist Architekturzustand.

B5-K09-Q006 – Async Servlet, AsyncContext und Non-blocking IOAsync Servlet entkoppelt Thread und Response, nicht Verantwortung und Ressourcen.
Frage:

„Wie funktioniert AsyncContext , und wann ist async Servlet sinnvoll?“

Kurz erklärt:

Async Servlet erlaubt, den Container-Request-Thread freizugeben und die Response später abzuschließen. Es macht Arbeit nicht automatisch schneller oder durable.

Musterantwort:

Mit request.startAsync() startet ein Servlet asynchrone Verarbeitung. Der Container-Thread kann freigegeben werden, und die Response wird später über AsyncContext.complete() abgeschlossen. Das ist sinnvoll, wenn eine Operation wartet, zum Beispiel auf einen externen Service oder ein Event, und man Container-Threads nicht blockieren möchte.

Async Servlet ist aber keine Job Engine. Wenn Arbeit lange dauert, absturzsicher sein muss oder wiederaufsetzbar sein soll, ist ein persistierter Job mit 202 Accepted , Queue oder Batch-Prozess besser. Das Jakarta-EE-Tutorial demonstriert Async Servlet mit AsyncContext für den Fall, dass ein Servlet auf eine langsame Ressource wartet und später eine Response erzeugt.

Senior-Antwort:

„Async Servlet spart Container-Threads bei Wartezeit. Es macht einen Prozess aber nicht zuverlässig, dauerhaft oder wiederaufsetzbar.“

Häufige Fehler:
  • complete() vergessen.
  • Async ohne Timeout.
  • Eigene Executor unlimitiert.
  • CPU-bound Arbeit in Async verschieben.
  • Request/Response nach Complete verwenden.
  • Filter nicht async-kompatibel.
  • Async als dauerhafte Job-Verarbeitung missbrauchen.
Best Practices:
  • Async nur bei echten Wartezeiten.
  • Timeout immer setzen.
  • complete() zuverlässig aufrufen.
  • Executor begrenzen und überwachen.
  • Fehlerpfade behandeln.
  • Lange Arbeit als Job modellieren.
  • Request-Daten vor async sicher extrahieren.
  • Async-Kompatibilität der Filter prüfen.
Stolperfalle:

Ein Async Request ohne Timeout und ohne complete() kann Ressourcen dauerhaft halten.

Merksatz:

Async Servlet entkoppelt Thread und Response, nicht Verantwortung und Ressourcen.

B5-K09-Q007 – Multipart, File Uploads und Request-GrößenlimitsUploads sind Eingänge für untrusted Bytes.
Frage:

„Wie implementieren Sie sichere File Uploads mit Servlet?“

Kurz erklärt:

Servlet unterstützt Multipart Requests über @MultipartConfig und Part . Uploads brauchen Größenlimits, Typprüfung, sichere Dateinamen und Speicherstrategie.

Musterantwort:

Ich aktiviere Multipart-Verarbeitung mit @MultipartConfig , setze maxFileSize und maxRequestSize , prüfe vorhandene Parts, validiere Content-Type und Dateiendung, verwende niemals den Client-Dateinamen direkt als Speicherpfad, scanne Dateien bei Bedarf und speichere Uploads kontrolliert in Object Storage, Quarantäne oder einem dedizierten Storage.

Ein Upload ist eine Security-Boundary. Der Client-Dateiname ist untrusted. Content-Type kann gefälscht sein. Große Dateien können Speicher und Disk füllen. Deshalb sind Limits und sichere Pfade Pflicht.

Senior-Antwort:

„File Upload ist kein Formularfeature. Es ist eine untrusted Binary-Ingestion-Pipeline.“

Häufige Fehler:
  • Keine Upload-Limits.
  • Client-Dateiname als Pfad verwenden.
  • Dateityp nur anhand Header vertrauen.
  • Uploads im Webroot speichern.
  • Temporäre Dateien nicht kontrollieren.
  • Große Uploads synchron weiterverarbeiten.
  • PII-Dateien unverschlüsselt speichern.
Best Practices:
  • maxFileSize und maxRequestSize setzen.
  • Dateinamen normalisieren und nicht als Pfad verwenden.
  • Content prüfen, nicht nur Header.
  • Uploads außerhalb Webroot speichern.
  • Malware Scan für riskante Dateien.
  • Storage und Retention definieren.
  • Große Uploads asynchron verarbeiten.
  • Metadaten und Dateiinhalt getrennt behandeln.
Stolperfalle:

Der vom Browser gesendete Dateiname kann Pfadbestandteile, Sonderzeichen oder Täuschungen enthalten und darf nie direkt verwendet werden.

Merksatz:

Uploads sind Eingänge für untrusted Bytes.

B5-K09-Q008 – ServletContext, Listener und InitialisierungServletContext ist global; global braucht Disziplin.
Frage:

„Wofür verwenden Sie ServletContext und Servlet Listener?“

Kurz erklärt:

ServletContext repräsentiert die Webanwendung. Listener reagieren auf Lifecycle-, Request-, Session- oder Attribute-Ereignisse.

Musterantwort:

ServletContext ist der Anwendungskontext einer Webanwendung. Er stellt Initialisierungsparameter, Ressourcen, Logging, Attribute und Kontextinformationen bereit. Listener nutze ich, um auf Lifecycle-Ereignisse zu reagieren: Anwendung gestartet oder beendet, Request gestartet oder beendet, Session erstellt oder zerstört, Attribute geändert.

Ich verwende Listener für Infrastrukturinitialisierung, Metrikzähler, Cleanup oder Brücken zu älteren Frameworks. Ich vermeide aber, eigene globale Service Locator oder Business-State in den ServletContext zu legen. In Jakarta EE ist CDI für Application Services meist die bessere Wahl.

Senior-Antwort:

„ServletContext ist Webapp-Infrastrukturzustand, nicht mein Application Service Registry.“

Häufige Fehler:
  • Nicht thread-safe Objekte global speichern.
  • Business-Services über ServletContext suchen.
  • Request-Listener mit teurer Arbeit.
  • Cleanup in contextDestroyed vergessen.
  • ServletContext als Cache ohne Eviction nutzen.
  • Listener-Reihenfolge annehmen, ohne sie zu konfigurieren.
Best Practices:
  • ServletContext für Webapp-Metadaten.
  • CDI für Services bevorzugen.
  • Globale Attribute thread-safe halten.
  • Listener klein und infrastrukturell.
  • Cleanup implementieren.
  • Request-Listener für leichte Metadaten.
  • Keine Geschäftslogik in Listener.
  • Lifecycle mit Tests oder Containerlogs prüfen.
Stolperfalle:

Ein Objekt im ServletContext wird von vielen Threads gesehen. Ohne Thread-Safety ist das ein globaler Race Condition.

Merksatz:

ServletContext ist global; global braucht Disziplin.

B5-K09-Q009 – Dispatching, Forward, Include, Error Pages und WrappersForward teilt einen Request; Redirect startet einen neuen.
Frage:

„Was ist der Unterschied zwischen forward , include und Redirect?“

Kurz erklärt:

RequestDispatcher kann Requests serverseitig weiterleiten ( forward ) oder Ressourcen in eine Response einfügen ( include ). Wrapper können Request/Response-Verhalten dekorieren.

Musterantwort:

forward ist eine serverseitige Weiterleitung innerhalb derselben Webanwendung. Der Client sieht keine neue URL. Der ursprüngliche Request und seine Attributes bleiben erhalten. include fügt die Ausgabe einer anderen Ressource in die aktuelle Response ein, typischerweise für Fragmente. Redirect ist eine clientseitige Weiterleitung: Der Server sendet einen 3xx-Status mit Location , und der Client macht einen neuen Request.

RequestDispatcher eignet sich für interne Views oder Fehlerseiten. Redirect eignet sich, wenn der Client eine neue URL sehen und einen neuen Request ausführen soll, zum Beispiel nach erfolgreichem POST im Post/Redirect/Get-Muster.

Senior-Antwort:

„Forward bleibt im Server. Redirect geht zum Client. Wer das verwechselt, verliert Request-Kontext oder baut falsche Navigation.“

Häufige Fehler:
  • Forward nach committed Response.
  • Redirect verwenden und Request Attributes erwarten.
  • Include-Ressourcen Statuscodes setzen lassen.
  • Interne JSPs direkt öffentlich machen.
  • Wrapper schreiben, aber Methoden nicht korrekt delegieren.
  • Body-Caching Wrapper ohne Größenlimit.
Best Practices:
  • Forward für interne serverseitige Navigation.
  • Redirect für neue Client-URL und PRG.
  • Include für Fragmente.
  • Interne Views unter /WEB-INF .
  • Vor Forward nicht flushen.
  • Wrapper klein und gezielt nutzen.
  • Response-Commit prüfen.
  • Moderne Templates oder JAX-RS für neue APIs bevorzugen.
Stolperfalle:

Request Attributes überleben einen Forward, aber nicht einen Redirect, weil Redirect einen neuen Request erzeugt.

Merksatz:

Forward teilt einen Request; Redirect startet einen neuen.

B5-K09-Q010 – Servlet in moderner Architektur: JAX-RS, Security, Observability und Anti-PatternsServlet ist selten sichtbar, aber oft entscheidend. 24h
Frage:

„Welche Rolle spielt Servlet heute noch in Jakarta-EE-Anwendungen?“

Kurz erklärt:

Servlet ist in moderner Jakarta-EE-Architektur meist Fundament und Infrastrukturgrenze. Direkte Servlets nutzt man gezielt; Filter und Containermechanik bleiben zentral.

Musterantwort:

Servlet ist weiterhin das Fundament der HTTP-Verarbeitung in Jakarta EE. Auch wenn ich fachliche REST APIs meistens mit JAX-RS baue, laufen Request, Response, Filter, Security, Sessions, Async und Deployment häufig durch Servlet-Mechanismen. Jakarta Servlet 6.1 ist ausdrücklich der Servlet-Release für Jakarta EE 11 und definiert die serverseitige API zur Verarbeitung von HTTP Requests und Responses.

Ich schreibe direkte Servlets nur, wenn ich niedrige Kontrolle brauche: Health/diagnostics, legacy endpoints, streaming downloads, low-level callbacks, bootstrapping oder spezielle Filter-/Wrapper-Logik. Für Business-APIs bevorzuge ich JAX-RS, weil es Ressourcenmodell, Content Negotiation, Message Body Mapping und Exception Mapping höherwertig abbildet.

Senior-Antwort:

„Servlet ist heute oft nicht die API-Schicht, sondern der HTTP-Unterbau. Genau deshalb muss man es verstehen.“

Häufige Fehler:
  • Business-APIs als manuelle Servlets bauen.
  • Servlet Filter mit Fachlogik überladen.
  • Sessions unbewusst erzeugen.
  • Bodies global loggen.
  • Fehlerbehandlung zwischen Servlet und JAX-RS inkonsistent.
  • Kein Requestgrößenlimit.
  • Security nur in einzelnen Servlets statt zentral.
Best Practices:
  • JAX-RS für REST APIs bevorzugen.
  • Servlets für Low-Level-Spezialfälle.
  • Servlet Filter für plattformweite HTTP-Infrastruktur.
  • Security und Observability zentralisieren.
  • Request-/Response-Body-Logging stark begrenzen.
  • Sessions bewusst vermeiden oder steuern.
  • Limits und Timeouts definieren.
  • Fehlerstrategie zwischen Container und JAX-RS abstimmen.
Stolperfalle:

JAX-RS abstrahiert HTTP, aber Servlet-Filter können JAX-RS-Verhalten vorher oder nachher massiv beeinflussen.

Merksatz:

Servlet ist selten sichtbar, aber oft entscheidend.

B5-K10 – Kapitel 10: Security / Jakarta Security 10 Karten

B5-K10-Q001 – Grundmodell: Authentication, Authorization, Principal und RolesAuthentication kennt den Caller; Authorization begrenzt seine Wirkung. 24h
Frage:

„Was ist der Unterschied zwischen Authentication und Authorization in Jakarta Security?“

Kurz erklärt:

Authentication stellt fest, wer der Caller ist. Authorization entscheidet, was dieser Caller tun darf. Principal und Roles sind zentrale Begriffe im Jakarta-Security-Modell.

Musterantwort:

Authentication beantwortet: „Wer ist der Caller?“ Authorization beantwortet: „Was darf dieser Caller tun?“ Nach erfolgreicher Authentifizierung gibt es typischerweise einen Caller Principal und Rollen oder Gruppen. Diese Rollen können über Annotationen wie @RolesAllowed oder programmatisch über SecurityContext geprüft werden.

Wichtig ist: Rollenprüfung ist oft nur die erste Stufe. In echten Systemen reicht account:read selten aus. Zusätzlich muss geprüft werden, ob der Caller genau dieses Konto lesen darf. Das ist Resource-Level Authorization und gehört meistens in eine fachliche Access Policy.

Senior-Antwort:

„Rollen sagen, ob jemand grundsätzlich etwas darf. Eine Access Policy sagt, ob er es an genau dieser Ressource darf.“

Häufige Fehler:
  • Rollen mit Berechtigungen auf konkrete Ressourcen verwechseln.
  • Nur Gateway-Security nutzen und Backend ungeprüft lassen.
  • Principal direkt als Domain-User behandeln.
  • Keine Mandantenprüfung.
  • 401 und 403 verwechseln.
  • Security-Entscheidungen im Controller verstreuen.
Best Practices:
  • Authentication und Authorization sauber trennen.
  • Rollen für grobe Berechtigungen.
  • Access Policies für objektbezogene Regeln.
  • Principal in fachliches User-Modell übersetzen.
  • Mandantengrenzen explizit prüfen.
  • 401 für nicht authentifiziert, 403 für nicht autorisiert.
  • Security-Entscheidungen zentralisieren.
  • Security-Pfade testen.
Stolperfalle:

@RolesAllowed("admin") ist keine vollständige Security-Architektur. Es ist eine grobe Zugriffsschranke.

Merksatz:

Authentication kennt den Caller; Authorization begrenzt seine Wirkung.

B5-K10-Q002 – SecurityContext und programmatische SecurityProgrammatische Security ist mächtig, aber sie braucht zentrale Policies.
Frage:

„Wann verwenden Sie SecurityContext statt nur Annotationen wie @RolesAllowed ?“

Kurz erklärt:

SecurityContext erlaubt programmatische Abfragen des aktuellen Callers, seiner Rollen und Security-bezogener Operationen.

Musterantwort:

Annotationen verwende ich für klare, deklarative Zugriffsschranken an Methoden oder Klassen. SecurityContext verwende ich, wenn ich den aktuellen Caller programmatisch brauche: für Audit, dynamische Access Policies, bedingtes Verhalten, Mapping auf fachliche User-IDs oder komplexere Prüfungen.

Die SecurityContext -API ist laut Jakarta-Security-Dokumentation ein injizierbarer Zugriffspunkt für Anwendungscode, um Security abzufragen und mit Jakarta Security zu interagieren.

Senior-Antwort:

„ SecurityContext gehört an Adapter- und Policy-Grenzen. Die Domain sollte mit expliziten fachlichen Actors arbeiten, nicht mit Container-APIs.“

Häufige Fehler:
  • SecurityContext tief in Domain-Objekte injizieren.
  • Principal-Name als stabile User-ID missverstehen.
  • Security-Logik überall verstreuen.
  • Async-Kontextverlust ignorieren.
  • Keine Tests für Rollenpfade.
  • null Principal nicht behandeln.
Best Practices:
  • Annotationen für statische Zugriffsschranken.
  • SecurityContext für dynamische Policies und Audit.
  • Principal in fachliches Actor-Modell übersetzen.
  • Security-Logik zentralisieren.
  • Async-Grenzen bewusst behandeln.
  • Rollenpfade testen.
  • Keine Domain-Abhängigkeit auf Jakarta Security.
  • Fallback für anonymen Caller definieren.
Stolperfalle:

Ein Principal-Name ist nicht automatisch eine immutable fachliche Benutzer-ID. Identitätsmapping muss bewusst sein.

Merksatz:

Programmatische Security ist mächtig, aber sie braucht zentrale Policies.

B5-K10-Q003 – Identity Store, Credentials und Credential ValidationIdentity Stores beantworten, ob Credentials zu einer Identität gehören.
Frage:

„Was ist ein Identity Store in Jakarta Security?“

Kurz erklärt:

Ein Identity Store validiert Credentials und liefert bei Erfolg Principal- und Gruppeninformationen zurück.

Musterantwort:

Ein Identity Store ist eine Komponente, die Credentials validiert und Identitätsinformationen zurückgibt. Bei Username/Password bedeutet das: Benutzer finden, Passwort sicher prüfen, Principal erzeugen und Gruppen oder Rollen liefern. Jakarta Security 4.0 ergänzt unter anderem einen In-memory Identity Store, der besonders für Tests, Demos und einfache Entwicklungsfälle relevant ist.

In Produktion sollte ein Identity Store sichere Passwortprüfung, Lockout-/Rate-Limit-Strategien, Audit, Timing-Angriffsschutz und klare Gruppen-/Rollenabbildung berücksichtigen. Häufig liegt die eigentliche Identität aber in einem externen Identity Provider; dann validiert die Anwendung eher Tokens als Passwörter.

Senior-Antwort:

„Ein Identity Store validiert Identität. Er sollte keine fachliche Autorisierung entscheiden, sondern verlässliche Identitäts- und Gruppeninformationen liefern.“

Häufige Fehler:
  • Passwörter im Klartext speichern.
  • Unterschiedliche Fehlermeldungen für unbekannten User und falsches Passwort.
  • Identity Store mit Businesslogik überladen.
  • Gruppen ungeprüft als Rollen verwenden.
  • Kein Rate Limiting.
  • Kein Audit bei fehlgeschlagenen Logins.
  • Test-Identity-Store in Produktion aktiv lassen.
Best Practices:
  • Passwörter nur gehasht speichern.
  • Sichere Passwortverifikation kapseln.
  • Identitäts- und Rollenmapping dokumentieren.
  • Login-Fehler generisch kommunizieren.
  • Rate Limiting und Lockout prüfen.
  • Audit für Login-Erfolg und Fehler.
  • In-memory Store nur für Tests/Demos.
  • Externe IdPs bevorzugen, wenn organisatorisch vorhanden.
Stolperfalle:

Ein In-memory Identity Store ist bequem für Tests, aber in Produktion fast immer ein Sicherheitsrisiko.

Merksatz:

Identity Stores beantworten, ob Credentials zu einer Identität gehören.

B5-K10-Q004 – HTTP Authentication Mechanisms und Login-FlowsAuthentication Mechanisms übersetzen HTTP Credentials in Container-Identität.
Frage:

„Was ist ein HttpAuthenticationMechanism in Jakarta Security?“

Kurz erklärt:

HTTP Authentication Mechanisms integrieren HTTP-basierte Authentifizierung mit dem Jakarta-Security- und Servlet-Container-Modell.

Musterantwort:

Ein HttpAuthenticationMechanism ist die Jakarta-Security-Schnittstelle für HTTP-basierte Authentifizierungsmechanismen. Er prüft einen HTTP Request, extrahiert Credentials, validiert sie meist über Identity Stores und informiert den Container über erfolgreichen Login oder fordert den Client heraus.

Jakarta Security 4.0 verbessert unter anderem die API für mehrere Authentication Mechanisms und führt Qualifier für eingebaute Authentication Mechanisms ein. Das ist wichtig, weil reale Anwendungen oft verschiedene Mechanismen kombinieren: Form Login, Basic, Bearer Token, mTLS, SSO oder interne Mechanismen.

Senior-Antwort:

„Ein Authentication Mechanism entscheidet, ob und wie ein Request authentifiziert wird. Was der Caller fachlich darf, entscheidet später die Authorization.“

Häufige Fehler:
  • Business-Autorisierung im Authentication Mechanism.
  • Credentials in unsicheren Headers ohne TLS.
  • Kein generisches Fehlerverhalten.
  • Externe IdP-Aufrufe ohne Timeout.
  • Token ohne Expiry prüfen.
  • Mehrere Mechanismen ohne klare Priorität.
  • Debug-Mechanismus in Produktion.
Best Practices:
  • Mechanismus nur für Authentication.
  • IdentityStoreHandler für Credential Validation nutzen.
  • TLS voraussetzen.
  • Fehler generisch halten.
  • Timeouts bei externen Calls.
  • Mehrere Mechanismen klar priorisieren.
  • Kein Logging von Credentials.
  • Tests für Challenge, Success und Failure.
Stolperfalle:

Ein Mechanismus, der Credentials loggt oder in unsicheren Headers akzeptiert, macht die beste Rollenprüfung wertlos.

Merksatz:

Authentication Mechanisms übersetzen HTTP Credentials in Container-Identität.

B5-K10-Q005 – Declarative Security: @RolesAllowed , @PermitAll , @DenyAllAnnotationen schützen Türen; Policies schützen Räume. 24h
Frage:

„Wie verwenden Sie @RolesAllowed , @PermitAll und @DenyAll sinnvoll?“

Kurz erklärt:

Declarative Security nutzt Annotationen, um Zugriff auf Klassen und Methoden zu beschränken. Sie ist gut für grobe, statische Zugriffskontrolle.

Musterantwort:

@RolesAllowed verwende ich für Methoden oder Klassen, die nur bestimmte Rollen aufrufen dürfen. @PermitAll markiert bewusst öffentlich zugängliche Methoden. @DenyAll sperrt eine Methode vollständig. Auf Klassenebene kann man einen Default setzen und auf Methodenebene verfeinern.

Declarative Security ist stark für klare, statische Schranken. Für dynamische Regeln wie „Nutzer darf nur eigene Rechnung lesen“ reicht sie nicht aus. Dort brauche ich zusätzliche Policy-Checks.

Senior-Antwort:

„Deklarative Security ist meine erste Zugriffsschicht. Fachliche Zugriffspolitik kommt zusätzlich dort, wo Ressourcen betroffen sind.“

Häufige Fehler:
  • @PermitAll aus Bequemlichkeit setzen.
  • Rollen zu grob modellieren.
  • Dynamische Autorisierung vergessen.
  • Methodensicherheit nur an Resource, nicht am Service.
  • Tests für negative Zugriffe fehlen.
  • Annotationen auf nicht-containerverwalteten Klassen erwarten.
Best Practices:
  • Default restriktiv setzen.
  • Rollen granular benennen.
  • @PermitAll bewusst und dokumentiert.
  • @DenyAll für gefährliche interne Methoden.
  • Dynamische Policies ergänzen.
  • Negative Security-Tests schreiben.
  • Rollenmodell zentral dokumentieren.
  • Authorization nicht nur im Frontend.
Stolperfalle:

Eine Rolle admin wird oft zum Sicherheits-Mülleimer. Besser sind fachlich benannte Berechtigungen.

Merksatz:

Annotationen schützen Türen; Policies schützen Räume.

B5-K10-Q006 – Password Security, Credential Storage und Login-SchutzPasswörter werden nicht verschlüsselt gespeichert, sondern langsam gehasht.
Frage:

„Wie speichern und prüfen Sie Passwörter sicher?“

Kurz erklärt:

Passwörter dürfen nie im Klartext gespeichert werden. Sie brauchen langsame, salted, adaptive Passwort-Hashing-Verfahren und Login-Schutz gegen Missbrauch.

Musterantwort:

Ich speichere Passwörter niemals im Klartext und nicht mit schnellen Hashes wie einfachem SHA-256. Ich verwende ein Passwort-Hashing-Verfahren mit Salt und ausreichend Kostenfaktor, zum Beispiel Argon2id, bcrypt, scrypt oder PBKDF2 je nach Plattformvorgabe. Der Algorithmus, Salt, Parameter und Hash müssen gespeichert werden, damit spätere Migration möglich ist.

Zusätzlich brauche ich Login-Schutz: Rate Limiting, Lockout oder adaptive Schutzmechanismen, Audit, generische Fehlermeldungen, MFA für sensitive Konten und sichere Passwort-Reset-Flows. Passwortprüfung allein ist keine vollständige Authentifizierungsarchitektur.

Senior-Antwort:

„Passwort-Hashing schützt gegen den Tag, an dem die Datenbank kopiert wurde. Login-Schutz schützt gegen den Angriff, der gerade läuft.“

Häufige Fehler:
  • Passwörter im Klartext.
  • Schnelle Hashes für Passwörter.
  • Salt vergessen.
  • Algorithmusparameter nicht speichern.
  • Login-Fehler zu detailliert.
  • Keine Rate Limits.
  • Passwörter in Logs oder Exceptions.
  • Passwort-Reset unsicher bauen.
Best Practices:
  • Langsame Passwort-Hashing-Verfahren.
  • Pro Passwort eigenes Salt.
  • Algorithmus und Parameter speichern.
  • Generische Login-Fehler.
  • Rate Limiting und Audit.
  • MFA für sensitive Aktionen.
  • Sichere Reset Tokens mit kurzer Laufzeit.
  • Hash-Migration planen.
Stolperfalle:

Ein schneller kryptographischer Hash ist gut für Integrität, aber schlecht für Passwortspeicherung.

Merksatz:

Passwörter werden nicht verschlüsselt gespeichert, sondern langsam gehasht.

B5-K10-Q007 – Web Security: Sessions, CSRF, Cookies, CORS und Security HeadersBrowser-Security ist Kontext-Security.
Frage:

„Wie sichern Sie eine Jakarta-EE-Webanwendung gegen typische Webangriffe ab?“

Kurz erklärt:

Web Security umfasst Sessions, Cookies, CSRF-Schutz, CORS, Security Headers und sichere Browser-Interaktion.

Musterantwort:

Ich sichere mehrere Ebenen: TLS überall, sichere Cookie-Konfiguration, Session-ID-Wechsel nach Login, CSRF-Schutz bei cookie-basierter Authentifizierung, strikte CORS-Policy, Security Headers wie CSP und X-Content-Type-Options , keine sensitiven Daten in URLs, serverseitige Autorisierung und sichere Fehlerkommunikation.

CORS ist keine Authentifizierung. CSRF ist vor allem relevant, wenn Browser automatisch Cookies mitsenden. Wenn eine API Bearer Tokens im Authorization Header nutzt und keine Cookies akzeptiert, ist das CSRF-Risiko anders als bei Session-Cookies.

Senior-Antwort:

„CORS entscheidet, wer im Browser lesen darf. Es entscheidet nicht, wer fachlich Zugriff bekommt.“

Häufige Fehler:
  • Access-Control-Allow-Origin: * mit Credentials.
  • CORS als Security-Ersatz verstehen.
  • CSRF bei Session-Cookies vergessen.
  • Session-ID nach Login nicht wechseln.
  • Sensitive Tokens in LocalStorage ohne Threat Model.
  • Cookies ohne Secure/HttpOnly.
  • Keine Security Headers.
  • Fehlende serverseitige Autorisierung.
Best Practices:
  • TLS erzwingen.
  • Cookies sicher konfigurieren.
  • CSRF-Schutz bei Cookie-Auth.
  • CORS allowlist-basiert.
  • Security Headers zentral setzen.
  • Session-Fixation verhindern.
  • Tokens nicht in URLs.
  • Authorization serverseitig prüfen.
Stolperfalle:

CORS falsch zu konfigurieren kann Daten im Browser freigeben; CORS richtig zu konfigurieren ersetzt aber keine Authentifizierung und Autorisierung.

Merksatz:

Browser-Security ist Kontext-Security.

B5-K10-Q008 – API Security: Bearer Tokens, JWT, OAuth2/OIDC und Service-to-ServiceToken-Verifikation ist mehr als Signaturprüfung.
Frage:

„Wie sichern Sie REST APIs mit Bearer Tokens oder JWTs ab?“

Kurz erklärt:

API Security verwendet häufig Bearer Tokens, JWTs, OAuth2/OIDC und Service-to-Service-Identitäten. Wichtig sind Verifikation, Claims, Scopes, Audience, Issuer, Expiry und Mandantengrenzen.

Musterantwort:

Ein Bearer Token muss vollständig validiert werden: Signatur, Issuer, Audience, Expiry, Not-Before, Algorithmus, Schlüsselrotation und relevante Claims. Danach mappe ich Claims auf interne Identität, Rollen, Scopes und Mandant. Eine gültige Signatur allein reicht nicht, wenn Audience oder Tenant falsch sind.

OAuth2 und OIDC definieren Flows und Identitäts-/Autorisierungsmodelle. In Jakarta EE kann Token-Verarbeitung über Jakarta Security, Containerintegration, MicroProfile JWT oder vorgelagerte Gateways erfolgen. Trotzdem muss der Backend-Service seine fachliche Autorisierung selbst prüfen.

Senior-Antwort:

„Ein JWT ist kein Ausweis, nur weil es drei Base64-Blöcke hat. Erst Signatur, Issuer, Audience, Expiry und Claims machen es verwendbar.“

Häufige Fehler:
  • JWT nur decodieren, nicht verifizieren.
  • Audience nicht prüfen.
  • Algorithmus nicht einschränken.
  • Expiry ignorieren.
  • Tenant Claim ungeprüft übernehmen.
  • Scopes als fachliche Berechtigung überdehnen.
  • Token in Logs schreiben.
  • Gateway-Validierung blind vertrauen.
Best Practices:
  • Signatur und Claims vollständig prüfen.
  • Issuer und Audience whitelisten.
  • Expiry und Not-Before beachten.
  • Scopes auf interne Permissions mappen.
  • Mandant explizit prüfen.
  • Tokens nie loggen.
  • JWK Cache und Rotation planen.
  • Introspection mit Timeout.
Stolperfalle:

Ein Token für Service A darf nicht automatisch für Service B gelten. Dafür gibt es Audience-Prüfung.

Merksatz:

Token-Verifikation ist mehr als Signaturprüfung.

B5-K10-Q009 – Multi-Tenancy, Least Privilege und Resource-Level AuthorizationTenant-Isolation muss überall gelten, nicht nur im Controller.
Frage:

„Wie verhindern Sie Datenlecks in Multi-Tenant-Jakarta-EE-Anwendungen?“

Kurz erklärt:

Multi-Tenant-Systeme brauchen strikte Mandantengrenzen, Least Privilege und objektbezogene Autorisierung. Rollen allein reichen nicht.

Musterantwort:

Ich behandle Tenant-ID als Sicherheitsgrenze. Jeder Request muss einen verifizierten Tenant-Kontext haben. Jede Datenabfrage muss tenant-gebunden sein. Jede Resource-Level-Policy muss prüfen, ob die Ressource zum Tenant gehört und ob der Caller innerhalb dieses Tenants berechtigt ist. Rollen oder Scopes aus einem Token reichen nicht, wenn sie nicht mit Tenant-Membership und Ressourceneigentum abgeglichen werden.

Least Privilege bedeutet: Ein Caller bekommt nur die minimal nötigen Berechtigungen. Administrative Berechtigungen sollten granular und auditierbar sein.

Senior-Antwort:

„In Multi-Tenant-Systemen ist Tenant-ID keine Filteroption. Sie ist eine Sicherheitsgrenze.“

Häufige Fehler:
  • Tenant-ID aus Header blind vertrauen.
  • Datenbankqueries ohne Tenant-Filter.
  • Cache Keys ohne Tenant.
  • Admin-Rolle zu breit.
  • Batch-Jobs ohne Tenant-Kontext.
  • Resource-Level Authorization vergessen.
  • Fehlende negative Cross-Tenant-Tests.
Best Practices:
  • Tenant-Kontext verifizieren.
  • Tenant-ID in allen Queries erzwingen.
  • Resource Ownership prüfen.
  • Permissions tenant-spezifisch.
  • Cache Keys tenant-aware.
  • Least Privilege granular modellieren.
  • Admin-Aktionen auditieren.
  • Cross-Tenant-Tests automatisieren.
Stolperfalle:

Ein Cache-Key wie invoice:123 ist in Multi-Tenant-Systemen gefährlich. Er muss tenant-aware sein.

Merksatz:

Tenant-Isolation muss überall gelten, nicht nur im Controller.

B5-K10-Q010 – Security Operations: Auditing, Secrets, Threat Modeling und Anti-PatternsSecurity ist Code plus Betrieb plus Nachweisbarkeit. 24h
Frage:

„Welche Security-Anti-Patterns sehen Sie in Jakarta-EE-Anwendungen?“

Kurz erklärt:

Security ist kein reines Coding-Thema. Betriebssicherheit umfasst Auditing, Secrets Management, sichere Logs, Monitoring, Threat Modeling, Incident Response und regelmäßige Reviews.

Musterantwort:

Typische Anti-Patterns sind: Security nur am Frontend oder Gateway, keine Resource-Level Authorization, breite Admin-Rollen, Secrets in Properties oder Logs, Passwörter mit schnellen Hashes, Tokens in Logs, keine CSRF-Strategie, CORS falsch verstanden, fehlende Tenant-Isolation, fehlendes Auditing, keine negativen Security-Tests und fehlendes Threat Modeling.

Security muss operationalisiert werden. Dazu gehören Audit-Events für Login, Logout, fehlgeschlagene Authentifizierung, Berechtigungsentscheidungen, Admin-Aktionen und Datenexporte. Secrets gehören in ein Secret Management System, nicht ins Repository. Logs müssen nützlich, aber datenschutzkonform sein.

Senior-Antwort:

„Security endet nicht bei @RolesAllowed . Sie endet erst bei Betrieb, Audit, Monitoring und der Fähigkeit, einen Incident zu erklären.“

Häufige Fehler:
  • Secrets im Git Repository.
  • Tokens oder Passwörter in Logs.
  • Audit nur für erfolgreiche Aktionen.
  • Keine Deny-Events.
  • Keine Alerting-Regeln.
  • Security Review erst nach Incident.
  • Admin-Aktionen nicht nachvollziehbar.
  • Compliance und Datenschutz ignorieren.
Best Practices:
  • Threat Modeling pro kritischem Flow.
  • Secrets zentral verwalten.
  • Keine Secrets in Logs.
  • Audit für Allow und Deny.
  • Admin-Aktionen besonders auditieren.
  • Security-Tests automatisieren.
  • Dependency- und Container-Scanning.
  • Least Privilege in Runtime-Rollen.
Stolperfalle:

Logs sind oft die zweite Datenbank des Systems — nur schlechter geschützt. Tokens und PII gehören dort nicht hinein.

Merksatz:

Security ist Code plus Betrieb plus Nachweisbarkeit.

B5-K11 – Kapitel 11: WebSocket / Jakarta WebSocket 10 Karten

B5-K11-Q001 – Grundmodell: @ServerEndpoint , Verbindung, Session und LifecycleWebSocket hält Verbindungen; REST verarbeitet Requests. 24h
Frage:

„Wie funktioniert ein Jakarta-WebSocket-Server-Endpunkt?“

Kurz erklärt:

Ein WebSocket-Endpunkt repräsentiert eine langlebige bidirektionale Verbindung. @ServerEndpoint veröffentlicht eine Klasse als WebSocket-Endpunkt, Lifecycle-Methoden wie @OnOpen , @OnMessage , @OnClose und @OnError reagieren auf Ereignisse.

Musterantwort:

Ein Jakarta-WebSocket-Server-Endpunkt wird mit @ServerEndpoint deklariert. Die Annotation definiert den URI, unter dem der Endpunkt im WebSocket-Server verfügbar ist. Die API-Dokumentation beschreibt @ServerEndpoint als Klassenannotation, die eine Klasse als WebSocket-Endpunkt im URI-Raum eines WebSocket-Servers veröffentlicht; die annotierte Klasse muss einen öffentlichen No-Arg-Konstruktor haben.

Der Container ruft Lifecycle-Methoden auf: @OnOpen beim Verbindungsaufbau, @OnMessage bei eingehenden Nachrichten, @OnClose beim Schließen und @OnError bei Fehlern. Die Session repräsentiert die konkrete WebSocket-Verbindung und erlaubt Senden, Schließen, Zugriff auf Session-ID, User Properties und Remote-Endpunkte.

Senior-Antwort:

„WebSocket ist eine Verbindung, kein einzelner Request. Deshalb muss ich Lifecycle, Zustand und Ressourcen aktiv managen.“

Häufige Fehler:
  • WebSocket wie REST behandeln.
  • Sessions in nicht thread-sicheren Collections speichern.
  • Keine Cleanup-Logik in @OnClose .
  • Kein @OnError .
  • Unbegrenzte Verbindungen erlauben.
  • Tenant- oder User-Kontext ungeprüft aus dem Pfad übernehmen.
  • Payloads vollständig in Logs schreiben.
Best Practices:
  • Endpunkte klein halten.
  • Session Registry thread-safe.
  • Cleanup in @OnClose und @OnError .
  • Verbindungsanzahl begrenzen.
  • User-/Tenant-Kontext beim Handshake validieren.
  • Asynchron senden für Broadcasts.
  • Payload-Größe begrenzen.
  • Observability pro Verbindung und Nachricht.
Stolperfalle:

Eine statische Session-Map funktioniert in einer einzelnen JVM, aber nicht automatisch in einem Cluster.

Merksatz:

WebSocket hält Verbindungen; REST verarbeitet Requests.

B5-K11-Q002 – Handshake, HTTP Upgrade, Configurator und Origin-PrüfungOrigin prüft Herkunft; Authentication prüft Identität.
Frage:

„Wie sichern und beeinflussen Sie den WebSocket-Handshake?“

Kurz erklärt:

Der WebSocket-Handshake startet als HTTP Request und wird per Upgrade zur WebSocket-Verbindung. Ein Configurator kann Origin prüfen, Requestdaten lesen und die Handshake-Response beeinflussen.

Musterantwort:

Der Handshake ist die Security- und Kontextgrenze der WebSocket-Verbindung. Hier prüfe ich Origin, Authentifizierung, Token, Cookies, Tenant, Subprotocols und gegebenenfalls Rate Limits. Jakarta WebSocket erlaubt eine ServerEndpointConfig.Configurator -Klasse, um Details des initialen HTTP Requests zu lesen, den Origin-Header zu prüfen, die Handshake-Response zu ändern und Subprotocols auszuwählen. Das Jakarta-EE-Tutorial beschreibt genau diese Einsatzfälle für Endpoint Configurators.

Wichtig ist: Origin ist kein Ersatz für Authentication. Es ist ein Browser-Schutzsignal. Der Server muss trotzdem Credentials oder Tokens validieren.

Senior-Antwort:

„Der Handshake ist mein letzter normaler HTTP-Moment. Danach muss die Verbindung bereits sicher und kontextualisiert sein.“

Häufige Fehler:
  • Origin-Prüfung vergessen.
  • Origin als Authentifizierung missverstehen.
  • Tokens aus Query Strings dauerhaft loggen.
  • Externe Auth-Calls ohne Timeout.
  • Tenant-ID aus Pfad blind vertrauen.
  • Kontext nicht in Session übertragen.
  • Fehlerhafte Handshake-Antworten ohne Diagnose.
Best Practices:
  • Handshake als Security-Grenze behandeln.
  • Origin allowlisten.
  • Authentifizierung beim Handshake prüfen.
  • Tokens nicht in Logs.
  • Tenant-Kontext verifizieren.
  • Subprotocols bewusst wählen.
  • Timeouts bei externen Prüfungen.
  • Correlation-ID übernehmen oder erzeugen.
Stolperfalle:

Ein WebSocket mit gültigem Origin, aber ohne echte Authentifizierung, ist nicht sicher.

Merksatz:

Origin prüft Herkunft; Authentication prüft Identität.

B5-K11-Q003 – Nachrichtenmodell: Text, Binary, Encoders und DecodersDas Protokoll beginnt nicht bei WebSocket, sondern bei der Bedeutung Ihrer Messages.
Frage:

„Wie gestalten Sie WebSocket-Nachrichtenverträge mit Jakarta WebSocket?“

Kurz erklärt:

Jakarta WebSocket unterstützt Text- und Binary-Messages sowie Encoders und Decoders zur Konvertierung zwischen Wire-Format und Java-Typen.

Musterantwort:

Ich definiere explizite Message-Typen, Versionen, Correlation-IDs und Fehlerformate. Für JSON-basierte Textnachrichten kann ich Encoders und Decoders verwenden, um zwischen JSON und Java-Records zu konvertieren. Für effiziente binäre Protokolle kommen Binary Messages oder eigene Encoder/Decoder infrage.

Wichtig ist, WebSocket-Nachrichten genauso als Verträge zu behandeln wie REST-DTOs oder Kafka-Events. Es braucht Versionierung, Validation, Größenlimits und klare Error Messages.

Senior-Antwort:

„WebSocket-Nachrichten sind Verträge. Der Unterschied zu REST ist nur der Transport, nicht die Verantwortung.“

Häufige Fehler:
  • Nachrichten ohne Typfeld.
  • Keine Versionierung.
  • Beliebige JSON-Strukturen akzeptieren.
  • Keine Größenlimits.
  • Decoder mit Businesslogik überladen.
  • Fehler nur als Freitext.
  • Binary wählen, obwohl Debuggability wichtiger ist.
Best Practices:
  • Message Envelope definieren.
  • Typ und Version immer mitsenden.
  • Correlation-ID für Requests.
  • Encoders/Decoders klein halten.
  • Validation nach Decoding.
  • Fehlerformat standardisieren.
  • Größenlimits setzen.
  • Contract Tests für Messages.
Stolperfalle:

Ein WebSocket ohne Message Envelope wird mit jedem neuen Feature schwerer kompatibel zu halten.

Merksatz:

Das Protokoll beginnt nicht bei WebSocket, sondern bei der Bedeutung Ihrer Messages.

B5-K11-Q004 – Synchrones und asynchrones Senden, Backpressure und langsame ClientsUnbegrenztes Senden ist versteckter Speicherverbrauch.
Frage:

„Wie gehen Sie mit langsamen WebSocket-Clients um?“

Kurz erklärt:

WebSocket-Sends können synchron oder asynchron erfolgen. Langsame Clients erzeugen Backpressure und müssen begrenzt, gepuffert oder getrennt werden.

Musterantwort:

Ich sende Broadcasts und häufige Updates asynchron und begrenze pro Client die ausstehenden Nachrichten. Wenn ein Client nicht mithalten kann, darf er nicht unbegrenzt Speicher im Server belegen. Ich nutze kleine Queues, Drop-/Coalescing-Strategien oder schließe langsame Clients mit einem passenden Close Code.

Synchrones Senden kann einfach sein, blockiert aber den aufrufenden Thread. Asynchrones Senden skaliert besser, verlangt aber Fehlerbehandlung und Backpressure-Kontrolle. Ohne diese Kontrolle kann ein einzelner langsamer Client ein ganzes Node destabilisieren.

Senior-Antwort:

„Backpressure ist bei WebSocket kein Sonderfall, sondern Normalbetrieb. Jeder Client braucht ein Limit.“

Häufige Fehler:
  • Broadcast synchron an alle Clients.
  • Unbegrenzte Queue pro Session.
  • Send-Fehler ignorieren.
  • Langsame Clients endlos halten.
  • Keine Send Timeouts.
  • Mehrere parallele Sends ohne Ordnungskonzept.
  • Große Messages ohne Limits.
Best Practices:
  • AsyncRemote für Broadcasts.
  • Pro-Client-Buffer begrenzen.
  • Send-Ergebnis auswerten.
  • Drop-/Coalescing-Strategie definieren.
  • Langsame Clients schließen.
  • Max Message Size setzen.
  • Metriken für Queue-Länge.
  • Broadcast über Event-Bus entkoppeln.
Stolperfalle:

Ein einziger langsamer Client kann bei unbegrenztem Buffering mehr Heap verbrauchen als tausend schnelle Clients.

Merksatz:

Unbegrenztes Senden ist versteckter Speicherverbrauch.

B5-K11-Q005 – Security: Authentifizierung, Autorisierung, Tenant-Grenzen und CSWSHDer Handshake authentifiziert die Verbindung; jede Message autorisiert die Aktion. 24h
Frage:

„Wie sichern Sie WebSocket-Verbindungen in Jakarta EE?“

Kurz erklärt:

WebSocket-Security muss beim Handshake und während der Verbindung geprüft werden. Risiken sind Cross-Site WebSocket Hijacking, fehlende Origin-Prüfung, unsichere Tokens und fehlende Resource-Level Authorization.

Musterantwort:

Ich sichere WebSockets in mehreren Stufen. Erstens beim Handshake: TLS, Origin-Prüfung, Authentifizierung, Tenant-Kontext, Subprotocols und Rate Limits. Zweitens während der Verbindung: Jede eingehende Message wird autorisiert, validiert und tenant-aware verarbeitet. Drittens im Betrieb: Verbindungsanzahl, Message-Größe, Idle Timeout, Audit und Abuse Detection.

Cross-Site WebSocket Hijacking ist besonders relevant, wenn Browser automatisch Session-Cookies beim WebSocket-Handshake senden. Deshalb sind Origin-Prüfung und echte Authentifizierung wichtig. Origin allein reicht nicht; Auth allein ohne Origin kann bei Cookie-basierten Sessions ebenfalls riskant sein.

Senior-Antwort:

„WebSocket-Security endet nicht beim Handshake. Jede Message ist eine Aktion und braucht Kontext, Validierung und Berechtigung.“

Häufige Fehler:
  • Auth nur beim ersten HTTP-Login, WebSocket aber ohne Prüfung.
  • Origin-Prüfung vergessen.
  • Tokens in Query Strings.
  • Tenant-ID aus URL blind vertrauen.
  • Keine Message-Level Authorization.
  • Keine Disconnect-Strategie bei Rollenentzug.
  • Sensitive Payloads loggen.
Best Practices:
  • TLS verpflichtend.
  • Origin allowlisten.
  • Authentication beim Handshake.
  • Keine Tokens in URLs.
  • Tenant-Kontext verifizieren.
  • Per-Message Authorization.
  • Idle und absolute Timeouts.
  • Zugriffsentzug durch Disconnect unterstützen.
Stolperfalle:

Cookie-basierte WebSocket-Authentifizierung ohne Origin-Prüfung öffnet die Tür für Cross-Site-WebSocket-Angriffe.

Merksatz:

Der Handshake authentifiziert die Verbindung; jede Message autorisiert die Aktion.

B5-K11-Q006 – Skalierung, Cluster, Pub/Sub und horizontale VerteilungVerbindungen sind lokal; Ereignisse müssen verteilt werden.
Frage:

„Wie skalieren Sie WebSocket-Anwendungen horizontal?“

Kurz erklärt:

In einem Cluster hält jeder Node nur seine lokalen WebSocket-Sessions. Globale Zustellung braucht Pub/Sub, Routing, Sticky Sessions oder eine zentrale Connection Registry.

Musterantwort:

Jeder Node kennt zunächst nur seine lokalen WebSocket-Verbindungen. Wenn ein Event auf Node A entsteht, der Zielclient aber mit Node B verbunden ist, braucht man eine Verteilungsstrategie: Pub/Sub über Kafka, JMS, Redis, Datenbank-Notify oder einen spezialisierten Message Broker; alternativ sticky sessions für Verbindungslokalität, aber das löst Broadcast und Failover nur teilweise.

Ich trenne deshalb lokale Session Registry von globaler Event-Verteilung. Der Broker verteilt Notification Events an alle Nodes oder gezielt an Nodes mit passenden Sessions. Jeder Node liefert nur an seine lokalen Sessions aus.

Senior-Antwort:

„Eine WebSocket-Session lebt auf genau einem Node. Skalierung bedeutet, Events zu dem Node zu bringen, nicht Sessions zu verschieben.“

Häufige Fehler:
  • Sessions in Redis speichern wollen.
  • Statische Registry als clusterfähig annehmen.
  • Broadcast synchron über alle Nodes.
  • Keine Reconnect-Strategie.
  • Keine Drain-Strategie beim Deployment.
  • Sticky Sessions als vollständige Skalierungslösung verstehen.
  • Keine Metrik für Verbindungen pro Node.
Best Practices:
  • Lokale Session Registry kapseln.
  • Globale Verteilung über Broker.
  • Node-ID und Verbindungsmetadaten erfassen.
  • Reconnect und Resubscribe unterstützen.
  • Deployment Drain implementieren.
  • Backpressure pro Node messen.
  • Sticky Sessions bewusst nutzen, nicht voraussetzen.
  • Broadcast-Strategie Lasttesten.
Stolperfalle:

jakarta.websocket.Session ist kein verteilbarer Clusterzustand. Speichern Sie Metadaten global, nicht die Verbindung selbst.

Merksatz:

Verbindungen sind lokal; Ereignisse müssen verteilt werden.

B5-K11-Q007 – Heartbeats, Timeouts, Reconnect und Connection LifecycleWebSocket braucht Wiederverbindung als Designprinzip.
Frage:

„Wie behandeln Sie Verbindungsabbrüche und Reconnects bei WebSocket?“

Kurz erklärt:

Langlebige Verbindungen brauchen Heartbeats, Idle Timeouts, Reconnect-Strategien und Wiederherstellung von Subscriptions oder Presence State.

Musterantwort:

Ich plane Verbindungsabbrüche als Normalfall. Browser schlafen ein, mobile Netze wechseln, Proxies schließen Idle Connections, Deployments trennen Sessions. Deshalb braucht der Client Reconnect mit Backoff und Resubscribe. Der Server braucht Idle Timeout, Heartbeats, Cleanup und klare Close Reasons.

Heartbeats können über WebSocket Ping/Pong oder Anwendungsping laufen. Wichtig ist, nicht nur TCP-Verbindung, sondern auch fachliche Liveness zu betrachten: Ist der Client noch konsumierend, autorisiert und subscribed?

Senior-Antwort:

„Eine WebSocket-Verbindung ist langlebig, aber nicht zuverlässig dauerhaft. Reconnect ist kein Fehlerpfad, sondern Kernfunktion.“

Häufige Fehler:
  • Keine Heartbeats.
  • Keine Reconnect-Strategie.
  • Subscriptions nur im flüchtigen Session State.
  • Zu häufige Heartbeats.
  • Tote Sessions nicht entfernen.
  • Deployments ohne Drain.
  • Keine Close Reasons.
Best Practices:
  • Disconnects als Normalfall.
  • Idle Timeout setzen.
  • Heartbeat-Intervall bewusst wählen.
  • Client-Reconnect mit Backoff.
  • Resubscribe nach Reconnect.
  • Server-State wiederherstellbar halten.
  • Close Reasons sinnvoll setzen.
  • Verbindungsmetriken erfassen.
Stolperfalle:

Wenn Subscription State nur in der aktuellen Session liegt, verliert ein Reconnect die fachliche Verbindung zum Stream.

Merksatz:

WebSocket braucht Wiederverbindung als Designprinzip.

B5-K11-Q008 – WebSocket vs REST, SSE, Polling und MessagingRealtime ist eine Anforderung, WebSocket nur eine Option.
Frage:

„Wann verwenden Sie WebSocket statt REST, SSE oder Polling?“

Kurz erklärt:

WebSocket ist nicht immer die beste Wahl. REST, SSE, Polling und Messaging haben andere Stärken und geringere Komplexität für viele Use Cases.

Musterantwort:

Ich verwende WebSocket, wenn ich bidirektionale, langlebige, latenzarme Kommunikation brauche: Chat, Collaboration, Trading, Gaming, Live Support, interaktive Dashboards mit Client-Kommandos. SSE ist oft besser, wenn nur der Server zum Browser streamt. REST ist besser für klassische request/response-Operationen. Polling ist einfach und ausreichend, wenn Echtzeit nicht kritisch ist. Backend-Messaging ist besser für durable Workflows.

WebSocket bringt Kosten: Verbindungzustand, Backpressure, Reconnect, Security, Skalierung und Betrieb. Wenn der Use Case nur alle 30 Sekunden Status prüfen muss, ist WebSocket oft Overengineering.

Senior-Antwort:

„WebSocket ist ein starkes Werkzeug, aber kein Standardtransport für alles. Ich bezahle Zustand nur, wenn der Use Case ihn rechtfertigt.“

Häufige Fehler:
  • WebSocket für einfache CRUD-APIs.
  • REST für echten Live-Chat.
  • SSE ignorieren.
  • Backend-Messaging mit Browser-WebSocket verwechseln.
  • Kein Fallback.
  • Echtzeitbedarf nicht quantifizieren.
  • Betriebsaufwand unterschätzen.
Best Practices:
  • Transport nach Use Case wählen.
  • Bidirektionalität prüfen.
  • Latenzanforderung quantifizieren.
  • Stateful-Kosten berücksichtigen.
  • SSE für server-only Push prüfen.
  • REST für CRUD und Commands.
  • Messaging für durable Backend-Arbeit.
  • Fallback und Degradation planen.
Stolperfalle:

WebSocket löst keine durable Backend-Verarbeitung. Wenn der Client offline ist, ist die Verbindung weg.

Merksatz:

Realtime ist eine Anforderung, WebSocket nur eine Option.

B5-K11-Q009 – Testing, Observability und BetriebsdiagnoseBei WebSocket ist die Verbindung selbst eine Metrik.
Frage:

„Wie testen und überwachen Sie WebSocket-Anwendungen?“

Kurz erklärt:

WebSocket-Systeme brauchen Tests für Lifecycle, Handshake, Message-Verträge, Security, Backpressure und Reconnect. Observability muss Verbindungen, Nachrichten und Fehler sichtbar machen.

Musterantwort:

Ich teste mehrere Ebenen. Unit-Tests für Encoder, Decoder, Message-Router und Policies. Integrationstests für echte WebSocket-Verbindungen, Handshake, Auth, Origin, Close Codes und Message-Flows. Lasttests für viele Verbindungen, langsame Clients und Reconnect-Stürme.

Observability braucht Metriken: aktive Verbindungen, Verbindungen pro Node, Inbound-/Outbound-Rate, Send-Lag, Buffer-Größe, Failed Sends, Close Reasons, Auth-Fehler, Heartbeat-Timeouts und Reconnect-Rate. Logs brauchen Correlation-ID, Session-ID und User/Tenant-Kontext, aber keine sensitiven Payloads.

Senior-Antwort:

„WebSocket ist Zustand plus Zeit. Ohne Metriken für Verbindungen, Buffer und Close Reasons debuggt man blind.“

Häufige Fehler:
  • Nur Encoder/Decoder testen.
  • Keine echten Container-Tests.
  • Keine Lasttests mit vielen Verbindungen.
  • Payloads unmaskiert loggen.
  • Close Reasons nicht auswerten.
  • Keine Metrik für Buffer-Länge.
  • Clusterverteilung nicht beobachten.
Best Practices:
  • Contract Tests für Messages.
  • Integrationstests mit echter Runtime.
  • Lasttests mit langsamen Clients.
  • Reconnect-Storms testen.
  • Close Codes messen.
  • Buffer und Send-Lag messen.
  • Logs ohne sensitive Payloads.
  • Correlation-ID und Tenant-Kontext.
Stolperfalle:

HTTP-Metriken allein zeigen WebSocket-Probleme kaum. Nach dem Handshake passiert der relevante Verkehr nicht mehr als einzelne HTTP Requests.

Merksatz:

Bei WebSocket ist die Verbindung selbst eine Metrik.

B5-K11-Q010 – Architektur, Anti-Patterns und produktionsreife Realtime-SystemeWebSocket liefert Ereignisse aus; es erzeugt nicht die Architektur. 24h
Frage:

„Welche WebSocket-Anti-Patterns sehen Sie in Enterprise-Systemen?“

Kurz erklärt:

Produktionsreife Realtime-Systeme trennen Fachereignisse, Broker-Verteilung, lokale WebSocket-Zustellung, Security, Backpressure und Client-Reconnect.

Musterantwort:

Häufige Anti-Patterns sind: WebSocket für CRUD verwenden, Businesslogik direkt im Endpoint, statische Session-Maps als Clusterlösung, keine Backpressure, keine Reconnect-Strategie, Tokens in Query Strings, keine Origin-Prüfung, keine Message-Versionierung, keine Close-Code-Analyse, unlimitierte Payloads, kein Tenant-Kontext und Broadcasts direkt aus Use Cases in lokale Sessions.

Produktionsreif ist ein anderes Design: Use Cases erzeugen fachliche Events. Diese werden über Outbox oder Broker verteilt. WebSocket-Nodes haben lokale Session Registries und liefern nur an passende lokale Sessions aus. Clients können reconnecten und resubscriben. Backpressure begrenzt Speicher. Security prüft Handshake und jede relevante Message.

Senior-Antwort:

„WebSocket ist mein Delivery-Adapter. Die fachliche Wahrheit entsteht vorher im Use Case und wird über Events verteilt.“

Häufige Fehler:
  • Use Case ruft direkt session.sendText .
  • Endpoint enthält Fachlogik.
  • Keine Outbox oder Brokerverteilung.
  • Keine Clusterstrategie.
  • Keine Backpressure.
  • Keine Message Contracts.
  • Keine Security pro Message.
  • Keine Betriebssicht auf Sessions.
Best Practices:
  • WebSocket als Transportadapter.
  • Fachlogik in Use Cases.
  • Events über Outbox/Broker.
  • Lokale Session Registry kapseln.
  • Message Contracts versionieren.
  • Backpressure pro Client.
  • Reconnect und Resubscribe.
  • Security beim Handshake und pro Message.
Stolperfalle:

Direkt aus einem Use Case in lokale WebSocket-Sessions zu senden koppelt Fachlogik an eine einzelne JVM und bricht im Cluster.

Merksatz:

WebSocket liefert Ereignisse aus; es erzeugt nicht die Architektur.

B5-K12 – Kapitel 12: Batch / Jakarta Batch 10 Karten

B5-K12-Q001 – Grundmodell: Job, Step, JobOperator und Job RepositoryBatch ist kontrollierte Langläuferverarbeitung mit Zustand. 24h
Frage:

„Was sind die Kernkonzepte von Jakarta Batch?“

Kurz erklärt:

Ein Batch Job besteht aus Steps. Steps können chunk-orientiert oder batchlet-orientiert sein. Der JobOperator startet, stoppt, restarted und inspiziert Jobs. Das Job Repository speichert Metadaten zu Job- und Step-Ausführungen.

Musterantwort:

Die Kernkonzepte sind Job, Step, JobInstance, JobExecution, StepExecution, JobOperator, Job Repository und Job Specification Language. Ein Job beschreibt den gesamten Batchprozess. Ein Step ist eine Verarbeitungseinheit. Eine JobInstance beschreibt einen logischen Job mit Parametern, während eine JobExecution eine konkrete Ausführung darstellt. Der JobOperator startet, stoppt, restarted und inspiziert Jobs.

Die Jakarta-Batch-Spezifikation beschreibt Job Specification Language, Java-Programmiermodell und Runtime Environment für Batch und ist für Jakarta-EE-Plattformen sowie Java-SE-Umgebungen ausgelegt.

Senior-Antwort:

„Ein Batch Job ist ein kontrollierter, wiederaufsetzbarer Geschäftsprozess für große Datenmengen — nicht einfach eine Methode, die nachts läuft.“

Häufige Fehler:
  • Batch Jobs wie einfache Timer-Methoden bauen.
  • Keine Job-Parameter definieren.
  • Keine Restart-Strategie.
  • Batchstatus nicht überwachen.
  • Fachliche und technische Fehler vermischen.
  • Job-Ausführungen nicht auditieren.
  • Große Datenmengen ohne Chunking verarbeiten.
Best Practices:
  • Job und Steps fachlich klar schneiden.
  • Job-Parameter explizit verwenden.
  • Job-Metadaten überwachen.
  • Chunk Steps für große Datenmengen.
  • Batchlets für einfache technische Aktionen.
  • Exit Status bewusst setzen.
  • Restart-Fähigkeit früh entwerfen.
  • Jobs idempotent und wiederholbar machen.
Stolperfalle:

Ein Cronjob ohne Job Repository, Parameter, Status und Restart-Konzept ist kein produktionsreifer Batchprozess.

Merksatz:

Batch ist kontrollierte Langläuferverarbeitung mit Zustand.

B5-K12-Q002 – Chunk Processing: ItemReader, ItemProcessor und ItemWriterChunking macht große Arbeit klein genug für Transaktionen.
Frage:

„Wie funktioniert Chunk Processing in Jakarta Batch?“

Kurz erklärt:

Chunk Processing verarbeitet Daten in wiederholten Einheiten: Lesen, optional Verarbeiten, Schreiben und Commit nach einer definierten Chunkgröße.

Musterantwort:

Chunk Processing ist das Standardmodell für große Datenmengen. Ein ItemReader liest einzelne Items, ein ItemProcessor transformiert oder filtert sie, und ein ItemWriter schreibt eine Liste von Items pro Chunk. Nach jedem Chunk wird typischerweise eine Transaktion committed und ein Checkpoint gespeichert.

Das ist wichtig, weil man Millionen Datensätze nicht in einem einzigen Transaktionskontext und nicht vollständig im Speicher verarbeiten sollte. Chunking begrenzt Speicher, macht Commits kontrollierbar und ermöglicht Restart ab einem Checkpoint.

Senior-Antwort:

„Chunk Processing ist Transaktions- und Speicherdisziplin: genug Arbeit pro Commit, aber nicht so viel, dass Fehler teuer werden.“

Häufige Fehler:
  • Alle Daten vorab in eine Liste laden.
  • Pro Item einzeln committen.
  • Chunkgröße ohne Messung wählen.
  • Writer nicht idempotent bauen.
  • CheckpointInfo nicht korrekt speichern.
  • Reader mit nicht serialisierbarem Checkpoint.
  • Processor mit externen Seiteneffekten.
Best Practices:
  • Reader streamend oder cursorbasiert.
  • Processor möglichst zustandsarm.
  • Writer bulk-orientiert.
  • Chunkgröße messen.
  • CheckpointInfo minimal und serialisierbar.
  • Seiteneffekte im Processor vermeiden.
  • Writer idempotent oder deduplizierend.
  • Fehler und Skip-Strategien definieren.
Stolperfalle:

Ein Reader, der beim Restart nicht exakt an der richtigen Stelle weiterliest, erzeugt verlorene oder doppelte Verarbeitung.

Merksatz:

Chunking macht große Arbeit klein genug für Transaktionen.

B5-K12-Q003 – Batchlets: Einfache Steps für technische oder einmalige AktionenBatchlet ist Aufgabe; Chunk ist Durchsatz.
Frage:

„Wann verwenden Sie einen Batchlet Step statt eines Chunk Steps?“

Kurz erklärt:

Batchlets sind einfache Step-Artefakte für taskartige Verarbeitung. Sie eignen sich für einzelne Aktionen, nicht für große itembasierte Datenströme.

Musterantwort:

Einen Batchlet verwende ich für einfache, abgeschlossene Aktionen: Datei verschieben, Report erzeugen, externe API einmal aufrufen, temporäre Tabellen vorbereiten, Cleanup durchführen oder eine technische Validierung ausführen. Chunk Steps verwende ich für wiederholte itembasierte Verarbeitung großer Datenmengen.

Ein Batchlet hat eine process() -Methode und kann einen Exit Status zurückgeben, der im Job Flow genutzt wird. Für lange Batchlets sollte stop() kooperativ implementiert werden, damit Stop-Anforderungen nicht ignoriert werden.

Senior-Antwort:

„Batchlets sind gut für Aufgaben. Chunks sind gut für Datenmengen.“

Häufige Fehler:
  • Große Datenmengen in Batchlets verarbeiten.
  • stop() ignorieren.
  • Keine idempotente Dateioperation.
  • Exit Status nicht bewusst setzen.
  • Exceptions verschlucken.
  • Batchlet mit vielen Verantwortungen überladen.
  • Keine Wiederholbarkeit bei technischen Aktionen.
Best Practices:
  • Batchlet für einzelne technische Aktionen.
  • Chunk für itembasierte Verarbeitung.
  • Exit Status bewusst zurückgeben.
  • stop() bei Langläufern implementieren.
  • Dateioperationen idempotent gestalten.
  • Batchlet klein halten.
  • Seiteneffekte dokumentieren.
  • Fehler nicht still ignorieren.
Stolperfalle:

Ein Batchlet mit einer riesigen Schleife über Millionen Datensätze ist meist ein versteckter, nicht restartbarer Chunk Step.

Merksatz:

Batchlet ist Aufgabe; Chunk ist Durchsatz.

B5-K12-Q004 – Job Parameters, Contexts und Step ScopeParameter machen Batch reproduzierbar.
Frage:

„Wie verwenden Sie Job Parameters und Contexts in Jakarta Batch?“

Kurz erklärt:

Job Parameters konfigurieren konkrete Ausführungen. JobContext und StepContext geben Zugriff auf Job- und Step-Metadaten, Properties und User Data.

Musterantwort:

Job Parameters beschreiben, welche fachliche Ausführung gemeint ist, zum Beispiel businessDate , tenantId , inputFile oder requestedBy . Dadurch wird ein Job reproduzierbar und auditierbar. JobContext liefert Job-weite Informationen und Properties. StepContext liefert Step-spezifische Informationen, Exit Status, Persistent User Data und transienten Step-Kontext.

Ich verwende Parameter nicht als versteckte globale Konfiguration, sondern als Teil der Job-Identität. Ein Daily Settlement für 2026-06-30 ist eine andere fachliche Ausführung als für 2026-07-01 .

Senior-Antwort:

„Ein Batchlauf ohne explizite Parameter ist später kaum erklärbar. Das Systemdatum ist kein fachlicher Jobvertrag.“

Häufige Fehler:
  • Parameter nicht speichern oder auditieren.
  • Business Date aus Systemdatum ableiten.
  • Große Daten in StepContext speichern.
  • Parameter als mutable globale Variablen verwenden.
  • Sensitive Parameter unmaskiert loggen.
  • @BatchProperty und Job Parameters verwechseln.
  • Restart ohne Parameterkonsistenz.
Best Practices:
  • Fachliche Job-Identität über Parameter.
  • Business Date explizit übergeben.
  • Parameter validieren.
  • Parameter auditieren und maskieren.
  • Contexts nur für kleine Metadaten.
  • @BatchProperty für Step-Konfiguration.
  • Keine großen Objekte in Contexts.
  • Restart mit identischen Parametern erzwingen.
Stolperfalle:

Wenn ein Job beim Restart erneut LocalDate.now() verwendet, verarbeitet er möglicherweise einen anderen fachlichen Tag als beim ersten Lauf.

Merksatz:

Parameter machen Batch reproduzierbar.

B5-K12-Q005 – Checkpointing, Restartability und IdempotenzEin restartbarer Job darf dieselbe Arbeit erneut sehen. 24h
Frage:

„Wie machen Sie Batch Jobs restartfähig?“

Kurz erklärt:

Checkpointing speichert Fortschritt. Restartability erlaubt Wiederaufnahme nach Fehlern. Idempotenz verhindert doppelte fachliche Effekte.

Musterantwort:

Restartfähigkeit entsteht durch drei Dinge: korrekte Checkpoints, stabile Eingabedaten und idempotente oder deduplizierende Writer. Der Reader muss nach einem Restart exakt an der richtigen Stelle weiterlesen. Die Eingabedatei oder Datenquelle darf sich zwischen Lauf und Restart nicht unkontrolliert ändern. Der Writer muss damit umgehen, dass ein Chunk bei Rollback oder Crash erneut geschrieben werden kann.

Ich verlasse mich nicht nur auf Checkpointing. Ich baue fachliche Deduplikation ein, zum Beispiel über externe IDs, Unique Constraints, Import-Run-IDs oder Upsert-Strategien.

Senior-Antwort:

„Restartability ist nicht nur ein Framework-Feature. Sie entsteht aus Checkpoint, stabiler Quelle und idempotenter Wirkung.“

Häufige Fehler:
  • Checkpoint zeigt auf falsches Item.
  • Eingabedatei nach Fehler überschreiben.
  • Writer erzeugt Duplikate beim Restart.
  • Seiteneffekte außerhalb der Transaktion.
  • CheckpointInfo nicht serialisierbar.
  • Restart mit anderen Parametern.
  • Keine eindeutigen fachlichen Schlüssel.
Best Practices:
  • CheckpointInfo minimal und exakt.
  • Eingabedaten immutable oder versioniert.
  • Writer idempotent gestalten.
  • Unique Constraints nutzen.
  • Import-Run-ID speichern.
  • Restart-Szenarien testen.
  • Seiteneffekte transaktional bündeln.
  • Fehlerhafte Chunks gezielt analysieren.
Stolperfalle:

Ein korrekter Checkpoint hilft wenig, wenn der Writer beim Wiederholen desselben Chunks doppelte Daten erzeugt.

Merksatz:

Ein restartbarer Job darf dieselbe Arbeit erneut sehen.

B5-K12-Q006 – Fehlerbehandlung: Skip, Retry, Rollback und Exit StatusBatchfehler brauchen Klassifikation, nicht Panik.
Frage:

„Wie behandeln Sie Fehler in Jakarta Batch professionell?“

Kurz erklärt:

Batchfehler müssen klassifiziert werden: skipbare Datenfehler, retrybare temporäre Fehler, rollbackpflichtige technische Fehler und finale Jobfehler.

Musterantwort:

Ich klassifiziere Fehler. Ungültige Eingabedaten sind häufig skipbar, wenn der Job mit anderen Datensätzen fortfahren darf. Temporäre Fehler wie Deadlocks, Timeouts oder kurzfristige Downstream-Probleme sind retrybar. Schwere technische oder konsistenzgefährdende Fehler führen zum Rollback und gegebenenfalls Job Failure.

Wichtig ist, Skip nicht als stilles Ignorieren zu behandeln. Jeder geskipte Datensatz braucht Reject Store, Grund, Input-Referenz und Reporting. Retry braucht Limits und Backoff-Strategie. Exit Status und Batch Status müssen für Betrieb und Monitoring verständlich sein.

Senior-Antwort:

„Skip heißt nicht ignorieren. Skip heißt isolieren, dokumentieren und bewusst weiterarbeiten.“

Häufige Fehler:
  • Alle Fehler skippen.
  • Datenfehler still ignorieren.
  • Retry ohne Limit.
  • Retry für nicht retrybare Fehler.
  • Kein Reject Reporting.
  • Exit Status nicht aussagekräftig.
  • Job als erfolgreich markieren, obwohl Rejects kritisch sind.
Best Practices:
  • Fehlerklassen bewusst definieren.
  • Skip nur mit Reporting.
  • Retry nur für temporäre Fehler.
  • Retry-Limits setzen.
  • Reject Store mit Input-Referenz.
  • Exit Status fachlich aussagekräftig.
  • Fehlerquote überwachen.
  • Abbruchgrenzen definieren.
Stolperfalle:

Ein Job mit vielen geskippten Datensätzen kann technisch erfolgreich, fachlich aber unbrauchbar sein.

Merksatz:

Batchfehler brauchen Klassifikation, nicht Panik.

B5-K12-Q007 – Partitionierung, Parallelisierung und SkalierungPartitionierung skaliert nur unabhängige Arbeit.
Frage:

„Wie skalieren Sie Batch Jobs mit Partitionierung?“

Kurz erklärt:

Partitionierung teilt einen Step in unabhängige Partitionen auf, die parallel verarbeitet werden können. Gute Partition Keys sind entscheidend.

Musterantwort:

Ich partitioniere entlang unabhängiger Datenbereiche: Tenant, Business Unit, Datum, ID-Range, Datei-Split oder Shard. Jede Partition muss möglichst unabhängig sein, damit keine Locks, Duplikate oder Reihenfolgekonflikte entstehen. Der PartitionMapper erzeugt einen Partition Plan mit Partition Properties, die Reader, Processor und Writer pro Partition nutzen.

Parallelisierung ist kein Selbstzweck. Sie muss zu Datenbank, Storage, Locks, Downstream-Systemen und Transaktionsmodell passen. Vier Threads können schneller sein als einer; vierzig Threads können die Datenbank zerstören.

Senior-Antwort:

„Parallelisierung ist nur gut, wenn die Daten unabhängig und die Downstreams belastbar sind.“

Häufige Fehler:
  • Partitionen schreiben dieselben Daten.
  • Threadzahl zu hoch setzen.
  • Hot Tenant ignorieren.
  • Keine partition-spezifischen Checkpoints.
  • Globale Reihenfolge trotz Partitionierung erwarten.
  • Downstream-Limits ignorieren.
  • Keine Metriken pro Partition.
Best Practices:
  • Unabhängige Partition Keys wählen.
  • Threadzahl begrenzen und messen.
  • Partition Properties klar definieren.
  • Skew analysieren.
  • Datenbanklocks beobachten.
  • Metriken pro Partition.
  • Partitionen idempotent verarbeiten.
  • Hotspots gesondert behandeln.
Stolperfalle:

Mehr Threads können einen Batch Job langsamer machen, wenn alle Partitionen auf denselben Indexbereich oder dieselbe Tabelle schreiben.

Merksatz:

Partitionierung skaliert nur unabhängige Arbeit.

B5-K12-Q008 – Listener, Monitoring, Metrics und Job OperationsBatch muss erklärbar sein, auch wenn niemand zugesehen hat.
Frage:

„Welche Observability brauchen produktive Batch Jobs?“

Kurz erklärt:

Listener erfassen Job- und Step-Ereignisse. Monitoring und Job Operations machen Batchläufe im Betrieb sichtbar und steuerbar.

Musterantwort:

Produktive Batch Jobs brauchen Metriken und Logs für Start, Ende, Dauer, Parameter, Status, Exit Status, gelesene Items, verarbeitete Items, geschriebene Items, Skips, Retries, Fehler, Partitionen und Durchsatz. Außerdem braucht der Betrieb Job Operations: laufende Jobs anzeigen, stoppen, restarten und Historie prüfen.

Der JobOperator ist die zentrale Runtime-API für Joboperationen wie Starten und Verwalten von Jobausführungen. Jakarta Batch definiert Java API und JSL genau für solche verwalteten Batchausführungen.

Senior-Antwort:

„Ein Batch Job ohne Metriken ist ein Nachtflug ohne Instrumente.“

Häufige Fehler:
  • Nur Stacktraces loggen.
  • Keine fachlichen Exit Status.
  • Keine Item Counts.
  • Rejects nur im Log.
  • Pro Item zu viel loggen.
  • Keine Job-Historie sichtbar.
  • Betrieb kann Jobs nicht stoppen.
Best Practices:
  • Metriken pro Job, Step und Chunk.
  • Parameter und Execution ID erfassen.
  • Sensitive Parameter maskieren.
  • Fachliche Exit Status setzen.
  • Rejects strukturiert speichern.
  • Pro-Item-Logging vermeiden.
  • Dashboards für Dauer und Durchsatz.
  • Alerts für Failure, lange Laufzeit und hohe Reject-Quote.
Stolperfalle:

Pro-Item-Logging wirkt diagnostisch hilfreich, kann aber bei Millionen Datensätzen den Job selbst zum Problem machen.

Merksatz:

Batch muss erklärbar sein, auch wenn niemand zugesehen hat.

B5-K12-Q009 – Scheduling, Concurrency und Deployment-BetriebBatch Scheduling braucht Exklusivität und Nachvollziehbarkeit.
Frage:

„Wie starten Sie Batch Jobs geplant und sicher in einem Cluster?“

Kurz erklärt:

Batch Jobs brauchen kontrolliertes Scheduling, Exklusivität, Wiederholschutz, Cluster-Koordination und Deployment-Strategien.

Musterantwort:

Ich starte produktive Batch Jobs nicht blind auf jedem Node. In einem Cluster brauche ich Exklusivität oder bewusste Partitionierung. Für einen Daily Settlement Job darf es pro Business Date meist nur eine aktive Ausführung geben. Das erreiche ich über Scheduler-Koordination, Datenbank-Lock, Job-Repository-Prüfung, externe Scheduler oder Plattformmechanismen.

Außerdem definiere ich, was bei Deployment passiert: Werden Jobs gestoppt, zu Ende laufen gelassen oder auf einem anderen Node restarted? Lange Batch Jobs brauchen klare Betriebsregeln.

Senior-Antwort:

„Der schwierigste Scheduler-Bug ist nicht, dass ein Job nicht läuft, sondern dass er zweimal läuft.“

Häufige Fehler:
  • Jeder Node startet denselben Job.
  • Keine Locking-Strategie.
  • Keine Prüfung auf bestehende JobExecution.
  • Business Date aus Systemzeit ohne Zeitzone.
  • Deployment killt laufende Jobs.
  • Verpasste Läufe nicht nachholen.
  • Keine Manuell-Start-Governance.
Best Practices:
  • Scheduler und Joblogik trennen.
  • Cluster-exklusiven Start sicherstellen.
  • Business Date explizit.
  • Doppelte Parameter-Ausführung verhindern.
  • Jobstatus vor Start prüfen.
  • Deployment-Strategie definieren.
  • Verpasste Läufe nachholbar machen.
  • Manuelle Starts auditieren.
Stolperfalle:

Ein Timer pro Node ist in einem Cluster schnell ein mehrfacher Batchstart.

Merksatz:

Batch Scheduling braucht Exklusivität und Nachvollziehbarkeit.

B5-K12-Q010 – Architektur, Datenqualität und Anti-PatternsBatch ist Datenverarbeitung plus Nachweisbarkeit. 24h
Frage:

„Welche Batch-Anti-Patterns sehen Sie in Enterprise-Systemen?“

Kurz erklärt:

Produktionsreife Batcharchitektur umfasst Datenqualität, Staging, Deduplikation, Audit, Wiederholbarkeit, Reject Handling, Monitoring und klare Betriebsprozesse.

Musterantwort:

Typische Anti-Patterns sind: ein riesiger monolithischer Job ohne Steps, keine Parameter, keine Restartfähigkeit, keine Deduplikation, keine Reject-Datei, keine Datenqualitätsmetriken, direkte Verarbeitung produktiver Tabellen ohne Staging, unkontrollierte Parallelisierung, keine Locking-Strategie, keine Exit Status, keine Runbooks und manuelle Korrekturen ohne Audit.

Gute Batcharchitektur trennt Ingestion, Staging, Validation, Processing, Publishing, Reporting und Archiving. Jeder Lauf bekommt eine Run-ID, Business Date, Input-Hash, Status, Zähler und Rejects. So kann man erklären, was verarbeitet wurde, was abgelehnt wurde und ob eine Wiederholung sicher ist.

Senior-Antwort:

„Batchqualität zeigt sich nicht daran, dass der Job endet. Sie zeigt sich daran, dass man danach erklären kann, was genau passiert ist.“

Häufige Fehler:
  • Monolithischer Nachtjob.
  • Keine Run-ID.
  • Keine Input-Hash-Prüfung.
  • Direkte Writes ohne Staging.
  • Rejects nur im Log.
  • Keine fachlichen Qualitätsmetriken.
  • Keine Wiederholbarkeit.
  • Ungeprüfte manuelle Fixes.
Best Practices:
  • Pipeline in klare Phasen schneiden.
  • Run-ID pro Ausführung.
  • Business Date und Input Hash speichern.
  • Staging für externe Daten.
  • Datenqualität messen.
  • Rejects strukturiert ablegen.
  • Deduplikation über fachliche Schlüssel.
  • Outbox für Folgeevents.
Stolperfalle:

Ein grüner technischer Status bedeutet nicht automatisch fachlichen Erfolg.

Merksatz:

Batch ist Datenverarbeitung plus Nachweisbarkeit.

B5-K13 – Kapitel 13: Interceptors / Jakarta Interceptors 10 Karten

B5-K13-Q001 – Grundmodell: Cross-Cutting Concerns und Invocation ChainInterceptors schneiden quer; Fachlogik läuft längs. 24h
Frage:

„Was ist ein Jakarta Interceptor, und wofür verwendet man ihn?“

Kurz erklärt:

Interceptors schalten sich um Methodenaufrufe oder Lifecycle-Ereignisse und erlauben Cross-Cutting Logic, ohne Fachmethoden direkt zu verschmutzen.

Musterantwort:

Ein Jakarta Interceptor ist eine Komponente, die sich in eine Invocation Chain einschaltet. Er kann vor und nach einem Methodenaufruf Logik ausführen, Parameter inspizieren oder verändern, das Ergebnis verarbeiten, Exceptions behandeln oder den Aufruf abbrechen. Jakarta Interceptors definiert genau dieses Interposing auf Business-Methodenaufrufe und bestimmte Lifecycle- oder Timeout-Events.

Typische Einsatzfälle sind Logging, Auditing, Metrics, Tracing, Security Checks, Validation, Rate Limiting oder technische Policies. Ich verwende Interceptors für Querschnittslogik, aber nicht für Kernfachlogik. Fachentscheidungen sollen im Use Case oder der Domain sichtbar bleiben.

Senior-Antwort:

„Interceptors sind gut für technische Querschnittslogik rund um managed method calls. Sobald eine fachliche Entscheidung verborgen wird, wird es gefährlich.“

Häufige Fehler:
  • Businessregeln in Interceptors verstecken.
  • context.proceed() vergessen.
  • Exceptions verschlucken.
  • Sensitive Parameter loggen.
  • Interceptor-Reihenfolge ignorieren.
  • Teure Remote Calls im Interceptor.
  • Interceptors auf nicht-managed Klassen erwarten.
Best Practices:
  • Interceptors für technische Querschnittslogik.
  • Fachlogik in Use Cases und Domain lassen.
  • context.proceed() bewusst genau einmal oder bewusst gar nicht aufrufen.
  • Exceptions transparent weiterreichen.
  • Sensitive Daten maskieren.
  • Reihenfolge dokumentieren.
  • Interceptors klein und testbar halten.
  • Performance messen.
Stolperfalle:

Ein Interceptor ohne context.proceed() ruft die Zielmethode nicht auf. Das kann gewollt sein, ist aber meistens ein schwerer Fehler.

Merksatz:

Interceptors schneiden quer; Fachlogik läuft längs.

B5-K13-Q002 – Interceptor Binding und Aktivierung mit @InterceptorBindingBindings verbinden Absicht mit Invocation Chain.
Frage:

„Wie funktioniert ein Interceptor Binding?“

Kurz erklärt:

Interceptor Bindings sind Annotationen, mit denen Interceptors an Zielklassen oder Zielmethoden gebunden werden.

Musterantwort:

Ein Interceptor Binding ist eine Annotation, die mit @InterceptorBinding markiert ist. Diese Annotation wird auf einen Interceptor und auf die Zielklasse oder Zielmethode gesetzt. Dadurch weiß der Container, welcher Interceptor für welchen Aufruf gelten soll.

Das Binding kann Parameter enthalten, etwa action oder resourceType . Damit kann der Interceptor kontextualisiert werden. Allerdings sollte man Binding-Parameter nicht überladen. Ein Audit-Interceptor darf technische Audit-Metadaten nutzen, aber nicht die gesamte Fachlogik aus Annotationen ableiten.

Senior-Antwort:

„Bindings machen Querschnittsverhalten sichtbar. Sie sollten deklarieren, nicht programmieren.“

Häufige Fehler:
  • Binding-Annotation ohne Runtime Retention.
  • Falsche Targets.
  • Interceptor nicht aktiviert.
  • Binding-Parameter als Businessregel missbrauchen.
  • Annotation auf nicht-managed Klasse.
  • Method-Level und Class-Level Semantik nicht definiert.
  • Audit direkt synchron und langsam persistieren.
Best Practices:
  • Bindings sprechend benennen.
  • Runtime Retention setzen.
  • Type und Method Target bewusst wählen.
  • Binding-Parameter klein halten.
  • Interceptors mit @Priority gezielt aktivieren.
  • Method-Level Overrides dokumentieren.
  • Technische Metadaten von Fachregeln trennen.
  • Interceptor-Verhalten testen.
Stolperfalle:

Eine Interceptor-Binding-Annotation ohne @Retention(RUNTIME) wird zur Laufzeit nicht wirksam.

Merksatz:

Bindings verbinden Absicht mit Invocation Chain.

B5-K13-Q003 – InvocationContext : Parameter, Context Data, Target und proceed()InvocationContext ist Kontext und Kontrollpunkt zugleich.
Frage:

„Welche Informationen liefert InvocationContext , und wie verwenden Sie sie sicher?“

Kurz erklärt:

InvocationContext gibt Zugriff auf Zielmethode, Target, Parameter, Context Data und steuert mit proceed() den weiteren Aufruf.

Musterantwort:

InvocationContext liefert die abgefangene Methode, das Target-Objekt, die Parameter, Context Data und die Möglichkeit, die Invocation Chain mit proceed() fortzusetzen. Die API-Dokumentation beschreibt unter anderem getMethod() , getParameters() , setParameters() und getContextData() ; getContextData() erlaubt Interceptors, Daten mit anderen Interceptors oder dem Invocation-Kontext zu teilen.

Ich verwende diese Möglichkeiten vorsichtig. Parameter zu verändern ist mächtig, aber kann Aufrufverträge überraschend verändern. Context Data ist gut für technische Metadaten wie Correlation-ID oder Tenant-ID innerhalb derselben Chain.

Senior-Antwort:

„ InvocationContext gibt mir viel Macht. Senior-Code nutzt diese Macht sparsam und vorhersehbar.“

Häufige Fehler:
  • Parameter unbemerkt verändern.
  • proceed() mehrfach ohne klare Absicht aufrufen.
  • contextData als globalen Speicher missverstehen.
  • Teure Reflection in jedem Call.
  • Exceptions verschlucken.
  • Falsche Casts bei Parametern.
  • Context Data über Threadgrenzen erwarten.
Best Practices:
  • InvocationContext nur gezielt nutzen.
  • Parameteränderung vermeiden oder sehr klar dokumentieren.
  • Context Data für technische Metadaten.
  • Annotation-Zugriff effizient halten.
  • proceed() -Semantik testen.
  • Target nicht als Fachmodell missbrauchen.
  • Context Data nicht über Async-Grenzen erwarten.
  • Fehlermeldungen mit Methodenkontext versehen.
Stolperfalle:

setParameters() kann den tatsächlich aufgerufenen Use Case verändern. Das sollte fast nie beiläufig passieren.

Merksatz:

InvocationContext ist Kontext und Kontrollpunkt zugleich.

B5-K13-Q004 – Ordering, @Priority und Interceptor Chain DesignDie Chain ist Architektur, nicht Zufall.
Frage:

„Wie kontrollieren Sie die Reihenfolge mehrerer Interceptors?“

Kurz erklärt:

Mehrere Interceptors bilden eine Chain. @Priority und Aktivierungsmechanismen bestimmen, welche Interceptors in welcher Reihenfolge ausgeführt werden.

Musterantwort:

Ich definiere eine klare Interceptor-Reihenfolge. Typischerweise kommen Tracing und Correlation früh, dann Tenant-/Security-Prüfungen, dann Validation oder Rate Limiting, dann Business Call und Auditing/Metrics um den gesamten Aufruf. @Priority kann genutzt werden, um Interceptors zu aktivieren und ihre relative Reihenfolge festzulegen.

Wichtig ist, die Reihenfolge nicht zufällig entstehen zu lassen. Wenn Audit vor Security läuft, kann man andere Events sehen als wenn Security vor Audit läuft. Wenn Metrics zu weit innen sitzt, misst man nicht die gesamte Pipeline.

Senior-Antwort:

„Interceptor-Reihenfolge ist kein Detail. Sie entscheidet, was gemessen, geprüft und auditiert wird.“

Häufige Fehler:
  • Reihenfolge unklar lassen.
  • Metrics zu weit innen messen.
  • Audit vor Kontextaufbau.
  • Security nach Businesslogik.
  • Mehrere Interceptors machen dieselbe Arbeit.
  • Implizite Ordering-Abhängigkeiten nicht dokumentieren.
  • Tests prüfen nur einzelne Interceptors.
Best Practices:
  • Interceptor Chain explizit designen.
  • Prioritätsbereiche definieren.
  • Correlation und Tracing früh.
  • Security und Tenant vor Business.
  • Audit und Metrics bewusst positionieren.
  • Reihenfolge dokumentieren.
  • Integrationstests für Chain.
  • Interceptors klein und orthogonal halten.
Stolperfalle:

Der äußerste Interceptor sieht auch die Zeit und Fehler aller inneren Interceptors. Das kann gewollt oder falsch sein.

Merksatz:

Die Chain ist Architektur, nicht Zufall.

B5-K13-Q005 – Lifecycle Interceptors: @PostConstruct , @PreDestroy und @AroundConstructLifecycle Interception greift ins Objektleben ein.
Frage:

„Welche Lifecycle-Ereignisse können Interceptors behandeln?“

Kurz erklärt:

Lifecycle Interceptors können Konstruktion, Initialisierung und Zerstörung managed objects umrahmen.

Musterantwort:

Jakarta Interceptors kann nicht nur Business-Methoden, sondern auch bestimmte Lifecycle-Ereignisse intercepteren. Dazu gehören Konstruktion über @AroundConstruct , Initialisierung über @PostConstruct und Zerstörung über @PreDestroy . Die Spezifikation beschreibt Interceptors für Business-Methodenaufrufe und bestimmte Ereignisse wie Lifecycle- und Timeout-Events.

Ich verwende Lifecycle Interceptors selten und gezielt, zum Beispiel für Diagnostik, Framework-Integration oder technische Initialisierungsprüfungen. Fachliche Initialisierung gehört normalerweise in explizite Komponentenlogik.

Senior-Antwort:

„Lifecycle Interceptors berühren das Objektleben selbst. Ich nutze sie selten, aber dann sehr bewusst.“

Häufige Fehler:
  • Fachlogik im Lifecycle Interceptor.
  • Startup-Fehler schwer diagnostizierbar machen.
  • Ressourcen zu früh oder zu spät verwenden.
  • context.proceed() vergessen.
  • Lifecycle-Interceptors für normale Methodenlogik missbrauchen.
  • Unterschiede zwischen Komponentenmodellen ignorieren.
Best Practices:
  • Lifecycle Interceptors sparsam verwenden.
  • Nur technische Lifecycle-Concerns.
  • Startup-/Shutdown-Fehler klar loggen.
  • proceed() bewusst aufrufen.
  • Keine Remote Calls im Startup ohne Timeout.
  • Ressourcenverantwortung klar halten.
  • Verhalten in Zielruntime testen.
  • Dokumentieren, welche Beans betroffen sind.
Stolperfalle:

Ein Fehler im Lifecycle Interceptor kann verhindern, dass die Anwendung korrekt startet oder stoppt.

Merksatz:

Lifecycle Interception greift ins Objektleben ein.

B5-K13-Q006 – Exception Handling, Retry und TransaktionsgrenzenRetry wiederholt Wirkung, nicht nur Code.
Frage:

„Ist ein Retry-Interceptor eine gute Idee?“

Kurz erklärt:

Interceptors können Exceptions beobachten, transformieren oder Retry ausführen. Dabei sind Transaktionsgrenzen und Nebenwirkungen kritisch.

Musterantwort:

Ein Retry-Interceptor kann sinnvoll sein, wenn er auf klar definierte, temporäre, idempotente Operationen angewendet wird. Er ist gefährlich, wenn er beliebige Methoden erneut ausführt. Wiederholungen können doppelte Zahlungen, doppelte Events oder inkonsistente Seiteneffekte erzeugen.

Besonders wichtig sind Transaktionsgrenzen. Wenn der Retry innerhalb derselben Transaktion läuft, kann eine bereits markierte Rollback-only-Transaktion nicht einfach geheilt werden. Häufig braucht jeder Versuch eine neue Transaktion oder der Retry muss außerhalb der transaktionalen Methode liegen. Für komplexe Resilience-Policies sind spezialisierte Resilience-Frameworks oft transparenter.

Senior-Antwort:

„Retry ist keine Fehlerbehandlung, sondern Wiederholung von Wirkung. Deshalb ist Idempotenz die Eintrittskarte.“

Häufige Fehler:
  • Retry auf nicht-idempotenten Methoden.
  • Alle Exceptions retryen.
  • Kein Backoff.
  • Retry innerhalb rollback-only Transaktion.
  • Exceptions in andere Typen ohne Cause umwandeln.
  • Auditing zählt nur letzten Versuch.
  • Retry-Storm erzeugen.
Best Practices:
  • Nur temporäre Fehler retryen.
  • Idempotenz voraussetzen.
  • Max Attempts definieren.
  • Backoff und Jitter nutzen.
  • Transaktionsgrenzen bewusst gestalten.
  • Exceptions nicht verschlucken.
  • Versuche metrisch erfassen.
  • Retry nicht für Businessvalidierung.
Stolperfalle:

Ein Retry-Interceptor kann eine Methode mehrfach ausführen. Das ist bei Zahlungen, E-Mails oder Event-Publishing gefährlich.

Merksatz:

Retry wiederholt Wirkung, nicht nur Code.

B5-K13-Q007 – Security, Auditing und Policy EnforcementInterceptors erzwingen Policies; sie sollten sie nicht verstecken.
Frage:

„Sollte man Security Checks mit Interceptors implementieren?“

Kurz erklärt:

Interceptors können Security- und Auditing-Policies erzwingen, sollten aber nicht zu unsichtbarer Fachautorisierung führen.

Musterantwort:

Für technische und deklarative Zugriffsschichten können Interceptors sinnvoll sein: @RequiresPermission("invoice:read") kann einen zentralen Policy-Check auslösen. Trotzdem muss man vorsichtig sein. Sicherheitsregeln müssen sichtbar, testbar und korrekt positioniert sein. Für objektbezogene Autorisierung braucht der Interceptor oft Resource IDs und fachlichen Kontext. Wenn dieser Kontext komplex wird, ist eine explizite Access Policy im Use Case oft lesbarer.

Interceptors sind gut für konsistente Policy Enforcement Points. Sie sind schlecht, wenn sie verstecken, warum eine fachliche Aktion erlaubt oder verboten ist.

Senior-Antwort:

„Ein Security-Interceptor ist ein Policy Enforcement Point. Die Policy selbst sollte klar modelliert und testbar bleiben.“

Häufige Fehler:
  • Security nur im Interceptor und nicht getestet.
  • Resource ID falsch extrahieren.
  • Kein Tenant-Kontext.
  • Self-Invocation umgeht Prüfung.
  • Zu grobe Permissions.
  • Security Exceptions unsauber mappen.
  • Policy-Cache ohne Invalidierung.
Best Practices:
  • Security-Interceptors für klare, wiederkehrende Checks.
  • Fachliche Access Policy zentral halten.
  • Resource-ID-Konventionen definieren.
  • Tenant immer berücksichtigen.
  • Negative Tests schreiben.
  • Self-Invocation vermeiden.
  • Audit von Denies ergänzen.
  • Kritische Checks explizit im Use Case absichern.
Stolperfalle:

Eine Methode innerhalb derselben Klasse aufzurufen kann Interceptor-basierte Security umgehen, wenn der Aufruf nicht über den Container-Proxy läuft.

Merksatz:

Interceptors erzwingen Policies; sie sollten sie nicht verstecken.

B5-K13-Q008 – Transactions, CDI, EJB und Self-InvocationInterceptors greifen an Containergrenzen, nicht an jedem Java-Aufruf.
Frage:

„Warum ist Self-Invocation bei Interceptors problematisch?“

Kurz erklärt:

Interceptors wirken auf managed invocations. Direkte Self-Invocation innerhalb derselben Instanz kann Interceptor-, Transaction- oder Security-Mechanismen umgehen.

Musterantwort:

Interceptors werden durch den Container auf managed invocations angewendet. Wenn eine Methode innerhalb derselben Klasse direkt eine andere Methode derselben Instanz aufruft, läuft dieser Aufruf möglicherweise nicht durch den Container-Proxy oder die Interceptor Chain. Dadurch greifen Interceptors, Transactions, Security oder Metrics nicht wie erwartet.

Deshalb strukturiere ich Services so, dass intercepted Methoden über managed Referenzen aufgerufen werden. Alternativ ziehe ich die Methode in eine separate Bean oder verwende explizite Programmatik, wenn Self-Invocation unvermeidbar ist.

Senior-Antwort:

„Wenn ein Framework über Proxies arbeitet, ist der Weg zum Objekt genauso wichtig wie die Methode selbst.“

Häufige Fehler:
  • Intercepted Methode intern direkt aufrufen.
  • Private Methoden annotieren und Wirkung erwarten.
  • Package-private Methoden mit unklarem Proxy-Verhalten.
  • Transaktionsgrenzen durch Refactoring brechen.
  • Tests instanziieren Klassen direkt mit new .
  • Interceptor-Wirkung nicht im Integrationstest prüfen.
  • Self-Invocation bei Security Checks.
Best Practices:
  • Intercepted Methoden über managed Bean aufrufen.
  • Kritische intercepted Methoden public und klar erreichbar halten.
  • Separate Beans für unterschiedliche Transaktionsgrenzen.
  • Integrationstests mit Container.
  • Keine Security nur auf intern aufgerufenen Methoden.
  • Proxy-Grenzen dokumentieren.
  • Direkte Instanziierung in Tests vermeiden.
  • Architekturregeln für Service-Schnitt festlegen.
Stolperfalle:

Eine Methode kann korrekt annotiert sein und trotzdem nicht intercepted werden, wenn sie intern direkt aufgerufen wird.

Merksatz:

Interceptors greifen an Containergrenzen, nicht an jedem Java-Aufruf.

B5-K13-Q009 – Interceptors vs Decorators, Filters, Observers und AOPCross-Cutting braucht Ebenendisziplin.
Frage:

„Wann verwenden Sie Interceptors, und wann besser Filter, Decorator oder Observer?“

Kurz erklärt:

Interceptors sind nur ein Mechanismus für Cross-Cutting Concerns. Filter, Decorators, Observers und explizite Services haben andere Einsatzbereiche.

Musterantwort:

Servlet oder JAX-RS Filter verwende ich auf HTTP-Ebene: Headers, CORS, Request Logging, Authentication-Boundary, Response-Handling. Interceptors verwende ich für managed method invocations: Metrics, Auditing, technische Security, Tenant Checks. CDI Decorators verwende ich, wenn ich gezielt ein Business-Interface dekorieren und fachnahes Verhalten ergänzen möchte. Observers verwende ich, wenn etwas auf ein Event reagieren soll, ohne den Aufruf zu umrahmen.

Wenn die Logik fachlich ist, ist ein expliziter Service oft besser als jede Querschnittsmagie.

Senior-Antwort:

„Das richtige Werkzeug hängt von der Ebene ab: HTTP, Method Invocation, Interface oder Event.“

Häufige Fehler:
  • HTTP-Logik im Interceptor.
  • Fachlogik im Filter.
  • Interceptor für interface-spezifisches Verhalten missbrauchen.
  • Observer für synchrone Pflichtlogik.
  • Decorator für globale technische Policies.
  • Zu viele Cross-Cutting-Mechanismen kombiniert.
  • Wirkung nicht dokumentiert.
Best Practices:
  • Mechanismus nach Ebene wählen.
  • HTTP-Themen in Filter.
  • Methodenthemen in Interceptor.
  • Interface-Verhalten in Decorator.
  • Eventreaktionen in Observer.
  • Fachregeln explizit halten.
  • Architekturregeln dokumentieren.
  • Cross-Cutting Code testen.
Stolperfalle:

Ein Interceptor ist kein Ersatz für einen klaren Use Case. Wenn die Regel fachlich ist, sollte sie explizit sichtbar sein.

Merksatz:

Cross-Cutting braucht Ebenendisziplin.

B5-K13-Q010 – Anti-Patterns, Testbarkeit und ProduktionsreifeIndirektes Verhalten braucht direkte Tests. 24h
Frage:

„Welche Interceptor-Anti-Patterns kennen Sie?“

Kurz erklärt:

Produktionsreife Interceptors sind klein, deterministisch, sichtbar, testbar, performant und haben klare Verantwortungen.

Musterantwort:

Typische Anti-Patterns sind: Businesslogik im Interceptor, unklare Reihenfolge, Exceptions verschlucken, Parameter verändern, Remote Calls ohne Timeout, sensitive Daten loggen, Interceptor auf Hot Paths ohne Messung, Security nur über Interceptor und ohne negative Tests, Self-Invocation ignorieren, Interceptor-Annotationen als versteckte Regelsprache und fehlende Integrationstests.

Produktionsreife Interceptors sind klein und fokussiert. Sie haben klare Bindings, dokumentierte Priorität, Tests für Erfolg und Fehler, sichere Logging-Strategie, Metriken und keine überraschenden Seiteneffekte.

Senior-Antwort:

„Interceptors sind produktionsreif, wenn ich ihre Wirkung erklären, messen und testen kann.“

Häufige Fehler:
  • Interceptor macht zu viel.
  • Businessentscheidungen in Annotationen.
  • Keine Integrationstests.
  • Keine negativen Security-Tests.
  • Sensitive Daten im Log.
  • Exceptions in RuntimeException ohne Cause.
  • Reihenfolge nicht dokumentiert.
  • Kein Performance-Budget.
Best Practices:
  • Single Responsibility pro Interceptor.
  • Klare Bindings.
  • Priorität dokumentieren.
  • Erfolg, Fehler und Edge Cases testen.
  • Integrationstests mit Container.
  • Keine sensitive Ausgabe.
  • Keine unlimitierten Remote Calls.
  • Metriken für Interceptor-Overhead.
Stolperfalle:

Ein Unit-Test der Zielklasse mit new beweist nicht, dass Interceptors in der Runtime greifen.

Merksatz:

Indirektes Verhalten braucht direkte Tests.

B5-K14 – Kapitel 14: EJB / Jakarta Enterprise Beans 10 Karten

B5-K14-Q001 – Grundmodell: Enterprise Bean Container, Session Beans und ServicesEJB liefert Containerdienste; Architektur liefert Grenzen. 24h
Frage:

„Was ist Jakarta Enterprise Beans, und welche Rolle spielt der Enterprise Bean Container?“

Kurz erklärt:

Enterprise Beans sind containerverwaltete Business-Komponenten. Der Container stellt Infrastruktur wie Transaktionen, Security, Pooling, Concurrency, Lifecycle, Remoting und Messaging-Integration bereit.

Musterantwort:

Jakarta Enterprise Beans definiert ein Komponentenmodell für Business-Services in Jakarta EE. Der Enterprise Bean Container verwaltet Instanzen, Lebenszyklus, Transaktionen, Security, Concurrency, Remoting und Integration mit anderen Jakarta-EE-Services. Das Jakarta-EE-Tutorial beschreibt den Nutzen von Enterprise Beans unter anderem damit, dass der Container system-level services wie Transaction Management und Security Authorization übernimmt, sodass sich Bean-Entwickler auf Businessprobleme konzentrieren können.

Die wichtigsten Bean-Typen sind Session Beans und Message-Driven Beans. Session Beans modellieren synchrone Business-Services. Message-Driven Beans konsumieren asynchron Nachrichten, typischerweise aus Jakarta Messaging. In modernen Anwendungen sollte man Enterprise Beans gezielt einsetzen und nicht als Ersatz für sauberes Domain-Design verstehen.

Senior-Antwort:

„EJB ist ein Container-Service-Modell. Ich nutze es dort, wo ich containerverwaltete Enterprise-Funktionen brauche — nicht als Ersatz für gute Fachmodellierung.“

Häufige Fehler:
  • EJB als Domain-Modell missverstehen.
  • Businesslogik über viele technische Beans verteilen.
  • Containergrenzen durch new umgehen.
  • Remote Views unnötig nutzen.
  • Transaktionsverhalten nicht verstehen.
  • Security nur implizit über Rollen annotieren.
  • Legacy-EJB unkritisch kopieren.
Best Practices:
  • Enterprise Beans als Application-Service-Komponenten nutzen.
  • Domainlogik klar kapseln.
  • CDI und EJB bewusst kombinieren.
  • Transaktionsgrenzen explizit designen.
  • Security-Regeln testen.
  • Remote Interfaces vermeiden, wenn lokal reicht.
  • Containerverhalten in Integrationstests prüfen.
  • Migration zu CDI/JTA prüfen, wenn EJB-Features nicht benötigt werden.
Stolperfalle:

Eine Enterprise Bean ist kein magisches Domain-Objekt. Sie ist eine managed Application-Komponente mit Container-Semantik.

Merksatz:

EJB liefert Containerdienste; Architektur liefert Grenzen.

B5-K14-Q002 – Stateless Session Beans und PoolingStateless heißt nicht feldlos, sondern nicht client-zustandsbehaftet.
Frage:

„Was bedeutet @Stateless wirklich?“

Kurz erklärt:

Stateless Session Beans sind containerverwaltete, typischerweise gepoolte Business-Komponenten ohne client-spezifischen Conversational State.

Musterantwort:

@Stateless bedeutet nicht, dass die Bean keine Felder haben darf. Es bedeutet, dass sie keinen client-spezifischen Conversational State zwischen Aufrufen halten darf. Der Container kann Instanzen poolen und beliebige freie Instanzen für verschiedene Clients verwenden.

Deshalb müssen Stateless Beans thread-safe im Sinne ihrer geteilten Abhängigkeiten sein und dürfen keine request- oder caller-spezifischen Daten in Instanzfeldern speichern. Lokale Variablen pro Methode sind sicher. Injected Services sind okay, wenn sie selbst korrekt scoped und thread-safe sind.

Senior-Antwort:

„Stateless bedeutet: Der Container darf mich austauschen. Alles, was ich für den Use Case brauche, muss im Aufruf oder in stabilen Abhängigkeiten liegen.“

Häufige Fehler:
  • Userdaten in Instanzfeldern speichern.
  • Pooling als Performancegarantie missverstehen.
  • Remote Gateway ohne Timeout aufrufen.
  • Poolgröße zu hoch setzen.
  • Transaktion über lange externe Calls offen halten.
  • Mutable Singleton-Abhängigkeiten ohne Thread-Safety.
  • Stateless Bean mit Conversational Workflow überladen.
Best Practices:
  • Kein client-spezifischer Zustand in Feldern.
  • Use-Case-Daten pro Methode laden.
  • Externe Calls mit Timeout.
  • Poolgröße als Lastkontrolle nutzen.
  • Transaktionsgrenzen bewusst setzen.
  • Idempotenz bei externen Wirkungen.
  • Stateless Beans klein halten.
  • Integrationstests mit echter Runtime.
Stolperfalle:

Ein Feld wie currentUserId in einer Stateless Bean ist ein Race Condition und Datenleck.

Merksatz:

Stateless heißt nicht feldlos, sondern nicht client-zustandsbehaftet.

B5-K14-Q003 – Stateful Session Beans und Conversational StateStateful EJB speichert Gesprächszustand, nicht Geschäftsvergessenheit.
Frage:

„Wann würden Sie eine @Stateful Session Bean verwenden?“

Kurz erklärt:

Stateful Session Beans halten client-spezifischen Conversational State über mehrere Methodenaufrufe hinweg. Sie sind mächtig, aber skalierungs- und lifecycle-sensibel.

Musterantwort:

Eine Stateful Session Bean verwende ich, wenn ein client-spezifischer Conversation State über mehrere Aufrufe hinweg containerverwaltet gehalten werden soll, etwa ein mehrstufiger Wizard oder ein klassischer Checkout-Dialog in einer serverseitigen Anwendung. Der Container hält die Bean einem Client zugeordnet und kann Passivation/Aktivierung unterstützen.

In modernen Web- und Microservice-Architekturen vermeide ich Stateful Beans meistens. Stattdessen speichere ich Conversation State explizit in Datenbank, Cache oder Client-seitigen Tokens. Das ist transparenter, clusterfreundlicher und besser kontrollierbar.

Senior-Antwort:

„Stateful Beans können Conversations elegant modellieren, aber sie kaufen diese Eleganz mit serverseitigem Zustand.“

Häufige Fehler:
  • Große Objektgraphen in Stateful Beans halten.
  • @Remove vergessen.
  • Stateful Beans für REST APIs nutzen.
  • Cluster-Failover ignorieren.
  • Nicht serialisierbare Zustände halten.
  • Conversation State und Domain State vermischen.
  • Lange offene Conversations ohne Timeout.
Best Practices:
  • Stateful Beans sparsam verwenden.
  • Conversation State klein halten.
  • @Remove konsequent nutzen.
  • Timeouts definieren.
  • Clusterfähigkeit prüfen.
  • Domain State persistieren, nicht verstecken.
  • Für REST expliziten Zustand bevorzugen.
  • Alternative mit Datenbank/Cache prüfen.
Stolperfalle:

Eine Stateful Bean ist kein Ersatz für persistierten Geschäftsprozesszustand, wenn Wiederaufnahme, Audit oder Cluster-Failover wichtig sind.

Merksatz:

Stateful EJB speichert Gesprächszustand, nicht Geschäftsvergessenheit.

B5-K14-Q004 – Singleton Session Beans, Startup und ConcurrencySingleton ist lokal einfach, verteilt gefährlich.
Frage:

„Wann verwenden Sie eine @Singleton Session Bean, und wie steuern Sie Concurrency?“

Kurz erklärt:

Singleton Session Beans haben genau eine Bean-Instanz pro Anwendung und können beim Start initialisiert werden. Concurrency muss bewusst gesteuert werden.

Musterantwort:

Eine Singleton Session Bean verwende ich für containerverwalteten, gemeinsam genutzten Anwendungszustand oder koordinierende Services, etwa Cache, Scheduler-Koordination, In-Memory-Registry oder Startup-Initialisierung. Mit @Startup kann sie beim Deployment initialisiert werden.

Concurrency ist kritisch. Der Container kann Locking steuern, etwa @Lock(READ) für parallele Lesezugriffe und @Lock(WRITE) für exklusive Änderungen. Alternativ kann Bean-Managed Concurrency verwendet werden, dann ist der Entwickler vollständig verantwortlich.

Senior-Antwort:

„Ein EJB-Singleton ist ein Singleton pro Anwendungskontext, nicht automatisch ein Singleton im gesamten Cluster.“

Häufige Fehler:
  • Singleton als globalen Datenbankersatz verwenden.
  • Clusterweite Eindeutigkeit annehmen.
  • Mutable State ohne Locking.
  • Lange Operationen unter WRITE-Lock.
  • Startup mit Remote Calls ohne Timeout.
  • Cache ohne Eviction.
  • Singleton für tenant-spezifischen Zustand.
Best Practices:
  • Singleton nur für klar begrenzten gemeinsamen Zustand.
  • Clustergrenzen explizit beachten.
  • Container-managed locks bewusst setzen.
  • Lange Arbeit nicht unter Lock halten.
  • Startup-Fehler klar behandeln.
  • Cache-Größe begrenzen.
  • Für verteilten Zustand externen Store nutzen.
  • Concurrency testen.
Stolperfalle:

@Singleton löst keine verteilte Koordination zwischen mehreren Nodes.

Merksatz:

Singleton ist lokal einfach, verteilt gefährlich.

B5-K14-Q005 – Container-Managed Transactions und Transaction AttributesTransaktionsattribute sind Architekturentscheidungen. 24h
Frage:

„Wie funktionieren Container-Managed Transactions in EJB?“

Kurz erklärt:

Container-Managed Transactions erlauben, Transaktionssemantik deklarativ über Transaction Attributes zu steuern.

Musterantwort:

Bei Container-Managed Transactions steuert der EJB-Container Transaktionsbeginn, Commit, Rollback und Propagation. Über @TransactionAttribute definiert man, wie eine Methode mit vorhandenen Transaktionen umgeht. Typische Attribute sind REQUIRED , REQUIRES_NEW , SUPPORTS , MANDATORY , NOT_SUPPORTED und NEVER .

REQUIRED ist der Standardfall: Eine vorhandene Transaktion wird genutzt, sonst wird eine neue gestartet. REQUIRES_NEW suspendiert eine bestehende Transaktion und startet eine neue. Das ist zum Beispiel für Audit verlockend, aber gefährlich: Audit kann committed werden, obwohl die Haupttransaktion später rollbackt. Solche Semantik muss bewusst sein.

Senior-Antwort:

„CMT nimmt mir Boilerplate ab, aber nicht die Verantwortung für Transaktionssemantik.“

Häufige Fehler:
  • REQUIRES_NEW unkritisch verwenden.
  • Remote Calls in langer DB-Transaktion.
  • Self-Invocation bei transaktionalen Methoden.
  • Checked Exceptions und Rollback-Regeln falsch verstehen.
  • Audit inkonsistent committen.
  • Transaktionsattribute nicht testen.
  • Transaktionen über UI- oder Batch-Grenzen ziehen.
Best Practices:
  • REQUIRED als Standard bewusst nutzen.
  • REQUIRES_NEW sparsam.
  • Transaktionen kurz halten.
  • Externe Calls außerhalb DB-Transaktion prüfen.
  • Rollback-Regeln explizit verstehen.
  • Self-Invocation vermeiden.
  • Integrationstests für Transaktionen.
  • Outbox für Event Publishing bevorzugen.
Stolperfalle:

REQUIRES_NEW kann Daten dauerhaft schreiben, obwohl der fachliche Hauptvorgang später fehlschlägt.

Merksatz:

Transaktionsattribute sind Architekturentscheidungen.

B5-K14-Q006 – Message-Driven Beans und asynchrone VerarbeitungMDB verbindet Messaging mit Containertransaktionen.
Frage:

„Was ist eine Message-Driven Bean, und warum gehört sie zu EJB?“

Kurz erklärt:

Message-Driven Beans sind Enterprise Beans für asynchrone Message-Verarbeitung. Sie integrieren Messaging, Transaktionen, Pooling und Container-Lifecycle.

Musterantwort:

Eine Message-Driven Bean ist eine containerverwaltete Komponente, die asynchron Nachrichten verarbeitet, typischerweise aus Jakarta Messaging. Sie implementiert häufig MessageListener , wird mit @MessageDriven konfiguriert und erhält Messages über den Container.

MDBs gehören zu Enterprise Beans, weil sie das EJB-Container-Modell für asynchrone Verarbeitung nutzen: Pooling, Transaktionen, Security-Kontext, Resource Management und Redelivery-Integration. Jakarta Messaging und Jakarta Enterprise Beans ergänzen sich hier sehr stark.

Senior-Antwort:

„Eine MDB ist ein Messaging-Adapter mit Containerdiensten. Die fachliche Verarbeitung bleibt in einem separaten Use Case.“

Häufige Fehler:
  • Businesslogik direkt in onMessage .
  • Keine Idempotenz.
  • Exceptions schlucken.
  • Keine DLQ-Strategie.
  • Poolgröße blind erhöhen.
  • Message Properties nicht validieren.
  • Keine Metriken pro Message-Typ.
Best Practices:
  • MDB als Adapter.
  • Processor als testbarer Use Case.
  • Messages validieren.
  • Idempotenz über Command-ID.
  • Exceptions für Redelivery korrekt propagieren.
  • DLQ konfigurieren.
  • Poolgröße abstimmen.
  • Monitoring für Redelivery und Fehler.
Stolperfalle:

Eine Exception in onMessage ist oft gewollt, damit der Container Rollback und Redelivery auslösen kann. Sie einfach zu schlucken ist gefährlich.

Merksatz:

MDB verbindet Messaging mit Containertransaktionen.

B5-K14-Q007 – Timers, Scheduling und @ScheduleScheduling ohne Exklusivität ist ein Risiko.
Frage:

„Wie funktionieren EJB Timer, und worauf achten Sie im Cluster?“

Kurz erklärt:

EJB Timer ermöglichen containerverwaltetes Scheduling. @Schedule deklariert automatische Timer-Methoden.

Musterantwort:

EJB Timer erlauben geplante Ausführung containerverwalteter Methoden. Mit @Schedule kann man deklarativ Zeitpläne definieren, etwa täglich um 02:15 Uhr. Persistent Timer überleben Server-Restarts, abhängig von Container und Konfiguration.

Im Cluster muss man besonders vorsichtig sein. Je nach Runtime und Konfiguration kann Scheduling clusterkoordiniert sein oder pro Node wirken. Für kritische Jobs verwende ich zusätzliche Exklusivität: Datenbank-Lock, Job-Repository-Prüfung oder externen Scheduler. Der Timer sollte nicht selbst die komplette Batchlogik enthalten, sondern einen Job Launcher aufrufen.

Senior-Antwort:

„Ein Timer ist ein Auslöser, kein Batchprozess. Kritische Arbeit braucht Status, Locking und Wiederholbarkeit.“

Häufige Fehler:
  • Timer läuft auf jedem Node.
  • Keine doppelte Startverhinderung.
  • Systemzeit ohne Zeitzone.
  • Lange Batchlogik direkt in Timer.
  • Keine Fehlerüberwachung.
  • Timer-Fehler still ignorieren.
  • Manuelle Nachläufe nicht vorgesehen.
Best Practices:
  • Timer als Trigger, nicht als Joblogik.
  • Business Date explizit berechnen.
  • Zeitzone setzen.
  • Cluster-Exklusivität sicherstellen.
  • Doppelte Starts verhindern.
  • Fehler und Dauer messen.
  • Batch oder Messaging delegieren.
  • Manuelle Wiederholung ermöglichen.
Stolperfalle:

In einem Cluster kann ein scheinbar harmloser Timer plötzlich mehrfach laufen, wenn keine Exklusivität garantiert ist.

Merksatz:

Scheduling ohne Exklusivität ist ein Risiko.

B5-K14-Q008 – Local, No-Interface und Remote ViewsLocal ist Aufruf; Remote ist Integration.
Frage:

„Was ist der Unterschied zwischen Local, No-Interface und Remote Views bei EJB?“

Kurz erklärt:

Enterprise Beans können lokale, no-interface oder remote Views anbieten. Remote Views überschreiten JVM- oder Prozessgrenzen und haben andere Semantik.

Musterantwort:

Eine Local View ist für Aufrufe innerhalb derselben Anwendung oder JVM gedacht und nutzt ein lokales Interface. Eine No-Interface View erlaubt lokale Aufrufe direkt über die Bean-Klasse, ohne separates Interface. Eine Remote View ist für entfernte Clients gedacht und hat verteilte Semantik: Parameter und Rückgaben müssen remote-tauglich sein, Latenz, Netzwerkfehler und Serialisierung sind relevant.

Die @EJB -Annotation kann eine Abhängigkeit auf eine lokale, no-interface oder remote View einer Enterprise Bean ausdrücken. Die API-Dokumentation beschreibt genau diese Auflösung einer Enterprise-Bean-Dependency.

Senior-Antwort:

„Remote EJB ist kein lokaler Methodenaufruf mit längerer Leitung. Es ist eine verteilte Systemgrenze.“

Häufige Fehler:
  • Remote EJB wie lokalen Methodenaufruf behandeln.
  • Feingranulare Remote Interfaces.
  • Nicht serialisierbare DTOs.
  • Domain Entities remote zurückgeben.
  • Netzwerkfehler ignorieren.
  • Transaktionsannahmen über Remote-Grenzen.
  • Remote View für neue Microservices verwenden, ohne Alternativen zu prüfen.
Best Practices:
  • Local oder No-Interface für interne Aufrufe.
  • Remote nur bei klarer Legacy- oder Plattformanforderung.
  • Remote Interfaces grobgranular.
  • DTOs statt Entities remote übertragen.
  • Netzwerkfehler modellieren.
  • Timeouts konfigurieren.
  • Servicegrenzen technologiearm halten.
  • Migration zu REST/Messaging prüfen.
Stolperfalle:

Ein chatty Remote Interface kann ein System massiv verlangsamen und bei Netzwerklatenz instabil machen.

Merksatz:

Local ist Aufruf; Remote ist Integration.

B5-K14-Q009 – Security, Roles und Method PermissionsRollen öffnen Türen; Policies prüfen Räume.
Frage:

„Wie funktioniert Security bei Enterprise Beans?“

Kurz erklärt:

EJB integriert sich mit deklarativer und programmatischer Security. Rollen können über Annotationen oder SessionContext geprüft werden.

Musterantwort:

EJB-Security kann deklarativ über Annotationen wie @RolesAllowed , @PermitAll und @DenyAll erfolgen. Zusätzlich kann eine Bean programmatisch über SessionContext den Caller Principal und Rollen abfragen. Das ist nützlich für Audit oder dynamische Entscheidungen.

Wichtig ist: Rollen prüfen nur grobe Berechtigungen. Objektbezogene Autorisierung, etwa „darf dieser Admin genau diesen User deaktivieren?“, muss zusätzlich über Access Policies erfolgen.

Senior-Antwort:

„Method Permissions schützen den Eingang zur Methode. Fachliche Autorisierung schützt die konkrete Ressource.“

Häufige Fehler:
  • Nur Rollenprüfung, keine Ressourcenprüfung.
  • Zu grobe Rolle admin .
  • Caller Principal nicht auditieren.
  • Security durch Self-Invocation umgehen.
  • @PermitAll aus Bequemlichkeit.
  • Keine negativen Security-Tests.
  • Frontend als einzige Security-Schicht.
Best Practices:
  • Deklarative Security für grobe Schranken.
  • Programmatische Security für Audit und dynamische Checks.
  • Resource Policies ergänzen.
  • Rollen granular benennen.
  • Deny by default.
  • Negative Tests schreiben.
  • Security-Kontext nicht in Domain leaken.
  • Admin-Aktionen auditieren.
Stolperfalle:

Eine erfolgreiche Rollenprüfung bedeutet nicht automatisch Zugriff auf jedes Objekt dieses Typs.

Merksatz:

Rollen öffnen Türen; Policies prüfen Räume.

B5-K14-Q010 – Moderne Architektur, Migration und Anti-PatternsModernisierung ersetzt Semantik, nicht nur Annotationen. 24h
Frage:

„Ist EJB heute noch sinnvoll, oder sollte man alles migrieren?“

Kurz erklärt:

Moderne Jakarta-EE-Architektur nutzt EJB gezielt oder kapselt Legacy-EJB hinter klaren Ports. Migration sollte evolutionär und risikobewusst erfolgen.

Musterantwort:

EJB ist nicht automatisch falsch. In bestehenden Jakarta-EE-Systemen kann es stabil und produktionsbewährt sein, besonders für CMT, MDBs, Timers und Security. Für neue Komponenten prüfe ich aber, ob CDI plus Jakarta Transactions, Jakarta Messaging, Jakarta REST und andere moderne Spezifikationen ausreichen. Wenn ja, bevorzuge ich leichtere, explizitere Komponenten.

Migration sollte nicht technisch-religiös sein. Ich migriere dort, wo EJB-Komplexität echten Schaden erzeugt: Remote-EJB-Kopplung, Stateful-Serverzustand, unklare Transaktionsgrenzen, schwer testbare Beans, veraltete Deployment-Modelle oder fehlende Cloud-Fähigkeit. Legacy-EJB kann als Adapter bestehen bleiben, während Fachlogik in moderne Use Cases wandert.

Senior-Antwort:

„Ich migriere nicht von EJB weg, weil es alt ist. Ich migriere dort, wo Kopplung, Betrieb oder Testbarkeit leiden.“

Häufige Fehler:
  • Big Bang Migration.
  • EJB entfernen, aber Semantik vergessen.
  • Remote EJB 1:1 zu chatty REST übersetzen.
  • Stateful Conversations ohne Zustandsmodell migrieren.
  • CMT durch falsche @Transactional -Grenzen ersetzen.
  • MDBs durch ungeprüfte Polling-Threads ersetzen.
  • Keine Regressionstests für Transaktionen und Security.
Best Practices:
  • EJB pragmatisch bewerten.
  • Fachlogik aus Infrastruktur lösen.
  • Legacy-EJB als Adapter kapseln.
  • Use Cases frameworkarm modellieren.
  • Transaktionssemantik dokumentieren.
  • Remote Interfaces grobgranular migrieren.
  • Stateful State explizit persistieren.
  • MDBs durch klare Messaging-Adapter ersetzen.
Stolperfalle:

EJB zu entfernen, ohne Containersemantik zu ersetzen, erzeugt oft subtilere Fehler als der alte Code hatte.

Merksatz:

Modernisierung ersetzt Semantik, nicht nur Annotationen.

B5-K15 – Kapitel 15: Concurrency / Jakarta Concurrency 10 Karten

B5-K15-Q001 – Grundmodell: Container-konforme NebenläufigkeitThreads im Container gehören dem Container. 24h
Frage:

„Warum sollte man in Jakarta EE keine eigenen Threads oder normalen Java-SE-Executors verwenden?“

Kurz erklärt:

Jakarta Concurrency stellt managed Executor- und Threading-Services bereit, damit nebenläufige Arbeit innerhalb des Jakarta-EE-Containers korrekt ausgeführt wird.

Musterantwort:

In Jakarta EE sollte Anwendungscode keine rohen Threads oder unmanaged Executors verwenden, weil diese außerhalb der Kontrolle des Containers laufen. Der Container kann dann Lifecycle, Shutdown, Security Context, Naming Context, ClassLoader, Monitoring, Resource Management und Thread Limits nicht zuverlässig verwalten. Jakarta Concurrency existiert genau deshalb: Die Spezifikation bietet standardisierte Concurrency APIs für Application Components, ohne die Containerintegrität zu gefährden.

Statt new Thread(...) , Executors.newFixedThreadPool(...) oder Timer verwende ich ManagedExecutorService , ManagedScheduledExecutorService , ContextService oder ManagedThreadFactory . Damit bleibt Nebenläufigkeit containerkonform und betreibbar.

Senior-Antwort:

„In Jakarta EE ist Nebenläufigkeit nicht verboten, aber sie muss containerverwaltet sein.“

Häufige Fehler:
  • new Thread() in einer JAX-RS Resource.
  • Executors.newCachedThreadPool() in einer CDI Bean.
  • Eigene Scheduler ohne Shutdown.
  • Threads halten alten ClassLoader nach Redeploy.
  • SecurityContext in async Tasks erwarten.
  • Keine Poollimits.
  • Keine Fehlerbehandlung für async Tasks.
Best Practices:
  • Nur managed Concurrency APIs im Container nutzen.
  • Executor-Ressourcen zentral konfigurieren.
  • Parallelität begrenzen.
  • Tasks klein und klar schneiden.
  • Fehlerpfade in Futures behandeln.
  • Kein request-spezifischer Zustand blind in Tasks.
  • Shutdown- und Redeploy-Verhalten testen.
  • Monitoring für Queue, Laufzeit und Fehler.
Stolperfalle:

Ein eigener Threadpool funktioniert im Test, verursacht aber im Application Server oft ClassLoader-Leaks, Shutdown-Probleme und unkontrollierte Last.

Merksatz:

Threads im Container gehören dem Container.

B5-K15-Q002 – ManagedExecutorService und asynchrone TasksManaged Executor ist Parallelität, nicht Persistenz.
Frage:

„Wie verwenden Sie ManagedExecutorService professionell?“

Kurz erklärt:

ManagedExecutorService ist der containerverwaltete Executor für asynchrone Tasks in Jakarta EE.

Musterantwort:

Ich verwende ManagedExecutorService , wenn ich begrenzte asynchrone Arbeit innerhalb des Containers ausführen möchte. Der Executor wird vom Container bereitgestellt, konfiguriert und überwacht. Tasks sollten klar begrenzt, idempotent oder wiederholbar, fehlerbehandelt und nicht unendlich laufend sein.

Jakarta Concurrency 3.1 definiert unter anderem ManagedExecutorService , ManagedScheduledExecutorService und ContextService als zentrale Mechanismen der Spezifikation.

Wichtig ist: Ein Executor ist keine durable Queue. Wenn die Anwendung abstürzt, sind laufende Tasks möglicherweise weg. Für zuverlässige Verarbeitung brauche ich Messaging, Batch, Outbox oder persistierte Jobs.

Senior-Antwort:

„Ein Managed Executor macht Arbeit parallel, aber nicht zuverlässig persistent. Für Dauerhaftigkeit brauche ich ein anderes Muster.“

Häufige Fehler:
  • Managed Executor als Queue missbrauchen.
  • Tasks ohne Timeout.
  • Keine Completion-Fehlerbehandlung.
  • Downstream-Last ignorieren.
  • Executor manuell herunterfahren.
  • Große Objektgraphen in Tasks capturen.
  • Request Context blind erwarten.
Best Practices:
  • Managed Executor injizieren oder per JNDI beziehen.
  • Tasks kurz und begrenzt halten.
  • Fehler mit exceptionally oder handle behandeln.
  • Timeouts auf Downstream-Calls.
  • Poolgröße und Queue-Limits konfigurieren.
  • Keine unbounded Fan-outs.
  • Durable Arbeit über Messaging/Batch.
  • Metriken für Taskdauer und Fehler.
Stolperfalle:

Ein Future ist kein Job Repository. Nach einem Neustart weiß niemand mehr, welcher Task fachlich offen war.

Merksatz:

Managed Executor ist Parallelität, nicht Persistenz.

B5-K15-Q003 – ManagedScheduledExecutorService und geplante AufgabenNicht jeder Schedule ist ein Batch Job.
Frage:

„Wann verwenden Sie ManagedScheduledExecutorService statt EJB Timer oder Jakarta Batch?“

Kurz erklärt:

ManagedScheduledExecutorService ist der containerverwaltete Scheduler für verzögerte oder wiederkehrende Tasks.

Musterantwort:

ManagedScheduledExecutorService verwende ich für technische, kurze, nichtkritische und lokal sinnvolle geplante Tasks, etwa Cache Refresh, Health Polling, kleine Cleanup-Aufgaben oder interne Periodik. Für geschäftskritische, persistente, auditierbare und restartbare Verarbeitung verwende ich eher EJB Timer plus Locking, Jakarta Batch oder einen externen Scheduler.

Ein managed scheduled executor ist containerkonform, aber nicht automatisch cluster-exklusiv, persistent oder job-restartfähig. Das muss man explizit prüfen.

Senior-Antwort:

„Scheduled Executor ist gut für technische Periodik. Kritische Geschäftsprozesse brauchen Status, Locking und Wiederaufsetzbarkeit.“

Häufige Fehler:
  • Kritische Jobs mit einfachem Scheduler bauen.
  • Keine Cluster-Exklusivität.
  • Überlappende Läufe.
  • Keine Timeouts.
  • Exceptions beenden still den Task.
  • Scheduler manuell shutdownen.
  • Kein Cancel bei Bean-Zerstörung.
Best Practices:
  • Für kurze technische Tasks nutzen.
  • Kritische Geschäftsprozesse über Batch/Scheduler.
  • Überlappung vermeiden.
  • Fehler im Task selbst behandeln.
  • Downstream-Timeouts setzen.
  • ScheduledFuture bei Shutdown canceln.
  • Clusterverhalten testen.
  • Metriken für letzte Ausführung.
Stolperfalle:

Ein lokaler Scheduler auf jedem Cluster-Node kann dieselbe Aufgabe mehrfach ausführen.

Merksatz:

Nicht jeder Schedule ist ein Batch Job.

B5-K15-Q004 – ContextService und Context PropagationKontext reist nicht von allein über Threadgrenzen.
Frage:

„Was ist Context Propagation, und warum ist sie in Jakarta EE wichtig?“

Kurz erklärt:

ContextService erstellt kontextualisierte Proxies für Tasks oder andere Objekte, damit relevante Containerkontexte in anderen Threads verfügbar sind.

Musterantwort:

Context Propagation bedeutet, dass relevante Kontextinformationen eines Aufrufs in eine asynchrone Ausführung übertragen werden. Dazu können je nach Runtime und Konfiguration Naming, ClassLoader, Security, CDI Request Context oder andere Containerkontexte gehören. Jakarta Concurrency stellt mit ContextService einen Standardmechanismus bereit, um kontextualisierte Proxies zu erzeugen. Die Jakarta-Concurrency-Spezifikation enthält ContextService als einen der zentralen Services neben den managed Executor Services.

Wichtig ist: Man darf nicht blind erwarten, dass jeder ThreadLocal, Request Scope oder Security Context automatisch überall verfügbar ist. Context Propagation muss bewusst gestaltet und getestet werden.

Senior-Antwort:

„Async bricht den Call Stack. Alles, was trotzdem mitkommen soll, muss bewusst propagiert oder explizit übergeben werden.“

Häufige Fehler:
  • Request-spezifischen Zustand in Async Task erwarten.
  • ThreadLocal-Leaks.
  • Tenant-Kontext nicht explizit übergeben.
  • SecurityContext in Worker-Thread blind nutzen.
  • Zu viel Kontext propagieren.
  • Context Propagation nicht testen.
  • Transaktionen über Threadgrenzen erwarten.
Best Practices:
  • Kontextbedarf explizit definieren.
  • Tenant und Correlation-ID möglichst explizit übergeben.
  • ContextService für technische Kontexte nutzen.
  • ThreadLocal Cleanup sicherstellen.
  • Sensitive Context-Daten minimieren.
  • Async-Kontext in Integrationstests prüfen.
  • Keine Transaktionspropagation über beliebige Threads erwarten.
  • Context-Verhalten dokumentieren.
Stolperfalle:

ThreadLocal-basierter Tenant-Kontext verschwindet oder leakt in Worker-Threads, wenn er nicht sauber verwaltet wird.

Merksatz:

Kontext reist nicht von allein über Threadgrenzen.

B5-K15-Q005 – ManagedThreadFactory und kontrollierte Thread-ErzeugungThreadFactory ist Ausnahme; Executor ist Standard.
Frage:

„Wann würden Sie ManagedThreadFactory verwenden?“

Kurz erklärt:

ManagedThreadFactory erzeugt containerverwaltete Threads, wenn eine API zwingend Thread-Erzeugung verlangt. Meist sind managed Executors vorzuziehen.

Musterantwort:

ManagedThreadFactory verwende ich selten. Sie ist sinnvoll, wenn ich mit einer Legacy-API oder Bibliothek integrieren muss, die explizit eine ThreadFactory oder einen eigenen Thread benötigt. Für normale asynchrone Arbeit ist ManagedExecutorService fast immer besser, weil er Pooling, Queueing und Task Lifecycle besser abbildet.

Wenn ich ManagedThreadFactory verwende, muss ich Shutdown, Interrupts, Schleifen, Fehlerbehandlung und Ressourcenbegrenzung selbst sehr sorgfältig bauen. Ein managed Thread ist besser als ein roher Thread, aber immer noch ein langfristiger Thread, der diszipliniert verwaltet werden muss.

Senior-Antwort:

„ManagedThreadFactory ist mein Notausgang für Thread-Erzeugung, nicht mein Standardweg für Async-Verarbeitung.“

Häufige Fehler:
  • ManagedThreadFactory für normale Tasks nutzen.
  • Kein Shutdown in @PreDestroy .
  • Interrupts ignorieren.
  • Endlosschleife ohne Timeout.
  • Unbounded Queue.
  • Fehler beendet Thread unbemerkt.
  • Langlaufende Threads pro Request erzeugen.
Best Practices:
  • Nur für Legacy-/Spezialfälle.
  • Managed Executor bevorzugen.
  • Stop-Flag und Interrupt unterstützen.
  • Bounded Queues verwenden.
  • Fehler sichtbar loggen und messen.
  • Threadanzahl streng begrenzen.
  • Keine Threads pro Request.
  • Shutdown testen.
Stolperfalle:

Ein managed Thread ohne sauberes Stop-Verhalten kann beim Redeploy genauso problematisch werden wie ein roher Thread.

Merksatz:

ThreadFactory ist Ausnahme; Executor ist Standard.

B5-K15-Q006 – Transactions, Persistence Context und Thread-GrenzenTransaktionen reisen nicht wie DTOs.
Frage:

„Was passiert mit Transaktionen und EntityManager , wenn Sie Arbeit in einen Managed Executor auslagern?“

Kurz erklärt:

Transaktionen und Persistence Contexts sind threadgebunden oder containergebunden. Man sollte sie nicht blind über Async-Grenzen tragen.

Musterantwort:

Ich gehe nicht davon aus, dass eine laufende Transaktion oder ein Persistence Context einfach in einen anderen Thread übertragen wird. Ein async Task sollte seine eigenen klaren Transaktionsgrenzen haben, typischerweise durch Aufruf einer managed Bean-Methode mit @Transactional . Ein EntityManager oder Entity-Objekte aus dem Request-Thread sollten nicht in andere Threads weitergereicht und dort lazy genutzt werden.

Die sichere Struktur ist: Im Hauptthread entscheide ich, welche Arbeit zu tun ist. Im Task rufe ich einen managed Service auf, der seine eigene Transaktion und seinen eigenen Persistence Context nutzt.

Senior-Antwort:

„Ich übergebe über Threadgrenzen IDs und Werte, nicht offene Persistence Contexts.“

Häufige Fehler:
  • EntityManager in mehrere Threads geben.
  • Managed Entities in Futures verwenden.
  • Lazy Loading im Worker-Thread.
  • Eine große Transaktion über parallele Tasks erwarten.
  • Connection Pool überlasten.
  • Self-Invocation bei @Transactional Task-Service.
  • Parallelität ohne DB-Lock-Analyse.
Best Practices:
  • IDs statt Entities in Tasks übergeben.
  • Pro Task eigene Transaktionsgrenze.
  • Managed Service aus Task aufrufen.
  • EntityManager nicht threadübergreifend nutzen.
  • Parallelität an Connection Pool ausrichten.
  • Locks und Isolation beachten.
  • Ergebnisse als DTOs zurückgeben.
  • DB-Last messen.
Stolperfalle:

Ein Entity wirkt wie ein normales Objekt, aber lazy Beziehungen und Persistence Context machen es in Async-Code gefährlich.

Merksatz:

Transaktionen reisen nicht wie DTOs.

B5-K15-Q007 – Virtual Threads in Jakarta Concurrency 3.1Virtual Threads machen Blocking billiger, nicht Ressourcen unendlich.
Frage:

„Welche Bedeutung haben Virtual Threads für Jakarta Concurrency 3.1?“

Kurz erklärt:

Jakarta Concurrency 3.1 unterstützt Java Virtual Threads in managed resources. Dadurch können blockierende Tasks containerkonform mit Virtual Threads ausgeführt werden.

Musterantwort:

Virtual Threads erlauben sehr viele blockierende, leichte Threads in Java 21. Jakarta Concurrency 3.1 integriert dieses Modell in managed resources, sodass Anwendungen Virtual Threads containerkonform nutzen können. Die Jakarta-EE-11-Release-Seite nennt ausdrücklich Unterstützung für Java-SE-Virtual-Threads in managed resources, etwa über @ManagedExecutorDefinition .

Wichtig ist: Virtual Threads ersetzen nicht Ressourcenlimits. Wenn 10.000 Virtual Threads gleichzeitig auf eine Datenbank mit 50 Connections zugreifen, gewinnt man nichts. Man braucht weiterhin Connection-Pool-Limits, Bulkheads, Timeouts, Backpressure und klare Architektur.

Senior-Antwort:

„Virtual Threads senken Threadkosten, aber sie erhöhen nicht die Kapazität meiner Datenbank.“

Häufige Fehler:
  • Virtual Threads unmanaged starten.
  • Ressourcenlimits vergessen.
  • CPU-bound Arbeit massiv parallelisieren.
  • Keine Timeouts.
  • ThreadLocal-Last ignorieren.
  • Blocking in synchronized Bereichen.
  • Virtual Threads als Ersatz für Architektur sehen.
Best Practices:
  • Virtual Threads über managed resources nutzen.
  • Für IO-bound Workloads einsetzen.
  • Connection Pools und Bulkheads begrenzen.
  • Timeouts überall setzen.
  • CPU-bound Arbeit separat begrenzen.
  • ThreadLocal-Nutzung prüfen.
  • Monitoring für Downstream-Sättigung.
  • Lasttests mit echter Infrastruktur.
Stolperfalle:

Mit Virtual Threads kann man viel leichter sehr viel Last erzeugen. Ohne Bulkheads ist das gefährlich.

Merksatz:

Virtual Threads machen Blocking billiger, nicht Ressourcen unendlich.

B5-K15-Q008 – Backpressure, Bulkheads und RessourcenlimitsParallelität braucht Grenzen.
Frage:

„Wie verhindern Sie, dass Managed Executors Ihre Anwendung überlasten?“

Kurz erklärt:

Asynchrone Verarbeitung braucht Backpressure. Executors allein begrenzen nicht automatisch alle Downstream-Ressourcen.

Musterantwort:

Ich begrenze Parallelität auf mehreren Ebenen: Executor-Pool, Queue-Länge, Semaphores/Bulkheads pro Downstream, Connection Pools, Rate Limits, Timeouts und Ablehnungsstrategien. Ein Managed Executor ist nur eine Ausführungskomponente. Er weiß nicht automatisch, dass ein externer E-Mail-Provider nur 50 parallele Requests verträgt.

Backpressure bedeutet, dass ein System bei Überlast nicht einfach unendlich Arbeit annimmt. Es muss verzögern, ablehnen, puffern, priorisieren oder auf Messaging/Batch ausweichen.

Senior-Antwort:

„Async ohne Backpressure ist nur eine elegantere Form von Überlast.“

Häufige Fehler:
  • Unbounded Executor Queue.
  • Virtual Threads ohne Downstream-Limits.
  • Kein Timeout.
  • Retry ohne Bulkhead.
  • Local Semaphore als clusterweites Limit missverstehen.
  • Ablehnung nicht fachlich modellieren.
  • Keine Metriken für Queue und Bulkhead.
Best Practices:
  • Executor und Downstream getrennt begrenzen.
  • Bounded Queues verwenden.
  • Bulkheads pro Downstream.
  • Timeouts und Retry-Budgets.
  • Ablehnung bewusst modellieren.
  • Metriken für Auslastung.
  • Clusterweite Limits bei Bedarf extern koordinieren.
  • Messaging für dauerhafte Pufferung.
Stolperfalle:

Ein Executor-Pool begrenzt Threads, aber nicht unbedingt die Anzahl offener HTTP- oder Datenbankoperationen in Ihrem Gesamtsystem.

Merksatz:

Parallelität braucht Grenzen.

B5-K15-Q009 – Fehlerbehandlung, Cancellation und TimeoutsAsync braucht explizite Fehlerwege.
Frage:

„Wie behandeln Sie Fehler und Timeouts bei Managed-Executor-Tasks?“

Kurz erklärt:

Async Tasks brauchen explizite Fehlerbehandlung, Timeouts und Cancellation. Sonst hängen Futures, Threads, Queues oder Downstream-Calls.

Musterantwort:

Jeder async Task braucht einen definierten Fehlerpfad. Bei CompletableFuture nutze ich handle , exceptionally , whenComplete , orTimeout oder completeOnTimeout . Downstream-Calls brauchen eigene Timeouts, weil ein Future-Timeout nicht automatisch jeden blockierenden HTTP- oder DB-Call sauber abbricht.

Cancellation ist kooperativ. future.cancel(true) signalisiert Abbruch, aber Code und Downstream-Clients müssen Interrupts und Timeouts respektieren. Deshalb schreibe ich Tasks so, dass sie abbrechbar sind und Ressourcen in finally freigeben.

Senior-Antwort:

„Ein Future ohne Fehlerpfad ist ein Fehler, der nur später und leiser passiert.“

Häufige Fehler:
  • Future nie beobachten.
  • Keine exceptionally -Kette.
  • Future-Timeout ohne Downstream-Timeout.
  • Cancellation als garantiertes Kill verstehen.
  • InterruptedException ignorieren.
  • Fehler nur loggen, aber fachlich nicht mappen.
  • Resources nicht in finally schließen.
Best Practices:
  • Fehlerpfad pro Future definieren.
  • Downstream-Timeouts setzen.
  • Future-Timeouts ergänzen.
  • Cancellation kooperativ implementieren.
  • Interrupts respektieren.
  • Metriken für Timeout und Failure.
  • Fallback fachlich begründen.
  • Keine stillen async Exceptions.
Stolperfalle:

orTimeout beendet nicht automatisch jeden blockierenden Downstream-Aufruf. Der Client selbst braucht Timeouts.

Merksatz:

Async braucht explizite Fehlerwege.

B5-K15-Q010 – Architektur, Anti-Patterns und produktionsreife Async-SystemeAsynchron heißt nicht automatisch robust. 24h
Frage:

„Welche Anti-Patterns sehen Sie bei Jakarta Concurrency?“

Kurz erklärt:

Produktionsreife Async-Architektur unterscheidet zwischen kurzlebiger In-Process-Parallelität und dauerhafter, wiederaufsetzbarer Hintergrundverarbeitung.

Musterantwort:

Typische Anti-Patterns sind: eigene Threads im Container, unmanaged Executors, async Tasks als durable Jobs missbrauchen, keine Backpressure, keine Timeouts, EntityManager über Threads teilen, Security-/Tenant-Kontext blind erwarten, unbounded Fan-out, keine Fehlerbeobachtung, keine Metriken, Scheduler pro Node ohne Exklusivität und Virtual Threads ohne Downstream-Limits.

Senior-Architektur unterscheidet klar: ManagedExecutorService für kurze, begrenzte In-Process-Arbeit; ManagedScheduledExecutorService für technische Periodik; ManagedThreadFactory nur für Spezialfälle; Messaging oder Batch für dauerhafte, wiederaufsetzbare Arbeit; externe Scheduler für kritische zeitgesteuerte Workflows.

Senior-Antwort:

„Async ist keine Architektur. Es ist eine Ausführungsform, die nur mit Grenzen, Status und Fehlerwegen produktionsreif wird.“

Häufige Fehler:
  • Async verwenden, um schlechte synchrone Architektur zu verstecken.
  • Keine Statusmodellierung.
  • Fire-and-forget ohne Audit.
  • Kritische Arbeit nur im Speicher.
  • Keine Limits.
  • Keine Fehlerbehandlung.
  • Async Tasks direkt aus JPA Entities bauen.
  • Parallelität ohne Capacity Planning.
Best Practices:
  • Execution Model bewusst wählen.
  • In-Process Tasks kurz halten.
  • Durable Arbeit persistieren.
  • Backpressure designen.
  • Timeouts und Cancellation.
  • Context explizit behandeln.
  • IDs statt Entities übergeben.
  • Metriken für Executor und Tasks.
Stolperfalle:

Fire-and-forget ist meistens fire-and-forget-the-error.

Merksatz:

Asynchron heißt nicht automatisch robust.

B6 – Band 6: OpenShift Professional

B6-K01 – Kapitel 1: OpenShift Core Concepts 10 Karten

B6-K01-Q001 – OpenShift vs Kubernetes: Plattform, Distribution und Enterprise-ErweiterungKubernetes orchestriert Container; OpenShift operationalisiert Kubernetes für Enterprise-Teams. 24h
Frage:

„Was ist der wichtigste Unterschied zwischen Kubernetes und OpenShift?“

Kurz erklärt:

OpenShift basiert auf Kubernetes, erweitert es aber zu einer vollständigen Enterprise-Plattform mit zusätzlichen APIs, Security Defaults, Developer Experience, Build-Funktionen, Registry, Routes, Operators und Betriebsautomatisierung.

Musterantwort:

Kubernetes ist die Container-Orchestrierungsbasis: Pods, Deployments, Services, ConfigMaps, Secrets, Volumes, Scheduling und API-Modell. OpenShift ist eine Enterprise-Plattform auf Kubernetes-Basis. Es bringt zusätzlich standardisierte Security, Multi-Tenancy, Developer Workflows, Builds, ImageStreams, integrierte Registry, Routes, Operator-basierten Betrieb, Web Console, oc CLI, Cluster Operators und eine opinionated Plattform-Governance.

Ich formuliere es im Interview so: Kubernetes ist der Orchestrierungskern; OpenShift ist die produktionsreife Plattformdistribution mit Enterprise-Defaults und Betriebsmodell.

Senior-Antwort:

„OpenShift ist Kubernetes plus Enterprise-Plattformentscheidungen. Ich muss beides verstehen: den Kubernetes-Kern und die OpenShift-Meinungen darüber.“

Häufige Fehler:
  • OpenShift als reine Kubernetes-UI missverstehen.
  • Security Defaults ignorieren.
  • Kubernetes-Beispiele blind übernehmen, die Root-Container voraussetzen.
  • Routes und Ingress verwechseln.
  • ImageStreams nicht verstehen.
  • Resource Limits ohne JVM-Abstimmung setzen.
  • OpenShift-spezifische Operatoren und Cluster Operators ignorieren.
Best Practices:
  • Kubernetes-Grundlagen beherrschen.
  • OpenShift-Erweiterungen gezielt lernen.
  • Security Defaults respektieren.
  • Anwendungen rootless und containerfreundlich bauen.
  • Requests/Limits bewusst definieren.
  • JVM auf Containerbetrieb abstimmen.
  • Routes, Services und NetworkPolicies klar modellieren.
  • OpenShift-Features nicht unreflektiert umgehen.
Stolperfalle:

Ein Container, der lokal mit Docker als Root läuft, scheitert auf OpenShift oft an restriktiveren Security Defaults.

Merksatz:

Kubernetes orchestriert Container; OpenShift operationalisiert Kubernetes für Enterprise-Teams.

B6-K01-Q002 – Cluster-Architektur: Control Plane, Worker Nodes und Cluster OperatorsOpenShift betreibt nicht nur Anwendungen, sondern auch sich selbst über Operators.
Frage:

„Wie ist ein OpenShift-Cluster grundsätzlich aufgebaut?“

Kurz erklärt:

Ein OpenShift-Cluster besteht aus Control Plane, Worker Nodes, Cluster Operators und Plattformdiensten. Operators verwalten zentrale Clusterfunktionen.

Musterantwort:

Ein OpenShift-Cluster besteht aus Control-Plane-Nodes und Worker-Nodes. Die Control Plane betreibt zentrale Steuerungskomponenten wie API Server, Scheduler, Controller Manager und OpenShift-spezifische APIs. Worker Nodes führen Anwendungs-Pods aus. Zusätzlich nutzt OpenShift Cluster Operators, um Plattformkomponenten wie Ingress, DNS, Monitoring, Console, Authentication, Network, Machine Config und andere Bestandteile zu installieren, zu überwachen und zu aktualisieren.

Red Hat beschreibt Operators als fundamentale Einheit der OpenShift-4-Codebasis und als Mechanismus, der manuelle Upgrades von Betriebssystemen und Control-Plane-Anwendungen reduziert.

Senior-Antwort:

„Wenn eine App auf OpenShift nicht erreichbar ist, prüfe ich nicht nur den Pod. Ich prüfe auch Ingress, DNS, Services, Endpoints, Nodes und Cluster Operators.“

Häufige Fehler:
  • Nur Anwendungs-Pods prüfen, nicht Cluster Operators.
  • Node Pressure ignorieren.
  • Control Plane und Worker-Rollen verwechseln.
  • Clusterweite Komponenten wie Ingress oder DNS nicht berücksichtigen.
  • Upgrades als reine App-Aufgabe verstehen.
  • Operator-Degraded-Zustand ignorieren.
  • oc get pods ohne Namespace verwenden und Systemzustand übersehen.
Best Practices:
  • Cluster Operators regelmäßig prüfen.
  • Node-Ressourcen beobachten.
  • App-Probleme von Plattformproblemen trennen.
  • oc get clusterversion und oc get clusteroperators kennen.
  • Systemnamespaces nicht ignorieren.
  • Ingress, DNS und Network bei Connectivity-Problemen prüfen.
  • Capacity Planning pro Node Pool.
  • Workloads sauber mit Requests/Limits schedulbar machen.
Stolperfalle:

Ein Running Pod bedeutet nicht, dass die Plattformfunktion, die ihn erreichbar macht, gesund ist.

Merksatz:

OpenShift betreibt nicht nur Anwendungen, sondern auch sich selbst über Operators.

B6-K01-Q003 – Projects, Namespaces und Multi-TenancyProjects strukturieren Ownership, nicht nur YAML.
Frage:

„Was ist ein OpenShift Project, und wie unterscheidet es sich von einem Kubernetes Namespace?“

Kurz erklärt:

OpenShift Projects sind eine OpenShift-Abstraktion über Kubernetes Namespaces und dienen als Arbeits- und Isolationsbereich für Teams, Anwendungen und Umgebungen.

Musterantwort:

Ein OpenShift Project basiert auf einem Kubernetes Namespace, ergänzt aber OpenShift-spezifische Verwaltung und Developer Experience. Projects ermöglichen Gruppen von Nutzern oder Entwicklern, gemeinsam an Ressourcen zu arbeiten; Red Hat beschreibt Projects als Mechanismus, der den Scope von Ressourcen definiert, User Access verwaltet und Quotas setzen kann.

In der Praxis nutze ich Projects zur Multi-Tenant-Isolation: pro Team, Anwendung, Environment oder Mandant. Dazu gehören RBAC, ResourceQuota, LimitRange, NetworkPolicies, ServiceAccounts, Secrets und Deployment-Governance.

Senior-Antwort:

„Ein Project ist mein Arbeits- und Governance-Raum. Dort entscheide ich, wer was deployen darf, wie viel Ressourcen verfügbar sind und welche Netzwerke offen sind.“

Häufige Fehler:
  • Alle Anwendungen im gleichen Project deployen.
  • Dev und Prod nur über Labels trennen.
  • Keine Quotas setzen.
  • Default Limits ohne JVM-Abstimmung.
  • Secrets projektübergreifend kopieren.
  • RBAC zu breit vergeben.
  • NetworkPolicies vergessen.
Best Practices:
  • Projects nach Team, Anwendung oder Environment schneiden.
  • Prod strikt getrennt behandeln.
  • ResourceQuota setzen.
  • LimitRange bewusst definieren.
  • RBAC minimal vergeben.
  • Secrets pro Project verwalten.
  • NetworkPolicies für Isolation nutzen.
  • Naming-Konventionen standardisieren.
Stolperfalle:

Ein Namespace trennt Ressourcen organisatorisch, aber ohne RBAC, Quotas und NetworkPolicies ist Multi-Tenancy nur halb umgesetzt.

Merksatz:

Projects strukturieren Ownership, nicht nur YAML.

B6-K01-Q004 – oc CLI, Web Console und Developer ExperienceNutze oc zum Sehen und Steuern, Git zum Erinnern.
Frage:

„Warum verwendet man auf OpenShift oc , wenn es doch kubectl gibt?“

Kurz erklärt:

oc ist die OpenShift CLI. Sie erweitert Kubernetes-CLI-Funktionen um OpenShift-spezifische Workflows wie Projects, Builds, Routes, ImageStreams und administrative Diagnose.

Musterantwort:

kubectl kann Kubernetes-Ressourcen verwalten. oc kann das ebenfalls, ergänzt aber OpenShift-spezifische Ressourcen und Workflows: Projects, Routes, Builds, ImageStreams, new-app , Login, administrative Befehle und Developer Experience. Red Hat beschreibt die CLI-Tools als Werkzeuge, die administrative und Entwickleroperationen über das Terminal vereinfachen.

Ein Senior sollte beide Welten verstehen: Kubernetes-standardnahe Befehle und OpenShift-spezifische Abkürzungen. In produktiven GitOps-Setups sollte man aber nicht nur imperativ mit oc new-app arbeiten, sondern Manifeste versionieren.

Senior-Antwort:

„ oc ist mein Skalpell für Diagnose und OpenShift-Workflows. Die Wahrheit produktiver Systeme liegt trotzdem im Git.“

Häufige Fehler:
  • Nur über Web Console konfigurieren.
  • Imperative Änderungen in Prod.
  • oc new-app als Produktionsdeployment nutzen.
  • Logs lesen, aber Events ignorieren.
  • Falsches Project aktiv.
  • oc delete pod als Problemlösung missverstehen.
  • Keine YAML-Versionierung.
Best Practices:
  • oc für Diagnose und OpenShift-spezifische Workflows.
  • Produktive Änderungen deklarativ.
  • Project-Kontext vor Befehlen prüfen.
  • Logs, Events und Describe kombinieren.
  • Admin-Befehle bewusst verwenden.
  • Web Console für Übersicht, Git für Wahrheit.
  • Manuelle Fixes nach GitOps zurückführen.
  • CLI-Kenntnisse im Team standardisieren.
Stolperfalle:

Wer im falschen Project arbeitet, debuggt oder verändert schnell die falsche Umgebung.

Merksatz:

Nutze oc zum Sehen und Steuern, Git zum Erinnern.

B6-K01-Q005 – Workloads: Pods, Deployments und DeploymentConfigsDeployment rollt aus; Readiness entscheidet, wann der Pod wirklich dient.
Frage:

„Sollte man auf OpenShift Deployments oder DeploymentConfigs verwenden?“

Kurz erklärt:

Pods sind die kleinste Ausführungseinheit. Deployments verwalten ReplicaSets und Rolling Updates. DeploymentConfigs sind OpenShift-spezifische ältere Workload-Ressourcen mit eigenen Triggern und Strategien.

Musterantwort:

Für neue Anwendungen bevorzuge ich Kubernetes Deployment , weil es Standard-Kubernetes ist, portabler bleibt und moderne Tooling-Ökosysteme besser unterstützt. DeploymentConfig ist eine OpenShift-spezifische Ressource, die in Bestandsplattformen vorkommt und eigene Trigger sowie Deployment-Mechaniken hat.

Ein Senior sollte DeploymentConfigs lesen und betreiben können, aber neue Plattformstandards in der Regel auf Deployment ausrichten, sofern kein spezifischer DeploymentConfig-Use Case existiert.

Senior-Antwort:

„Ich kann DeploymentConfigs betreiben, aber für neue Services bevorzuge ich Kubernetes Deployments, solange kein OpenShift-spezifischer Trigger zwingend nötig ist.“

Häufige Fehler:
  • LivenessProbe für Readiness missbrauchen.
  • Keine StartupProbe bei langsamen JVM-Apps.
  • DeploymentConfig und Deployment vermischen.
  • Image-Tags wie latest in Prod.
  • Keine Resource Requests.
  • Rolling Update ohne Kapazität.
  • Readiness zu früh grün.
Best Practices:
  • Neue Apps mit Deployment standardisieren.
  • DeploymentConfigs in Legacy verstehen.
  • Probes fachlich korrekt designen.
  • StartupProbe für langsame Starts.
  • Immutable Image Tags.
  • RollingUpdate-Kapazität planen.
  • Graceful Shutdown implementieren.
  • Pod Disruption und Availability beachten.
Stolperfalle:

Eine Liveness-Probe, die während des normalen JVM-Starts fehlschlägt, erzeugt einen Endlos-Neustart.

Merksatz:

Deployment rollt aus; Readiness entscheidet, wann der Pod wirklich dient.

B6-K01-Q006 – Services, Routes und externe ErreichbarkeitRoute rein, Service weiter, Pod verarbeitet.
Frage:

„Was ist der Unterschied zwischen Service und Route in OpenShift?“

Kurz erklärt:

Services machen Pods clusterintern erreichbar. Routes machen Services über den OpenShift Ingress Controller extern erreichbar.

Musterantwort:

Ein Service bietet eine stabile interne Adresse und Load Balancing zu passenden Pods. Eine Route ist eine OpenShift-Ressource, die einen Service extern über HTTP oder HTTPS erreichbar macht. Routes werden vom OpenShift Ingress Controller umgesetzt. Die Red-Hat-Dokumentation zu Routes beschreibt unter anderem, dass der Ingress Controller Endpoints auswählt und auch Session Persistence über Cookies konfigurieren kann.

Ein Service ist also primär clusterintern; eine Route ist die externe HTTP(S)-Eingangstür.

Senior-Antwort:

„Der Service ist die interne stabile Adresse. Die Route ist der externe HTTP-Vertrag.“

Häufige Fehler:
  • Service ohne passende Selector.
  • Route auf falschen Service-Port.
  • TLS-Termination nicht verstehen.
  • App generiert falsche HTTPS-URLs.
  • Sticky Sessions unnötig aktivieren.
  • Route als TCP-Load-Balancer missverstehen.
  • Readiness-Probe ignorieren.
Best Practices:
  • Service intern, Route extern.
  • TLS-Strategie bewusst wählen.
  • Forwarded Headers korrekt konfigurieren.
  • Stateless Services bevorzugen.
  • Route-Hostnames standardisieren.
  • Readiness vor Traffic sicherstellen.
  • Session Persistence vermeiden, wenn möglich.
  • Zertifikatsmanagement automatisieren.
Stolperfalle:

Wenn TLS am Router terminiert, muss die Anwendung Forwarded Headers verstehen, sonst entstehen falsche Redirects oder Callback URLs.

Merksatz:

Route rein, Service weiter, Pod verarbeitet.

B6-K01-Q007 – Builds, ImageStreams und integrierte RegistryBuild erzeugt Images; ImageStreams strukturieren deren Nutzung.
Frage:

„Was sind ImageStreams, und warum gibt es sie in OpenShift?“

Kurz erklärt:

OpenShift kann Container Images bauen, in ImageStreams referenzieren und in einer integrierten Registry verwalten.

Musterantwort:

ImageStreams sind OpenShift-Abstraktionen für Container Images und Tags. Sie enthalten nicht selbst die Image-Daten, sondern stellen eine virtuelle Sicht auf verwandte Images bereit. Red Hat beschreibt ImageStreams genau als Abstraktion zum Referenzieren von Container Images innerhalb von OpenShift; ImageStreams enthalten keine tatsächlichen Image-Daten, sondern eine virtuelle Sicht auf zusammengehörige Images.

Sie ermöglichen Build- und Deployment-Automatisierung: Wenn ein ImageStreamTag aktualisiert wird, können Builds oder Deployments darauf reagieren. Zusammen mit BuildConfigs und der integrierten Registry entsteht ein Plattform-Workflow vom Quellcode zum Image.

Senior-Antwort:

„Ein ImageStream ist nicht das Image selbst. Er ist OpenShifts kontrollierte Sicht auf Image-Versionen und Trigger.“

Häufige Fehler:
  • ImageStream mit Registry verwechseln.
  • latest in Produktion verwenden.
  • Builds ohne Reproduzierbarkeit.
  • Keine Image-Scanning-Strategie.
  • Root-Images bauen.
  • Maven-Cache in Runtime-Image lassen.
  • Build und Deployment unkontrolliert triggern.
Best Practices:
  • Immutable Tags für Produktion.
  • ImageStreams gezielt nutzen.
  • Build-Pipelines versionieren.
  • Security Scans integrieren.
  • Non-root Images bauen.
  • Multi-stage Builds oder S2I bewusst wählen.
  • Runtime Images klein halten.
  • Image-Promotion statt Neu-Build pro Umgebung.
Stolperfalle:

Ein automatischer ImageChange-Trigger kann in Produktion unbeabsichtigt Deployments auslösen, wenn Image-Promotion nicht kontrolliert ist.

Merksatz:

Build erzeugt Images; ImageStreams strukturieren deren Nutzung.

B6-K01-Q008 – Security Defaults: ServiceAccounts, SCCs und Rootless ContainersOpenShift vertraut Workloads nicht automatisch — und das ist gut.
Frage:

„Warum laufen manche Container auf OpenShift nicht, obwohl sie lokal mit Docker funktionieren?“

Kurz erklärt:

OpenShift hat restriktive Security Defaults. ServiceAccounts und Security Context Constraints bestimmen, unter welchen Bedingungen Pods laufen dürfen.

Musterantwort:

OpenShift hat strengere Security Defaults als viele lokale Docker-Setups. Container laufen typischerweise nicht als beliebiger Root-Container mit breiten Linux-Capabilities. Security Context Constraints, kurz SCCs, steuern, welche Rechte Pods haben dürfen. Red Hat beschreibt SCCs als Mechanismus, mit dem Administratoren die Permissions von Pods kontrollieren; diese Permissions bestimmen, welche Aktionen ein Pod ausführen kann und auf welche Ressourcen er Zugriff hat.

Wenn ein Image Root voraussetzt, feste UID erwartet, Schreibrechte in geschützten Verzeichnissen braucht oder Capabilities verlangt, scheitert es auf OpenShift oft. Die Lösung ist nicht, pauschal privileged SCC zu geben, sondern das Image rootless und OpenShift-kompatibel zu bauen.

Senior-Antwort:

„Wenn ein Container auf OpenShift Root braucht, ist meistens das Image falsch, nicht OpenShift zu streng.“

Häufige Fehler:
  • Privileged SCC als schnelle Lösung.
  • Images mit Root-Annahme.
  • App schreibt in read-only Verzeichnisse.
  • Feste UID im Image erzwingen.
  • Logs in Dateien statt stdout.
  • ServiceAccounts zu breit berechtigen.
  • SCCs direkt ohne Plattformfreigabe ändern.
Best Practices:
  • Images rootless bauen.
  • Keine Root-Abhängigkeit.
  • Logs nach stdout.
  • Schreibpfade explizit.
  • Capabilities minimieren.
  • ServiceAccount pro Workload.
  • SCC nur minimal erweitern.
  • Security-Failures ernst nehmen.
Stolperfalle:

Privileged SCC löst das Symptom, aber öffnet eine große Sicherheitslücke im Multi-Tenant-Cluster.

Merksatz:

OpenShift vertraut Workloads nicht automatisch — und das ist gut.

B6-K01-Q009 – Operators und Operator Lifecycle ManagerOperators betreiben Software, nicht nur starten sie.
Frage:

„Was ist ein Operator in OpenShift, und warum ist das Konzept so wichtig?“

Kurz erklärt:

Operators automatisieren Installation, Betrieb, Updates und Day-2-Operations von Anwendungen oder Plattformkomponenten. OLM verwaltet Operator-Installation und Lifecycle.

Musterantwort:

Ein Operator ist ein Kubernetes-natives Automatisierungsmuster. Er erweitert die Plattform typischerweise mit Custom Resource Definitions und einem Controller, der gewünschten Zustand in tatsächlichen Zustand überführt. In OpenShift sind Operators besonders wichtig, weil sie nicht nur Anwendungen, sondern auch Plattformkomponenten betreiben. Red Hat beschreibt Operators als fundamentale Einheit der OpenShift-4-Codebasis.

Der Operator Lifecycle Manager hilft, Operators aus Katalogen zu installieren, zu aktualisieren und zu verwalten. Für Anwendungsteams bedeutet das: Viele Plattformdienste wie Datenbanken, Messaging, Service Mesh oder Observability werden als Operatoren bereitgestellt.

Senior-Antwort:

„Ein Operator ist automatisiertes Betriebswissen. Deshalb muss ich ihn wie Plattformcode behandeln, nicht wie irgendein Deployment.“

Häufige Fehler:
  • Operator als normale App behandeln.
  • Custom Resource ändern, ohne Operatorverhalten zu verstehen.
  • Operator-Rechte nicht prüfen.
  • Updates automatisch ohne Testumgebung.
  • Operator-Status ignorieren.
  • CRDs ohne Ownership.
  • Zu viele Operators ohne Governance installieren.
Best Practices:
  • Operators über Governance freigeben.
  • Channels und Update-Strategien bewusst wählen.
  • Custom Resources versionieren.
  • Operator-Status und Events prüfen.
  • Testumgebung für Operator-Upgrades.
  • RBAC und Scope prüfen.
  • Runbooks für Operator-betriebene Services.
  • Nicht jeden Dienst zwanghaft per Operator betreiben.
Stolperfalle:

Ein Operator mit breiten Clusterrechten kann mehr Schaden verursachen als eine normale Anwendung.

Merksatz:

Operators betreiben Software, nicht nur starten sie.

B6-K01-Q010 – OpenShift für Java/Spring Boot: Container, Probes, Config und RuntimeJava auf OpenShift ist JVM-Tuning plus Plattformdisziplin. 24h
Frage:

„Was sind die wichtigsten Punkte beim Betrieb einer Spring-Boot- oder Java-Anwendung auf OpenShift?“

Kurz erklärt:

Java-Anwendungen auf OpenShift brauchen containerfreundliche Images, korrekte JVM-Memory-Konfiguration, Health Probes, ConfigMaps, Secrets, Resource Limits und Graceful Shutdown.

Musterantwort:

Ich achte auf acht Dinge: non-root Image, kleine reproduzierbare Container, JVM-Memory passend zu Limits, Readiness/Liveness/Startup-Probes, Config über ConfigMaps und Secrets, saubere Resource Requests/Limits, Graceful Shutdown und Observability.

Für Spring Boot nutze ich getrennte Health Groups für Readiness und Liveness. Readiness darf erst grün sein, wenn die App Traffic verarbeiten kann. Liveness darf nicht wegen temporärer Downstream-Probleme fehlschlagen. Startup-Probe schützt langsame JVM-Starts.

Senior-Antwort:

„Eine Java-App läuft nicht gut auf OpenShift, weil sie startet. Sie läuft gut, wenn JVM, Containerlimits, Probes und Plattformverhalten zusammenpassen.“

Häufige Fehler:
  • Heap gleich Containerlimit setzen.
  • Keine StartupProbe.
  • Liveness prüft Datenbank.
  • Secrets als Klartext in Image.
  • Logs in Datei.
  • Keine Requests.
  • Zu niedrige CPU-Limits.
  • SIGTERM nicht sauber behandeln.
Best Practices:
  • Non-root Images.
  • JVM an Containerlimits anpassen.
  • Readiness für Traffic-Fähigkeit.
  • Liveness nur für Prozessgesundheit.
  • StartupProbe für langsamen Start.
  • ConfigMaps und Secrets trennen.
  • Logs nach stdout.
  • Graceful Shutdown implementieren.
Stolperfalle:

Wenn die Liveness-Probe die Datenbank prüft, kann ein kurzer DB-Ausfall alle Pods neu starten und den Incident verschlimmern.

Merksatz:

Java auf OpenShift ist JVM-Tuning plus Plattformdisziplin.

B6-K02 – Kapitel 2: Projects, Namespaces und RBAC 10 Karten

B6-K02-Q001 – Project vs Namespace: Bedeutung, Scope und OwnershipNamespace ist Scope; Project ist Ownership. 24h
Frage:

„Was ist der Unterschied zwischen einem OpenShift Project und einem Kubernetes Namespace?“

Kurz erklärt:

Ein Kubernetes Namespace ist die technische Scoping-Einheit. Ein OpenShift Project ist die OpenShift-Abstraktion darüber, mit zusätzlicher Developer Experience, Verwaltung und Plattform-Governance.

Musterantwort:

Technisch basiert ein OpenShift Project auf einem Kubernetes Namespace. Der Namespace ist die Kubernetes-Ressourcengrenze für Objekte wie Pods, Services, ConfigMaps, Secrets, Roles und RoleBindings. Das Project ist die OpenShift-Abstraktion, die Benutzern eine Arbeitsumgebung mit Zugriffskontrolle, Quotas und OpenShift-spezifischer Verwaltung bereitstellt.

Ich sage im Interview: Namespace ist die technische API-Ebene; Project ist die OpenShift-Plattform- und Ownership-Ebene. In der Praxis behandle ich ein Project als Vertrag zwischen Plattformteam und Anwendungsteam: Wer besitzt es, welche Umgebung repräsentiert es, welche Quotas gelten, welche Netzwerke sind erlaubt und wer darf deployen?

Senior-Antwort:

„Ein Project ist für mich kein Ordner im Cluster. Es ist ein Ownership-, Security- und Ressourcenvertrag.“

Häufige Fehler:
  • Alle Anwendungen in einem gemeinsamen Project betreiben.
  • Dev, Test und Prod nur über Labels trennen.
  • Project-Namen ohne Ownership-Konvention.
  • Keine Umgebungskennzeichnung.
  • Secrets zwischen Projects manuell kopieren.
  • Project als harte Mandantengrenze überschätzen.
  • Platform-Governance erst nachträglich einführen.
Best Practices:
  • Projects nach Ownership und Umgebung schneiden.
  • Prod strikt von Nicht-Prod trennen.
  • Labels für Domain, Team und Environment standardisieren.
  • Project-Provisioning automatisieren.
  • RBAC pro Project minimal vergeben.
  • Secrets und ConfigMaps pro Project verwalten.
  • Quotas und LimitRanges verpflichtend definieren.
  • NetworkPolicies als Teil des Project-Standards ausrollen.
Stolperfalle:

Ein Project isoliert Namen und Policies, aber ohne RBAC, Quotas und NetworkPolicies ist es noch keine belastbare Multi-Tenancy-Grenze.

Merksatz:

Namespace ist Scope; Project ist Ownership.

B6-K02-Q002 – RBAC-Grundmodell: Subject, Role, Rule und BindingRBAC ist API-Autorisierung, nicht nur Benutzerverwaltung.
Frage:

„Wie funktioniert RBAC in OpenShift?“

Kurz erklärt:

RBAC besteht aus Subjects, Roles, Rules und Bindings. Eine Role definiert erlaubte Aktionen; ein Binding weist diese Rolle Benutzern, Gruppen oder ServiceAccounts zu.

Musterantwort:

RBAC beantwortet die Frage: „Darf dieses Subject diese Aktion auf dieser Ressource in diesem Scope ausführen?“ Subjects sind User, Groups oder ServiceAccounts. Roles enthalten Rules aus API Groups, Resources und Verbs. RoleBindings binden eine Role an Subjects innerhalb eines Namespaces beziehungsweise Projects. ClusterRoles und ClusterRoleBindings wirken clusterweit oder können über RoleBindings namespaced verwendet werden.

Red Hat beschreibt RBAC-Objekte als Mechanismus, der bestimmt, ob ein Benutzer eine bestimmte Aktion innerhalb eines Projects ausführen darf. Clusteradministratoren können Cluster Roles und Bindings nutzen, um Zugriff auf die Plattform selbst und auf alle Projects zu kontrollieren. ( docs.redhat.com )

Senior-Antwort:

„RBAC schützt die Plattform-API. Wer Deployments, Secrets oder RoleBindings ändern darf, beeinflusst direkt Produktionssicherheit.“

Häufige Fehler:
  • cluster-admin für normale Teams.
  • edit in Produktion an Entwicklergruppen.
  • Secrets-Lesezugriff unterschätzen.
  • RoleBinding im falschen Namespace.
  • ClusterRoleBinding statt RoleBinding.
  • ServiceAccounts mit zu breiten Rechten.
  • Keine regelmäßigen Access Reviews.
Best Practices:
  • Least Privilege anwenden.
  • Gruppen statt Einzeluser binden.
  • Prod-Rechte restriktiv.
  • Secrets-Zugriff separat betrachten.
  • ServiceAccounts minimal berechtigen.
  • ClusterRoleBindings stark begrenzen.
  • RBAC als Code versionieren.
  • Access Reviews regelmäßig durchführen.
Stolperfalle:

edit wirkt harmlos, kann aber in vielen Setups indirekt zu sehr mächtigen Aktionen führen, besonders wenn ServiceAccounts und Secrets zugänglich sind.

Merksatz:

RBAC ist API-Autorisierung, nicht nur Benutzerverwaltung.

B6-K02-Q003 – ClusterRole, Role, ClusterRoleBinding und RoleBindingBinding bestimmt den Wirkungsradius.
Frage:

„Wann verwenden Sie RoleBinding und wann ClusterRoleBinding?“

Kurz erklärt:

Roles sind namespaced. ClusterRoles sind clusterweit definierte Rollen. RoleBindings wirken namespaced; ClusterRoleBindings wirken clusterweit.

Musterantwort:

Ich verwende RoleBinding, wenn ein Subject Rechte nur in einem bestimmten Project bekommen soll. Das kann auch eine ClusterRole referenzieren, wirkt dann aber nur im Namespace des RoleBindings. ClusterRoleBinding verwende ich nur, wenn Rechte wirklich clusterweit gelten sollen, zum Beispiel für Plattformadministration oder clusterweite Read-only-Rollen.

Die Faustregel lautet: Wenn ich nicht bewusst clusterweite Rechte brauche, verwende ich RoleBinding. ClusterRoleBinding ist ein Hochrisiko-Werkzeug.

Senior-Antwort:

„Eine ClusterRole ist nicht automatisch gefährlich. Gefährlich wird sie, wenn ich sie per ClusterRoleBinding zu breit vergebe.“

Häufige Fehler:
  • ClusterRoleBinding für Project-Zugriff.
  • cluster-admin für CI/CD.
  • edit clusterweit vergeben.
  • Eigene ClusterRoles ohne Review.
  • RoleBinding im falschen Namespace.
  • Fehlende Dokumentation für Bindings.
  • Keine Entfernung alter Bindings.
Best Practices:
  • RoleBinding bevorzugen.
  • ClusterRoleBinding nur für echte Clusteraufgaben.
  • Vordefinierte Rollen verstehen.
  • Prod-Rollen restriktiver als Dev.
  • CI/CD pro Project berechtigen.
  • Clusterweite Rollen reviewen.
  • Bindings mit Owner und Zweck annotieren.
  • Rechte regelmäßig auditieren.
Stolperfalle:

Ein RoleBinding auf eine ClusterRole bleibt namespaced. Ein ClusterRoleBinding macht die Berechtigung clusterweit.

Merksatz:

Binding bestimmt den Wirkungsradius.

B6-K02-Q004 – Built-in Roles: view, edit, admin und cluster-adminBuilt-in Roles beschleunigen Start; Governance macht sie sicher.
Frage:

„Was ist der Unterschied zwischen view , edit , admin und cluster-admin ?“

Kurz erklärt:

OpenShift bringt vordefinierte Rollen mit. Sie beschleunigen Governance, müssen aber verstanden und passend zum Environment eingesetzt werden.

Musterantwort:

view ist für lesenden Zugriff gedacht. edit erlaubt Änderungen an vielen namespaced Ressourcen und ist für Entwickler in nicht-produktiven Umgebungen häufig passend. admin erlaubt Verwaltung eines Projects inklusive vieler administrativer Ressourcen im Namespace. cluster-admin ist praktisch vollständige Clusteradministration und darf nur sehr wenigen Plattformadministratoren oder Break-Glass-Prozessen gehören.

Ich vergebe diese Rollen fast immer über Gruppen und RoleBindings pro Project. Für Produktion ist view für Entwickler oft ausreichend; Deployments erfolgen über dedizierte Pipeline-ServiceAccounts.

Senior-Antwort:

„Built-in Roles sind ein guter Start, aber kein Ersatz für ein durchdachtes Betriebs- und Sicherheitsmodell.“

Häufige Fehler:
  • cluster-admin an CI/CD.
  • admin für alle Entwickler in Prod.
  • edit in Prod ohne Review.
  • Menschen und Pipelines gleiche Rechte.
  • Temporäre Rechte nicht entfernen.
  • Break-Glass nicht auditieren.
  • Built-in Roles ohne Verständnis verwenden.
Best Practices:
  • Gruppenbasiertes RBAC.
  • Prod restriktiver als Dev.
  • Pipelines separat berechtigen.
  • Built-in Roles prüfen und bei Bedarf eigene Rollen.
  • cluster-admin minimal und auditierbar.
  • Break-Glass-Prozess definieren.
  • Rechte zeitlich begrenzen.
  • Access Reviews automatisieren.
Stolperfalle:

Ein Mensch mit Produktions- edit kann im Incident hilfreich sein, aber dauerhaft ist das ein hohes Risiko.

Merksatz:

Built-in Roles beschleunigen Start; Governance macht sie sicher.

B6-K02-Q005 – ServiceAccounts für Anwendungen und PipelinesServiceAccounts sind Maschinenidentitäten; behandle sie wie privilegierte Benutzer.
Frage:

„Warum sollte jede Anwendung einen eigenen ServiceAccount haben?“

Kurz erklärt:

ServiceAccounts sind Identitäten für Pods, Anwendungen und Automatisierungen. Sie sollten workload- oder pipeline-spezifisch und minimal berechtigt sein.

Musterantwort:

Ein ServiceAccount ist eine OpenShift/Kubernetes-Identität für Workloads. Red Hat beschreibt ServiceAccounts als flexible Möglichkeit, API-Zugriff zu kontrollieren, ohne reguläre Benutzer-Credentials zu teilen. ( docs.redhat.com )

Jede Anwendung sollte einen eigenen ServiceAccount haben, damit ihre API-Rechte, SCC-Zuordnung, Secrets, Audit-Spuren und Sicherheitsgrenzen klar sind. Der Default-ServiceAccount ist für produktive Workloads meist zu unspezifisch. Pipelines sollten wiederum eigene ServiceAccounts haben, getrennt von Runtime-ServiceAccounts.

Senior-Antwort:

„Der Runtime-Pod und die Deployment-Pipeline sind unterschiedliche Akteure. Sie brauchen unterschiedliche Identitäten.“

Häufige Fehler:
  • Default-ServiceAccount für alles.
  • Pipeline und Runtime gleiche Identität.
  • ServiceAccount mit cluster-admin .
  • Secrets über ServiceAccount-Tokens exfiltrierbar.
  • Nicht benötigte API-Rechte.
  • ServiceAccount projektübergreifend falsch binden.
  • Alte langlebige Tokens nicht rotieren.
Best Practices:
  • ServiceAccount pro Workload.
  • Pipeline-ServiceAccounts getrennt.
  • Runtime-Rechte minimal.
  • API-Zugriff nur bei echtem Bedarf.
  • RoleBindings namespaced.
  • Tokens nicht loggen.
  • ServiceAccount-Nutzung auditieren.
  • SCC- und RBAC-Zuordnung dokumentieren.
Stolperfalle:

Ein kompromittierter Pod mit zu breitem ServiceAccount ist ein direkter Weg zur Cluster- oder Project-Kompromittierung.

Merksatz:

ServiceAccounts sind Maschinenidentitäten; behandle sie wie privilegierte Benutzer.

B6-K02-Q006 – ResourceQuota und LimitRange als Governance-WerkzeugeQuota begrenzt das Project; LimitRange begrenzt den einzelnen Workload.
Frage:

„Warum sind ResourceQuota und LimitRange für OpenShift-Multi-Tenancy wichtig?“

Kurz erklärt:

ResourceQuota begrenzt den Gesamtverbrauch eines Projects. LimitRange setzt Default-, Minimal- und Maximalwerte für einzelne Container oder Ressourcen.

Musterantwort:

ResourceQuota verhindert, dass ein Project unbegrenzt CPU, Memory, Pods, PVCs oder andere Objekte verbraucht. LimitRange setzt Standards und Grenzen auf Container- oder Pod-Ebene, damit Workloads nicht ohne Requests/Limits laufen oder extrem große Ressourcen anfordern.

Red Hat beschreibt Resource Quotas als Schutz vor Erschöpfung von Storage- und anderen Ressourcen; OpenShift zählt Ressourcen gegen Quotas, wenn Objekte im Server Storage existieren. ( docs.redhat.com )

Senior-Antwort:

„Quotas sind nicht Bürokratie. Sie sind Fairness und Schutz vor Nachbarschaftsschäden im Multi-Tenant-Cluster.“

Häufige Fehler:
  • Keine Quotas in Shared Clustern.
  • Default Limits ohne Java-Abstimmung.
  • Requests viel zu niedrig setzen.
  • Limits zu nah am Heap.
  • CPU Limits ohne Throttling-Verständnis.
  • Quotas nicht an Team-Budget koppeln.
  • Keine Alerts auf Quota-Auslastung.
Best Practices:
  • ResourceQuota pro Project.
  • LimitRange als Default-Sicherheitsnetz.
  • Java-Workloads explizit dimensionieren.
  • Requests auf realistische Baseline setzen.
  • Limits mit Headroom für JVM.
  • Quota-Auslastung monitoren.
  • Team-Budgets transparent machen.
  • Große Workloads separat reviewen.
Stolperfalle:

Ein Java-Heap von 1 GiB in einem Container mit 1 GiB Limit ist fast sicher falsch, weil die JVM mehr als Heap braucht.

Merksatz:

Quota begrenzt das Project; LimitRange begrenzt den einzelnen Workload.

B6-K02-Q007 – NetworkPolicies und Project-IsolationAPI-Isolation ist nicht Netzwerkisolation.
Frage:

„Reicht RBAC aus, um Projects voneinander zu isolieren?“

Kurz erklärt:

NetworkPolicies steuern Pod-zu-Pod-Netzwerkverkehr. Sie ergänzen Project-Isolation und RBAC, weil RBAC nur API-Zugriffe regelt.

Musterantwort:

Nein. RBAC regelt API-Zugriffe: Wer darf Ressourcen sehen, ändern oder löschen? NetworkPolicies regeln Netzwerkverkehr zwischen Pods. Wenn Pods verschiedener Projects sich per Netzwerk erreichen können, verhindert RBAC allein keine Laufzeitkommunikation.

Red Hat beschreibt, dass standardmäßig alle Pods in einem Project von anderen Pods und Netzwerkendpunkten erreichbar sein können; um Pods zu isolieren, erstellt man NetworkPolicy-Objekte, die erlaubte eingehende Verbindungen definieren. ( docs.redhat.com )

Senior-Antwort:

„RBAC schützt die API. NetworkPolicy schützt den Laufzeitverkehr. Für Multi-Tenancy brauche ich beides.“

Häufige Fehler:
  • RBAC als Netzwerksicherheit missverstehen.
  • Keine Default-Deny-Strategie.
  • Zu breite NamespaceSelector.
  • Fehlende Labels auf Namespaces.
  • Egress komplett offen.
  • Policies ohne Tests deployen.
  • DNS/Egress-Abhängigkeiten vergessen.
Best Practices:
  • RBAC und NetworkPolicies kombinieren.
  • Default Deny pro Prod-Project prüfen.
  • Kommunikationsmatrix definieren.
  • Namespace- und Pod-Labels standardisieren.
  • Ingress und Egress bewusst modellieren.
  • Policies testen.
  • DNS, Monitoring und Service Mesh berücksichtigen.
  • Connectivity-Diagnose dokumentieren.
Stolperfalle:

Ein Entwickler kann keine fremden Pods sehen, aber seine Anwendung kann sie möglicherweise trotzdem erreichen, wenn Netzwerkverkehr offen ist.

Merksatz:

API-Isolation ist nicht Netzwerkisolation.

B6-K02-Q008 – Prod-Zugriff, Break-Glass und AuditierbarkeitProduktionsrechte brauchen Zweck, Zeit und Spur.
Frage:

„Wie gestalten Sie sicheren Produktionszugriff auf OpenShift?“

Kurz erklärt:

Produktionszugriff sollte restriktiv, zeitlich begrenzt, ticketgebunden und auditierbar sein. Break-Glass ist ein Notfallprozess, kein Dauerzustand.

Musterantwort:

Ich trenne normalen Lesezugriff, Deployment-Rechte, Betriebsrechte und Break-Glass-Zugriff. Entwickler erhalten in Prod typischerweise view oder eine feinere Read-only-Rolle. Deployments laufen über Pipeline-ServiceAccounts. Schreibender menschlicher Zugriff ist temporär, ticketgebunden, genehmigt und auditierbar.

Break-Glass bedeutet: Im Notfall kann jemand erhöhte Rechte bekommen, aber mit starker Nachvollziehbarkeit, kurzer Laufzeit, Alarmierung und anschließendem Review. Dauerhafte cluster-admin - oder admin -Rechte für viele Menschen sind kein Break-Glass, sondern fehlende Governance.

Senior-Antwort:

„Break-Glass ist kontrollierte Ausnahme mit Spuren. Wenn alle dauerhaft Admin sind, gibt es keinen Break-Glass-Prozess.“

Häufige Fehler:
  • Dauerhafte Admin-Rechte für alle Seniors.
  • Gemeinsamer Admin-Account.
  • Persönliche Tokens in CI/CD.
  • Kein Ablaufdatum für temporäre Rechte.
  • Keine Ticketreferenz.
  • Kein Review nach Break-Glass.
  • Prod-Schreibrechte ohne Alarmierung.
Best Practices:
  • Prod read-only für viele, write für wenige.
  • Pipeline-ServiceAccounts für Deployments.
  • Temporäre RoleBindings mit Ablauf.
  • Ticketreferenz annotieren.
  • Break-Glass alarmieren.
  • Zugriff regelmäßig reviewen.
  • Gemeinsame Accounts verbieten.
  • CI/CD ohne persönliche Tokens.
Stolperfalle:

Persönliche Tokens in Deployment-Pipelines zerstören Auditierbarkeit und erschweren Offboarding.

Merksatz:

Produktionsrechte brauchen Zweck, Zeit und Spur.

B6-K02-Q009 – RBAC-Diagnose: can-i , who-can , Impersonation und TroubleshootingRBAC ist eine konkrete Frage: Wer darf welches Verb auf welche Ressource in welchem Scope?
Frage:

„Wie debuggen Sie ein RBAC-Problem in OpenShift?“

Kurz erklärt:

RBAC-Diagnose prüft effektive Rechte: Was darf ein Subject tun, wer darf eine Aktion ausführen und welches Binding gewährt Rechte?

Musterantwort:

Ich prüfe zuerst die konkrete Aktion: Verb, Ressource, API Group, Namespace und Subject. Dann nutze ich oc auth can-i , um effektive Berechtigungen zu prüfen. Mit Impersonation kann ich testen, was ein bestimmter User darf. Mit oc adm policy who-can prüfe ich, wer eine Aktion ausführen kann. Danach inspiziere ich RoleBindings und ClusterRoleBindings.

Die wichtigste Frage ist nicht „hat der User Zugriff?“, sondern „welche konkrete API-Aktion auf welcher Ressource in welchem Scope schlägt fehl?“

Senior-Antwort:

„RBAC debugge ich nicht mit Bauchgefühl. Ich zerlege den Fehler in Subject, Verb, Resource, API Group und Scope.“

Häufige Fehler:
  • Sofort admin vergeben.
  • Verb falsch identifizieren.
  • API Group vergessen.
  • Namespace falsch.
  • Subresource pods/log vergessen.
  • User statt Gruppe prüfen.
  • ClusterRoleBindings übersehen.
Best Practices:
  • Fehler auf konkrete API-Aktion reduzieren.
  • can-i vor Role-Änderung.
  • who-can für Risikoanalyse.
  • RoleBindings und ClusterRoleBindings prüfen.
  • Subresources beachten.
  • Gruppenmitgliedschaft validieren.
  • Änderungen minimal durchführen.
  • Diagnose dokumentieren.
Stolperfalle:

pods lesen zu dürfen bedeutet nicht automatisch, pods/log lesen zu dürfen.

Merksatz:

RBAC ist eine konkrete Frage: Wer darf welches Verb auf welche Ressource in welchem Scope?

B6-K02-Q010 – RBAC-Governance, GitOps und Anti-PatternsRBAC ohne Governance wird langsam zu cluster-admin . 24h
Frage:

„Welche RBAC-Anti-Patterns sehen Sie in OpenShift-Plattformen?“

Kurz erklärt:

RBAC-Governance bedeutet: Rollen und Bindings sind versioniert, reviewt, minimal, nachvollziehbar, automatisiert und regelmäßig geprüft.

Musterantwort:

Häufige Anti-Patterns sind: cluster-admin für CI/CD, dauerhafte Prod-Admin-Rechte für Entwickler, RoleBindings für Einzelpersonen statt Gruppen, manuelle Änderungen ohne Git, Default-ServiceAccount für alles, Secrets-Leserechte als Standard, ClusterRoleBindings für Project-Zugriff, keine Access Reviews, keine Break-Glass-Prozesse und fehlende Trennung von Runtime- und Deployment-Identitäten.

Gute Governance behandelt RBAC wie Code: Pull Request, Review, Owner, Zweck, Umgebung, Ablauf bei temporären Rechten, automatisierte Policy Checks und regelmäßige Rezertifizierung.

Senior-Antwort:

„RBAC, das nicht versioniert ist, ist nach einigen Monaten nur noch Hoffnung.“

Häufige Fehler:
  • Manuelle oc adm policy Änderungen in Prod.
  • Keine Owner-Annotation.
  • Keine Zweckbeschreibung.
  • Keine Entfernung temporärer Rechte.
  • Einzeluser statt Gruppen.
  • Runtime und Deploy-Rechte vermischt.
  • Secrets-Zugriff nicht separat kontrolliert.
  • Keine Policy Checks in PRs.
Best Practices:
  • RBAC versionieren.
  • RoleBindings über Gruppen.
  • Owner und Zweck annotieren.
  • Temporäre Rechte mit Ablauf.
  • GitOps für Anwendung von RBAC.
  • Policy-as-Code für verbotene Muster.
  • Regelmäßige Access Reviews.
  • ClusterRoleBindings besonders reviewen.
Stolperfalle:

Temporäre Rechte sind nur dann temporär, wenn ein System sie automatisch entfernt oder zumindest aktiv überprüft.

Merksatz:

RBAC ohne Governance wird langsam zu cluster-admin .

B6-K03 – Kapitel 3: Pods, Deployments und DeploymentConfigs 10 Karten

B6-K03-Q001 – Pod-Grundmodell: Container, Sidecars, Volumes und LifecyclePods laufen; Controller erhalten. 24h
Frage:

„Was ist ein Pod, und warum deployt man in OpenShift meistens keine einzelnen Pods direkt?“

Kurz erklärt:

Ein Pod ist die kleinste schedulbare Einheit in Kubernetes und OpenShift. Er enthält einen oder mehrere Container, gemeinsame Netzwerkidentität, optionale Volumes und Lifecycle-Konfiguration.

Musterantwort:

Ein Pod ist die kleinste Ausführungseinheit. Container innerhalb eines Pods teilen sich Netzwerknamespace, können über localhost kommunizieren und gemeinsam Volumes nutzen. Trotzdem deployt man produktive Anwendungen fast nie als einzelne Pods direkt, sondern über Controller wie Deployments, StatefulSets, Jobs oder DaemonSets. Der Controller sorgt für gewünschte Replica-Anzahl, Rollouts, Self-Healing und Reconciliation.

Ein einzelner Pod ist vergänglich. Wenn er gelöscht wird oder ein Node ausfällt, braucht man einen Controller, der ihn neu erstellt.

Senior-Antwort:

„Ein Pod ist eine Laufzeithülle, keine stabile Architekturgrenze. Stabilität entsteht durch Controller, Services und deklarativen Zustand.“

Häufige Fehler:
  • Einzelne Pods produktiv deployen.
  • Pod-IP in Konfiguration speichern.
  • Logs in Dateien statt stdout.
  • Temporäre Daten im Image-Verzeichnis schreiben.
  • Keine Resource Requests.
  • Sidecar ohne Lifecycle-Abstimmung.
  • Pod als stabile fachliche Einheit betrachten.
Best Practices:
  • Pods über Controller verwalten.
  • Pod-Zustand möglichst ephemeral halten.
  • Services für stabile Erreichbarkeit nutzen.
  • Logs nach stdout/stderr.
  • Requests und Limits setzen.
  • Volumes bewusst einsetzen.
  • Sidecars nur bei klarer Kopplung.
  • Pod-Lifecycle in Tests berücksichtigen.
Stolperfalle:

Eine Pod-IP ist vergänglich. Wer sie in Konfiguration oder Clients speichert, baut Instabilität ein.

Merksatz:

Pods laufen; Controller erhalten.

B6-K03-Q002 – Deployment, ReplicaSet und deklarativer gewünschter ZustandDeployment beschreibt; Controller versöhnt.
Frage:

„Wie funktioniert ein Kubernetes Deployment auf OpenShift?“

Kurz erklärt:

Ein Deployment beschreibt den gewünschten Zustand einer Anwendung: Replica-Anzahl, Pod-Template, Update-Strategie und Labels. Der Controller erzeugt ReplicaSets und Pods, um diesen Zustand zu erreichen.

Musterantwort:

Ein Deployment beschreibt deklarativ, wie viele Replicas einer Anwendung laufen sollen und welches Pod-Template genutzt wird. Der Deployment-Controller erzeugt ReplicaSets, und ReplicaSets erzeugen Pods. Wenn das Pod-Template geändert wird, erstellt das Deployment ein neues ReplicaSet und rollt die neue Version aus.

Das ist der Kern des deklarativen Modells: Ich beschreibe den Zielzustand, der Controller reconciled den Istzustand dahin. In OpenShift ist das Deployment für neue Anwendungen meist der Standard, weil es Kubernetes-nativ und portabel ist.

Senior-Antwort:

„Ein Deployment ist kein Startskript. Es ist ein deklarativer Vertrag, den der Controller permanent durchsetzt.“

Häufige Fehler:
  • Selector und Pod Labels passen nicht.
  • Manuelle Pod-Änderungen erwarten.
  • Keine Resource Requests.
  • RollingUpdate ohne Kapazität.
  • Image-Tag mutiert ohne Template-Änderung.
  • Zu aggressive Rollouts.
  • Keine Strategie für Rollback.
Best Practices:
  • Deployment für neue stateless Workloads.
  • Labels und Selector stabil halten.
  • Pod-Template als einzige Quelle für Runtime-Änderungen.
  • Immutable Image Tags.
  • RollingUpdate bewusst konfigurieren.
  • Ressourcen für Surge einplanen.
  • Rollout-Status überwachen.
  • Manifeste in Git versionieren.
Stolperfalle:

Wenn Selector und Pod-Labels nicht zusammenpassen, verwaltet das Deployment keine passenden Pods oder kann nicht korrekt erstellt werden.

Merksatz:

Deployment beschreibt; Controller versöhnt.

B6-K03-Q003 – DeploymentConfig: OpenShift-spezifische Workload-RessourceDeploymentConfig ist OpenShift-Erbe; Deployment ist Kubernetes-Standard.
Frage:

„Was ist eine DeploymentConfig, und warum sollte man sie kennen?“

Kurz erklärt:

DeploymentConfig ist eine OpenShift-spezifische Ressource für Deployments, die eigene Trigger, Strategien und OpenShift-Integration bietet. Sie ist in Legacy-OpenShift-Umgebungen häufig.

Musterantwort:

Eine DeploymentConfig ist eine OpenShift-spezifische Workload-Ressource aus der API Group apps.openshift.io/v1 . Die OpenShift-API-Dokumentation listet sie als eigene Ressource mit Operationen wie Lesen, Aktualisieren und Löschen von DeploymentConfigs. Sie definiert ein Pod-Template und verwaltet neue Deployments bei Image- oder Konfigurationsänderungen.

Man sollte sie kennen, weil viele OpenShift-Bestandsplattformen DeploymentConfigs nutzen, besonders zusammen mit ImageStreams und ImageChange-Triggern. Für neue Services bevorzuge ich aber meist Kubernetes Deployments, sofern keine spezifische DeploymentConfig-Funktion benötigt wird.

Senior-Antwort:

„DeploymentConfig ist OpenShift-spezifisch und in Legacy wichtig. Für neue Workloads brauche ich einen guten Grund, sie einem Deployment vorzuziehen.“

Häufige Fehler:
  • DeploymentConfig und Deployment gleich behandeln.
  • ImageChange-Trigger in Prod unkontrolliert lassen.
  • Automatische Rollouts ohne Freigabe.
  • Legacy-DeploymentConfigs nicht dokumentieren.
  • Migration ohne Trigger-Ersatz.
  • Rollout-Historie falsch interpretieren.
  • latest mit automatischem Trigger in Prod.
Best Practices:
  • DeploymentConfigs in Legacy verstehen.
  • Neue Workloads bevorzugt mit Deployment.
  • ImageChange-Trigger kontrollieren.
  • Prod-Rollouts explizit freigeben.
  • Migration geplant durchführen.
  • Trigger-Semantik dokumentieren.
  • Image-Promotion statt unkontrollierter Auto-Rollouts.
  • DeploymentConfig nicht unnötig neu einführen.
Stolperfalle:

Ein automatischer ImageChange-Trigger kann ein Produktionsdeployment auslösen, nur weil ein Tag aktualisiert wurde.

Merksatz:

DeploymentConfig ist OpenShift-Erbe; Deployment ist Kubernetes-Standard.

B6-K03-Q004 – Rolling Updates, Recreate und Deployment-StrategienRolling Update braucht Koexistenzfähigkeit.
Frage:

„Wie funktioniert ein Rolling Update, und wann ist Recreate besser?“

Kurz erklärt:

Deployment-Strategien bestimmen, wie alte Pods durch neue Pods ersetzt werden. Rolling Updates ermöglichen meist Deployments ohne Downtime, brauchen aber kompatible Anwendungen.

Musterantwort:

Ein Rolling Update ersetzt alte Pods schrittweise durch neue Pods. Neue Pods werden gestartet, müssen readiness erreichen, und erst dann werden alte Pods entfernt. Red Hat beschreibt Rolling Deployment sinngemäß als Strategie, bei der Instanzen der alten Version langsam durch Instanzen der neuen Version ersetzt werden.

Recreate beendet zuerst alte Pods und startet danach neue. Das verursacht Downtime, kann aber sinnvoll sein, wenn nur eine Version gleichzeitig laufen darf, zum Beispiel bei nicht kompatiblen Datenbankmigrationen, exklusiven Locks oder zustandsbehafteten Legacy-Anwendungen.

Senior-Antwort:

„Rolling Update funktioniert nur, wenn zwei Versionen kurzzeitig koexistieren dürfen. Das ist eine Architekturfrage, keine YAML-Einstellung.“

Häufige Fehler:
  • Rolling Update trotz inkompatibler Versionen.
  • Datenbankmigration nicht rückwärtskompatibel.
  • Readiness zu früh grün.
  • Kein Graceful Shutdown.
  • maxSurge ohne Kapazität.
  • Liveness-Probe killt Rollout.
  • Keine Rollback-Strategie.
Best Practices:
  • Rolling Update als Standard für stateless Services.
  • Recreate nur bewusst.
  • Backward-compatible Datenbankmigrationen.
  • Readiness korrekt implementieren.
  • Graceful Shutdown.
  • Surge-Kapazität planen.
  • Rollout überwachen.
  • Rollback testen.
Stolperfalle:

Eine nicht rückwärtskompatible Datenbankmigration kann ein technisch korrektes Rolling Update fachlich zerstören.

Merksatz:

Rolling Update braucht Koexistenzfähigkeit.

B6-K03-Q005 – Probes: Startup, Readiness und LivenessReadiness nimmt Traffic weg; Liveness nimmt Leben weg.
Frage:

„Wie unterscheiden sich Startup-, Readiness- und Liveness-Probe?“

Kurz erklärt:

Startup-Probe schützt langsame Starts, Readiness steuert Traffic, Liveness steuert Neustarts.

Musterantwort:

Startup-Probe prüft, ob die Anwendung initial erfolgreich gestartet ist. Solange sie aktiv ist, schützt sie die Anwendung davor, durch Liveness zu früh neugestartet zu werden. Readiness entscheidet, ob ein Pod Traffic erhalten soll. Liveness entscheidet, ob der Container als defekt betrachtet und neu gestartet werden soll.

Für Java und Spring Boot ist das kritisch: Der Start kann durch JVM-Warmup, Migrationen, Classpath, Framework-Initialisierung oder externe Verbindungen länger dauern. Deshalb sollte man Startup-Probe verwenden und Liveness nicht mit Downstream-Abhängigkeiten überladen.

Senior-Antwort:

„Readiness schützt den Traffic. Liveness schützt den Prozess. Wer beides vermischt, erzeugt Neustartstürme.“

Häufige Fehler:
  • Keine StartupProbe bei langsamer JVM.
  • Liveness prüft Datenbank.
  • Readiness sofort grün.
  • Probe-Endpunkt zu teuer.
  • Timeout zu kurz.
  • FailureThreshold ohne Startzeitverständnis.
  • Gleicher Endpunkt für alle Probes ohne Semantik.
Best Practices:
  • StartupProbe für Java-Apps mit relevantem Startup.
  • Readiness für Traffic-Fähigkeit.
  • Liveness für Prozessdefekt.
  • Probe-Endpunkte billig halten.
  • Downstreams in Liveness vermeiden.
  • Initiale Werte messen.
  • Probe-Konfiguration lasttesten.
  • Graceful Shutdown mit Readiness kombinieren.
Stolperfalle:

Wenn Liveness die Datenbank prüft, kann ein kurzer Datenbankausfall alle App-Pods neu starten und den Incident verschlimmern.

Merksatz:

Readiness nimmt Traffic weg; Liveness nimmt Leben weg.

B6-K03-Q006 – Resources: Requests, Limits, QoS, OOMKilled und CPU-ThrottlingRequests planen; Limits begrenzen; die JVM muss dazwischen atmen.
Frage:

„Wie dimensionieren Sie Java-Workloads auf OpenShift?“

Kurz erklärt:

Requests reservieren planbare Ressourcen, Limits begrenzen Verbrauch. Falsche Werte verursachen OOMKills, CPU-Throttling oder schlechte Cluster-Auslastung.

Musterantwort:

Ich dimensioniere Java-Workloads anhand von Messdaten: Heap, Non-Heap, Thread Stacks, Direct Memory, Native Memory, CPU-Verbrauch, GC-Verhalten, Startup-Spitzen und Lastprofil. Requests sollten eine realistische Baseline für Scheduling sein. Limits sollten Schutz bieten, aber genug Headroom für JVM-Native-Memory lassen.

Ich setze nicht einfach -Xmx gleich Containerlimit. Die JVM braucht mehr als Heap. Für viele moderne Java-Images nutze ich MaxRAMPercentage , aber validiere es mit realer Last.

Senior-Antwort:

„Java braucht Containerlimits, aber die JVM ist mehr als Heap. Ohne Headroom kauft man OOMKills.“

Häufige Fehler:
  • Keine Requests setzen.
  • Memory-Limit gleich Heap setzen.
  • CPU-Limit zu niedrig.
  • JVM-Defaults nicht prüfen.
  • HikariCP zu groß pro Pod.
  • Zu viele Threads.
  • OOMKilled nur als App-Bug interpretieren.
Best Practices:
  • Ressourcen mit Lasttests bestimmen.
  • Heap mit Headroom setzen.
  • MaxRAMPercentage bewusst nutzen.
  • CPU-Throttling monitoren.
  • OOMKilled Events prüfen.
  • Connection Pools an Pod-Zahl anpassen.
  • Requests realistisch setzen.
  • Limits nicht blind standardisieren.
Stolperfalle:

CPU-Limits können Latenzprobleme verursachen, die wie Applikationsprobleme aussehen, aber eigentlich Throttling sind.

Merksatz:

Requests planen; Limits begrenzen; die JVM muss dazwischen atmen.

B6-K03-Q007 – ConfigMaps, Secrets und Pod-Template-ÄnderungenConfig ändern heißt nicht automatisch App-Verhalten ändern.
Frage:

„Warum sieht mein Pod eine geänderte ConfigMap oder Secret nicht sofort?“

Kurz erklärt:

ConfigMaps und Secrets injizieren Konfiguration in Pods. Änderungen daran führen nicht immer automatisch zu einem neuen Rollout, wenn das Pod-Template unverändert bleibt.

Musterantwort:

Es hängt davon ab, wie die Konfiguration konsumiert wird. Environment Variables werden beim Containerstart gesetzt und ändern sich in einem laufenden Container nicht. ConfigMap- oder Secret-Volumes können aktualisiert werden, aber die Anwendung muss Änderungen auch neu laden. Viele Java-Anwendungen lesen Konfiguration nur beim Start.

Für produktive Workloads bevorzuge ich daher kontrollierte Rollouts bei Konfigurationsänderungen, zum Beispiel durch Änderung einer Annotation im Pod-Template. Dadurch erzeugt das Deployment neue Pods mit klarer Versionierung.

Senior-Antwort:

„Konfiguration ist Runtime-Vertrag. Ich will wissen, welche Pod-Version mit welcher Config-Version läuft.“

Häufige Fehler:
  • Secrets in ConfigMaps speichern.
  • Env Vars ändern und Runtime-Update erwarten.
  • ConfigMap ändern ohne Rollout.
  • Keine Versionierung von Config.
  • Secret-Rotation ohne Restart-Plan.
  • Sensitive Werte in Logs.
  • Config pro Umgebung im Image backen.
Best Practices:
  • ConfigMaps für nicht-sensitive Konfiguration.
  • Secrets für sensitive Werte.
  • Environment-spezifische Config außerhalb des Images.
  • Rollout bei Startzeit-Konfiguration.
  • Pod-Template-Annotation für Config-Version.
  • Secret-Rotation planen.
  • Sensitive Werte nicht loggen.
  • Config als Code versionieren.
Stolperfalle:

Eine geänderte Secret-Env-Var wird in einem laufenden Java-Prozess nicht magisch aktualisiert.

Merksatz:

Config ändern heißt nicht automatisch App-Verhalten ändern.

B6-K03-Q008 – Scheduling: NodeSelector, Taints, Tolerations und AffinityScheduling ist Ressourcen- und Ausfallsicherheitsdesign.
Frage:

„Wie steuern Sie, wo Workloads auf OpenShift laufen?“

Kurz erklärt:

Scheduling entscheidet, auf welchen Nodes Pods laufen. NodeSelector, Affinity, Anti-Affinity, Taints und Tolerations steuern Platzierung und Isolation.

Musterantwort:

Ich steuere Scheduling über mehrere Mechanismen. nodeSelector erzwingt einfache Label-basierte Platzierung. Affinity und Anti-Affinity erlauben weichere oder komplexere Regeln, zum Beispiel Pods über Nodes verteilen. Taints auf Nodes verhindern Scheduling, solange Pods keine passende Toleration haben. Das ist nützlich für dedizierte Node Pools, spezielle Hardware oder isolierte Workloads.

Ich setze Scheduling-Regeln sparsam ein. Zu harte Regeln führen schnell zu Pending Pods, wenn keine passenden Nodes verfügbar sind.

Senior-Antwort:

„Scheduling-Regeln sind Kapazitätsverträge. Je härter ich sie formuliere, desto eher bekomme ich Pending Pods.“

Häufige Fehler:
  • Zu harte NodeSelector.
  • Toleration ohne Verständnis des Taints.
  • Anti-Affinity required statt preferred.
  • Alle Replicas auf einem Node.
  • Node Labels manuell inkonsistent.
  • Spezielle Node Pools überfragmentieren.
  • Scheduling-Probleme als App-Bug interpretieren.
Best Practices:
  • Scheduling-Regeln sparsam.
  • Anti-Affinity für kritische Replicas.
  • Preferred statt required, wo möglich.
  • Node Pools klar dokumentieren.
  • Kapazität passend planen.
  • Pending Pods über Events debuggen.
  • Labels standardisieren.
  • Workload-Isolation bewusst einsetzen.
Stolperfalle:

Eine Toleration zieht einen Pod nicht aktiv auf einen Node. Sie erlaubt nur Scheduling auf tainted Nodes.

Merksatz:

Scheduling ist Ressourcen- und Ausfallsicherheitsdesign.

B6-K03-Q009 – Rollout-Diagnose: CrashLoopBackOff, ImagePullBackOff, Pending und Route 503Events erklären, warum die Plattform handelt; Logs erklären, was die App sagt.
Frage:

„Wie debuggen Sie ein fehlgeschlagenes Deployment auf OpenShift?“

Kurz erklärt:

Rollout-Diagnose kombiniert Deployment-Status, Pods, Events, Logs, ReplicaSets, Services, Endpoints und Routes.

Musterantwort:

Ich gehe systematisch vor. Erst prüfe ich oc rollout status , dann Pods und Events. Bei CrashLoopBackOff lese ich aktuelle und vorherige Logs sowie Exit Code und Probes. Bei ImagePullBackOff prüfe ich Image-Name, Tag, Registry, Pull Secret und ImageStream. Bei Pending prüfe ich Scheduler-Events, Resources, NodeSelector, Taints und PVCs. Bei Route 503 prüfe ich Service, Selector, Endpoints, Readiness und Route-Port.

Ich springe nicht sofort in App-Logs. Oft ist das Problem Scheduling, Image Pull, Probe, Service Selector oder Route-Konfiguration.

Senior-Antwort:

„Ich debugge OpenShift-Rollouts schichtweise: Deployment, Pod, Events, Logs, Service, Endpoints, Route.“

Häufige Fehler:
  • Nur Logs prüfen.
  • Events ignorieren.
  • Service-Selector nicht prüfen.
  • Endpoints nicht prüfen.
  • Route-Port falsch.
  • ImagePullSecret vergessen.
  • Pending Pod als App-Fehler sehen.
  • OOMKilled nicht erkennen.
Best Practices:
  • Diagnose von außen nach innen.
  • Rollout-Status prüfen.
  • Events immer lesen.
  • logs --previous bei Crashloops.
  • Service und Endpoints prüfen.
  • Route-Port validieren.
  • OOMKilled und Exit Codes beachten.
  • Diagnose-Runbook pflegen.
Stolperfalle:

Eine Route 503 ist sehr oft kein Routerproblem, sondern ein Service ohne ready Endpoints.

Merksatz:

Events erklären, warum die Plattform handelt; Logs erklären, was die App sagt.

B6-K03-Q010 – Produktionsreife Workload-Standards und Anti-PatternsProduktionsreife ist ein Manifest-Standard, nicht ein Bauchgefühl. 24h
Frage:

„Welche Standards sollte jedes produktive Deployment auf OpenShift erfüllen?“

Kurz erklärt:

Produktionsreife Workloads brauchen Standards für Images, Ressourcen, Probes, Security, Labels, Rollouts, Config, Observability und Shutdown.

Musterantwort:

Ein produktives Deployment sollte mindestens haben: stabile Labels, eindeutige Owner, immutable Image Tag, eigenen ServiceAccount, Resource Requests/Limits, Startup-/Readiness-/Liveness-Probes, non-root-kompatibles Image, restriktiven SecurityContext, Graceful Shutdown, Config-/Secret-Strategie, Rollout-Strategie, Observability und GitOps-Verwaltung.

Anti-Patterns sind latest in Prod, keine Requests, Liveness als Downstream-Check, Root-Container, Default-ServiceAccount, manuelle Änderungen im Cluster, keine Readiness, keine StartupProbe bei Java, zu große Connection Pools und Deployment ohne Rollback-Plan.

Senior-Antwort:

„Ein gutes Deployment manifestiert Betriebswissen: Ressourcen, Gesundheit, Sicherheit, Ownership und Rollout-Verhalten.“

Häufige Fehler:
  • latest in Produktion.
  • Keine Resource Requests.
  • Default-ServiceAccount.
  • Keine Probes.
  • Liveness prüft Datenbank.
  • Root-Container.
  • Manuelle oc edit Änderungen.
  • Keine Config-Versionierung.
Best Practices:
  • Workload-Templates standardisieren.
  • GitOps als Source of Truth.
  • Immutable Images.
  • ServiceAccount pro App.
  • Probes verpflichtend.
  • Resource-Werte aus Messdaten.
  • SecurityContext restriktiv.
  • Labels für Ownership.
Stolperfalle:

Ein Deployment, das nur Image und Port enthält, ist kein produktionsreifes Deployment.

Merksatz:

Produktionsreife ist ein Manifest-Standard, nicht ein Bauchgefühl.

B6-K04 – Kapitel 4: Services, Routes und Ingress 10 Karten

B6-K04-Q001 – Service-Grundmodell: ClusterIP, Selector, Endpoints und internes Load BalancingService-Namen sind stabil; Pods sind vergänglich. 24h
Frage:

„Wie funktioniert ein Service in OpenShift?“

Kurz erklärt:

Ein Service stellt eine stabile interne Netzwerkadresse bereit und leitet Traffic an Pods weiter, die über Labels selektiert werden.

Musterantwort:

Ein Service abstrahiert eine Gruppe von Pods hinter einer stabilen virtuellen Adresse. Pods sind vergänglich; ihre IPs ändern sich. Der Service nutzt Labels und Selector, um passende Pods zu finden. OpenShift beziehungsweise Kubernetes erstellt daraus Endpoints oder EndpointSlices. Traffic an die ClusterIP des Services wird auf gesunde Backend-Pods verteilt. Red Hat beschreibt, dass OpenShift Kubernetes-Labels und Selector verwendet, um zu verfolgen, welche Pods zu welchem Service gehören, und Traffic zur ClusterIP auf gesunde Pods verteilt.

Für mich ist der Service der interne Vertrag: „Unter diesem Namen und Port ist diese Anwendung im Cluster erreichbar.“

Senior-Antwort:

„Ein Service stabilisiert die Netzwerkadresse, nicht die Anwendung. Ob Traffic wirklich ankommt, entscheidet die Kette aus Selector, Readiness und Endpoints.“

Häufige Fehler:
  • Service-Selector passt nicht zu Pod-Labels.
  • targetPort zeigt auf falschen Port.
  • Readiness bleibt false, Service hat keine Endpoints.
  • Pod-IP in Clients konfigurieren.
  • Service-Port und Container-Port verwechseln.
  • Mehrere Apps teilen unabsichtlich gleiche Labels.
  • Keine sauberen Namenskonventionen.
Best Practices:
  • Service-Selector stabil und eindeutig halten.
  • Benannte Ports verwenden.
  • Readiness korrekt implementieren.
  • Service-Namen als interne Verträge behandeln.
  • Pod-IPs nie als Client-Konfiguration nutzen.
  • Labels standardisieren.
  • Endpoints bei Connectivity-Problemen prüfen.
  • Services pro fachlicher Schnittstelle modellieren.
Stolperfalle:

Eine Route oder ein Service kann korrekt existieren und trotzdem keinen Traffic liefern, wenn keine ready Endpoints dahinterstehen.

Merksatz:

Service-Namen sind stabil; Pods sind vergänglich.

B6-K04-Q002 – Route-Grundmodell: Externe HTTP(S)-ErreichbarkeitRoute veröffentlicht; Service verteilt; Pod verarbeitet.
Frage:

„Was ist eine OpenShift Route?“

Kurz erklärt:

Eine Route macht einen Service extern über den OpenShift Ingress Controller erreichbar, typischerweise über HTTP oder HTTPS.

Musterantwort:

Eine Route ist eine OpenShift-spezifische Ressource, die externen HTTP(S)-Traffic zu einem Service leitet. Der OpenShift Router beziehungsweise Ingress Controller verarbeitet den eingehenden Hostnamen, findet die passende Route und proxied den Request zum Ziel-Service. Red Hat beschreibt Routes als Teil von „Ingress and load balancing“ und dokumentiert dort die Erstellung, Absicherung und Konfiguration von Routes.

Eine Route ist deshalb der externe Vertrag einer Anwendung: Hostname, TLS-Modus, Ziel-Service, Ziel-Port und optionale Routing-Regeln.

Senior-Antwort:

„Der Service ist intern. Die Route ist der externe HTTP-Vertrag mit Hostname, TLS und Ziel-Port.“

Häufige Fehler:
  • Route zeigt auf falschen Service-Port.
  • Hostname kollidiert mit anderer Route.
  • TLS-Termination falsch verstanden.
  • Forwarded Headers nicht konfiguriert.
  • Route existiert, aber Service hat keine Endpoints.
  • HTTP nicht auf HTTPS umgeleitet.
  • Route als generischen TCP-Proxy missverstehen.
Best Practices:
  • Route als externen API-Vertrag behandeln.
  • TLS-Modus bewusst wählen.
  • Service-Port korrekt referenzieren.
  • HTTPS-Redirect aktivieren.
  • Forwarded Headers in der App unterstützen.
  • Hostname-Konventionen standardisieren.
  • Route-Status prüfen.
  • Externe APIs über API Gateway absichern, wenn nötig.
Stolperfalle:

Eine Route 503 bedeutet oft nicht, dass der Router defekt ist, sondern dass der Service keine ready Endpoints hat.

Merksatz:

Route veröffentlicht; Service verteilt; Pod verarbeitet.

B6-K04-Q003 – TLS-Termination: Edge, Re-encrypt und PassthroughEdge sieht HTTP; passthrough sieht nur TLS.
Frage:

„Was ist der Unterschied zwischen edge, re-encrypt und passthrough TLS bei OpenShift Routes?“

Kurz erklärt:

TLS-Termination bestimmt, wo TLS beendet wird: am Router, am Router plus erneuter TLS-Verbindung zum Backend oder direkt im Pod.

Musterantwort:

Bei edge termination endet TLS am OpenShift Router. Vom Router zum Backend-Service geht Traffic typischerweise unverschlüsselt weiter. Bei re-encrypt endet TLS ebenfalls am Router, aber der Router baut anschließend eine neue TLS-Verbindung zum Backend auf. Bei passthrough beendet der Router TLS nicht; der verschlüsselte Traffic wird direkt zum Backend weitergeleitet.

Red Hat dokumentiert für edge Routes, dass der Ingress Controller die TLS-Verschlüsselung beendet, bevor Traffic zum Ziel-Pod weitergeleitet wird. Für passthrough gilt: Da der Router TLS nicht terminiert, kann er HTTP-Inhalte nicht lesen; Red Hat weist deshalb darauf hin, dass path-based routing bei passthrough TLS nicht verfügbar ist.

Senior-Antwort:

„TLS-Termination ist kein YAML-Detail. Sie entscheidet, wer Zertifikate kontrolliert, wer HTTP sieht und wo Verschlüsselung endet.“

Häufige Fehler:
  • Edge TLS nutzen, aber App erwartet HTTPS.
  • Passthrough verwenden und Path Routing erwarten.
  • Destination CA bei re-encrypt falsch.
  • Backend-Zertifikat abgelaufen.
  • OAuth Redirects mit HTTP statt HTTPS.
  • TLS-Modus nicht dokumentieren.
  • Zertifikate manuell ohne Rotation verwalten.
Best Practices:
  • TLS-Modus pro API bewusst wählen.
  • Edge für einfache HTTP-Apps.
  • Re-encrypt für Ende-zu-Ende-Verschlüsselung.
  • Passthrough nur bei echtem Bedarf.
  • Forwarded Headers korrekt konfigurieren.
  • Zertifikatsrotation automatisieren.
  • TLS-Fehler mit Router- und App-Logs debuggen.
  • Security-Anforderungen dokumentieren.
Stolperfalle:

Passthrough TLS erlaubt dem Router kein HTTP-Path-basiertes Routing, weil der Router den verschlüsselten Inhalt nicht lesen kann.

Merksatz:

Edge sieht HTTP; passthrough sieht nur TLS.

B6-K04-Q004 – Ingress Controller, Router und HAProxy-basierte Routing-SchichtRoutes definieren; Router liefern aus.
Frage:

„Welche Rolle spielt der Ingress Controller in OpenShift?“

Kurz erklärt:

Der OpenShift Ingress Controller betreibt die Router-Schicht, die Routes und Ingress-Ressourcen verarbeitet und externen Traffic in den Cluster leitet.

Musterantwort:

Der Ingress Controller ist die zentrale Komponente für externen HTTP(S)-Traffic. Er betreibt Router-Pods, die Routes und Ingress-Ressourcen auswerten und Requests an Services weiterleiten. Red Hat beschreibt den Ingress Operator als Komponente, die die IngressController API implementiert und externe Zugriffe auf Cluster-Services ermöglicht, indem sie HAProxy-basierte Ingress Controller deployt und verwaltet.

Wenn externe Erreichbarkeit gestört ist, prüfe ich deshalb nicht nur die Route, sondern auch Ingress Controller, Router-Pods und den ingress Cluster Operator.

Senior-Antwort:

„Eine Route ist Konfiguration. Der Ingress Controller ist die laufende Infrastruktur, die diese Konfiguration wirksam macht.“

Häufige Fehler:
  • Route-Probleme nur in App suchen.
  • Router-Pods nicht prüfen.
  • clusteroperator ingress ignorieren.
  • Ingress Controller mit Route verwechseln.
  • Zu viele Routes ohne Kapazitätsplanung.
  • Router-Timeouts nicht kennen.
  • Keine Trennung interner und externer Ingress-Pfade.
Best Practices:
  • Ingress Operator und Router-Pods überwachen.
  • Separate Ingress Controller bei Bedarf.
  • Router-Kapazität planen.
  • TLS-Last berücksichtigen.
  • Route- und Router-Metriken nutzen.
  • Timeouts bewusst konfigurieren.
  • Ingress Sharding nur mit Governance.
  • App- und Plattformdiagnose kombinieren.
Stolperfalle:

Wenn viele Routes betroffen sind, ist es oft kein App-Problem, sondern ein Ingress- oder Router-Problem.

Merksatz:

Routes definieren; Router liefern aus.

B6-K04-Q005 – Kubernetes Ingress vs OpenShift RouteRoute ist OpenShift-nativ; Ingress ist Kubernetes-standardisiert; Governance entscheidet.
Frage:

„Sollte man in OpenShift Routes oder Kubernetes Ingress verwenden?“

Kurz erklärt:

Ingress ist Kubernetes-standardisiert; Route ist OpenShift-spezifisch. Beide können externen HTTP(S)-Traffic zu Services führen, unterscheiden sich aber in API, Features und Plattformkonventionen.

Musterantwort:

Das hängt von Plattformstandard, Portabilität und benötigten Features ab. Routes sind OpenShift-nativ, sehr verbreitet und integrieren sich tief in OpenShift-Routing. Ingress ist Kubernetes-standardisiert und kann sinnvoll sein, wenn Manifeste möglichst portabel bleiben sollen oder Tooling Ingress erwartet. Red Hat beschreibt Routes als OpenShift-Netzwerkressource, die einer Kubernetes-Ingress-Ressource ähnelt; der default OpenShift Router nutzt HTTP-Header der eingehenden Anfrage, um die Verbindung zu proxien.

Ich würde in einer OpenShift-zentrierten Plattform oft Routes standardisieren, aber Ingress unterstützen, wenn Teams Kubernetes-portable Manifeste oder spezielle Controller-Integrationen brauchen.

Senior-Antwort:

„Route und Ingress sind nicht nur YAML-Varianten. Sie sind Plattformentscheidungen über Portabilität, Features und Governance.“

Häufige Fehler:
  • Route und Ingress doppelt für denselben Host.
  • Controller-spezifische Ingress-Annotations unportabel nutzen.
  • Route-Features in Ingress erwarten.
  • TLS-Semantik nicht vergleichen.
  • Plattformstandard ignorieren.
  • Manifeste „portabel“ nennen, obwohl sie controller-spezifisch sind.
  • Ingress ohne Security-Governance.
Best Practices:
  • Plattformstandard definieren.
  • Routes für OpenShift-native Workflows prüfen.
  • Ingress für Kubernetes-Portabilität nutzen, wenn sinnvoll.
  • Doppelte Host-Definitionen vermeiden.
  • TLS- und Header-Verhalten testen.
  • Gateway/API-Gateway für komplexe API-Governance prüfen.
  • Ressource und Controller gemeinsam betrachten.
  • Dokumentieren, welche Ressource Teams verwenden sollen.
Stolperfalle:

Ingress ist zwar Kubernetes-Standard, aber viele reale Ingress-Manifeste sind wegen Controller-Annotations trotzdem nicht wirklich portabel.

Merksatz:

Route ist OpenShift-nativ; Ingress ist Kubernetes-standardisiert; Governance entscheidet.

B6-K04-Q006 – Path-based Routing, Hostnames und Wildcard RoutesRouting nach Pfad braucht sichtbares HTTP.
Frage:

„Wie funktioniert host- und path-based Routing mit OpenShift Routes?“

Kurz erklärt:

Routes können host- und pfadbasiert auf unterschiedliche Services zeigen. Path-based Routing funktioniert aber nicht mit passthrough TLS.

Musterantwort:

OpenShift Routes nutzen Hostnamen und optional Pfade, um HTTP(S)-Requests zum passenden Service zu leiten. Ein Host wie shop.apps.cluster.example.com kann unterschiedliche Pfade haben, zum Beispiel / für das Frontend und /api für die API. Das funktioniert nur, wenn der Router den HTTP-Request lesen kann. Bei passthrough TLS ist der Inhalt verschlüsselt; deshalb ist path-based routing damit nicht verfügbar. Red Hat dokumentiert ausdrücklich, dass path-based routing bei passthrough TLS nicht verfügbar ist, weil der Router TLS nicht terminiert und den Request-Inhalt nicht lesen kann.

Senior-Antwort:

„Path Routing funktioniert nur, wenn der Router HTTP sehen kann. Bei passthrough sieht er nur verschlüsselten Traffic.“

Häufige Fehler:
  • Path Routing mit passthrough TLS erwarten.
  • App-Context-Path nicht anpassen.
  • Cookies kollidieren unter gleichem Host.
  • CORS und Security Header uneinheitlich.
  • Hostnamen ohne Governance vergeben.
  • Wildcard Routes unkontrolliert erlauben.
  • API Gateway durch viele Path-Routes ersetzen.
Best Practices:
  • Hostnamen zentral verwalten.
  • Path Routing bewusst und sparsam.
  • Apps auf Base Path testen.
  • Passthrough-Einschränkungen beachten.
  • Wildcard Routes kontrollieren.
  • API Gateway für komplexe API-Policies.
  • Route-Konflikte automatisiert prüfen.
  • Security Header konsistent setzen.
Stolperfalle:

Eine App, die absolute Redirects auf /login erzeugt, funktioniert hinter einem Pfadpräfix oft falsch.

Merksatz:

Routing nach Pfad braucht sichtbares HTTP.

B6-K04-Q007 – Session Affinity, Sticky Sessions und Stateless ServicesStateless skaliert; Sticky klebt.
Frage:

„Sollte man Sticky Sessions auf OpenShift verwenden?“

Kurz erklärt:

Sticky Sessions binden einen Client an ein Backend. Sie können Legacy-Apps helfen, verschlechtern aber Skalierung und Resilienz, wenn sie unnötig sind.

Musterantwort:

Nur wenn es wirklich nötig ist. Moderne Services sollten stateless sein, damit jeder Pod jede Anfrage verarbeiten kann. Sticky Sessions sind ein Workaround für serverseitigen Session-Zustand, zum Beispiel bei Legacy-Webanwendungen. Red Hat dokumentiert, dass der Ingress Controller Endpoints auswählt und Session Persistence über Cookies konfiguriert werden kann.

Sticky Sessions erhöhen Kopplung an einzelne Pods, verschlechtern Lastverteilung und erschweren Rollouts. Für Java-Apps ist es meist besser, Sessions extern zu speichern, Tokens zu verwenden oder den Service zustandslos zu gestalten.

Senior-Antwort:

„Sticky Sessions sind manchmal nötig, aber sie sind ein Symptom von serverseitigem Zustand. Für moderne Services will ich sie vermeiden.“

Häufige Fehler:
  • Sticky Sessions als Standard aktivieren.
  • In-Memory-Session-Zustand in stateless Plattform.
  • Hotspots ignorieren.
  • Rollouts ohne Session-Drain.
  • Auth-Zustand im Pod speichern.
  • Session-Replikation ohne Kostenbewertung.
  • Sticky Sessions als Ersatz für Persistenz.
Best Practices:
  • Stateless Services bevorzugen.
  • Sticky Sessions nur für Legacy.
  • Session-Zustand externisieren.
  • Rollouts mit Session-Drain planen.
  • Hotspots monitoren.
  • Auth über OIDC/JWT statt Server-Session.
  • Legacy-Migration priorisieren.
  • Session-Timeouts bewusst setzen.
Stolperfalle:

Sticky Sessions schützen nicht vor Pod-Ausfall. Wenn der Pod weg ist, ist auch der in-memory Zustand weg.

Merksatz:

Stateless skaliert; Sticky klebt.

B6-K04-Q008 – Timeouts, Upload-Größen, Header und Router-AnpassungenTimeouts müssen schichtenübergreifend zusammenpassen.
Frage:

„Warum schlägt ein Upload oder langer Request über eine Route fehl, obwohl die Anwendung lokal funktioniert?“

Kurz erklärt:

Routes und Router haben Timeouts, Balancing-Verhalten und Limits. Anwendung, Router und Clients müssen zusammenpassen.

Musterantwort:

Weil zwischen Client und Anwendung zusätzliche Schichten liegen: Router, Service, Pod-Netzwerk, Anwendungserver und eventuell API Gateway oder Proxy. Jeder Layer kann eigene Timeouts, Header-Limits, Body-Größen und Connection-Verhalten haben. Eine lokale App mit 5-Minuten-Upload funktioniert nicht automatisch über eine Route mit kürzerem Router-Timeout.

Ich prüfe deshalb Router-Annotationen, Ingress-Controller-Defaults, App-Server-Time-outs, Client-Time-outs, Upload-Limits, Memory-Verhalten und Streaming-Implementierung.

Senior-Antwort:

„Wenn ein Request durch fünf Schichten läuft, gewinnt der kürzeste Timeout. Deshalb definiere ich Timeouts als Budget, nicht als Einzelwert.“

Häufige Fehler:
  • Nur App-Timeout erhöhen.
  • Router-Timeout ignorieren.
  • Große Uploads in Heap puffern.
  • Keine Client-Timeout-Abstimmung.
  • Zu lange Timeouts für alle Routes.
  • Uploads über synchronen Request erzwingen.
  • Keine Limits gegen Missbrauch.
Best Practices:
  • Timeout-Budget über alle Layer definieren.
  • Route-Anpassungen dokumentieren.
  • Upload-Größen begrenzen.
  • Streaming statt Heap-Puffer.
  • Große Dateien direkt zu Object Storage.
  • Lange Prozesse asynchron gestalten.
  • Router-Metriken beobachten.
  • Security und DoS-Risiko beachten.
Stolperfalle:

Ein höherer Route-Timeout löst kein Problem, wenn die Java-App den Upload vollständig im Heap puffert.

Merksatz:

Timeouts müssen schichtenübergreifend zusammenpassen.

B6-K04-Q009 – NetworkPolicy, Ingress-Traffic und Router-Zugriff auf PodsIngress braucht Netzwerk-Erlaubnis bis zum Pod.
Frage:

„Warum kann eine Route 503 liefern, obwohl Route und Service korrekt aussehen?“

Kurz erklärt:

NetworkPolicies können externen Traffic indirekt blockieren, wenn Router-Pods nicht zu Anwendungs-Pods kommunizieren dürfen.

Musterantwort:

Eine mögliche Ursache ist NetworkPolicy. Wenn ein Project Default-Deny-Ingress nutzt, müssen Verbindungen vom OpenShift Ingress Controller beziehungsweise Router zu den Backend-Pods erlaubt werden. Red Hat dokumentiert für NetworkPolicy-Beispiele, dass man eine Policy definieren kann, um nur Verbindungen vom OpenShift Ingress Controller zuzulassen.

Route, Service und Endpoints können formal korrekt sein, aber der Router erreicht die Pods nicht, wenn NetworkPolicy den Traffic blockiert.

Senior-Antwort:

„Eine Route öffnet nicht automatisch jede NetworkPolicy. Der Router muss die Pods im Cluster-Netzwerk erreichen dürfen.“

Häufige Fehler:
  • Default Deny ohne Allow vom Router.
  • Namespace-Labels falsch.
  • Nur Service prüfen, nicht NetworkPolicy.
  • Egress für App vergessen.
  • DNS-Traffic blockieren.
  • Policies ungetestet in Prod.
  • Cluster-spezifische Labels hart codieren ohne Standard.
Best Practices:
  • NetworkPolicy-Standard pro Project definieren.
  • Ingress Controller explizit erlauben.
  • DNS und Plattformdienste berücksichtigen.
  • Policies mit Connectivity-Tests validieren.
  • Route-503-Diagnose um NetworkPolicy erweitern.
  • Labels zentral standardisieren.
  • Security und Plattformteam abstimmen.
  • Default Deny schrittweise einführen.
Stolperfalle:

Service-Endpunkte vorhanden heißt nicht automatisch, dass Netzwerkpolicies den Traffic zulassen.

Merksatz:

Ingress braucht Netzwerk-Erlaubnis bis zum Pod.

B6-K04-Q010 – Diagnose, Architekturstandards und Anti-PatternsNetzwerkprobleme debuggt man entlang des Pfads, nicht entlang der Vermutung. 24h
Frage:

„Wie debuggen Sie systematisch ein Problem mit externer Erreichbarkeit auf OpenShift?“

Kurz erklärt:

Produktionsreife Netzwerkarchitektur braucht Diagnosepfade, Standards für Services/Routes/TLS und klare Anti-Pattern-Vermeidung.

Musterantwort:

Ich gehe den Traffic-Pfad entlang: DNS und Hostname, Route, Router/Ingress Controller, Service, Endpoints, Pod-Readiness, Pod-Logs, Events und NetworkPolicies. Wenn viele Anwendungen betroffen sind, prüfe ich Ingress Controller, Router-Pods und den ingress Cluster Operator. Wenn nur eine Anwendung betroffen ist, prüfe ich Route-Port, Service-Selector, Endpoints und Readiness.

Ich vermeide Ratespiele. Ein externer 503 kann durch Route, Service, Endpoints, Readiness, NetworkPolicy, Router oder App verursacht werden.

Senior-Antwort:

„Externe Erreichbarkeit ist eine Kette. Ich debugge sie von außen nach innen und validiere jede Schicht.“

Häufige Fehler:
  • Direkt App-Code verdächtigen.
  • Endpoints nicht prüfen.
  • Route-Port und Service-Port verwechseln.
  • TLS-Termination nicht verstehen.
  • Forwarded Headers ignorieren.
  • Sticky Sessions als Standard.
  • Keine Route-Governance.
  • Timeouts pro Team unterschiedlich und undokumentiert.
Best Practices:
  • Traffic-Pfad schichtweise prüfen.
  • Route, Service, Endpoints immer zusammen betrachten.
  • TLS-Strategie standardisieren.
  • Forwarded Headers in Java-Apps konfigurieren.
  • Route-Hostnames zentral verwalten.
  • NetworkPolicies in Runbooks aufnehmen.
  • Timeouts als Plattformstandard.
  • Sticky Sessions vermeiden.
Stolperfalle:

Ein einzelnes Symptom wie 503 sagt noch nicht, ob Route, Router, Service, Readiness oder App schuld ist.

Merksatz:

Netzwerkprobleme debuggt man entlang des Pfads, nicht entlang der Vermutung.

B6-K05 – Kapitel 5: Builds, ImageStreams und Source-to-Image 10 Karten

B6-K05-Q001 – BuildConfig, Build und Build-StrategienBuildConfig baut Images; Delivery entscheidet, wann sie wohin dürfen. 24h
Frage:

„Was ist eine BuildConfig in OpenShift?“

Kurz erklärt:

Eine BuildConfig beschreibt den Buildprozess. Ein Build ist eine konkrete Ausführung dieser BuildConfig. Die Build-Strategie bestimmt, wie aus Source Code ein Image entsteht.

Musterantwort:

Eine BuildConfig ist die Definition des gesamten Buildprozesses: Source, Build-Strategie, Output, Trigger, Ressourcen und Policies. Ein Build ist eine konkrete Ausführung dieser Definition. Red Hat beschreibt Builds als Prozess, der Eingaben wie Source Code in ein resultierendes Objekt transformiert, meistens ein lauffähiges Image; eine BuildConfig ist die Definition dieses gesamten Buildprozesses.

Build-Strategien können zum Beispiel Dockerfile-basierte Builds oder Source-to-Image Builds sein. In modernen Plattformen entscheide ich bewusst, ob ich OpenShift BuildConfig nutze oder ob Builds in einer externen CI/CD-Plattform beziehungsweise OpenShift Pipelines laufen.

Senior-Antwort:

„Eine BuildConfig beschreibt, wie ein Image entsteht. Sie ist nicht automatisch eine vollständige Delivery-Strategie.“

Häufige Fehler:
  • BuildConfig als universelles CI/CD-System missverstehen.
  • Tests und Security Scans auslassen.
  • Build und Deployment unkontrolliert koppeln.
  • latest als Output für Produktion.
  • Secrets im Image hinterlassen.
  • Maven Cache ins Runtime-Image kopieren.
  • Build-Logs mit Credentials.
Best Practices:
  • Buildprozess explizit versionieren.
  • Output in ImageStreamTag oder Registry kontrollieren.
  • Build- und Deployment-Promotion trennen.
  • Immutable Tags für Releases.
  • Build-Secrets sicher handhaben.
  • Tests und Scans integrieren.
  • Runtime-Image klein halten.
  • BuildConfig bewusst gegen Pipelines abwägen.
Stolperfalle:

Wenn jeder Git- oder Image-Trigger automatisch in Produktion deployt, fehlt eine kontrollierte Promotion-Grenze.

Merksatz:

BuildConfig baut Images; Delivery entscheidet, wann sie wohin dürfen.

B6-K05-Q002 – ImageStreams und ImageStreamTagsTags benennen; Digests beweisen.
Frage:

„Was ist ein ImageStream, und warum nutzt OpenShift ihn?“

Kurz erklärt:

ImageStreams sind OpenShift-Abstraktionen über Container Images. ImageStreamTags referenzieren konkrete Image-Versionen oder externe Images.

Musterantwort:

Ein ImageStream ist eine OpenShift-Ressource, die Image-Versionen und Tags innerhalb des Clusters verfolgt. Er speichert nicht selbst die Image-Layer, sondern referenziert Images und macht deren Versionen für Builds und Deployments nutzbar. Red Hat beschreibt, dass man ImageStreams und Tags verwenden kann, um Container Images zu erstellen, zu aktualisieren und Versionsänderungen zu verfolgen.

ImageStreams ermöglichen Automatisierung: Builds und Deployments können ImageStreams beobachten und bei neuen Images reagieren. Das ist mächtig, aber in Produktion muss man Trigger und Promotion kontrollieren.

Senior-Antwort:

„Ein ImageStream ist OpenShifts Sicht auf Image-Versionen. Entscheidend ist, welchen Digest ich wirklich ausrolle.“

Häufige Fehler:
  • ImageStream mit Registry verwechseln.
  • Mutierbare Tags in Produktion.
  • Nicht wissen, welcher Digest läuft.
  • ImageChange-Trigger unkontrolliert.
  • Externe Registry-Ausfälle nicht berücksichtigen.
  • ImageStreams nicht bereinigen.
  • Image-Promotion durch Rebuild statt Retag.
Best Practices:
  • ImageStreams bewusst nutzen.
  • Digest-basiert nachvollziehen.
  • Immutable Release Tags.
  • Promotion durch Retagging desselben Digests.
  • Trigger in Produktion kontrollieren.
  • ImageStreams regelmäßig prüfen.
  • Registry-Zugriffe absichern.
  • ImageStreamTags mit Release-Metadaten versehen.
Stolperfalle:

Ein Tag ist ein Name, kein unveränderlicher Beweis. Für Produktion zählt der Digest.

Merksatz:

Tags benennen; Digests beweisen.

B6-K05-Q003 – Source-to-Image: Build ohne eigenes DockerfileS2I verschiebt Buildwissen vom App-Team ins Builder Image.
Frage:

„Was ist Source-to-Image, und wann ist es sinnvoll?“

Kurz erklärt:

Source-to-Image, kurz S2I, erzeugt lauffähige Container Images, indem Source Code in ein Builder Image injiziert wird, das Build- und Run-Skripte bereitstellt.

Musterantwort:

Source-to-Image ist ein OpenShift-Buildmechanismus, bei dem Entwickler kein eigenes Dockerfile schreiben müssen. Ein Builder Image enthält die Build- und Runtime-Logik. Der Source Code wird in dieses Image injiziert, ein Assemble-Skript baut die Anwendung, und daraus entsteht ein lauffähiges Image. Red Hat beschreibt S2I als Werkzeug zum Erstellen reproduzierbarer Container Images, das ready-to-run Images erzeugt, indem Anwendungscode in ein Container Image injiziert wird.

S2I ist sinnvoll für standardisierte Developer-Workflows und Teams, die schnell und sicher mit vorgegebenen Builder Images starten sollen. Für hochoptimierte Java-Runtime-Images, spezielle Layering-Strategien oder Supply-Chain-Anforderungen ist ein kontrolliertes Dockerfile, Buildpacks oder eine Pipeline oft besser.

Senior-Antwort:

„S2I nimmt Teams Dockerfile-Komplexität ab, aber ich muss das Builder Image als Plattformprodukt betreiben.“

Häufige Fehler:
  • S2I als Blackbox ohne Runtime-Verständnis.
  • Builder Image nicht patchen.
  • Tests im Build deaktivieren.
  • Große Images akzeptieren.
  • Maven Dependencies ohne Cache.
  • Keine Kontrolle über JVM-Defaults.
  • S2I in Produktion ohne Security-Scanning.
Best Practices:
  • Builder Images zentral pflegen.
  • JDK- und Runtime-Version kontrollieren.
  • Security Scans integrieren.
  • Build-Parameter dokumentieren.
  • S2I für Standardfälle nutzen.
  • Spezialfälle mit Dockerfile/Pipeline bauen.
  • Ergebnisimage analysieren.
  • Non-root-Kompatibilität prüfen.
Stolperfalle:

S2I ohne Kontrolle über Builder Image und JVM-Defaults ist bequem, aber nicht automatisch produktionsreif.

Merksatz:

S2I verschiebt Buildwissen vom App-Team ins Builder Image.

B6-K05-Q004 – Dockerfile Builds und Java-Multi-Stage ImagesS2I standardisiert; Dockerfile kontrolliert.
Frage:

„Wann bevorzugen Sie Dockerfile Builds gegenüber S2I?“

Kurz erklärt:

Dockerfile Builds geben maximale Kontrolle über Build- und Runtime-Image. Multi-Stage Builds trennen Build-Werkzeuge von Runtime-Artefakten.

Musterantwort:

Ich bevorzuge Dockerfile Builds, wenn ich genaue Kontrolle über Base Image, Layering, Build-Schritte, Runtime-Inhalt, JVM-Optionen, non-root User und Security Hardening brauche. Für Java ist Multi-Stage besonders sinnvoll: Im Build-Stage nutze ich JDK und Maven/Gradle; im Runtime-Stage landet nur das fertige Artefakt mit minimalen Runtime-Abhängigkeiten.

S2I ist gut für standardisierte Fälle. Dockerfile ist besser für optimierte, explizite und auditierbare Images — vorausgesetzt Teams beherrschen Container-Builds und Security-Regeln.

Senior-Antwort:

„Dockerfile gibt Kontrolle. Kontrolle ist nur gut, wenn Teams auch Verantwortung für Security, Layering und Runtime-Verhalten übernehmen.“

Häufige Fehler:
  • Build-Tools im Runtime-Image.
  • Root-User.
  • Secrets per ARG oder ENV ins Image.
  • Unpinned Base Image.
  • latest im Dockerfile.
  • Maven Cache ungünstig.
  • Tests im Image-Build immer skippen.
  • App schreibt in nicht beschreibbare Pfade.
Best Practices:
  • Multi-Stage Builds.
  • UBI oder freigegebene Base Images.
  • Non-root kompatibel.
  • Base Images regelmäßig patchen.
  • Secrets nicht in Layers.
  • Dependencies vor Source kopieren.
  • Runtime Image klein halten.
  • JVM-Optionen über Env.
Stolperfalle:

Ein Image, das lokal als Root funktioniert, kann auf OpenShift wegen Security Defaults scheitern.

Merksatz:

S2I standardisiert; Dockerfile kontrolliert.

B6-K05-Q005 – Build-Trigger: ConfigChange, ImageChange und WebhooksTrigger starten Arbeit; Governance entscheidet Wirkung.
Frage:

„Welche Build-Trigger gibt es, und welche Risiken haben sie?“

Kurz erklärt:

Build-Trigger starten Builds automatisch bei Konfigurationsänderungen, neuen Builder Images oder Webhook-Ereignissen.

Musterantwort:

Typische Build-Trigger sind ConfigChange, ImageChange und Webhooks wie GitHub oder Generic Webhook. ConfigChange startet bei BuildConfig-Änderungen. ImageChange startet, wenn sich ein referenziertes ImageStreamTag ändert, zum Beispiel ein Builder Image. Webhooks starten Builds bei Git-Ereignissen.

Das Risiko ist unkontrollierte Automatisierung. Ein neuer Builder-Image-Tag kann viele Builds starten. Ein Webhook kann ohne Governance beliebig viele Builds auslösen. In Produktion will ich klare Freigabe- und Promotion-Grenzen, nicht jeden automatischen Trigger direkt bis zum Deployment.

Senior-Antwort:

„Trigger sind gut für Feedback, aber schlecht als unkontrollierte Produktionsfreigabe.“

Häufige Fehler:
  • Webhook Secret im Git speichern.
  • ImageChange-Trigger in Prod unkontrolliert.
  • Builder Image latest ohne Tests.
  • Build und Prod-Deployment gekoppelt.
  • Keine Ressourcenlimits für Builds.
  • Trigger-Schleifen.
  • Keine Auditierbarkeit von Build-Auslösern.
Best Practices:
  • Trigger in Dev/Test gezielt nutzen.
  • Prod-Promotion entkoppeln.
  • Webhook Secrets sicher verwalten.
  • Builder Image Updates testen.
  • Build-Ressourcen begrenzen.
  • Trigger-Dokumentation.
  • Immutable Release-Tags.
  • Automatische Builds nicht automatisch releasen.
Stolperfalle:

Ein ImageChange-Trigger auf java:latest kann Builds auslösen, obwohl niemand am Anwendungscode gearbeitet hat.

Merksatz:

Trigger starten Arbeit; Governance entscheidet Wirkung.

B6-K05-Q006 – Build Output, Registry und Image-PromotionBuild once, promote the digest.
Frage:

„Wie promoten Sie Images auf OpenShift von Dev nach Prod?“

Kurz erklärt:

Build Output definiert, wohin das gebaute Image geschrieben wird. Image-Promotion bewegt geprüfte Images durch Umgebungen, idealerweise ohne Rebuild.

Musterantwort:

Ich baue ein Image genau einmal, scanne und teste es, und promote anschließend denselben Image-Digest durch die Umgebungen. In OpenShift kann das über ImageStreams und oc tag erfolgen. Der entscheidende Punkt ist: Prod bekommt nicht ein neu gebautes „gleiches“ Image, sondern dasselbe geprüfte Artefakt.

Red Hat dokumentiert das Verwalten von Build Output als eigenes BuildConfig-Thema und beschreibt, dass Build Output konfiguriert, wohin das resultierende Image geschrieben wird.

Senior-Antwort:

„Ich deploye in Prod nicht etwas Gleichgebautes, sondern dasselbe geprüfte Artefakt.“

Häufige Fehler:
  • Rebuild pro Umgebung.
  • Snapshot Dependencies.
  • Prod mit latest .
  • Kein Image-Scan vor Promotion.
  • Keine Signierung.
  • Tag bewegt sich unkontrolliert.
  • Deployment referenziert nicht freigegebenes Artefakt.
Best Practices:
  • Einmal bauen, mehrfach promoten.
  • Digest-Identität bewahren.
  • Immutable Release Tags.
  • Security Scan vor Prod.
  • SBOM und Signatur.
  • Promotion auditiert.
  • Umgebungsconfig außerhalb des Images.
  • Keine Secrets im Image.
Stolperfalle:

Ein Rebuild in Prod kann ein anderes Base Image oder andere Dependencies enthalten als der getestete Build.

Merksatz:

Build once, promote the digest.

B6-K05-Q007 – Build-Secrets, Pull-Secrets und sichere CredentialsSecrets gehören in Mounts, nicht in Layers.
Frage:

„Wie gehen Sie mit Secrets in OpenShift Builds um?“

Kurz erklärt:

Builds benötigen oft Credentials für Git, Maven, Gradle, npm oder Registries. Diese Credentials dürfen nicht ins Image oder in Logs gelangen.

Musterantwort:

Build-Secrets müssen nur während des Builds verfügbar sein und dürfen nicht im finalen Image landen. Ich nutze OpenShift Secrets für Git-Credentials, Maven Settings, Registry Pull-Secrets oder Zertifikate. Ich vermeide ARG , ENV oder RUN echo secret , weil diese Werte in Image Layers, Build Logs oder History landen können.

Wichtig ist außerdem, Build- und Runtime-Secrets zu trennen. Ein Maven-Repository-Token gehört in den Build, nicht in den Runtime-Pod.

Senior-Antwort:

„Ein Build-Secret darf den Build sehen, aber das fertige Image darf sich nicht daran erinnern.“

Häufige Fehler:
  • Credentials im Dockerfile.
  • Secrets als Build Args.
  • Maven settings ins Runtime Image kopieren.
  • Tokens in Build Logs.
  • Gleiche Secrets für Build und Runtime.
  • Pull Secrets zu breit berechtigen.
  • Keine Secret-Rotation.
Best Practices:
  • Build-Secrets nur temporär mounten.
  • Runtime-Secrets trennen.
  • Secrets nie in Image Layers.
  • Logs auf Secret-Leaks prüfen.
  • Tokens minimal berechtigen.
  • Secret-Rotation planen.
  • Interne Repository-Proxies nutzen.
  • Image-Scans auf Secrets.
Stolperfalle:

Ein Secret, das in einem Dockerfile-Layer geschrieben und später gelöscht wird, kann trotzdem in der Image-History erhalten bleiben.

Merksatz:

Secrets gehören in Mounts, nicht in Layers.

B6-K05-Q008 – Build-Ressourcen, Parallelität und Build-PerformanceBuilds brauchen Capacity Planning wie Anwendungen.
Frage:

„Wie verhindern Sie, dass Builds den OpenShift-Cluster überlasten?“

Kurz erklärt:

Builds verbrauchen Clusterressourcen. Run Policy, Requests/Limits und Deadline steuern Build-Verhalten, Parallelität und Ressourcenverbrauch.

Musterantwort:

Ich begrenze Builds wie andere Workloads: Resource Requests/Limits, Quotas, Build Run Policies, maximale Builddauer, getrennte Build-Node-Pools und Monitoring. Red Hat dokumentiert fortgeschrittene Build-Operationen wie das Setzen von Build-Ressourcen, maximaler Dauer, Zuweisung zu Nodes, Chained Builds, Build-Pruning und Build Run Policies.

Für Java sind Builds oft speicher- und netzwerkintensiv, weil Maven/Gradle Dependencies laden und Tests ausführen. Ohne Limits können parallele Builds Cluster, Registry und Artifact Repository belasten.

Senior-Antwort:

„Builds sind auch Workloads. Wenn ich sie nicht begrenze, konkurrieren sie mit Produktion um Clusterressourcen.“

Häufige Fehler:
  • Builds ohne Limits.
  • Parallele Builds unbegrenzt.
  • Native Builds auf normalen Worker Nodes.
  • Kein Maven Cache oder Proxy.
  • Builddauer unbegrenzt.
  • Alte Builds nicht prunen.
  • Testcontainers ohne Plattformkonzept.
Best Practices:
  • Build-Ressourcen setzen.
  • Build-Parallelität begrenzen.
  • RunPolicy passend wählen.
  • Builddauer begrenzen.
  • Build-Node-Pools prüfen.
  • Maven/Gradle Proxy nutzen.
  • Alte Builds prunen.
  • Build-Metriken überwachen.
Stolperfalle:

Ein Build-Sturm nach einem Base-Image-Update kann den Cluster genauso belasten wie ein Traffic-Peak.

Merksatz:

Builds brauchen Capacity Planning wie Anwendungen.

B6-K05-Q009 – Image Security: Scanning, SBOM, Signierung und Base ImagesKein Prod-Image ohne Herkunft, Scan und Identität.
Frage:

„Welche Security-Anforderungen stellen Sie an Images auf OpenShift?“

Kurz erklärt:

Container Images sind Supply-Chain-Artefakte. Sie müssen gescannt, nachvollziehbar, signiert und aus gepflegten Base Images gebaut werden.

Musterantwort:

Ich erwarte mindestens: vertrauenswürdige Base Images, regelmäßige Patches, non-root-Kompatibilität, keine Secrets im Image, Vulnerability Scans, SBOM, Signierung, Digest-basierte Promotion, Policy Enforcement und nachvollziehbare Build-Herkunft. OpenShift kann Images bauen und deren Lifecycle verwalten, aber Supply-Chain-Sicherheit muss als Plattformprozess definiert werden.

Für Java ist besonders wichtig, dass Base Image, JDK/JRE, Betriebssystempakete und Anwendungdependencies regelmäßig geprüft und aktualisiert werden.

Senior-Antwort:

„Ein Image ist ein ausführbarer Liefergegenstand. Ich muss wissen, wer es gebaut hat, woraus es besteht und ob es vertrauenswürdig ist.“

Häufige Fehler:
  • Ungeprüfte Base Images.
  • Keine CVE-Scans.
  • Keine SBOM.
  • Images ohne Digest deployen.
  • Secrets im Image.
  • Signatur ohne Enforcement.
  • Scanner-Ergebnisse ignorieren.
  • JDK nicht patchen.
Best Practices:
  • Freigegebene Base Images.
  • Regelmäßige Rebuilds bei CVEs.
  • Dependency- und Image-Scans.
  • SBOM erzeugen.
  • Images signieren.
  • Deploy-Policies erzwingen.
  • Digest-Promotion.
  • Non-root Images.
Stolperfalle:

Eine Signatur allein schützt nichts, wenn der Cluster nicht erzwingt, dass nur signierte Images laufen dürfen.

Merksatz:

Kein Prod-Image ohne Herkunft, Scan und Identität.

B6-K05-Q010 – Build-Architektur, Golden Paths und Anti-PatternsBuild-Governance ist Supply-Chain-Architektur. 24h
Frage:

„Welche Anti-Patterns sehen Sie bei Builds und ImageStreams auf OpenShift?“

Kurz erklärt:

Eine produktionsreife Build-Architektur definiert Golden Paths für Teams: Wie wird gebaut, geprüft, signiert, promoted, deployed und auditiert?

Musterantwort:

Typische Anti-Patterns sind: latest in Produktion, Rebuild pro Umgebung, Secrets im Image, Build ohne Scan, Build ohne Tests, ImageChange-Trigger direkt nach Prod, mutierbare Tags, keine SBOM, keine Signierung, kein Digest-Nachweis, zu große Images, Root-Container, veraltete Base Images, manuelle Promotions ohne Audit und BuildConfig als unkontrollierte Delivery-Pipeline.

Ein reifer Golden Path baut einmal, testet, scannt, signiert, erzeugt SBOM, promoted denselben Digest und deployed deklarativ über GitOps.

Senior-Antwort:

„Ein guter Golden Path nimmt Teams Arbeit ab und gibt der Plattform Kontrolle zurück.“

Häufige Fehler:
  • Jeder Service baut anders.
  • Manuelle Builds für Prod.
  • Keine Artefaktidentität.
  • Unterschiedliche JDKs pro Umgebung.
  • Build- und Runtime-Image vermischt.
  • Security-Ausnahmen ohne Ablauf.
  • Plattformteam erzwingt Standards ohne Developer Experience.
  • Keine Dokumentation.
Best Practices:
  • Golden Path definieren.
  • Build-Standards als Templates/Pipelines.
  • Einmal bauen, Digest promoten.
  • Security Gates automatisieren.
  • GitOps für Deployments.
  • Ausnahmen kontrollieren.
  • Build-Metriken erfassen.
  • Base Images zentral pflegen.
Stolperfalle:

Wenn der Golden Path zu langsam oder unpraktisch ist, bauen Teams Schatten-CI/CD daneben.

Merksatz:

Build-Governance ist Supply-Chain-Architektur.

B6-K06 – Kapitel 6: SecurityContextConstraints, Security und ServiceAccounts 10 Karten

B6-K06-Q001 – SCC-Grundmodell: Was SecurityContextConstraints kontrollierenRBAC autorisiert API-Aktionen; SCC autorisiert Pod-Rechte. 24h
Frage:

„Was ist eine SecurityContextConstraint in OpenShift?“

Kurz erklärt:

SecurityContextConstraints kontrollieren, welche Security-Einstellungen ein Pod verwenden darf, etwa UID-Strategie, privilegierte Container, Hostzugriff, Volumes, Capabilities, SELinux und seccomp.

Musterantwort:

Eine SecurityContextConstraint ist eine OpenShift-Ressource, die festlegt, welche Sicherheitskontexte Pods verwenden dürfen. Sie wirkt bei der Admission eines Pods. Wenn ein Pod eine Security-Einstellung verlangt, die durch keine passende SCC erlaubt ist, wird der Pod nicht zugelassen. Die OpenShift-Security-API beschreibt SecurityContextConstraints als Ressource, die Requests kontrolliert, welche den SecurityContext eines Containers beeinflussen.

SCCs sind damit vergleichbar mit einer Sicherheitszulassung für Pods. RBAC entscheidet, wer API-Aktionen ausführen darf; SCCs entscheiden, unter welchen Betriebssystem- und Containerrechten ein Pod laufen darf.

Senior-Antwort:

„SCCs sind die OpenShift-Grenze zwischen einem normalen Pod und einem Pod, der gefährliche Linux- oder Hostrechte verlangt.“

Häufige Fehler:
  • SCCs als RBAC-Ersatz verstehen.
  • Privileged SCC als schnelle Lösung.
  • Root-Image bauen.
  • HostPath für App-Daten verwenden.
  • Zusätzliche Capabilities ohne Bedarf.
  • ServiceAccount nicht explizit setzen.
  • Admission-Fehler nicht lesen.
Best Practices:
  • Restriktive SCCs als Standard.
  • Workloads rootless-kompatibel bauen.
  • Keine unnötigen Capabilities.
  • Kein Hostzugriff für normale Apps.
  • Eigener ServiceAccount pro Workload.
  • SCC-Ausnahmen reviewen.
  • Admission-Fehler sorgfältig lesen.
  • Images gegen OpenShift-Security testen.
Stolperfalle:

Wenn ein Pod wegen SCC abgelehnt wird, ist das meist kein Plattformfehler, sondern ein Hinweis auf riskante Containerannahmen.

Merksatz:

RBAC autorisiert API-Aktionen; SCC autorisiert Pod-Rechte.

B6-K06-Q002 – restricted-v2 , Random UID und Rootless ImagesOpenShift-kompatible Images brauchen keine Root-Identität.
Frage:

„Warum läuft mein Container lokal als Root, aber auf OpenShift nicht?“

Kurz erklärt:

OpenShift verwendet restriktive Defaults wie restricted-v2 . Workloads sollten ohne Root, ohne feste Root-Annahmen und mit random UID kompatibel sein.

Musterantwort:

OpenShift ist bewusst restriktiver als ein lokales Docker-Setup. Viele Pods laufen unter restriktiven SCCs wie restricted-v2 , die verhindern, dass Container privilegiert oder mit unsicheren User-/Capability-Einstellungen laufen. Red Hat beschreibt, dass restricted-v2 standardmäßig allen authentifizierten Benutzern verfügbar ist und in den meisten Fällen verwendet wird; diese SCC nutzt eine MustRunAsRange -Strategie für runAsUser .

Das bedeutet: Ein Image darf nicht davon ausgehen, als Root zu laufen oder in beliebige Verzeichnisse schreiben zu können. Die saubere Lösung ist ein OpenShift-kompatibles, rootless Image.

Senior-Antwort:

„Wenn mein Image Root braucht, ist das ein Design Smell. OpenShift zwingt mich, Container wirklich containerfähig zu bauen.“

Häufige Fehler:
  • USER root .
  • App schreibt nach /app .
  • Logs in Dateien.
  • Feste UID-Annahme.
  • chmod 777 als Standardlösung.
  • anyuid für normale Apps.
  • Docker lokal als Sicherheitsbeweis nehmen.
Best Practices:
  • Rootless-kompatible Images.
  • Keine Root-Annahmen.
  • Schreibpfade explizit.
  • Logs nach stdout/stderr.
  • Temporäre Daten in /tmp oder Volume.
  • Gruppenrechte sauber setzen.
  • Images auf OpenShift testen.
  • anyuid nur als begründete Ausnahme.
Stolperfalle:

anyuid behebt das Symptom, aber nicht die Ursache: ein Image, das Root-Annahmen enthält.

Merksatz:

OpenShift-kompatible Images brauchen keine Root-Identität.

B6-K06-Q003 – SCC-Auswahl, Priorität und ServiceAccount-BindingsSCC-Ausnahmen gehören zur Workload-Identität.
Frage:

„Wie bekommt ein Pod Zugriff auf eine bestimmte SCC?“

Kurz erklärt:

SCCs werden typischerweise über RBAC-/Policy-Bindings an Benutzer, Gruppen oder ServiceAccounts verfügbar gemacht. Pods verwenden dann eine passende SCC bei Admission.

Musterantwort:

Ein Pod selbst „bekommt“ nicht direkt eine SCC. Entscheidend ist das Subject, das den Pod erstellt beziehungsweise der ServiceAccount, unter dem der Pod läuft. Eine SCC muss diesem Subject verfügbar sein. In der Praxis bindet man SCC-Ausnahmen meist an einen spezifischen ServiceAccount, nicht an ganze Benutzergruppen. Red Hat beschreibt, dass SCCs ähnlich wie RBAC Ressourcen genutzt werden, um die Permissions von Pods zu kontrollieren, und dass Default-SCCs bei Installation angelegt werden.

Ich prüfe immer: Welcher ServiceAccount wird im Pod verwendet? Welche SCC wurde tatsächlich angewendet? Das sieht man häufig an der Pod-Annotation openshift.io/scc .

Senior-Antwort:

„SCC-Ausnahmen gehören an konkrete ServiceAccounts, nicht an Menschenmengen oder ganze Plattformbereiche.“

Häufige Fehler:
  • anyuid an system:authenticated .
  • SCC an alle ServiceAccounts eines Projects.
  • Verwendete SCC nicht prüfen.
  • SCC-Ausnahme nicht dokumentieren.
  • Default-ServiceAccount mit SCC-Ausnahme.
  • SCC-Zugriff nie entfernen.
  • Legacy-Ausnahme wird Dauerzustand.
Best Practices:
  • SCC-Ausnahmen an spezifische ServiceAccounts.
  • Nie an breite Gruppen ohne Not.
  • Applied SCC prüfen.
  • Ausnahmen annotieren und reviewen.
  • Default-ServiceAccount vermeiden.
  • SCC-Zugriff regelmäßig auditieren.
  • Migration weg von Sonder-SCC planen.
  • Least Privilege auch bei SCCs.
Stolperfalle:

Wenn der Default-ServiceAccount eine breite SCC bekommt, erben plötzlich viele Workloads mehr Rechte als geplant.

Merksatz:

SCC-Ausnahmen gehören zur Workload-Identität.

B6-K06-Q004 – anyuid , privileged und gefährliche AusnahmenJe mächtiger die SCC, desto enger muss die Ausnahme sein.
Frage:

„Warum ist privileged oder anyuid auf OpenShift gefährlich?“

Kurz erklärt:

anyuid und privileged sind mächtige SCCs. Sie sollten nur für begründete, eng begrenzte Ausnahmefälle verwendet werden.

Musterantwort:

anyuid erlaubt Containern, mit beliebigen UIDs zu laufen, und kann Root-Annahmen wieder ermöglichen. privileged ist deutlich riskanter, weil es Container weitreichende Host- und Kernel-nahe Rechte geben kann. Normale Anwendungen sollten diese SCCs nicht benötigen.

Red Hat weist bei älteren SCC-Beispielen darauf hin, dass das Gewähren von anyuid an alle authentifizierten Benutzer dazu führt, dass Container als beliebiger Benutzer laufen dürfen; das illustriert den breiten Sicherheitsimpact einer solchen Vergabe.

In der Praxis ist anyuid manchmal für Legacy-Images eine temporäre Brücke. privileged gehört fast ausschließlich Infrastrukturkomponenten wie Storage-, Network- oder Node-Agents.

Senior-Antwort:

„ anyuid ist eine Legacy-Brücke. privileged ist Infrastrukturterritorium. Beides ist kein normaler App-Standard.“

Häufige Fehler:
  • anyuid projektweit vergeben.
  • privileged für normale Apps.
  • Keine Ablaufdaten.
  • Keine Risikoanalyse.
  • Keine Alternativen prüfen.
  • Legacy-Ausnahme wird dauerhaft.
  • Ausnahme an Default-ServiceAccount.
Best Practices:
  • restricted-v2 als Zielstandard.
  • anyuid nur eng begrenzt.
  • privileged nur für Infra-Komponenten.
  • Ausnahmeprozess mit Ticket.
  • Ablauf- und Remediation-Datum.
  • ServiceAccount-spezifische Bindung.
  • Security Review.
  • Migration auf rootless Image priorisieren.
Stolperfalle:

Eine breit vergebene anyuid -SCC macht viele OpenShift-Sicherheitsvorteile zunichte.

Merksatz:

Je mächtiger die SCC, desto enger muss die Ausnahme sein.

B6-K06-Q005 – Pod Security Admission und SCC-ZusammenspielSCC entscheidet in OpenShift konkret; PSA hilft beim Standardisieren. 24h
Frage:

„Wie verhalten sich Pod Security Admission und SCCs in OpenShift zueinander?“

Kurz erklärt:

OpenShift nutzt SCCs und unterstützt zugleich Kubernetes Pod Security Admission Konzepte. SCC-Admission kann SecurityContext-Felder mutieren und validieren.

Musterantwort:

OpenShift hat traditionell SCCs als zentrales Pod-Sicherheitsmodell. Kubernetes Pod Security Admission, kurz PSA, ergänzt standardisierte Pod-Sicherheitsprofile wie privileged, baseline und restricted. In OpenShift muss man verstehen, dass SCCs weiterhin eine zentrale Rolle spielen. Red Hat beschreibt, dass der SCC-Controller SecurityContext-Felder eines Pods mutieren kann, zum Beispiel den seccomp profile default setzen, und den Pod-SecurityContext gegen die passende SCC validiert.

Als Senior verlasse ich mich nicht auf nur ein Label oder nur eine Annahme. Ich prüfe, welche SCC tatsächlich angewendet wurde und welche Pod-Security-Warnungen oder Audits aktiv sind.

Senior-Antwort:

„In OpenShift denke ich SCC zuerst, PSA zusätzlich. Entscheidend ist, welcher SecurityContext am Ende wirklich zugelassen wurde.“

Häufige Fehler:
  • PSA und SCC gleichsetzen.
  • Nur Namespace-Labels prüfen.
  • Angewendete SCC nicht auslesen.
  • Warnings ignorieren.
  • Ausnahmen nicht dokumentieren.
  • Vendor-Workloads ungeprüft deployen.
  • Security Admission erst in Prod testen.
Best Practices:
  • SCC als OpenShift-Kernmodell verstehen.
  • PSA-Warn/Audit bewusst nutzen.
  • Applied SCC prüfen.
  • Workloads restricted-kompatibel bauen.
  • Ausnahmen zentral dokumentieren.
  • Security Admission in CI testen.
  • Namespace-Labels standardisieren.
  • Plattformrichtlinien klar kommunizieren.
Stolperfalle:

Ein Namespace-Label sagt nicht automatisch, welche SCC ein konkreter Pod tatsächlich verwendet hat.

Merksatz:

SCC entscheidet in OpenShift konkret; PSA hilft beim Standardisieren.

B6-K06-Q006 – SecurityContext: Capabilities, Privilege Escalation, seccomp und SELinuxCapabilities sind Root-Rechte in Scheiben — auch kleine Scheiben sind Rechte.
Frage:

„Welche SecurityContext-Einstellungen erwarten Sie bei produktiven Java-Workloads?“

Kurz erklärt:

securityContext beschreibt gewünschte Container-Sicherheitsoptionen. SCCs entscheiden, ob diese Optionen erlaubt sind und können Defaults setzen.

Musterantwort:

Für normale Java-Workloads erwarte ich mindestens: keine Privilege Escalation, keine unnötigen Linux Capabilities, idealerweise drop: ALL , kein privileged Mode, kein HostNetwork, keine HostPath-Volumes und ein Default-seccomp-Profil. Red Hat beschreibt, dass neu erstellte Pods in OpenShift-Kontexten mit restricted-v2 und Default-seccomp-Profil runtime/default arbeiten können; der SCC-Controller kann seccomp-Felder entsprechend setzen.

Die Regel ist: Eine Business-Anwendung sollte keine Host- oder Kernel-nahen Rechte benötigen. Wenn doch, ist eine Architektur- oder Vendor-Review nötig.

Senior-Antwort:

„Eine normale Java-App braucht keine Kernel-nahen Rechte. Wenn sie welche verlangt, frage ich zuerst warum.“

Häufige Fehler:
  • Capabilities hinzufügen ohne Begründung.
  • allowPrivilegeEscalation offen lassen.
  • HostNetwork für App-Workloads.
  • HostPath für normale Daten.
  • seccomp ignorieren.
  • Vendor fordert privileged ohne Review.
  • SecurityContext nur kopieren, nicht verstehen.
Best Practices:
  • Privilege Escalation deaktivieren.
  • Capabilities droppen.
  • Default seccomp verwenden.
  • Keine Host-Rechte für Business-Apps.
  • SELinux nicht umgehen.
  • SecurityContext explizit definieren.
  • Ausnahmen dokumentieren.
  • Vendor-Anforderungen prüfen.
Stolperfalle:

Eine einzelne hinzugefügte Capability kann den Sicherheitsradius eines Containers deutlich erweitern.

Merksatz:

Capabilities sind Root-Rechte in Scheiben — auch kleine Scheiben sind Rechte.

B6-K06-Q007 – ServiceAccounts als Workload-IdentitätenJeder Workload braucht eine Identität — aber nicht jede Identität braucht API-Rechte.
Frage:

„Warum sollte ein Deployment einen eigenen ServiceAccount verwenden?“

Kurz erklärt:

ServiceAccounts sind Maschinenidentitäten für Pods. Sie beeinflussen API-Rechte, SCC-Zugriff und Auditierbarkeit eines Workloads.

Musterantwort:

Ein ServiceAccount ist die Identität des Pods gegenüber der Kubernetes/OpenShift API. Red Hat beschreibt ServiceAccounts als flexible Möglichkeit, API-Zugriff zu kontrollieren, ohne reguläre Benutzer-Credentials zu teilen.

Ein eigener ServiceAccount pro Workload macht Rechte, SCC-Ausnahmen und Audit nachvollziehbar. Der Default-ServiceAccount ist zu unspezifisch. Wenn eine Anwendung keinen Kubernetes-API-Zugriff braucht, kann man außerdem automountServiceAccountToken: false prüfen, um Token-Exposition zu reduzieren.

Senior-Antwort:

„Ein ServiceAccount ist keine Formalität. Er ist die Maschinenidentität meines Pods.“

Häufige Fehler:
  • Default-ServiceAccount in Produktion.
  • Token automatisch mounten ohne Bedarf.
  • Runtime-ServiceAccount mit Deploy-Rechten.
  • SCC-Ausnahme am falschen ServiceAccount.
  • ServiceAccount-Namen nicht standardisieren.
  • Keine Audit-Zuordnung.
  • Ein ServiceAccount für viele Apps.
Best Practices:
  • ServiceAccount pro Workload.
  • Runtime und Pipeline trennen.
  • Token-Automount deaktivieren, wenn nicht benötigt.
  • Minimal-RBAC.
  • SCC-Ausnahmen an spezifische ServiceAccounts.
  • ServiceAccounts labeln und dokumentieren.
  • Default-ServiceAccount vermeiden.
  • Rechte regelmäßig prüfen.
Stolperfalle:

Ein Pod ohne fachlichen API-Bedarf trägt trotzdem oft automatisch ein Token. Das ist unnötiger Angriffsraum.

Merksatz:

Jeder Workload braucht eine Identität — aber nicht jede Identität braucht API-Rechte.

B6-K06-Q008 – Image Security: Rootless, Read-only Filesystem und SchreibpfadeSichere Container schreiben bewusst.
Frage:

„Wie bauen Sie OpenShift-kompatible Java-Images sicher?“

Kurz erklärt:

Sichere Images laufen rootless, benötigen keine Schreibrechte im Root Filesystem und verwenden explizite Volumes für temporäre oder persistente Daten.

Musterantwort:

Ich baue Images so, dass sie ohne Root laufen, keine privilegierten Rechte benötigen, Logs nach stdout schreiben, keine Secrets enthalten und nur in explizit erlaubte Pfade schreiben. Wenn möglich, setze ich readOnlyRootFilesystem: true und mounte beschreibbare Pfade wie /tmp oder dedizierte Volumes.

OpenShift SCCs erzwingen oder validieren viele dieser Eigenschaften. Die SecurityContextConstraints-API kontrolliert Requests, die den SecurityContext eines Containers beeinflussen.

Senior-Antwort:

„Ein sicheres Image weiß genau, wo es schreiben darf — und nirgendwo sonst.“

Häufige Fehler:
  • App schreibt ins Installationsverzeichnis.
  • Read-only Root aktivieren ohne /tmp .
  • Logs in Dateien.
  • Temporäre Reports ohne Größenlimit.
  • Secrets ins Image.
  • Cache-Verzeichnisse nicht konfigurieren.
  • Framework schreibt ins Home-Verzeichnis.
Best Practices:
  • Rootless Images.
  • Read-only Root Filesystem prüfen.
  • Explizite Schreibpfade.
  • /tmp als Volume.
  • Logs nach stdout.
  • emptyDir begrenzen.
  • Secrets nie ins Image.
  • Runtime-Verzeichnisse dokumentieren.
Stolperfalle:

readOnlyRootFilesystem klingt einfach, bricht aber Apps, die heimlich in /app , /home oder Framework-Cache-Verzeichnisse schreiben.

Merksatz:

Sichere Container schreiben bewusst.

B6-K06-Q009 – Security-Diagnose: Admission Errors, SCC-Annotation und EventsAdmission-Fehler stehen in Events, Runtime-Fehler in Logs.
Frage:

„Wie debuggen Sie ein SCC- oder SecurityContext-Problem auf OpenShift?“

Kurz erklärt:

SCC-Probleme debuggt man über Events, Pod-Descriptions, Admission-Fehler, ServiceAccount-Rechte und die angewendete SCC-Annotation.

Musterantwort:

Ich prüfe zuerst die Events und die Pod-Beschreibung. Admission-Fehler sagen meist sehr konkret, welche Security-Einstellung nicht erlaubt ist. Dann prüfe ich den verwendeten ServiceAccount, die angewendete SCC über die Pod-Annotation openshift.io/scc und ob der ServiceAccount eine bestimmte SCC nutzen darf.

Red Hat beschreibt, dass der SCC-Controller SecurityContext-Felder mutieren und anschließend gegen die passende SCC validieren kann. Genau deshalb ist die effektiv angewendete SCC wichtig, nicht nur das, was im YAML steht.

Senior-Antwort:

„Bei SCC-Problemen lese ich zuerst Events. Wenn der Container nie startet, helfen App-Logs nicht.“

Häufige Fehler:
  • Logs suchen, obwohl Container nie startet.
  • Events ignorieren.
  • ServiceAccount nicht prüfen.
  • Falsche SCC vermuten.
  • Sofort privileged vergeben.
  • Runtime-Permission und Admission verwechseln.
  • SCC-Ausnahme ohne Ursachenanalyse.
Best Practices:
  • Events zuerst lesen.
  • ServiceAccount identifizieren.
  • Applied SCC auslesen.
  • can-i use scc prüfen.
  • Admission von Runtime-Fehler trennen.
  • Minimalen Fix wählen.
  • Image-Fixes bevorzugen.
  • Ausnahme dokumentieren.
Stolperfalle:

Permission denied im App-Log und „forbidden by SCC“ in Events sind unterschiedliche Problemklassen.

Merksatz:

Admission-Fehler stehen in Events, Runtime-Fehler in Logs.

B6-K06-Q010 – Security-Governance, Policy-as-Code und Anti-PatternsGovernance ist Security, die wiederholbar bleibt. 24h
Frage:

„Welche Security-Anti-Patterns sehen Sie auf OpenShift?“

Kurz erklärt:

Security-Governance macht Container-Sicherheit überprüfbar: Standards, Ausnahmen, Policy-as-Code, GitOps, Audits und kontinuierliche Reduktion von Sonderrechten.

Musterantwort:

Typische Anti-Patterns sind: privileged für Business-Apps, anyuid als Standard, Root-Images, Default-ServiceAccount in Produktion, automatisch gemountete Tokens ohne Bedarf, HostPath-Volumes, breite Capabilities, Secrets im Image, Logs mit Secrets, keine SCC-Audits, keine Ausnahmefristen und manuelle Sicherheitsänderungen ohne GitOps.

Gute OpenShift-Security ist automatisiert. Ich setze Standards über Templates, Golden Paths, Policy-as-Code und CI/CD-Prüfungen durch. Ausnahmen sind möglich, aber müssen dokumentiert, begründet, befristet und regelmäßig reviewt sein.

Senior-Antwort:

„Sicherheit skaliert nur, wenn sie automatisiert, verständlich und in den Golden Path eingebaut ist.“

Häufige Fehler:
  • Security nur dokumentieren, nicht erzwingen.
  • Ausnahmen ohne Ablauf.
  • Plattformteam als manueller Gatekeeper.
  • Fehlermeldungen unverständlich.
  • Golden Path fehlt.
  • Vendor-Images blind vertrauen.
  • Compliance nur jährlich prüfen.
  • Secrets im Build oder Image.
Best Practices:
  • Security-Standards als Code.
  • Golden Paths für Teams.
  • CI- und Admission-Checks.
  • SCC-Ausnahmen befristen.
  • Runtime-Audits.
  • Default-ServiceAccount vermeiden.
  • Rootless Images verpflichtend.
  • Privileged nur für Infrastruktur.
Stolperfalle:

Wenn Security-Ausnahmen einfacher sind als sichere Standards, werden Ausnahmen zum Standard.

Merksatz:

Governance ist Security, die wiederholbar bleibt.

B6-K07 – Kapitel 7: ConfigMaps, Secrets und Environment Management 10 Karten

B6-K07-Q001 – ConfigMap-Grundmodell: Nicht-sensitive Konfiguration externalisierenImages liefern Code; ConfigMaps liefern nicht-sensitive Umgebung. 24h
Frage:

„Was ist eine ConfigMap, und wann verwenden Sie sie?“

Kurz erklärt:

ConfigMaps speichern nicht-sensitive Konfiguration als Key-Value-Daten oder Dateien und können in Pods als Environment Variables, Command Arguments oder Volumes verwendet werden.

Musterantwort:

Eine ConfigMap ist ein Kubernetes/OpenShift-Objekt für nicht-sensitive Konfigurationsdaten. Ich verwende sie für Werte wie Feature Flags, Timeouts, URLs interner Services, Cache-TTLs, Spring Profiles oder ganze Konfigurationsdateien. Red Hat beschreibt, dass ConfigMaps genutzt werden können, um einzelne Environment Variables, alle gültigen Environment Variables oder Konfigurationsdateien in Container einzubringen.

Wichtig ist: Eine ConfigMap ist nicht für Passwörter, Tokens, Zertifikatsschlüssel oder andere sensitive Daten gedacht. Dafür verwende ich Secrets oder ein externes Secret-Management-System.

Senior-Antwort:

„Eine ConfigMap ist Teil des Runtime-Vertrags. Sie gehört nicht ins Image, sondern kontrolliert neben das Deployment.“

Häufige Fehler:
  • Passwörter in ConfigMaps.
  • Umgebungskonfiguration ins Image backen.
  • ConfigMap ändern und Runtime-Reload erwarten.
  • Keine Config-Version im Pod-Template.
  • ConfigMaps ohne Owner.
  • Eine riesige ConfigMap für viele Apps.
  • Profile als Ersatz für saubere Umgebungskonfiguration missbrauchen.
Best Practices:
  • ConfigMaps nur für nicht-sensitive Daten.
  • Image und Umgebung trennen.
  • Config pro Anwendung und Umgebung verwalten.
  • Config als Code versionieren.
  • Config-Version im Pod-Template annotieren.
  • Kontrollierte Rollouts bei Startzeit-Config.
  • Kleine, klare ConfigMaps.
  • Owner und Zweck labeln.
Stolperfalle:

Eine ConfigMap ist kein Secret. Wer Tokens dort speichert, macht sie leichter sichtbar und auditkritisch.

Merksatz:

Images liefern Code; ConfigMaps liefern nicht-sensitive Umgebung.

B6-K07-Q002 – Secrets: Sensitive Daten, Mounts und Environment VariablesSecrets sind nicht deshalb sicher, weil sie Secret heißen.
Frage:

„Wie verwenden Sie Secrets sicher in OpenShift?“

Kurz erklärt:

Secrets speichern sensitive Werte wie Passwörter, Tokens, private Schlüssel oder Zugangsdaten. Pods können Secrets als Environment Variables, Dateien in Volumes oder Image Pull Credentials nutzen.

Musterantwort:

Ich verwende Secrets für sensitive Daten und trenne sie klar von ConfigMaps. Ein Secret kann in Pods als Environment Variable, als gemountete Datei oder durch Kubelet beim Image Pull verwendet werden. Red Hat dokumentiert für Secrets genau diese Nutzungsformen: als Environment Variables, als Dateien in einem Volume oder durch Kubelet beim Pull von Images.

Sicher heißt aber nicht nur „Secret-Objekt verwenden“. Ich achte auf RBAC, Secret-Rotation, keine Secrets in Images, keine Secrets in Logs, keine Secrets in Git im Klartext, möglichst externe Secret Stores und kontrollierte Rollouts nach Rotation.

Senior-Antwort:

„Ein Kubernetes Secret ist ein sensitives API-Objekt. Der wichtigste Schutz ist nicht nur Base64, sondern RBAC, Rotation und Leak-Vermeidung.“

Häufige Fehler:
  • Secrets in Git im Klartext.
  • Secrets in ConfigMaps.
  • Secrets ins Image bauen.
  • Secrets in Logs ausgeben.
  • Breites get secrets RBAC.
  • Rotation ohne Rollout-Plan.
  • Eine Secret für viele Services teilen.
  • Secret-Env-Var-Änderung ohne Restart erwarten.
Best Practices:
  • Secrets nur für sensitive Daten.
  • Secret-Zugriff minimal berechtigen.
  • Externe Secret Manager prüfen.
  • Secrets nicht ins Image.
  • Secrets nicht loggen.
  • Rotation planen und testen.
  • Secret-Version im Pod-Template annotieren.
  • Runtime- und Build-Secrets trennen.
Stolperfalle:

Base64 ist keine Verschlüsselung. Ein Secret ist nur so sicher wie Zugriffskontrolle, Storage-Schutz und Betriebsprozesse.

Merksatz:

Secrets sind nicht deshalb sicher, weil sie Secret heißen.

B6-K07-Q003 – Environment Variables, envFrom und explizite BindingsBequemlichkeit injiziert Werte; Explizitheit injiziert Vertrauen.
Frage:

„Wann verwenden Sie envFrom , und wann explizite env -Bindings?“

Kurz erklärt:

Environment Variables sind einfache Runtime-Konfiguration. envFrom importiert viele Keys, explizite env -Einträge binden einzelne Werte gezielt.

Musterantwort:

envFrom ist bequem, wenn viele nicht-sensitive ConfigMap-Werte direkt als Environment Variables übernommen werden sollen. Explizite env -Bindings verwende ich für wichtige, sicherheitsrelevante oder besonders klare Werte, vor allem Secrets. Red Hat dokumentiert, dass ConfigMaps genutzt werden können, um einzelne Environment Variables oder alle Keys mit gültigen Environment-Variable-Namen in Container zu populieren.

In Produktion bevorzuge ich oft explizite Bindings für kritische Werte, weil sie besser lesbar, reviewbar und weniger fehleranfällig sind. envFrom kann versehentlich mehr Werte injizieren als gedacht.

Senior-Antwort:

„ envFrom ist bequem, aber explizite Bindings sind oft sicherer und reviewbarer.“

Häufige Fehler:
  • envFrom mit Secrets breit nutzen.
  • Ungültige Environment-Namen.
  • Runtime-Änderung erwarten.
  • Namenskollisionen zwischen ConfigMaps.
  • Zu viele Werte und keine Übersicht.
  • Typfehler erst zur Laufzeit.
  • Keine Config-Validierung im Start.
Best Practices:
  • Explizite env für kritische Werte.
  • envFrom für einfache nicht-sensitive Gruppen.
  • Namenskonventionen verwenden.
  • Config bei Start validieren.
  • Secrets bewusst benennen.
  • Namenskollisionen vermeiden.
  • Config-Änderungen mit Rollout koppeln.
  • @ConfigurationProperties validieren.
Stolperfalle:

Environment Variables sind Startzeitwerte. Eine geänderte ConfigMap ändert nicht automatisch die laufende JVM-Konfiguration.

Merksatz:

Bequemlichkeit injiziert Werte; Explizitheit injiziert Vertrauen.

B6-K07-Q004 – ConfigMaps und Secrets als VolumesVolumes liefern Dateien; Anwendungen müssen Änderungen verstehen.
Frage:

„Wann mounten Sie ConfigMaps oder Secrets als Volume statt als Environment Variables?“

Kurz erklärt:

ConfigMaps und Secrets können als Dateien in Volumes gemountet werden. Das ist besonders geeignet für Konfigurationsdateien, Zertifikate, Truststores oder Key-Material.

Musterantwort:

Ich mounte sie als Volume, wenn die Anwendung Dateien erwartet: application.yml , Zertifikate, Truststores, Keystores, Lizenzdateien oder größere Konfigurationsblöcke. Environment Variables sind gut für kleine Werte, aber nicht ideal für mehrzeilige Zertifikate oder ganze Konfigurationsdateien.

Secrets als Dateien haben zudem Vorteile bei Dateirechten und teilweise bei Rotation, sofern die Anwendung Reload unterstützt. Trotzdem gilt: Viele Java-Anwendungen lesen Dateien nur beim Start. Deshalb braucht Secret- oder Config-Rotation häufig einen kontrollierten Restart.

Senior-Antwort:

„Wenn eine Anwendung eine Datei erwartet, mounte ich eine Datei. Aber ich plane trotzdem, wie die JVM Änderungen erkennt.“

Häufige Fehler:
  • Zertifikate als lange Env Vars.
  • Private Keys mit falschen Dateirechten.
  • SubPath nutzen und Updates erwarten.
  • App reloadet Datei nicht.
  • Config-Dateien im Image hart codieren.
  • Secrets in ConfigMap-Dateien.
  • Keine Rollout-Strategie für Zertifikatsrotation.
Best Practices:
  • Dateien als Volume mounten.
  • Secrets read-only.
  • Dateirechte bewusst setzen.
  • Config-Version annotieren.
  • Rotation mit Restart oder Reload planen.
  • SubPath-Einschränkungen kennen.
  • Zertifikate nicht loggen.
  • Truststore-Strategie dokumentieren.
Stolperfalle:

Gemountete Dateien können sich ändern, aber die Java-Anwendung nutzt trotzdem alte Werte, wenn sie sie nur beim Start geladen hat.

Merksatz:

Volumes liefern Dateien; Anwendungen müssen Änderungen verstehen.

B6-K07-Q005 – Rollouts bei Config- und Secret-ÄnderungenNur Änderungen am Pod-Template rollen Pods.
Frage:

„Wie stellen Sie sicher, dass Config- oder Secret-Änderungen wirklich in laufenden Pods wirksam werden?“

Kurz erklärt:

ConfigMap- und Secret-Änderungen lösen bei Kubernetes Deployments nicht automatisch einen neuen Rollout aus, solange sich das Pod-Template nicht ändert.

Musterantwort:

Ich löse einen kontrollierten Rollout aus, wenn die Anwendung Konfiguration beim Start liest. Das kann über oc rollout restart , eine geänderte Annotation im Pod-Template oder automatisierte Checksums in Helm/Kustomize/GitOps erfolgen. Entscheidend ist: Das Pod-Template muss sich ändern, damit ein Deployment neue Pods erstellt.

Bei Secrets plane ich Rotation besonders sorgfältig. Neue Credentials müssen oft parallel gültig sein, damit alte und neue Pods während eines Rolling Updates gleichzeitig funktionieren.

Senior-Antwort:

„Config-Änderung ist nicht gleich Pod-Änderung. Wenn die JVM beim Start liest, brauche ich einen neuen Pod.“

Häufige Fehler:
  • ConfigMap ändern und keinen Rollout auslösen.
  • Secret rotieren ohne alte Credentials parallel gültig zu lassen.
  • Alle Services gleichzeitig neu starten.
  • Keine Rollback-Möglichkeit.
  • Keine Config-Version sichtbar.
  • Runtime-Reload ohne Test.
  • Readiness bei Configfehler zu spät.
Best Practices:
  • Config-/Secret-Checksums im Pod-Template.
  • Kontrollierte Rollouts.
  • Secret-Rotation mit Übergangsphase.
  • Config-Version sichtbar annotieren.
  • Rollout-Status überwachen.
  • Rollback vorbereiten.
  • Config-Validierung beim Start.
  • Rollouts staffeln.
Stolperfalle:

Eine Secret-Env-Var aktualisiert sich nicht im laufenden Container. Ohne Restart nutzt die App weiter den alten Wert.

Merksatz:

Nur Änderungen am Pod-Template rollen Pods.

B6-K07-Q006 – Spring Boot, Profiles und @ConfigurationProperties auf OpenShiftKonfiguration braucht Typen, Validierung und Ownership.
Frage:

„Wie machen Sie Spring-Boot-Konfiguration auf OpenShift robust?“

Kurz erklärt:

Spring Boot sollte OpenShift-Konfiguration typisiert und validiert binden. @ConfigurationProperties verhindert viele Runtime-Fehler durch falsche Env Vars.

Musterantwort:

Ich externalisiere Umgebungskonfiguration über ConfigMaps und Secrets, binde sie in Spring Boot über @ConfigurationProperties und validiere sie beim Start. Dadurch fällt eine fehlerhafte Config früh und eindeutig auf, statt später in einem Request einen Nullpointer oder Timeout-Fehler zu erzeugen.

Profiles sind nützlich, aber ich missbrauche sie nicht als komplettes Environment-Management. Das Image bleibt gleich; umgebungsspezifische Werte kommen aus ConfigMaps und Secrets.

Senior-Antwort:

„Ich will, dass falsche Config beim Start scheitert, nicht drei Stunden später im produktiven Request.“

Häufige Fehler:
  • @Value überall verstreuen.
  • Keine Validierung.
  • Profile pro Umgebung ins Image bauen.
  • Defaults verstecken Prod-Fehler.
  • Timeout-Werte als Strings ohne Typ.
  • Secrets als normale Properties loggen.
  • Config nur im Wiki dokumentieren.
Best Practices:
  • @ConfigurationProperties nutzen.
  • Bean Validation.
  • Config-Kontrakt testen.
  • Defaults bewusst und sparsam.
  • Secrets niemals loggen.
  • Environment-Namen standardisieren.
  • Profiles nur für Verhalten, nicht als Secret-Store.
  • Startfehler früh zulassen.
Stolperfalle:

Zu großzügige Defaults können fehlende Produktionskonfiguration verschleiern.

Merksatz:

Konfiguration braucht Typen, Validierung und Ownership.

B6-K07-Q007 – External Secrets Operator, Vault und Secret-SynchronisationSecrets gehören in einen Secret Store; Pods brauchen nur Projektionen.
Frage:

„Warum reicht ein Kubernetes Secret allein in Enterprise-Umgebungen oft nicht aus?“

Kurz erklärt:

External Secrets Operator synchronisiert Secrets aus externen Secret Stores in Kubernetes/OpenShift Secrets.

Musterantwort:

Kubernetes Secrets sind nützlich für Pod-Integration, aber Enterprise-Secret-Management braucht meist mehr: zentrale Rotation, Zugriffskontrolle, Audit, Versionierung, dynamische Credentials, HSM/KMS-Integration und Trennung von Plattform und Anwendungsteams. Deshalb werden häufig Vault, Cloud Secret Manager oder External Secrets Operator eingesetzt.

Red Hat dokumentiert den External Secrets Operator for Red Hat OpenShift als clusterweiten Service, der die External-Secrets-Anwendung bereitstellt und verwaltet. Der Operator ist nicht standardmäßig installiert und muss explizit installiert werden.

Senior-Antwort:

„Das Kubernetes Secret ist die lokale Projektion. Die Wahrheit sollte in einem dafür gebauten Secret-System liegen.“

Häufige Fehler:
  • Kubernetes Secret als Source of Truth.
  • Externe Rotation ohne App-Rollout.
  • Zu breite ClusterSecretStore-Rechte.
  • SecretStore und App-Team-Grenzen vermischen.
  • Keine Auditierung im externen Store.
  • RefreshInterval falsch wählen.
  • Operator-Limits ignorieren.
Best Practices:
  • Externer Secret Store als Wahrheit.
  • OpenShift Secret als Projektion.
  • SecretStore-Zugriffe minimal.
  • Rotation und Rollout koppeln.
  • Audit im Secret Store.
  • Team-/Namespace-Grenzen beachten.
  • Operator-Status überwachen.
  • Fallback-Strategie definieren.
Stolperfalle:

Secret-Synchronisation rotiert nicht automatisch die laufende Anwendung. Die App muss neue Werte laden oder neu gestartet werden.

Merksatz:

Secrets gehören in einen Secret Store; Pods brauchen nur Projektionen.

B6-K07-Q008 – Environment Management mit GitOps, Kustomize und HelmUmgebungen unterscheiden sich durch Overlays, nicht durch neue Artefakte.
Frage:

„Wie organisieren Sie Konfiguration für Dev, Test und Prod auf OpenShift?“

Kurz erklärt:

Environment Management organisiert gleiche Anwendung mit unterschiedlichen Umgebungskonfigurationen. GitOps, Kustomize und Helm helfen, Unterschiede deklarativ und reviewbar zu verwalten.

Musterantwort:

Ich trenne Basismanifeste von umgebungsspezifischen Overlays. Das Image bleibt möglichst gleich; Unterschiede wie Namespace, Replicas, Limits, ConfigMaps, ExternalSecrets, Routes oder Feature Flags werden deklarativ pro Umgebung verwaltet. GitOps macht Änderungen reviewbar und auditierbar.

Ich vermeide Kopieren kompletter YAML-Sets pro Umgebung, weil das Drift erzeugt. Stattdessen nutze ich Kustomize-Overlays, Helm Values oder ein Plattform-Template-System mit klaren Konventionen.

Senior-Antwort:

„Environment-Unterschiede sollen sichtbar und absichtlich sein, nicht durch kopierte YAML-Fragmente zufällig entstehen.“

Häufige Fehler:
  • YAML pro Umgebung komplett kopieren.
  • Prod-Manifeste manuell editieren.
  • Image pro Umgebung neu bauen.
  • Secrets im Git.
  • Zu viele Helm Values ohne Schema.
  • Keine Reviewpflicht für Config.
  • Dev/Test unterscheiden sich zu stark von Prod.
Best Practices:
  • Base plus Overlays.
  • GitOps als Source of Truth.
  • Gleicher Image-Digest durch Umgebungen.
  • Secrets extern oder verschlüsselt.
  • Config-Reviews.
  • Unterschiede minimieren.
  • Values/Overlays validieren.
  • Environments klar labeln.
Stolperfalle:

Wenn jedes Environment eigene komplette Manifeste hat, testet man selten wirklich das, was in Produktion läuft.

Merksatz:

Umgebungen unterscheiden sich durch Overlays, nicht durch neue Artefakte.

B6-K07-Q009 – Feature Flags, Runtime Toggles und KonfigurationsrisikenFlags sind temporäre Architekturentscheidungen.
Frage:

„Sind ConfigMaps ein gutes Feature-Flag-System?“

Kurz erklärt:

Feature Flags erlauben Verhalten ohne neues Image zu ändern. Auf OpenShift müssen sie versioniert, validiert, auditiert und rolloutfähig sein.

Musterantwort:

Für einfache, selten geänderte Toggles können ConfigMaps ausreichen. Für komplexe Feature-Flag-Anforderungen wie pro User, pro Tenant, pro Prozent-Rollout, Live-Änderungen, Audit, Experimente oder Kill Switches ist ein spezialisiertes Feature-Flag-System besser.

ConfigMaps sind deklarativ und gut GitOps-fähig, aber viele Java-Apps lesen sie beim Start. Dann braucht eine Flag-Änderung einen Rollout. Das ist für manche Flags okay, für echte Runtime-Toggles nicht.

Senior-Antwort:

„Ein Feature Flag ist produktiver Codepfad, nicht nur ein Boolean in YAML.“

Häufige Fehler:
  • Flags ohne Owner.
  • Flags nie entfernen.
  • Kritischer Kill Switch braucht Rollout.
  • Flag-Änderungen ohne Audit.
  • Zu viele Kombinationen ungetestet.
  • Secrets als Flags.
  • Dynamische Flags ohne Fallback.
Best Practices:
  • Einfache Startzeitflags über ConfigMap.
  • Kritische Runtime-Flags über dediziertes System.
  • Owner und Ablaufdatum für Flags.
  • Flags dokumentieren.
  • Defaults sicher wählen.
  • Flag-Änderungen auditen.
  • Kombinationen testen.
  • Alte Flags entfernen.
Stolperfalle:

Ein Kill Switch, der einen vollständigen Rollout braucht, ist im Incident möglicherweise zu langsam.

Merksatz:

Flags sind temporäre Architekturentscheidungen.

B6-K07-Q010 – Governance, Drift, Validierung und Anti-PatternsKonfiguration ohne Governance ist Schatten-Code. 24h
Frage:

„Welche Anti-Patterns sehen Sie bei OpenShift-Konfiguration?“

Kurz erklärt:

Konfigurations-Governance verhindert Drift, Secret-Leaks, unkontrollierte Änderungen und schwer nachvollziehbare Produktionszustände.

Musterantwort:

Typische Anti-Patterns sind: Secrets in ConfigMaps, Config im Image, manuelle oc edit -Änderungen in Prod, keine Versionierung, keine Validierung, fehlende Owner, Config-Änderungen ohne Rollout, kopierte YAML pro Umgebung, unklare Defaults, ungetestete Secret-Rotation, zu breite Secret-Leserechte und fehlende Drift Detection.

Eine reife Plattform behandelt Konfiguration wie Code: versioniert, reviewt, validiert, auditiert, rolloutfähig und beobachtbar.

Senior-Antwort:

„Konfiguration ist produktiver Systemzustand. Wenn sie nicht versioniert und validiert ist, betreiben wir Zufall.“

Häufige Fehler:
  • oc edit in Prod.
  • Keine GitOps-Quelle.
  • Secrets in Klartext.
  • Config ohne Schema.
  • Defaults verstecken Fehler.
  • Keine Rollout-Strategie.
  • Keine Owner-Labels.
  • Environment Drift.
Best Practices:
  • Config als Code.
  • GitOps als Source of Truth.
  • Schema oder typed binding.
  • Secret Scans.
  • Owner und Environment Labels.
  • Config-Version in Pod-Template.
  • Drift Detection.
  • Pull Requests und Reviews.
Stolperfalle:

Eine manuelle Config-Änderung im Cluster ist schnell — und nach dem nächsten GitOps-Sync verschwunden oder, schlimmer, unbemerkt produktiv.

Merksatz:

Konfiguration ohne Governance ist Schatten-Code.

B6-K08 – Kapitel 8: Persistent Storage, PVCs und StorageClasses 10 Karten

B6-K08-Q001 – Persistent Storage Grundmodell: PV, PVC und PodPods sind vergänglich; PVCs geben ihnen dauerhaften Speicher. 24h
Frage:

„Wie funktionieren PV und PVC in OpenShift?“

Kurz erklärt:

Ein PersistentVolume ist die clusterweite Storage-Ressource. Ein PersistentVolumeClaim ist die namespaced Anfrage einer Anwendung nach Speicher. Der Pod mountet den PVC.

Musterantwort:

Ein PersistentVolume, kurz PV, repräsentiert bereitgestellten persistenten Speicher im Cluster. Ein PersistentVolumeClaim, kurz PVC, ist die Anfrage einer Anwendung nach Speicher mit bestimmter Größe, Access Mode und optionaler StorageClass. OpenShift nutzt das Kubernetes Persistent-Volume-Framework; Entwickler können PVCs nutzen, ohne die konkrete Storage-Infrastruktur kennen zu müssen.

Ein Pod mountet nicht direkt „irgendeine Festplatte“, sondern referenziert einen PVC. Der PVC wird an ein passendes PV gebunden oder dynamisch über eine StorageClass provisioniert.

Senior-Antwort:

„PVCs lösen Persistenz im Pod-Lifecycle, aber sie ersetzen keine Datenarchitektur.“

Häufige Fehler:
  • Ephemeral Container Filesystem als persistent betrachten.
  • PVC für jeden temporären Cache nutzen.
  • PV und PVC verwechseln.
  • Access Mode ignorieren.
  • Mehrere Replicas auf RWO-PVC planen.
  • StorageClass nicht bewusst wählen.
  • Keine Backup-Anforderungen definieren.
Best Practices:
  • Persistent Storage nur bei echtem Bedarf.
  • PVCs deklarativ verwalten.
  • StorageClass bewusst wählen.
  • Access Mode passend zum Workload.
  • Backups und Restore testen.
  • Daten nicht im Image speichern.
  • Temporäre Daten über emptyDir .
  • Fachlichen Zustand bevorzugt in Datenbanken oder Object Storage.
Stolperfalle:

Ein PVC macht Daten persistent, aber nicht automatisch hochverfügbar, gesichert oder performant.

Merksatz:

Pods sind vergänglich; PVCs geben ihnen dauerhaften Speicher.

B6-K08-Q002 – StorageClasses und dynamisches ProvisioningPVC fordert Speicher an; StorageClass definiert, welche Art Speicher geliefert wird.
Frage:

„Was ist eine StorageClass, und warum ist sie wichtig?“

Kurz erklärt:

Eine StorageClass beschreibt eine Storage-Klasse und ermöglicht dynamisches Provisioning von PVs nach Bedarf.

Musterantwort:

Eine StorageClass beschreibt und klassifiziert Storage, den Anwendungen über PVCs anfordern können. Sie enthält den Provisioner, Parameter, Reclaim Policy, Volume Binding Mode und optional Volume Expansion. Red Hat beschreibt StorageClass als Ressource, die Storage beschreibt und klassifiziert, der angefordert werden kann, und Parameter für dynamisch provisionierten Storage bereitstellt.

StorageClasses sind wichtig, weil nicht jeder Speicher gleich ist. Datenbanken brauchen andere Eigenschaften als Logs, Reports, Shared Files oder temporäre Build-Artefakte.

Senior-Antwort:

„StorageClass ist kein technischer Default, sondern ein Vertrag über Performance, Verfügbarkeit, Kosten und Lifecycle.“

Häufige Fehler:
  • Default StorageClass blind verwenden.
  • StorageClass nicht im PVC angeben.
  • Performance-Eigenschaften nicht prüfen.
  • RWX für Datenbank nutzen, nur weil es bequem ist.
  • Reclaim Policy nicht verstehen.
  • VolumeBindingMode ignorieren.
  • StorageClass pro Team unkontrolliert erstellen.
Best Practices:
  • StorageClasses als Plattformprodukt definieren.
  • Default StorageClass bewusst setzen.
  • Klassen nach Use Case benennen.
  • Performance und Backup-Policy dokumentieren.
  • WaitForFirstConsumer bei zonalem Storage prüfen.
  • Volume Expansion bewusst erlauben.
  • Reclaim Policy verstehen.
  • Teams klare Storage-Auswahl geben.
Stolperfalle:

Die Default StorageClass ist nicht automatisch die richtige StorageClass für produktive Datenbanken.

Merksatz:

PVC fordert Speicher an; StorageClass definiert, welche Art Speicher geliefert wird.

B6-K08-Q003 – Access Modes: RWO, ROX, RWX und RWOPAccess Mode ist Storage-Fähigkeit, nicht Anwendungslogik.
Frage:

„Was ist der Unterschied zwischen RWO, ROX, RWX und RWOP?“

Kurz erklärt:

Access Modes beschreiben, wie ein Volume von Nodes oder Pods gelesen und geschrieben werden darf. Sie sind Fähigkeiten des Storage-Backends, keine beliebige Anwendungseinstellung.

Musterantwort:

Access Modes beschreiben die Fähigkeiten eines Volumes. Red Hat nennt als Beispiele ReadWriteOnce , ReadOnlyMany , ReadWriteMany und ReadWriteOncePod . ReadWriteOnce bedeutet, dass ein Volume von einem Node read-write gemountet werden kann. ReadOnlyMany erlaubt read-only Mounts von mehreren Nodes. ReadWriteMany erlaubt read-write Mounts von mehreren Nodes. ReadWriteOncePod begrenzt read-write Zugriff auf einen einzelnen Pod.

Der wichtigste Punkt: Access Mode sagt nicht automatisch, dass die Anwendung parallele Dateizugriffe korrekt verarbeitet. RWX kann technisch möglich sein, aber fachlich trotzdem falsch.

Senior-Antwort:

„RWX löst Mounting, aber nicht Konsistenz. Gemeinsames Schreiben bleibt eine Anwendungsfrage.“

Häufige Fehler:
  • RWX als Standard wählen.
  • Mehrere Java-Replicas schreiben unkoordiniert in dieselben Dateien.
  • Datenbank auf ungeeignetem RWX-Storage.
  • Access Mode mit App-Konsistenz verwechseln.
  • StorageClass unterstützt angeforderten Mode nicht.
  • RWO-PVC in Deployment mit mehreren Replicas.
  • File Locking ignorieren.
Best Practices:
  • RWO für viele Stateful Workloads.
  • RWX nur bei echtem Shared-Filesystem-Bedarf.
  • App-Konsistenz prüfen.
  • Datenbanken auf geeigneter StorageClass.
  • Access Mode dokumentieren.
  • Mehrere Replicas mit PVC bewusst planen.
  • Shared Writes vermeiden.
  • Object Storage statt RWX prüfen.
Stolperfalle:

Nur weil mehrere Pods ein Volume schreiben können, heißt das nicht, dass die Anwendung race-free ist.

Merksatz:

Access Mode ist Storage-Fähigkeit, nicht Anwendungslogik.

B6-K08-Q004 – StatefulSets, VolumeClaimTemplates und stabile IdentitätDeployment repliziert Verhalten; StatefulSet bewahrt Identität.
Frage:

„Wann verwenden Sie StatefulSet statt Deployment?“

Kurz erklärt:

StatefulSets geben Pods stabile Identität und eigene PVCs über VolumeClaimTemplates. Sie sind für zustandsbehaftete Workloads besser geeignet als Deployments.

Musterantwort:

Ich verwende StatefulSet, wenn Pods stabile Identität, geordnete Starts/Stopps oder stabile, pod-spezifische Volumes brauchen. Typische Beispiele sind Datenbanken, Broker, Cluster-Knoten oder Systeme, bei denen node-0 , node-1 , node-2 unterschiedliche Rollen oder eigene Daten haben.

Ein Deployment ist für stateless Replikate ideal. Ein StatefulSet ist für zustandsbehaftete Replikate gedacht, bei denen jeder Pod eine eigene Identität und oft einen eigenen PVC braucht.

Senior-Antwort:

„StatefulSet gibt stabile Identität und Volumes. Es gibt mir aber keinen automatischen Datenbankbetrieb.“

Häufige Fehler:
  • Datenbank als Deployment mit gemeinsamem PVC.
  • StatefulSet als einfache Hochverfügbarkeit verstehen.
  • Keine Backup-/Restore-Strategie.
  • VolumeClaimTemplates nicht verstehen.
  • PVCs bleiben nach StatefulSet-Löschung bestehen.
  • Rolling Updates ohne Cluster-Protokoll-Verständnis.
  • Storage-Latenz ignorieren.
Best Practices:
  • StatefulSet für pod-spezifischen Zustand.
  • Operatoren für komplexe Datenbanken bevorzugen.
  • Backup/Restore testen.
  • StorageClass passend wählen.
  • Graceful Shutdown.
  • Cluster-Protokolle verstehen.
  • PVC-Lifecycle dokumentieren.
  • Monitoring pro Pod und Volume.
Stolperfalle:

Ein StatefulSet macht aus einer Datenbank nicht automatisch ein sicheres HA-System.

Merksatz:

Deployment repliziert Verhalten; StatefulSet bewahrt Identität.

B6-K08-Q005 – Volume Expansion, Reclaim Policy und LifecycleMehr Speicher kauft Zeit, nicht Ordnung.
Frage:

„Wie erweitert man persistenten Speicher auf OpenShift, und welche Risiken gibt es?“

Kurz erklärt:

Volume Expansion erweitert PVCs, wenn StorageClass und Backend es unterstützen. Reclaim Policy bestimmt, was mit dem PV nach PVC-Löschung passiert.

Musterantwort:

Wenn die StorageClass allowVolumeExpansion: true unterstützt und das CSI-Backend Expansion erlaubt, kann man die PVC-Größe erhöhen. Red Hat dokumentiert, dass man mit CSI Storage Volumes nach ihrer Erstellung erweitern kann.

Risiken sind: Backend unterstützt Expansion nicht, Dateisystem muss wachsen, Anwendung muss mit zusätzlichem Speicher umgehen, Monitoring muss angepasst werden, und Expansion ist nicht automatisch eine Lösung für unkontrolliertes Datenwachstum. Reclaim Policy ist ebenfalls wichtig: Delete kann Storage nach PVC-Löschung entfernen; Retain bewahrt Daten, muss aber manuell gehandhabt werden.

Senior-Antwort:

„Volume Expansion ist ein Betriebswerkzeug, keine Daten-Lifecycle-Strategie.“

Häufige Fehler:
  • Expansion ohne Retention-Plan.
  • StorageClass unterstützt Expansion nicht.
  • PVC verkleinern wollen.
  • Reclaim Policy nicht kennen.
  • Prod-Daten durch PVC-Löschung verlieren.
  • Backup-Fenster nicht anpassen.
  • Disk-Full in der App nicht behandeln.
Best Practices:
  • Volume-Auslastung monitoren.
  • Expansion vorab testen.
  • StorageClass-Fähigkeiten dokumentieren.
  • Retention und Archivierung.
  • Backups vor kritischen Storage-Änderungen.
  • Reclaim Policy bewusst wählen.
  • PVC-Löschung in Prod schützen.
  • Disk-Full-Fehler in Apps behandeln.
Stolperfalle:

Ein PVC mit Reclaim Policy Delete kann beim Löschen Daten endgültig entfernen, je nach Storage-Backend und Provisioner.

Merksatz:

Mehr Speicher kauft Zeit, nicht Ordnung.

B6-K08-Q006 – CSI, Storage Backends und OpenShift Data FoundationCSI standardisiert den Anschluss, nicht die Eigenschaften des Speichers.
Frage:

„Was ist CSI, und warum ist es für OpenShift Storage wichtig?“

Kurz erklärt:

CSI ist die standardisierte Schnittstelle für Storage-Provider. OpenShift kann verschiedene Storage-Backends über CSI und Operatoren integrieren.

Musterantwort:

CSI steht für Container Storage Interface. Es ist die standardisierte Schnittstelle, über die Kubernetes und OpenShift Storage-Backends als persistenten Speicher konsumieren können. Red Hat beschreibt, dass CSI OpenShift ermöglicht, Storage von Backends zu verwenden, die die CSI-Schnittstelle implementieren.

Das ist wichtig, weil OpenShift nicht an ein einzelnes Storage-System gebunden ist. Storage kann von Cloud-Disks, SAN, Ceph, OpenShift Data Foundation, NFS-ähnlichen Systemen oder anderen CSI-kompatiblen Backends kommen.

Senior-Antwort:

„CSI abstrahiert Storage-Integration, aber es macht Storage-Backends nicht gleichwertig.“

Häufige Fehler:
  • Alle Workloads auf eine StorageClass.
  • CSI-Fähigkeiten nicht prüfen.
  • RWX-Storage für latenzkritische Datenbank.
  • Keine Topology-Awareness.
  • Storage-Operator-Status ignorieren.
  • Backup-Fähigkeit mit Persistenz verwechseln.
  • Provider-Limits nicht kennen.
Best Practices:
  • StorageClasses nach Backend-Fähigkeiten dokumentieren.
  • CSI-Treiber-Status überwachen.
  • Workload-Anforderungen definieren.
  • Storage-Performance testen.
  • Backup/Snapshot-Fähigkeiten prüfen.
  • ODF/Backend-Operatoren überwachen.
  • Provider-Limits dokumentieren.
  • Storage-Topology verstehen.
Stolperfalle:

Ein PVC sieht immer ähnlich aus, aber die darunterliegende StorageClass kann völlig andere Performance- und Verfügbarkeitsmerkmale haben.

Merksatz:

CSI standardisiert den Anschluss, nicht die Eigenschaften des Speichers.

B6-K08-Q007 – Backup, Snapshots, Restore und Disaster RecoveryKein Restore-Test, kein Backup.
Frage:

„Warum ist ein PVC kein Backup?“

Kurz erklärt:

Persistenz ist kein Backup. Snapshots und Backups müssen konsistent, getestet und in Restore-Prozesse integriert sein.

Musterantwort:

Ein PVC sorgt dafür, dass Daten einen Pod-Neustart überleben. Er schützt nicht automatisch vor versehentlichem Löschen, Datenkorruption, falscher Migration, Ransomware, Applikationsbug, Storage-Ausfall oder Clusterverlust. Backup bedeutet: Ich kann einen definierten Zustand wiederherstellen und habe das Restore getestet.

Snapshots können Teil der Strategie sein, aber sie müssen anwendungskonsistent sein. Für Datenbanken reicht ein crash-konsistenter Storage-Snapshot nicht immer. Man braucht Datenbank-Backup-Mechanismen, WAL/Transaction-Logs oder Operator-gestützte Backups.

Senior-Antwort:

„Persistenz hält Daten am Leben. Backup beweist, dass ich sie zurückholen kann.“

Häufige Fehler:
  • PVC als Backup verstehen.
  • Snapshots nie restoren.
  • Keine Datenbankkonsistenz.
  • Secrets/Config nicht mitsichern.
  • Backup im selben Failure Domain.
  • Restore-Rechte fehlen.
  • RTO/RPO nicht mit Fachbereich abgestimmt.
Best Practices:
  • Backup-Anforderungen pro Workload.
  • Restore regelmäßig testen.
  • Anwendungskonsistenz sicherstellen.
  • Datenbanknative Backups nutzen.
  • Snapshots als Ergänzung.
  • Secrets/Config/GitOps-Zustand berücksichtigen.
  • RPO/RTO definieren.
  • Backup-Monitoring.
Stolperfalle:

Ein Snapshot, der nie zurückgespielt wurde, ist eine Hoffnung, kein Backup.

Merksatz:

Kein Restore-Test, kein Backup.

B6-K08-Q008 – Local Storage, Node Affinity und AusfallrisikenLocal Storage ist schnell, aber lokal bleibt lokal.
Frage:

„Wann ist Local Storage auf OpenShift sinnvoll, und welche Risiken hat es?“

Kurz erklärt:

Local Storage nutzt lokale Disks eines Nodes. Das kann sehr performant sein, koppelt Workloads aber stark an Node-Verfügbarkeit.

Musterantwort:

Local Storage ist sinnvoll für Workloads, die sehr niedrige Latenz oder lokale Datenpfade brauchen und auf Anwendungsebene Replikation oder Wiederherstellung beherrschen. Red Hat beschreibt, dass OpenShift mit local volumes persistenten Speicher über lokale Geräte wie Disk oder Partition bereitstellen kann, nutzbar über die normale PVC-Schnittstelle.

Das Risiko ist Node-Kopplung. Wenn der Node ausfällt, ist das lokale Volume nicht einfach auf einem anderen Node verfügbar. Deshalb eignet sich Local Storage eher für Systeme, die Daten replizieren, wiederaufbauen oder auf Node-Ausfall vorbereitet sind.

Senior-Antwort:

„Local Storage kauft Latenz mit Node-Kopplung. Das ist nur gut, wenn die Anwendung diesen Trade-off beherrscht.“

Häufige Fehler:
  • Single-Source-of-Truth auf Local Storage.
  • Node-Ausfall nicht planen.
  • Keine Replikation.
  • Local Storage wie Netzwerkstorage behandeln.
  • Scheduling-Einschränkungen ignorieren.
  • Backup vergessen.
  • StatefulSet-Placement nicht verstehen.
Best Practices:
  • Nur für geeignete Workloads.
  • Anwendung muss Replikation/Recovery können.
  • Node-Ausfall testen.
  • Backup trotzdem planen.
  • Monitoring pro Node und Disk.
  • Scheduling-Auswirkungen verstehen.
  • Nicht für einfache Shared Files.
  • Dokumentierte Failure-Semantik.
Stolperfalle:

Local Storage ist persistent gegenüber Pod-Neustart, aber nicht hochverfügbar gegenüber Node-Ausfall.

Merksatz:

Local Storage ist schnell, aber lokal bleibt lokal.

B6-K08-Q009 – Java-Anwendungen, Dateisystemzustand und Cloud-native AlternativenDateien sind Daten — aber nicht jede Datei braucht ein PVC.
Frage:

„Wann würden Sie für eine Java-Anwendung keinen PVC verwenden, obwohl Dateien verarbeitet werden?“

Kurz erklärt:

Nicht jeder Dateizustand gehört auf einen PVC. Für viele Java-Use-Cases sind Datenbank, Object Storage oder Messaging robuster als geteilte Dateisysteme.

Musterantwort:

Ich verwende keinen PVC, wenn Dateien nur temporär verarbeitet oder besser in Object Storage gespeichert werden. Uploads, Dokumente, Bilder und Reports sind oft besser in S3-kompatiblem Object Storage aufgehoben. Der Pod nutzt dann emptyDir nur für temporäre Verarbeitung. Metadaten liegen in der Datenbank.

PVCs sind sinnvoll, wenn die Anwendung wirklich ein POSIX-Dateisystem braucht. Aber PVCs für Shared Uploads zwischen vielen Replicas führen schnell zu RWX-, Locking-, Backup- und Skalierungsproblemen.

Senior-Antwort:

„Wenn ich Dateien habe, brauche ich nicht automatisch ein Dateisystem. Oft brauche ich Object Storage und gute Metadaten.“

Häufige Fehler:
  • RWX-PVC für alle Uploads.
  • Dateien als Integrationsschnittstelle zwischen Services.
  • Große Uploads im Heap.
  • Temporäre Dateien ohne Limit.
  • Dokumente im Pod-Dateisystem.
  • Keine Cleanup-Strategie.
  • Object Storage nicht als Option prüfen.
Best Practices:
  • Fachliche Blobs in Object Storage.
  • Metadaten in Datenbank.
  • Temporäre Verarbeitung in begrenztem emptyDir .
  • Streaming statt Heap.
  • PVC nur bei echtem Dateisystembedarf.
  • Shared Files vermeiden.
  • Cleanup für temporäre Daten.
  • Backups und Lifecycle im Zielsystem.
Stolperfalle:

RWX-PVC wirkt wie die einfachste Lösung für Uploads, wird aber bei Skalierung, Locking und Backup schnell teuer.

Merksatz:

Dateien sind Daten — aber nicht jede Datei braucht ein PVC.

B6-K08-Q010 – Storage-Diagnose, Monitoring und Anti-PatternsStorage ist eine Kette — PVC ist nur ein Glied. 24h
Frage:

„Wie debuggen Sie Storage-Probleme auf OpenShift?“

Kurz erklärt:

Storage-Diagnose prüft PVC, PV, StorageClass, Events, Pod-Mounts, CSI-Treiber, Backend und Anwendungssymptome.

Musterantwort:

Ich prüfe systematisch: PVC-Status, Events, gebundenes PV, StorageClass, Access Mode, StorageClass-Provisioner, Pod-Events, Mount-Fehler, Node-Platzierung und CSI-/Storage-Backend-Status. Wenn die Anwendung läuft, prüfe ich zusätzlich Disk-Full, IO-Latenz, Fehler im App-Log, Datenbankmetriken und Volume-Auslastung.

Typische Zustände sind Pending PVC wegen fehlender StorageClass, nicht unterstütztem Access Mode oder Provisioner-Problemen; Pod bleibt hängen wegen Mount-Fehler; Anwendung crasht wegen Permission oder Disk-Full.

Senior-Antwort:

„Storage debugge ich nicht am Pod allein. Ich gehe von App-Symptom über PVC, PV, StorageClass bis zum Backend.“

Häufige Fehler:
  • Nur App-Logs prüfen.
  • PVC-Events ignorieren.
  • StorageClass nicht prüfen.
  • RWO-Mount-Konflikte übersehen.
  • Permission-Probleme als Storage-Ausfall sehen.
  • Keine Storage-Metriken.
  • Backup ohne Restore-Test.
  • RWX als Standardlösung.
Best Practices:
  • PVC/PV/Events systematisch prüfen.
  • StorageClass-Fähigkeiten kennen.
  • CSI-/Storage-Operator überwachen.
  • Volume-Auslastung alerten.
  • IO-Latenz messen.
  • Disk-Full in Apps behandeln.
  • Backups und Restores testen.
  • Access Mode passend wählen.
Stolperfalle:

Ein bound PVC bedeutet nicht, dass Storage performant, richtig gemountet oder anwendungsseitig korrekt nutzbar ist.

Merksatz:

Storage ist eine Kette — PVC ist nur ein Glied.

B6-K09 – Kapitel 9: Operators, OLM und Custom Resources 10 Karten

B6-K09-Q001 – Operator-Grundmodell: Betriebswissen als ControllerOperators automatisieren Betrieb, nicht nur Deployment. 24h
Frage:

„Was ist ein Operator in OpenShift?“

Kurz erklärt:

Ein Operator ist ein Controller, der fachliches oder betriebliches Wissen über eine Anwendung automatisiert: Installation, Konfiguration, Skalierung, Backup, Restore, Upgrade und Fehlerbehandlung.

Musterantwort:

Ein Operator ist ein Kubernetes-natives Automatisierungsmuster. Er erweitert die Plattform mit eigenen APIs, meistens über CRDs, und betreibt einen Controller, der Custom Resources beobachtet. Der Benutzer beschreibt den gewünschten Zustand, zum Beispiel „PostgreSQLCluster mit drei Replicas und Backup“. Der Operator reconciled diesen Zustand, indem er Kubernetes-Ressourcen erzeugt und betriebliche Aktionen ausführt.

Red Hat beschreibt Operators als Methode zum Verpacken, Deployen und Verwalten einer Kubernetes-Anwendung. Für mich ist ein Operator deshalb automatisiertes Betriebswissen, nicht nur ein Installationsskript.

Senior-Antwort:

„Ein Operator ist codiertes Betriebswissen. Wenn er nur YAML installiert, ist er kein guter Operator.“

Häufige Fehler:
  • Operator als einmaligen Installer verstehen.
  • Custom Resource ändern, ohne Operatorverhalten zu kennen.
  • Status-Felder ignorieren.
  • Operator-Rechte unterschätzen.
  • Operator ohne Backup-/Restore-Konzept nutzen.
  • Jeden Dienst per Operator betreiben wollen.
  • Operator-Logs nicht in Diagnose einbeziehen.
Best Practices:
  • Operatoren als Plattformcode behandeln.
  • Custom Resources versionieren.
  • Status und Conditions prüfen.
  • Operator-Dokumentation lesen.
  • Backups und Restores testen.
  • Rechte und Scope prüfen.
  • Operator-Upgrades testen.
  • Runbooks für operatorbetriebene Services erstellen.
Stolperfalle:

Ein Operator macht einen zustandsbehafteten Dienst nicht automatisch sicher. Backup, Restore, Monitoring und Upgrade müssen geprüft werden.

Merksatz:

Operators automatisieren Betrieb, nicht nur Deployment.

B6-K09-Q002 – CRD und Custom Resource: OpenShift-API erweiternCRD definiert den Typ; CR beschreibt den Wunsch; Controller macht daraus Realität.
Frage:

„Was ist der Unterschied zwischen CRD und Custom Resource?“

Kurz erklärt:

Eine CRD erweitert die Kubernetes/OpenShift API um neue Ressourcentypen. Eine Custom Resource ist eine konkrete Instanz dieses Typs.

Musterantwort:

Eine Custom Resource Definition definiert einen neuen API-Typ im Cluster: Gruppe, Version, Kind, Scope und Schema. Eine Custom Resource ist eine konkrete Instanz dieses Typs. Wenn eine CRD PostgreSQLCluster definiert, ist orders-db als PostgreSQLCluster eine Custom Resource.

Der Operator beobachtet diese Custom Resources und führt Reconciliation aus. Ohne Controller ist eine CRD zunächst nur ein neuer Datentyp in der API. Der Wert entsteht durch den Controller, der daraus Verhalten macht.

Senior-Antwort:

„Eine CRD ist eine API-Erweiterung. Deshalb behandle ich sie wie ein öffentliches API-Design, nicht wie internes YAML.“

Häufige Fehler:
  • CRD ohne Schema.
  • Status in spec schreiben.
  • spec und status vermischen.
  • Breaking Changes ohne Versionierung.
  • Zu große CRs.
  • Controller schreibt ständig Updates.
  • CRD als Datenbank missbrauchen.
Best Practices:
  • CRD-Schema definieren.
  • spec als gewünschter Zustand.
  • status als beobachteter Zustand.
  • Versionierung planen.
  • Backward Compatibility.
  • Kleine, klare Custom Resources.
  • Conditions verwenden.
  • Controller-Updates sparsam.
Stolperfalle:

Eine CRD ohne guten Controller ist nur ein Datenschema, kein automatisierter Betrieb.

Merksatz:

CRD definiert den Typ; CR beschreibt den Wunsch; Controller macht daraus Realität.

B6-K09-Q003 – OLM-Grundmodell: Operator Lifecycle ManagerOLM installiert nicht nur Operators; OLM verwaltet ihren Lebenszyklus.
Frage:

„Welche Rolle spielt OLM in OpenShift?“

Kurz erklärt:

OLM verwaltet Installation, Updates und Berechtigungen von Operators im Cluster.

Musterantwort:

OLM ist die Lifecycle-Schicht für Operators. Es hilft, Operators aus Katalogen zu finden, zu installieren, zu aktualisieren und mit den erforderlichen RBAC-Rechten zu versehen. Die Red-Hat-Dokumentation beschreibt OLM als Komponente, die Installation, Upgrade und RBAC von Operators im Cluster kontrolliert.

In der Praxis bedeutet das: Ich installiere nicht einfach ein beliebiges Deployment. Ich abonniere einen Operator aus einem Katalog, wähle Channel, Namespace und Approval-Strategie, und OLM erzeugt InstallPlans, CSVs und die benötigten Ressourcen.

Senior-Antwort:

„OLM ist Paketmanager, Lifecycle-Controller und Governance-Punkt für Operators.“

Häufige Fehler:
  • Operator manuell ohne OLM installieren, obwohl Plattform OLM nutzt.
  • Automatische Updates ohne Test.
  • InstallPlans ignorieren.
  • OperatorGroup falsch konfigurieren.
  • CSV-Status nicht prüfen.
  • OLM als Anwendungsteam-Detail behandeln.
  • Operator-Upgrades ohne Change-Prozess.
Best Practices:
  • OLM-Konzepte verstehen.
  • Operator-Installation als Plattformänderung behandeln.
  • Channels bewusst wählen.
  • Approval-Strategie definieren.
  • InstallPlans prüfen.
  • CSV-Status beobachten.
  • Operator-Upgrades in Testclustern validieren.
  • Dokumentierte Ownership.
Stolperfalle:

Ein automatisches Operator-Update kann produktive Custom Resources beeinflussen, auch wenn kein Anwendungscode geändert wurde.

Merksatz:

OLM installiert nicht nur Operators; OLM verwaltet ihren Lebenszyklus.

B6-K09-Q004 – OperatorHub, CatalogSource und Package-AuswahlCatalogSource ist Supply Chain für Operators.
Frage:

„Was sind OperatorHub und CatalogSource?“

Kurz erklärt:

OperatorHub zeigt verfügbare Operators aus CatalogSources. CatalogSources stellen Metadaten, Pakete und Versionen bereit.

Musterantwort:

OperatorHub ist die UI beziehungsweise Discovery-Schicht, über die Benutzer Operators finden können. CatalogSources liefern die Metadaten und Pakete, aus denen OLM Operators installieren kann. Red Hat beschreibt OperatorHub als Benutzeroberfläche zum Entdecken von Operators, die mit OLM zusammenarbeitet, das Operators im Cluster installiert und verwaltet. Eine CatalogSource ist laut Red Hat ein Store von Metadaten, häufig durch ein Index-Image in einer Container Registry repräsentiert; OLM fragt CatalogSources ab, um Operators und deren Abhängigkeiten zu entdecken.

In Enterprise-Umgebungen werden oft nur freigegebene Kataloge erlaubt, damit Teams nicht beliebige Operators installieren.

Senior-Antwort:

„OperatorHub ist die Schaufensterfläche. CatalogSource entscheidet, welche Produkte überhaupt im Regal liegen.“

Häufige Fehler:
  • Community Operators ohne Review in Prod.
  • Alle Kataloge offen lassen.
  • Disconnected Updates nicht planen.
  • CatalogSource-Status ignorieren.
  • Operator-Version aus falschem Katalog.
  • Keine Freigabeprozesse.
  • Kataloge nicht versionieren.
Best Practices:
  • Operator-Kataloge governancefähig machen.
  • Nur freigegebene Operatoren anbieten.
  • Katalogversionen pinnen.
  • Disconnected Mirrors testen.
  • CatalogSource-Status überwachen.
  • Operator-Auswahl dokumentieren.
  • Security Review für neue Operators.
  • Anwendungsteams nicht direkt beliebige Operatoren installieren lassen.
Stolperfalle:

Ein installierbarer Operator ist nicht automatisch ein freigegebener Operator.

Merksatz:

CatalogSource ist Supply Chain für Operators.

B6-K09-Q005 – Subscription, Channel, InstallPlan und Approval StrategySubscription folgt dem Channel; InstallPlan verändert den Cluster.
Frage:

„Warum ist installPlanApproval: Manual in Produktion oft sinnvoll?“

Kurz erklärt:

Eine Subscription beschreibt, welchen Operator aus welchem Katalog und Channel OLM installieren oder aktualisieren soll. InstallPlans beschreiben konkrete Installations- oder Upgrade-Schritte.

Musterantwort:

Manual Approval verhindert, dass Operator-Upgrades automatisch produktive Plattformdienste verändern. Eine Subscription sagt OLM, welchen Operator, Channel und Katalog es verfolgen soll. Wenn neue Versionen verfügbar sind, erzeugt OLM einen InstallPlan. Bei manueller Approval-Strategie muss dieser Plan explizit genehmigt werden.

Red Hat dokumentiert Administrator-Aufgaben zum Aktualisieren installierter Operators über OLM. In Produktion will ich Operator-Upgrades prüfen, testen und freigeben, bevor sie auf produktive Custom Resources wirken.

Senior-Antwort:

„Ein Operator-Upgrade ist ein Plattform-Change. Deshalb behandle ich InstallPlans wie Deployments mit Change-Risiko.“

Häufige Fehler:
  • Automatic Approval für kritische Operatoren.
  • Channel unbewusst wählen.
  • InstallPlan nicht lesen.
  • Prod aktualisiert vor Test.
  • startingCSV falsch verstehen.
  • Subscription driftet manuell.
  • Keine Rollback-Strategie.
Best Practices:
  • Prod mit Manual Approval für kritische Operators.
  • Channels bewusst wählen.
  • Staging-Upgrades zuerst.
  • InstallPlans prüfen.
  • Operator Release Notes lesen.
  • Upgrade-Fenster planen.
  • Abhängige CRs und Services prüfen.
  • Subscription als Code verwalten.
Stolperfalle:

Automatic Approval kann im ungünstigen Moment einen Betreiber aktualisieren, der anschließend kritische Stateful Services reconciled.

Merksatz:

Subscription folgt dem Channel; InstallPlan verändert den Cluster.

B6-K09-Q006 – ClusterServiceVersion, OperatorGroup und InstallationsscopeCSV zeigt die installierte Version; OperatorGroup zeigt den Wirkungsbereich.
Frage:

„Was sind ClusterServiceVersion und OperatorGroup?“

Kurz erklärt:

ClusterServiceVersion beschreibt installierte Operator-Metadaten und Status. OperatorGroup definiert, in welchen Namespaces ein Operator wirkt.

Musterantwort:

Eine ClusterServiceVersion, kurz CSV, beschreibt eine installierte Operator-Version: Metadaten, bereitgestellte APIs, benötigte Berechtigungen, Status und Installationszustand. Die OperatorHub-API-Dokumentation beschreibt ClusterServiceVersion als Custom Resource vom Typ ClusterServiceVersionSpec .

Eine OperatorGroup bestimmt den Installations- beziehungsweise Wirkungsscope eines Operators, insbesondere welche Namespaces beobachtet werden. Falsche OperatorGroups führen häufig zu Installationsproblemen oder unerwartetem Operator-Scope.

Senior-Antwort:

„Operator-Scope ist Security- und Betriebsarchitektur. Je breiter der Scope, desto größer der Blast Radius.“

Häufige Fehler:
  • Keine OperatorGroup im Namespace.
  • Mehrere inkompatible OperatorGroups.
  • Operator clusterweit installieren ohne Bedarf.
  • CSV-Fehler ignorieren.
  • CR im falschen Namespace erstellen.
  • Zu breite RBAC-Rechte.
  • Installationsscope nicht dokumentieren.
Best Practices:
  • Operator-Scope bewusst wählen.
  • CSV-Status prüfen.
  • OperatorGroup versionieren.
  • TargetNamespaces dokumentieren.
  • Clusterweite Operators stark reviewen.
  • CR-Erstellungsorte standardisieren.
  • RBAC minimal halten.
  • Installation in Testumgebung validieren.
Stolperfalle:

Ein Operator kann korrekt installiert erscheinen, aber CRs ignorieren, wenn sie außerhalb seines OperatorGroup-Scopes liegen.

Merksatz:

CSV zeigt die installierte Version; OperatorGroup zeigt den Wirkungsbereich.

B6-K09-Q007 – Reconcile Loop, Status, Conditions und IdempotenzReconcile ist Wiederholung mit Gedächtnis im Status.
Frage:

„Was bedeutet Reconciliation bei einem Operator?“

Kurz erklärt:

Der Reconcile Loop beobachtet Custom Resources und bringt den tatsächlichen Zustand wiederholt in Richtung gewünschter Zustand. Status und Conditions kommunizieren Ergebnis und Fortschritt.

Musterantwort:

Reconciliation bedeutet, dass der Operator regelmäßig oder ereignisgetrieben prüft, ob der tatsächliche Zustand dem gewünschten Zustand in der Custom Resource entspricht. Wenn nicht, führt er Schritte aus, um den Zustand anzunähern. Diese Logik muss idempotent sein: mehrfaches Ausführen darf keine falschen Nebeneffekte erzeugen.

Ein guter Operator schreibt aussagekräftigen Status und Conditions. spec gehört dem Benutzer, status gehört dem Controller. Conditions wie Ready , Degraded , Progressing oder Available helfen Betrieb und Automatisierung.

Senior-Antwort:

„Ein Operator ist ein geduldiger Controller, kein synchroner Befehlsempfänger.“

Häufige Fehler:
  • Reconcile wie synchronen Request behandeln.
  • Status nicht schreiben.
  • Status in Spec speichern.
  • Nicht-idempotente Aktionen.
  • Permanente Status-Updates.
  • Fehler nicht retryfähig modellieren.
  • Manuelle Änderungen am erzeugten StatefulSet erwarten.
Best Practices:
  • Reconcile idempotent.
  • Spec/Status trennen.
  • Conditions klar definieren.
  • observedGeneration nutzen.
  • Status nur bei Änderung schreiben.
  • Fehler retryfähig machen.
  • OwnerReferences setzen.
  • Events für wichtige Aktionen.
Stolperfalle:

Wenn ein Reconcile-Schritt nicht idempotent ist, kann ein Retry produktive Schäden verursachen.

Merksatz:

Reconcile ist Wiederholung mit Gedächtnis im Status.

B6-K09-Q008 – Operator Security: RBAC, Scope und Blast RadiusOperators haben Macht; Macht braucht Scope.
Frage:

„Warum sind Operators aus Security-Sicht besonders kritisch?“

Kurz erklärt:

Operators benötigen oft starke Rechte. Ihr Scope und RBAC bestimmen den möglichen Schaden bei Fehlern oder Kompromittierung.

Musterantwort:

Operators sind kritisch, weil sie nicht nur laufen, sondern Ressourcen im Cluster verändern. Sie können StatefulSets, Secrets, PVCs, Services, CRDs oder andere Objekte erzeugen und ändern. Wenn ein Operator clusterweite Rechte hat, ist sein Blast Radius groß. OLM verwaltet zwar Installation, Upgrade und RBAC von Operators, aber die Plattform muss entscheiden, welche Rechte akzeptabel sind.

Deshalb prüfe ich vor Operator-Freigabe: Welche RBAC-Rechte braucht er? Ist er namespaced oder clusterweit? Welche Secrets kann er lesen? Welche CRDs installiert er? Welche Images nutzt er? Wer aktualisiert ihn?

Senior-Antwort:

„Ein Operator ist ein aktiver Cluster-Akteur. Seine Rechte sind Plattformrisiko, nicht nur App-Konfiguration.“

Häufige Fehler:
  • Clusterwide Installation ohne Bedarf.
  • Operators aus Community-Katalog ohne Review.
  • Secrets-Lesezugriff unterschätzen.
  • Operator-ServiceAccount mit zu breiten Rechten.
  • Kein Image-Scanning für Operator-Images.
  • Automatische Updates ohne Security Review.
  • CRDs ohne API-Design-Review.
Best Practices:
  • Operator-RBAC reviewen.
  • Scope minimieren.
  • Clusterweite Operators streng prüfen.
  • Secrets-Zugriff begrenzen.
  • Kataloge kontrollieren.
  • Operator-Images scannen.
  • Update-Strategie definieren.
  • Ownership dokumentieren.
Stolperfalle:

Ein Operator, der Secrets in vielen Namespaces lesen darf, ist ein sehr attraktives Angriffsziel.

Merksatz:

Operators haben Macht; Macht braucht Scope.

B6-K09-Q009 – Operator-Upgrades, CRD-Versionierung und KompatibilitätOperator-Upgrade ist API-Upgrade plus Betriebsupgrade.
Frage:

„Was prüfen Sie vor einem Operator-Upgrade?“

Kurz erklärt:

Operator-Upgrades können CRDs, Controller-Logik, Statusverhalten, RBAC und erzeugte Workloads verändern. Sie müssen getestet und kompatibel geplant werden.

Musterantwort:

Ich prüfe Release Notes, Channel, InstallPlan, CRD-Versionen, vorhandene Custom Resources, RBAC-Änderungen, erzeugte Workloads, Backup/Restore, mögliche Breaking Changes und Testcluster-Ergebnisse. Red Hat dokumentiert, dass Clusteradministratoren mit OLM bereits installierte Operators aktualisieren können. Genau deshalb behandle ich Operator-Upgrades als Plattformänderungen.

Besonders wichtig ist CRD-Kompatibilität. Wenn CRD-Schema oder Controller-Semantik sich ändern, können bestehende Custom Resources plötzlich anders reconciled werden.

Senior-Antwort:

„Ein Operator-Upgrade kann den Betreiber und das betriebene System ändern. Deshalb teste ich beides.“

Häufige Fehler:
  • Operator-Upgrade wie App-Rollout behandeln.
  • CRD-Änderungen ignorieren.
  • Keine Tests mit echten CRs.
  • Automatic Approval in Prod.
  • Kein Backup vor Stateful-Upgrade.
  • Release Notes nicht lesen.
  • Anwendungskompatibilität ignorieren.
Best Practices:
  • Manual Approval für kritische Operators.
  • Testcluster zuerst.
  • Bestehende CRs prüfen.
  • InstallPlan analysieren.
  • Backup/Restore sichern.
  • Release Notes lesen.
  • Wartungsfenster planen.
  • Status überwachen.
Stolperfalle:

Ein erfolgreich aktualisierter Operator bedeutet nicht, dass alle bestehenden Custom Resources kompatibel und gesund reconciled wurden.

Merksatz:

Operator-Upgrade ist API-Upgrade plus Betriebsupgrade.

B6-K09-Q010 – Operator-Governance, Plattformstrategie und Anti-PatternsOperatoren automatisieren Betrieb — aber Governance betreibt die Operatoren. 24h
Frage:

„Welche Anti-Patterns sehen Sie beim Einsatz von Operators auf OpenShift?“

Kurz erklärt:

Operator-Governance stellt sicher, dass Operators kontrolliert, sicher, aktualisierbar und mit klarer Ownership betrieben werden.

Musterantwort:

Typische Anti-Patterns sind: Operatoren ohne Owner, Community-Operatoren ohne Review, clusterweite Installation ohne Bedarf, Automatic Updates in Prod, keine Backup-/Restore-Tests, CRDs ohne API-Verständnis, Operatoren als Blackbox, fehlende Runbooks, zu viele unterschiedliche Operatoren für ähnliche Zwecke, keine Katalogkontrolle und keine Exit-Strategie.

Red Hat beschreibt OperatorHub als Discovery-Schicht und OLM als Installations- und Managementmechanismus. Diese Mechanismen sind hilfreich, ersetzen aber keine Plattform-Governance.

Senior-Antwort:

„Operators sind Plattformprodukte. Wer sie installiert, übernimmt Lifecycle, Security und Support.“

Häufige Fehler:
  • Operator ohne Owner.
  • Installieren per Klick ohne GitOps.
  • Keine RBAC-Prüfung.
  • Keine Upgrade-Tests.
  • Keine Backup-Tests.
  • Automatische Prod-Updates.
  • Mehrere konkurrierende Operatoren für denselben Zweck.
  • CRDs als unkontrollierte Plattform-APIs.
Best Practices:
  • Operator-Onboarding-Prozess.
  • Freigegebene CatalogSources.
  • GitOps-managed Subscriptions.
  • Manual Approval für kritische Prod-Operators.
  • RBAC- und Scope-Review.
  • CRD-API-Review.
  • Monitoring und Runbooks.
  • Backup/Restore-Pflicht für Stateful-Dienste.
Stolperfalle:

Ein Operator kann die Installation vereinfachen und den Betrieb gleichzeitig komplexer machen, wenn Ownership und Upgrade-Strategie fehlen.

Merksatz:

Operatoren automatisieren Betrieb — aber Governance betreibt die Operatoren.

B6-K10 – Kapitel 10: Networking, SDN/OVN-Kubernetes und NetworkPolicies 10 Karten

B6-K10-Q001 – OpenShift Networking Grundmodell: Pod Network, Service Network und Cluster NetworkNetzwerkdiagnose beginnt mit dem Pfad, nicht mit der Vermutung. 24h
Frage:

„Welche Netzwerkebenen unterscheiden Sie in OpenShift?“

Kurz erklärt:

OpenShift Networking verbindet Pods, Services, Nodes und externe Netze. Pods kommunizieren über das Pod-Netzwerk, Services abstrahieren stabile interne Endpunkte, Ingress/Egress kontrollieren Traffic über Clustergrenzen.

Musterantwort:

Ich unterscheide mindestens Pod Network, Service Network, Node Network, Ingress, Egress und DNS. Das Pod Network ermöglicht Pod-zu-Pod-Kommunikation. Das Service Network stellt stabile virtuelle Adressen für Services bereit. Das Node Network verbindet die Nodes. Ingress bringt externen Traffic über Routes oder Ingress in den Cluster. Egress beschreibt Traffic von Pods zu externen Systemen. DNS verbindet Namen mit Services und externen Zielen.

OpenShift stellt Werkzeuge bereit, um Traffic in den Cluster und aus dem Cluster heraus zu verwalten; Red Hat beschreibt in der Networking-Übersicht, dass man Mechanismen für externen Zugriff auf Anwendungen und sicheren Zugriff von Anwendungen auf externe Services konfigurieren muss.

Senior-Antwort:

„OpenShift Networking ist ein Pfadmodell. Ich frage immer: Von welchem Pod zu welchem Ziel, über welchen Namen, welchen Service, welche Policy und welchen Egress?“

Häufige Fehler:
  • Service-Probleme mit Pod-Netzwerk verwechseln.
  • DNS-Fehler als App-Bug interpretieren.
  • Egress-Firewall ignorieren.
  • NetworkPolicy nur für Ingress betrachten.
  • Externe Provider ohne Timeout- und Retry-Strategie.
  • Pod-IP als stabile Adresse verwenden.
  • Ingress und Egress nicht getrennt analysieren.
Best Practices:
  • Netzwerkpfad schichtweise denken.
  • Services statt Pod-IPs verwenden.
  • DNS und Endpoints prüfen.
  • Ingress und Egress getrennt modellieren.
  • NetworkPolicies früh definieren.
  • Timeouts und Retries bewusst konfigurieren.
  • Netzwerkdiagnose in Runbooks aufnehmen.
  • App-Metriken und Netzwerkmetriken korrelieren.
Stolperfalle:

Ein Timeout ist kein Beweis für einen langsamen Service. Er kann DNS, NetworkPolicy, Egress, TLS, Router oder Downstream bedeuten.

Merksatz:

Netzwerkdiagnose beginnt mit dem Pfad, nicht mit der Vermutung.

B6-K10-Q002 – OVN-Kubernetes: Architektur, Overlay und Open vSwitchOVN-Kubernetes setzt deklarierte Netzwerkregeln im Cluster-Datenpfad um.
Frage:

„Was ist OVN-Kubernetes in OpenShift?“

Kurz erklärt:

OVN-Kubernetes ist der default CNI Network Provider in OpenShift und nutzt OVN sowie Open vSwitch zur Umsetzung des Cluster-Netzwerks.

Musterantwort:

OVN-Kubernetes ist der Netzwerkprovider, der Pod-Netzwerk, NetworkPolicies und weitere Netzwerkfunktionen in OpenShift implementiert. In OpenShift 4.21 ist OVN-Kubernetes der default network provider. Red Hat beschreibt, dass OVN-Kubernetes auf Open Virtual Network basiert, overlay-basiertes Networking bereitstellt und Open vSwitch auf jedem Node verwendet. OVN konfiguriert OVS auf jedem Node, um die gewünschte Netzwerkkonfiguration umzusetzen.

Aus Anwendungssicht sieht man meistens nur Services, DNS, Routes und Policies. Aus Plattformperspektive ist OVN-Kubernetes die Ebene, die diese deklarativen Regeln im Datenpfad realisiert.

Senior-Antwort:

„OVN-Kubernetes ist die Implementierungsebene. Ich debugge erst den deklarativen Pfad und gehe dann tiefer in OVN, wenn das Muster cluster- oder nodeweit ist.“

Häufige Fehler:
  • OVN als Anwendungsthema überinterpretieren.
  • Network-Operator-Status ignorieren.
  • Node-spezifische Netzwerkprobleme nur in App suchen.
  • NetworkPolicy-Fehler mit OVN-Bug verwechseln.
  • Overlay-Overhead ohne Messung beschuldigen.
  • OVN-Komponenten ohne Plattformrechte verändern.
  • Keine ClusterOperator-Diagnose.
Best Practices:
  • Network Operator überwachen.
  • OVN-Komponenten bei clusterweiten Problemen prüfen.
  • App- und Plattformdiagnose trennen.
  • NetworkPolicy zuerst logisch prüfen.
  • Node-bezogene Muster erkennen.
  • Runbooks für OVN-Diagnose definieren.
  • Änderungen an Netzwerkprovider nur über Plattformprozesse.
  • Metrics und Logs korrelieren.
Stolperfalle:

Nicht jedes Netzwerkproblem ist ein OVN-Problem. Sehr oft ist es Service-Selector, DNS, Policy oder App-Timeout.

Merksatz:

OVN-Kubernetes setzt deklarierte Netzwerkregeln im Cluster-Datenpfad um.

B6-K10-Q003 – Kubernetes NetworkPolicy: Ingress, Egress und Default DenyDefault-Deny ist sicher — aber nur mit vollständigem Kommunikationswissen.
Frage:

„Wie funktionieren NetworkPolicies in OpenShift?“

Kurz erklärt:

NetworkPolicies definieren erlaubten Pod-Traffic. Default-Deny-Policies blockieren standardmäßig Traffic, bis explizite Allow-Regeln existieren.

Musterantwort:

NetworkPolicies sind Kubernetes-Ressourcen, die Pod-Kommunikation einschränken. Sie selektieren Ziel-Pods über podSelector und definieren erlaubte Ingress- oder Egress-Verbindungen. Ohne passende Policy ist Traffic je nach Namespace-Zustand oft offen; mit Default-Deny wird Traffic blockiert, bis explizite Regeln erlauben.

OpenShift stellt Werkzeuge zur Sicherung von Netzwerkverkehr bereit, insbesondere NetworkPolicies und Administrative NetworkPolicies. Red Hat beschreibt, dass OpenShift Regeln erstellen kann, die kontrollieren, welche Komponenten miteinander kommunizieren dürfen.

Senior-Antwort:

„NetworkPolicies sind Kommunikationsverträge. Ich schreibe sie aus Architekturwissen, nicht durch Trial-and-Error in Produktion.“

Häufige Fehler:
  • Default-Deny ohne DNS-Ausnahmen.
  • Egress vergessen.
  • Falsche Namespace-Labels.
  • Zu breite podSelector: {} in Allow-Policy.
  • Ports vergessen.
  • NetworkPolicy als RBAC-Ersatz verstehen.
  • Policies ohne Tests ausrollen.
Best Practices:
  • Kommunikationsmatrix erstellen.
  • Default-Deny schrittweise einführen.
  • Namespace- und Pod-Labels standardisieren.
  • Ingress und Egress getrennt modellieren.
  • DNS und Plattformdienste berücksichtigen.
  • Policies testen.
  • Regeln so klein wie möglich.
  • NetworkPolicy in Git versionieren.
Stolperfalle:

Default-Deny für Egress blockiert auch DNS und externe Abhängigkeiten, wenn sie nicht explizit erlaubt sind.

Merksatz:

Default-Deny ist sicher — aber nur mit vollständigem Kommunikationswissen.

B6-K10-Q004 – AdminNetworkPolicy und BaselineAdminNetworkPolicyLokale Policies regeln Anwendungen; AdminPolicies regeln die Plattform.
Frage:

„Was ist der Unterschied zwischen NetworkPolicy und AdminNetworkPolicy?“

Kurz erklärt:

AdminNetworkPolicy und BaselineAdminNetworkPolicy sind clusterweite Netzwerkpolicy-APIs für Cluster- und Netzwerkadministratoren.

Musterantwort:

NetworkPolicy ist namespace-scoped und wird typischerweise von Anwendungsteams oder Plattform-GitOps pro Project verwaltet. AdminNetworkPolicy ist cluster-scoped und richtet sich an Cluster- und Netzwerkadministratoren. Red Hat beschreibt, dass AdminNetworkPolicy und BaselineAdminNetworkPolicy clusterweite Netzwerkpolicy-APIs sind; AdminNetworkPolicy kann clusterweite Regeln definieren, während NetworkPolicy weiterhin namespace-scoped ist.

Der wichtige Architekturpunkt ist Governance: Anwendungsteams definieren Service-Kommunikation; Plattformteams definieren globale Sicherheitsgrenzen, die nicht von einzelnen Namespaces beliebig umgangen werden sollen.

Senior-Antwort:

„NetworkPolicy ist Team-Kommunikationsvertrag; AdminNetworkPolicy ist Plattform-Leitplanke.“

Häufige Fehler:
  • Nur namespace NetworkPolicy prüfen.
  • Clusterweite Policies nicht dokumentieren.
  • AdminNetworkPolicy ohne Staging testen.
  • Zu breite Deny-Regeln.
  • Anwendungsteams nicht informieren.
  • Keine Ausnahmestrategie.
  • Policy-Prioritäten nicht verstehen.
Best Practices:
  • Clusterweite Policies zentral verwalten.
  • GitOps und Reviewpflicht.
  • Staging vor Prod.
  • Ausnahmen dokumentieren.
  • Anwendungsteams informieren.
  • Diagnose-Runbooks aktualisieren.
  • Globale Policies klein und klar halten.
  • Netzwerk-Governance mit Security abstimmen.
Stolperfalle:

Wenn eine Verbindung trotz lokaler Allow-Policy scheitert, kann eine clusterweite AdminNetworkPolicy die Ursache sein.

Merksatz:

Lokale Policies regeln Anwendungen; AdminPolicies regeln die Plattform.

B6-K10-Q005 – Egress: Egress Firewall, Egress IP und externe AbhängigkeitenEgress ist ausgehende Firewall-Architektur für Pods. 24h
Frage:

„Wie kontrollieren Sie ausgehenden Traffic von Pods zu externen Systemen?“

Kurz erklärt:

Egress-Kontrollen steuern, wohin Pods ausgehend kommunizieren dürfen und über welche Quell-IP externer Traffic den Cluster verlässt.

Musterantwort:

Ich kombiniere mehrere Ebenen: NetworkPolicy für Pod-Egress innerhalb und teilweise außerhalb des Clusters, EgressFirewall für projektbezogene Allow/Deny-Regeln zu externen Zielen und EgressIP, wenn externe Systeme eine stabile Quell-IP für Allowlisting brauchen. Red Hat beschreibt, dass OVN-Kubernetes Egress-Firewall-Regeln in der definierten Reihenfolge von oben nach unten auswertet; die erste passende Regel gilt. Red Hat beschreibt außerdem, dass man mit OVN-Kubernetes Egress IPs einem Namespace oder bestimmten Pods in einem Namespace zuweisen kann.

Für Java-Apps ist wichtig: Externe REST-Clients brauchen Timeouts, Circuit Breaker und klare Fehlermeldungen, aber die Plattform muss Egress-Pfade und Firewall-Regeln liefern.

Senior-Antwort:

„Egress ist ein Sicherheitsvertrag: wohin, über welche Quell-IP, mit welchem Protokoll und welcher Fehlerstrategie.“

Häufige Fehler:
  • Alle Egress-Ziele erlauben.
  • DNS-basierte Regeln ohne DNS-Verhalten zu verstehen.
  • Regelreihenfolge ignorieren.
  • EgressIP nicht hochverfügbar planen.
  • Retries ohne Backoff.
  • Provider-Allowlisting nicht dokumentieren.
  • Netzwerkfehler in Java als App-Bug behandeln.
Best Practices:
  • Egress-Ziele explizit dokumentieren.
  • Default-Deny für kritische Namespaces prüfen.
  • EgressFirewall-Regeln geordnet und minimal.
  • EgressIP für Allowlisting.
  • DNS, TLS und Proxy-Konfiguration testen.
  • Java-Timeouts und Retries begrenzen.
  • Externe Abhängigkeiten observierbar machen.
  • Egress-Änderungen über Change-Prozess.
Stolperfalle:

Eine Allow-Regel unterhalb einer breiten Deny-Regel wirkt nicht, wenn die erste passende Regel bereits Deny ist.

Merksatz:

Egress ist ausgehende Firewall-Architektur für Pods.

B6-K10-Q006 – DNS, Service Discovery und NamensauflösungService Discovery beginnt bei DNS im Pod.
Frage:

„Wie funktioniert Service Discovery in OpenShift?“

Kurz erklärt:

DNS ermöglicht Service Discovery im Cluster und Namensauflösung externer Ziele. Viele Connectivity-Probleme beginnen mit DNS.

Musterantwort:

Service Discovery läuft primär über Kubernetes DNS. Ein Service bekommt einen DNS-Namen im Cluster, zum Beispiel payment-service.payment-prod.svc.cluster.local . Pods können Services im gleichen Namespace oft kurz per Service-Name ansprechen, namespaceübergreifend sollte man den qualifizierten Namen verwenden.

Für externe Ziele hängt die Auflösung von Cluster-DNS, Forwarding, Proxy- und Netzwerkregeln ab. Wenn eine Java-App UnknownHostException wirft, prüfe ich zuerst DNS im Pod, dann NetworkPolicy/Egress und externe DNS-Erreichbarkeit.

Senior-Antwort:

„Ich teste Namensauflösung dort, wo die App läuft: im Pod, im Namespace und mit denselben Egress-Regeln.“

Häufige Fehler:
  • Service-Namen ohne Namespace in anderen Projects verwenden.
  • DNS-Fehler als Downstream-Ausfall behandeln.
  • Egress-DNS blockieren.
  • JVM-DNS-Caching ignorieren.
  • Hardcodierte IP-Adressen.
  • ExternalName als Sicherheitsgrenze missverstehen.
  • Debugging nur aus lokalem Rechner statt aus Pod.
Best Practices:
  • Qualifizierte Service-Namen für Cross-Namespace.
  • DNS aus Pod testen.
  • Externe Namen und Egress gemeinsam prüfen.
  • JVM-DNS-Cache-Verhalten kennen.
  • Keine Pod-IPs hardcoden.
  • Service Discovery über Services.
  • Timeouts bei DNS/Connect unterscheiden.
  • Debug-Tools in Runbooks bereitstellen.
Stolperfalle:

Ein Name, der vom Laptop auflöst, muss im Pod noch lange nicht auflösen.

Merksatz:

Service Discovery beginnt bei DNS im Pod.

B6-K10-Q007 – Multiple Networks, Multus und User-Defined NetworksMehr Netzwerke bedeuten mehr Möglichkeiten — und mehr Verantwortung.
Frage:

„Wann braucht man mehrere Netzwerke für Pods auf OpenShift?“

Kurz erklärt:

Multiple Networks ermöglichen Pods zusätzliche Netzwerkinterfaces, zum Beispiel für Spezialnetze, Provider-Integration, NFV oder getrennte Datenpfade.

Musterantwort:

Mehrere Netzwerke sind sinnvoll, wenn ein Pod zusätzlich zum normalen Cluster-Netzwerk an ein separates Netz angeschlossen werden muss: Telco/NFV, Spezialhardware, Partnernetze, Legacy-Netze, Storage-Netze oder isolierte Datenpfade. OpenShift unterstützt dafür zusätzliche Netzwerkmechanismen wie NetworkAttachmentDefinitions. In OpenShift 4.21 dokumentiert Red Hat außerdem User Defined Networks; ohne Kubernetes NetworkPolicy können Pods innerhalb eines UDN miteinander kommunizieren, sind aber standardmäßig von Pods in anderen UDNs isoliert.

Für normale Java-Microservices brauche ich Multiple Networks selten. Meist reichen Services, Routes, NetworkPolicies und Egress-Kontrollen.

Senior-Antwort:

„Multiple Networks sind Spezialwerkzeug. Für normale Microservices will ich zuerst Services, Egress und Policies sauber nutzen.“

Häufige Fehler:
  • Multiple Networks als Standard nutzen.
  • App bindet auf falsches Interface.
  • Routing nicht verstehen.
  • NetworkPolicy nur für Primary Network betrachten.
  • IPAM-Konflikte.
  • Security nicht mitdenken.
  • Plattformteam nicht einbinden.
Best Practices:
  • Multiple Networks nur bei klarem Bedarf.
  • Plattformfreigabe und Dokumentation.
  • IPAM sauber planen.
  • App-Bind-Adressen testen.
  • Routing und DNS validieren.
  • Security und Monitoring erweitern.
  • Runbooks für zweite Interfaces.
  • Normale Apps im Default Network halten.
Stolperfalle:

Ein zweites Interface löst nicht automatisch Routing, Security und Observability — es verdoppelt oft die Diagnosekomplexität.

Merksatz:

Mehr Netzwerke bedeuten mehr Möglichkeiten — und mehr Verantwortung.

B6-K10-Q008 – NetworkPolicy-Design: Labels, Kommunikationsmatrix und SkalierungErst Matrix, dann Policy.
Frage:

„Wie entwerfen Sie NetworkPolicies für viele Microservices?“

Kurz erklärt:

Gutes NetworkPolicy-Design beginnt mit einer Kommunikationsmatrix und einem stabilen Labelmodell.

Musterantwort:

Ich beginne nicht mit YAML, sondern mit einer Kommunikationsmatrix: Source, Target, Port, Protokoll, Zweck und Owner. Daraus entstehen NetworkPolicies. Danach brauche ich ein stabiles Labelmodell für Namespaces und Pods. Ohne gute Labels werden Policies fragil.

Red Hat weist bei OVN-Kubernetes-NetworkPolicy-Optimierungen darauf hin, dass man NetworkPolicy-Design berücksichtigen sollte, um Flow Count zu reduzieren und externen IP-Traffic bei Bedarf zu erlauben. Das zeigt: Policy-Design ist auch Skalierungs- und Betriebsfrage.

Senior-Antwort:

„NetworkPolicy ist Architektur in YAML. Ohne Kommunikationsmatrix ist es Raten.“

Häufige Fehler:
  • Policies ohne Abhängigkeitsanalyse.
  • Instabile Labels.
  • Teams definieren Labels unterschiedlich.
  • Observability-Traffic vergessen.
  • DNS vergessen.
  • Externe OAuth-/OIDC-Endpunkte vergessen.
  • Zu granulare Policies ohne Tooling.
Best Practices:
  • Kommunikationsmatrix pflegen.
  • Labels zentral standardisieren.
  • Policy-Templates verwenden.
  • Default-Deny stufenweise.
  • Denied Traffic beobachten.
  • Plattformdienste berücksichtigen.
  • Review bei neuen Abhängigkeiten.
  • Policies als Code.
Stolperfalle:

Observability und DNS werden beim Default-Deny fast immer vergessen, wenn Teams nur fachliche Calls betrachten.

Merksatz:

Erst Matrix, dann Policy.

B6-K10-Q009 – Netzwerkdiagnose: Debug Pods, tcpdump, Flow Tracking und EventsTeste Netzwerk dort, wo der Traffic startet.
Frage:

„Wie debuggen Sie ein Netzwerkproblem zwischen zwei OpenShift-Services?“

Kurz erklärt:

Netzwerkdiagnose kombiniert App-Pod, Debug-Pod, DNS, Services, Endpoints, NetworkPolicies, Events, OVN-Komponenten und externe Tests.

Musterantwort:

Ich beginne mit dem konkreten Pfad: Source-Pod, Zielname, Zielport, Namespace, Service, Endpoints und Policies. Dann teste ich aus einem Debug-Pod im gleichen Namespace. Ich prüfe DNS, TCP-Verbindung, TLS, HTTP-Status und NetworkPolicies. Wenn das Problem mehrere Apps oder Nodes betrifft, prüfe ich Network ClusterOperator und OVN-Komponenten.

Red Hat dokumentiert in OpenShift 4.21 ein eigenes Kapitel zum Tracking von Network Flows im OVN-Kubernetes Network Plugin, was zeigt, dass tiefe Netzwerkdiagnose über einfache Pod-Logs hinausgehen kann.

Senior-Antwort:

„Ich debugge Netzwerk aus der Perspektive des betroffenen Pods. Alles andere ist nur ein indirekter Test.“

Häufige Fehler:
  • Vom Laptop statt aus Pod testen.
  • Nur HTTP testen, DNS ignorieren.
  • Service-Endpunkte nicht prüfen.
  • NetworkPolicy vergessen.
  • TLS-Fehler als Netzwerkfehler behandeln.
  • OVN debuggen, obwohl Label falsch ist.
  • Keine Client-Metriken.
Best Practices:
  • Immer aus Source-Namespace testen.
  • DNS, TCP, TLS und HTTP trennen.
  • Service/Endpoints prüfen.
  • Policies und AdminPolicies prüfen.
  • Egress-Kontrollen prüfen.
  • Node/OVN erst bei breitem Muster.
  • Debug-Images standardisieren.
  • Runbooks pflegen.
Stolperfalle:

Ein erfolgreicher Test vom Bastion Host sagt nichts darüber aus, ob der Pod mit seinen Policies und Egress-Regeln verbinden darf.

Merksatz:

Teste Netzwerk dort, wo der Traffic startet.

B6-K10-Q010 – Netzwerk-Governance, Zero Trust und Anti-PatternsSichere Netzwerke sind explizit, versioniert und beobachtbar. 24h
Frage:

„Welche Netzwerk-Anti-Patterns sehen Sie auf OpenShift?“

Kurz erklärt:

Netzwerk-Governance verbindet NetworkPolicies, AdminNetworkPolicies, Egress-Regeln, Labelstandards, GitOps und Diagnose zu einem kontrollierbaren Sicherheitsmodell.

Musterantwort:

Typische Anti-Patterns sind: alle Pods dürfen mit allem sprechen, keine Egress-Kontrolle, Default-Deny ohne Kommunikationsmatrix, Policies ohne Labelstandard, externe Provider direkt aus jedem Namespace erreichbar, EgressIP ohne Ownership, manuelle Policy-Änderungen, keine DNS-Ausnahmen, kein Observability-Traffic, AdminNetworkPolicies ohne Dokumentation und Netzwerkdiagnose nur durch Plattformadmins.

OpenShift stellt NetworkPolicies und Administrative NetworkPolicies bereit, um zu kontrollieren, welche Komponenten kommunizieren dürfen. Cluster- und Netzwerkadministratoren können AdminNetworkPolicy nutzen, um Netzwerkregeln auf Cluster-Ebene zu definieren. Eine reife Plattform nutzt beides: Team-nahe Policies und globale Leitplanken.

Senior-Antwort:

„Netzwerk-Governance muss Kommunikation minimieren, aber Fehler erklärbar halten.“

Häufige Fehler:
  • Big-Bang-Default-Deny.
  • Kein Labelstandard.
  • Keine Kommunikationsmatrix.
  • Externe Abhängigkeiten unbekannt.
  • Policies nur manuell.
  • Kein GitOps.
  • Blockierter Traffic nicht sichtbar.
  • Plattformdienste vergessen.
Best Practices:
  • Kommunikationsmatrix verpflichtend.
  • NetworkPolicies als Code.
  • Labelstandard.
  • Default-Deny stufenweise.
  • Egress-Ziele freigeben.
  • AdminNetworkPolicy für globale Leitplanken.
  • Observability und DNS standardisiert erlauben.
  • Policy-Tests automatisieren.
Stolperfalle:

Default-Deny ohne Diagnose und Kommunikationsmatrix erzeugt Incidents, nicht Sicherheit.

Merksatz:

Sichere Netzwerke sind explizit, versioniert und beobachtbar.

B6-K11 – Kapitel 11: OpenShift Pipelines, GitOps und CI/CD 10 Karten

B6-K11-Q001 – CI/CD-Grundmodell: Build, Test, Scan, Promote, DeployCI baut Vertrauen; GitOps liefert Zustand. 24h
Frage:

„Wie sieht eine produktionsreife CI/CD-Strategie für Java auf OpenShift aus?“

Kurz erklärt:

CI/CD verbindet Codeänderung, Build, Qualitätssicherung, Security, Artefakt-Promotion und Deployment. Moderne OpenShift-Plattformen trennen Build-Pipeline und Deployment-GitOps bewusst.

Musterantwort:

Eine produktionsreife Strategie baut ein Image genau einmal, testet und scannt es, erzeugt SBOM und Signatur, promoted denselben Digest durch Umgebungen und deployed über GitOps. CI erzeugt und validiert Artefakte. CD beschreibt den gewünschten Clusterzustand deklarativ in Git. OpenShift Pipelines eignet sich für Kubernetes-native Build- und Test-Automatisierung; OpenShift GitOps eignet sich für deklarative kontinuierliche Auslieferung über Argo CD. Red Hat beschreibt OpenShift Pipelines als Kubernetes-native CI/CD-Lösung auf Basis von Tekton-Bausteinen. OpenShift GitOps verwendet Argo CD als deklarativen GitOps-Engine.

Der zentrale Punkt ist: Produktion erhält nicht „irgendwie neu gebaut“, sondern ein geprüftes, identifizierbares Artefakt.

Senior-Antwort:

„CI produziert ein vertrauenswürdiges Artefakt. GitOps macht daraus kontrollierten Clusterzustand.“

Häufige Fehler:
  • Pro Umgebung neu bauen.
  • latest in Produktion.
  • Deployment direkt aus CI ohne GitOps-Spur.
  • Tests nach Deployment statt vor Promotion.
  • Security Scans nur manuell.
  • Kein Rollback über Git.
  • Persönliche Tokens in Pipelines.
  • Secrets in Pipeline-Logs.
Best Practices:
  • Einmal bauen, Digest promoten.
  • CI und GitOps-CD trennen.
  • Tests und Scans automatisieren.
  • SBOM und Signatur erzeugen.
  • GitOps als Source of Truth.
  • Rollback über Git und Digest.
  • Pipeline-ServiceAccounts minimal berechtigen.
  • Secrets sicher mounten.
Stolperfalle:

Eine Pipeline, die direkt nach main in Produktion deployt, ist schnell — aber ohne Promotion-Grenze, Audit und Rollback oft unreif.

Merksatz:

CI baut Vertrauen; GitOps liefert Zustand.

B6-K11-Q002 – OpenShift Pipelines und Tekton-GrundmodellTekton macht CI/CD zu deklarativen Kubernetes-Ressourcen.
Frage:

„Was sind die wichtigsten Tekton-Bausteine in OpenShift Pipelines?“

Kurz erklärt:

OpenShift Pipelines basiert auf Tekton. Tekton modelliert CI/CD über Kubernetes-CRDs wie Tasks, Pipelines, PipelineRuns und Workspaces.

Musterantwort:

Die wichtigsten Bausteine sind Task, Pipeline, TaskRun, PipelineRun, Workspace, Params und Results. Ein Task beschreibt wiederverwendbare Schritte. Eine Pipeline verknüpft Tasks zu einem Flow. Ein PipelineRun ist eine konkrete Ausführung einer Pipeline. Workspaces stellen geteilten Arbeitsbereich bereit, zum Beispiel Source Code. Params machen Tasks konfigurierbar. Results geben Werte zwischen Tasks weiter.

Red Hat beschreibt OpenShift Pipelines als cloud-native CI/CD-Lösung auf Kubernetes-Ressourcenbasis, die Tekton-Bausteine verwendet. Der Vorteil ist, dass CI/CD selbst deklarativ und Kubernetes-nativ wird.

Senior-Antwort:

„Tekton ist CI/CD als Kubernetes-API. Deshalb gelten dieselben Plattformregeln wie für normale Workloads.“

Häufige Fehler:
  • Tasks zu groß und nicht wiederverwendbar.
  • Keine Ressourcenlimits für TaskRuns.
  • Pipeline-ServiceAccount zu mächtig.
  • Secrets als Params ausgeben.
  • Maven Cache fehlt.
  • JDK-Version nicht festgelegt.
  • CI-Logs enthalten Credentials.
Best Practices:
  • Tasks klein und wiederverwendbar.
  • PipelineRuns versioniert erzeugen.
  • ServiceAccounts minimal berechtigen.
  • Workspaces bewusst nutzen.
  • Params für nicht-sensitive Werte.
  • Secrets über Workspaces oder Secret Mounts.
  • Ressourcenlimits setzen.
  • Maven/Gradle Cache einplanen.
Stolperfalle:

Ein PipelineRun ist nicht außerhalb der Plattform. Er ist ein Pod mit Rechten, Netzwerk und Secrets — also ein Sicherheitsobjekt.

Merksatz:

Tekton macht CI/CD zu deklarativen Kubernetes-Ressourcen.

B6-K11-Q003 – PipelineRun, ServiceAccounts, Workspaces und SecretsPipeline-Identitäten sind Produktionsidentitäten in Warteschleife.
Frage:

„Wie sichern Sie Tekton PipelineRuns auf OpenShift ab?“

Kurz erklärt:

PipelineRuns sind konkrete Pipeline-Ausführungen. Sie laufen mit einem ServiceAccount, nutzen Workspaces und benötigen oft Secrets für Git, Registry oder Artifact Repositories.

Musterantwort:

Ich sichere PipelineRuns wie Workloads ab: dedizierter ServiceAccount, minimale RBAC-Rechte, getrennte Build- und Deploy-Rechte, Secrets nur als Mounts, keine Secrets in Params oder Logs, Ressourcenlimits, NetworkPolicies und klare Namespace-Grenzen. PipelineRuns laufen als Pods und können Registry-, Git- oder Clusterzugriff haben. Deshalb sind sie sicherheitskritisch.

Besonders wichtig ist die Trennung: Ein Build-ServiceAccount darf Images bauen und pushen, aber nicht automatisch Prod-Deployments ändern. Deployment erfolgt über GitOps oder einen separaten, kontrollierten Promotion-Prozess.

Senior-Antwort:

„Ein PipelineRun ist ein privilegierter Automatisierungs-Pod. Ich gebe ihm exakt die Rechte, die seine Stage braucht — nicht mehr.“

Häufige Fehler:
  • Pipeline-ServiceAccount mit cluster-admin .
  • Build- und Prod-Deploy-Rechte vermischen.
  • Secrets als Params.
  • Secrets in Logs.
  • Workspaces nie bereinigen.
  • Kein Ressourcenlimit.
  • PipelineRuns in App-Namespaces ohne Governance.
Best Practices:
  • Dedizierte Pipeline-ServiceAccounts.
  • Least Privilege.
  • Build und Deploy trennen.
  • Secrets als Mounts.
  • Secrets maskieren und nicht loggen.
  • Workspaces bewusst wählen.
  • Ressourcenlimits.
  • Pipeline-Namespace-Grenzen.
Stolperfalle:

Secrets in Tekton Results oder Params können in Statusobjekten sichtbar werden und damit leichter geleakt werden.

Merksatz:

Pipeline-Identitäten sind Produktionsidentitäten in Warteschleife.

B6-K11-Q004 – Java Build Pipeline: Maven, Tests, Image Build und ScanPipeline-Stages trennen Verantwortung.
Frage:

„Wie bauen Sie eine Tekton-Pipeline für einen Java-Service?“

Kurz erklärt:

Eine Java-Build-Pipeline sollte Tests, Packaging, Image Build und Security Scan klar trennen und reproduzierbare Artefakte erzeugen.

Musterantwort:

Ich schneide die Pipeline in klare Stages: Source Checkout, Unit Tests, Packaging, Image Build, Image Scan, SBOM, Signierung und optional GitOps-Update. Für Java achte ich auf JDK-Version, Maven/Gradle Cache, interne Repository Credentials, Testcontainers-Konzept und reproduzierbare Builds. Das Image wird einmal gebaut und über Digest weiterverwendet.

OpenShift Pipelines liefert dafür Kubernetes-native Pipeline-Ressourcen. Die Pipeline ist selbst deklarativ und kann als Code versioniert werden.

Senior-Antwort:

„Eine gute Java-Pipeline ist kein langer Shell-Script-Container, sondern eine Kette klarer, prüfbarer Stages.“

Häufige Fehler:
  • Tests überspringen.
  • Build und Scan nicht trennen.
  • Maven Credentials im Image.
  • Keine reproduzierbaren Builds.
  • Snapshots in Releases.
  • Zu großer Pipeline-ServiceAccount.
  • Kein Digest-Output.
  • Security Scan ohne Gate.
Best Practices:
  • Klare Stages.
  • JDK-Version pinnen.
  • Dependency Cache.
  • Unit und Integration Tests trennen.
  • Image einmal bauen.
  • Scan als Gate.
  • SBOM erzeugen.
  • Signatur erzeugen.
Stolperfalle:

Wenn Build-Tasks alle Secrets und Rechte erhalten, wird die Pipeline einfacher — und gefährlicher.

Merksatz:

Pipeline-Stages trennen Verantwortung.

B6-K11-Q005 – OpenShift GitOps und Argo CD GrundmodellGit ist der Wunsch; Argo CD ist der Wächter. 24h
Frage:

„Was ist GitOps, und warum ist es für OpenShift sinnvoll?“

Kurz erklärt:

OpenShift GitOps nutzt Argo CD, um den gewünschten Zustand aus Git mit dem Clusterzustand zu vergleichen und zu synchronisieren.

Musterantwort:

GitOps bedeutet: Der gewünschte Zustand von Anwendungen und Plattformkonfiguration liegt deklarativ in Git. Ein Controller, in OpenShift GitOps typischerweise Argo CD, vergleicht Git-Zustand und Clusterzustand und synchronisiert Abweichungen. Red Hat beschreibt OpenShift GitOps als Operator, der Argo CD als deklarative GitOps-Engine nutzt und GitOps-Workflows über OpenShift- und Kubernetes-Infrastruktur ermöglicht.

Der Nutzen ist Auditierbarkeit, Reproduzierbarkeit, Drift-Erkennung, kontrolliertes Rollback und klare Trennung zwischen Artefakt-Erzeugung und Deployment.

Senior-Antwort:

„GitOps ist nicht YAML im Git. GitOps ist ein Betriebsmodell: gewünschter Zustand, Controller, Drift-Erkennung und nachvollziehbare Änderung.“

Häufige Fehler:
  • CI patcht Cluster und GitOps gleichzeitig.
  • Manuelle Änderungen im Cluster.
  • Secrets im Git im Klartext.
  • Automatisches Prune ohne Verständnis.
  • Argo CD mit zu breiten Rechten.
  • GitOps-Repo ohne Review.
  • Image-Tags mutieren ohne Git-Änderung.
Best Practices:
  • Git als Source of Truth.
  • CI aktualisiert Git, nicht direkt Prod.
  • Pull Requests für Prod.
  • Drift Detection aktiv.
  • Argo CD Projects zur Begrenzung.
  • RBAC für Argo CD.
  • Secrets extern oder verschlüsselt.
  • Image-Digests statt latest .
Stolperfalle:

Wenn CI direkt ins Cluster deployt und Argo CD später aus Git zurückdreht, haben Sie zwei Quellen der Wahrheit.

Merksatz:

Git ist der Wunsch; Argo CD ist der Wächter.

B6-K11-Q006 – Image Promotion und GitOps-UpdatePromotion aktualisiert Git; GitOps aktualisiert den Cluster.
Frage:

„Wie verbinden Sie OpenShift Pipelines mit OpenShift GitOps?“

Kurz erklärt:

Nach erfolgreichem Build und Scan aktualisiert die Pipeline das GitOps-Repository mit dem geprüften Image-Digest. Argo CD deployed anschließend aus Git.

Musterantwort:

Die Pipeline baut, testet, scannt und signiert das Image. Danach aktualisiert sie nicht direkt das Produktionsdeployment im Cluster, sondern das GitOps-Repository. Zum Beispiel schreibt sie einen neuen Image-Digest in ein Kustomize-Overlay oder Helm Values. Argo CD erkennt die Git-Änderung und synchronisiert den Cluster.

Diese Trennung ist wichtig: Pipeline erzeugt Artefakt und Git-Änderung; GitOps kontrolliert den Clusterzustand. Dadurch bleiben Audit, Review, Drift Detection und Rollback sauber.

Senior-Antwort:

„Die Pipeline darf das Artefakt promoten, aber der Clusterzustand kommt aus Git.“

Häufige Fehler:
  • Pipeline patcht Prod-Deployment direkt.
  • GitOps-Repo wird umgangen.
  • Mutierbare Tags statt Digest.
  • Kein Review für Prod.
  • Pipeline hat zu breite Git-Rechte.
  • Keine Signaturprüfung.
  • Automatische Commits ohne Kontext.
Best Practices:
  • Pipeline aktualisiert GitOps, nicht Prod direkt.
  • Digest aus Build-Result nutzen.
  • Dev automatisch, Prod per PR.
  • Commit-Metadaten mit Build-ID.
  • Git-Rechte minimal.
  • Rollback über Git-Revert.
  • Signatur und Scan vor Promotion.
  • Argo CD Sync-Status überwachen.
Stolperfalle:

Ein GitOps-Update mit einem Tag wie latest ist kein echtes Promotion-Signal, weil der Inhalt des Tags nicht stabil ist.

Merksatz:

Promotion aktualisiert Git; GitOps aktualisiert den Cluster.

B6-K11-Q007 – Tekton Triggers und Pipelines as CodeAutomatische Trigger brauchen automatische Grenzen.
Frage:

„Wie starten Sie Pipelines automatisch bei Git-Events?“

Kurz erklärt:

Tekton Triggers starten Pipelines ereignisgetrieben, zum Beispiel bei Git-Webhooks. Pipelines as Code beschreibt PipelineRuns im Repository und integriert sie stärker in Pull-Request-Workflows.

Musterantwort:

Mit Tekton Triggers kann ich Webhook-Ereignisse empfangen und daraus PipelineRuns erzeugen. Typische Bausteine sind EventListener, TriggerBinding und TriggerTemplate. Alternativ kann Pipelines as Code genutzt werden, bei dem PipelineRuns im Git-Repository definiert und durch Pull Requests oder Pushes ausgelöst werden. Red Hat dokumentiert bei aktuellen OpenShift-Pipelines-Komponenten auch Pipelines as Code als Bestandteil des Pipelines-Ökosystems.

Wichtig sind Sicherheit und Governance: Webhook Secrets, Repository-Zuordnung, Rechte des Trigger-ServiceAccounts und Schutz vor Build-Stürmen.

Senior-Antwort:

„Ein Webhook ist ein Eingang in meine Build-Plattform. Ich behandle ihn wie eine API mit Authentifizierung und Missbrauchsschutz.“

Häufige Fehler:
  • Webhook ohne Secret.
  • Jeder Branch löst teure Pipeline aus.
  • Pull Requests aus Forks mit Secrets.
  • Trigger-ServiceAccount zu mächtig.
  • Keine Event-Validierung.
  • Keine Rate Limits.
  • PipelineRuns ohne Aufbewahrungsstrategie.
Best Practices:
  • Webhook-Signaturen prüfen.
  • Branch- und Event-Filter.
  • PR-Builds ohne sensitive Secrets.
  • Trigger-ServiceAccount minimal.
  • Concurrency begrenzen.
  • PipelineRuns prunen.
  • Pipelines as Code für Teamnähe prüfen.
  • Main- und PR-Flows trennen.
Stolperfalle:

PRs aus Forks dürfen nicht unkontrolliert Pipelines mit produktiven Secrets ausführen.

Merksatz:

Automatische Trigger brauchen automatische Grenzen.

B6-K11-Q008 – Security: Tekton Chains, SBOM, Signierung und Supply ChainDeploybar ist nur, was nachweisbar vertrauenswürdig ist.
Frage:

„Wie sichern Sie die Software Supply Chain in OpenShift Pipelines ab?“

Kurz erklärt:

Supply-Chain-Security stellt sicher, dass Images aus bekanntem Source gebaut, geprüft, signiert und vor Deployment verifiziert werden.

Musterantwort:

Ich brauche eine nachvollziehbare Kette von Source zu Image zu Deployment: Commit, Build, Tests, Scans, SBOM, Signatur, Provenance und Policy Enforcement. Tekton Chains kann im Tekton-Ökosystem zur Erzeugung und Verwaltung von Supply-Chain-Metadaten beitragen; aktuelle Installationsprofile von Red Hat OpenShift Pipelines können Tekton Chains als Komponente enthalten.

Wichtig ist aber: Signieren allein reicht nicht. Der Cluster oder die Deployment-Pipeline muss verifizieren, dass nur freigegebene, signierte und policy-konforme Images deployed werden.

Senior-Antwort:

„Ich vertraue nicht einem Image-Tag. Ich vertraue einer geprüften, signierten, nachvollziehbaren Artefaktkette.“

Häufige Fehler:
  • Scans ohne Gate.
  • Signatur ohne Enforcement.
  • SBOM erzeugen, aber nicht speichern.
  • Snapshot Dependencies.
  • Base Images nicht patchen.
  • Secrets in Pipeline-Logs.
  • Untrusted PRs mit Secrets.
Best Practices:
  • Commit und Image-Digest verknüpfen.
  • SBOM erzeugen und speichern.
  • Images signieren.
  • Signatur vor Deployment prüfen.
  • Scans als Gates.
  • CVE-Ausnahmen dokumentieren.
  • Keine Snapshot Dependencies in Releases.
  • Trusted Base Images.
Stolperfalle:

Eine SBOM, die niemand versioniert oder bei CVEs abfragt, ist nur Compliance-Dekoration.

Merksatz:

Deploybar ist nur, was nachweisbar vertrauenswürdig ist.

B6-K11-Q009 – GitOps-Governance: Argo CD Projects, RBAC, Sync Windows und DriftGitOps ohne Project-Grenzen ist Automatisierung ohne Leitplanken.
Frage:

„Wie sichern Sie OpenShift GitOps gegen Fehlkonfiguration und zu breite Rechte ab?“

Kurz erklärt:

Argo CD Projects, RBAC, Sync Policies und Drift Detection begrenzen, wer was aus welchem Git-Repository in welche Namespaces deployen darf.

Musterantwort:

Ich verwende Argo CD Projects, um erlaubte Repositories, Zielcluster, Namespaces und Ressourcentypen zu begrenzen. Ich setze RBAC, trenne Dev/Test/Prod, definiere Sync Policies bewusst, nutze Reviews für Prod und überwache Drift. OpenShift GitOps verwendet Argo CD als deklarativen GitOps-Engine und unterstützt Workflows über Cluster und Umgebungen.

Argo CD darf nicht global alles aus jedem Repo in jeden Namespace deployen. Sonst wird GitOps zur privilegierten Hintertür.

Senior-Antwort:

„GitOps braucht Grenzen. Sonst ist es nur ein sehr mächtiger Bot mit Git-Zugang.“

Häufige Fehler:
  • Default Project für alles.
  • Argo CD darf clusterweit alles.
  • Prod ohne PR-Review.
  • Automatisches Prune falsch aktiviert.
  • Drift ignorieren.
  • Manuelle Cluster-Änderungen tolerieren.
  • Secrets im GitOps-Repo.
Best Practices:
  • AppProjects pro Domain/Environment.
  • Least Privilege für Argo CD.
  • Cluster-Ressourcen stark begrenzen.
  • Prod-Reviews.
  • Sync Windows bei Bedarf.
  • Drift Alerts.
  • Secrets extern verwalten.
  • Application-Schnitt standardisieren.
Stolperfalle:

Das Argo-CD- default -Project ist bequem, aber in Enterprise-Umgebungen oft viel zu offen.

Merksatz:

GitOps ohne Project-Grenzen ist Automatisierung ohne Leitplanken.

B6-K11-Q010 – CI/CD-Anti-Patterns und Enterprise Delivery PlatformDelivery-Plattformen skalieren Vertrauen, nicht nur Deployments. 24h
Frage:

„Welche CI/CD-Anti-Patterns sehen Sie auf OpenShift, und wie vermeiden Sie sie?“

Kurz erklärt:

Eine Enterprise Delivery Platform verbindet Geschwindigkeit, Sicherheit, Auditierbarkeit und Betriebsfähigkeit. Anti-Patterns entstehen, wenn CI/CD nur als Script-Automatisierung verstanden wird.

Musterantwort:

Die größten Anti-Patterns sind Rebuild pro Umgebung, latest in Produktion, direkte Cluster-Patches aus CI, übermächtige ServiceAccounts, Secrets in Logs, keine Security Gates, GitOps ohne Governance, fehlende Rollbacks und manuelle Hotfixes im Cluster. Ich vermeide sie durch Golden Paths: standardisierte Tekton Tasks, Build-once-Promotion, Digest-Pinning, GitOps-Deployment, Least Privilege, Signierung, SBOM, Scan-Gates und klare Freigabeprozesse.

OpenShift Pipelines und OpenShift GitOps liefern Bausteine, aber Plattformreife entsteht durch ein konsistentes Betriebsmodell. Pipelines automatisieren CI/CD auf Kubernetes-Ressourcenbasis. GitOps nutzt Argo CD als deklarative Engine für konsistente Deployments.

Senior-Antwort:

„CI/CD ist kein Script, das schneller deployt. CI/CD ist ein kontrolliertes Wertstromsystem von Commit bis Produktion.“

Häufige Fehler:
  • Toolmigration ohne Prozessdesign.
  • Jenkinsfile eins zu eins nach Tekton kopieren.
  • Kein Platform Product Thinking.
  • Security Gates manuell.
  • Teams müssen alles selbst bauen.
  • Kein Exception-Prozess.
  • Keine Metriken.
  • CI/CD-Ownership unklar.
Best Practices:
  • Golden Paths bereitstellen.
  • Pipeline-Templates.
  • GitOps-Templates.
  • Self-Service mit Leitplanken.
  • Security automatisieren.
  • Artefaktidentität erzwingen.
  • Secrets sauber verwalten.
  • Delivery-Metriken erfassen.
Stolperfalle:

Wenn der Golden Path langsamer oder komplizierter ist als der manuelle Weg, wird er umgangen.

Merksatz:

Delivery-Plattformen skalieren Vertrauen, nicht nur Deployments.

B6-K12 – Kapitel 12: Monitoring, Logging und Observability 10 Karten

B6-K12-Q001 – Observability-Grundmodell: Metrics, Logs, Traces und EventsObservability ist erklärbare Produktion. 24h
Frage:

„Was ist Observability, und wie unterscheidet sie sich von Monitoring?“

Kurz erklärt:

Observability kombiniert mehrere Signale. Metrics zeigen Trends und Zustände, Logs erklären Details, Traces zeigen verteilte Pfade, Events erklären Plattformaktionen.

Musterantwort:

Monitoring beantwortet primär: „Ist etwas kaputt oder außerhalb definierter Schwellwerte?“ Observability geht weiter und hilft zu verstehen, warum ein System sich so verhält. Dazu kombiniert man Metriken, Logs, Traces, Events und Kontext.

OpenShift Observability umfasst laut Red Hat Echtzeit-Sichtbarkeit, Monitoring und Analyse von Metriken, Logs, Traces und Events. In der Praxis bedeutet das: Ein Alert zeigt ein Symptom, Metriken zeigen Muster, Logs liefern Details, Traces zeigen den Request-Pfad und Events erklären Plattformentscheidungen.

Senior-Antwort:

„Monitoring sagt mir, dass etwas brennt. Observability hilft mir, den Brandherd zu finden.“

Häufige Fehler:
  • Nur Logs sammeln.
  • Nur CPU und Memory monitoren.
  • Keine Trace IDs in Logs.
  • Keine Anwendungsmeter.
  • Events ignorieren.
  • High-Cardinality-Labels.
  • Debug-Logging dauerhaft aktiv.
  • Dashboards ohne Alerts.
Best Practices:
  • Vier Signale kombinieren.
  • Golden Signals definieren.
  • Trace IDs in Logs.
  • Strukturierte Logs.
  • Application Metrics.
  • JVM Metrics.
  • Plattform Events nutzen.
  • Sampling und Retention bewusst setzen.
Stolperfalle:

Viele Logs sind keine Observability. Ohne Struktur, Korrelation und Metriken sind sie nur teurer Text.

Merksatz:

Observability ist erklärbare Produktion.

B6-K12-Q002 – OpenShift Monitoring Stack: Prometheus, Alertmanager und User Workload MonitoringPrometheus liebt stabile Labels und hasst explodierende Kardinalität.
Frage:

„Wie monitoren Sie eigene Anwendungen auf OpenShift?“

Kurz erklärt:

OpenShift Monitoring sammelt Plattform- und optional User-Workload-Metriken. ServiceMonitor und PodMonitor beschreiben, wo Prometheus Metriken scrapen soll.

Musterantwort:

Für Anwendungen nutze ich User Workload Monitoring, sofern es in der Plattform aktiviert ist. Die Anwendung stellt Prometheus-kompatible Metriken bereit, zum Beispiel über Spring Boot Actuator und Micrometer. Dann definiere ich ServiceMonitor oder PodMonitor, damit Prometheus den Endpunkt scrapen kann.

Red Hat beschreibt, dass OpenShift Monitoring Best Practices out of the box liefert und der Clusteradministrator die Monitoring-Stack-Konfiguration mit unterstützten Konfigurationen anpassen kann. Wichtig ist: Plattformmonitoring und Applikationsmonitoring müssen getrennt, aber korrelierbar sein.

Senior-Antwort:

„Ich monitore nicht nur Containerressourcen. Ich monitore das Verhalten der Anwendung: Latenz, Fehler, Durchsatz und Abhängigkeiten.“

Häufige Fehler:
  • Actuator-Metriken öffentlich exposen.
  • User IDs als Metriklabels.
  • Zu kurze Scrape-Intervalle.
  • Keine ServiceMonitor-Labels passend zur Plattform.
  • Management-Port nicht abgesichert.
  • Nur Infrastrukturmetriken, keine App-Metriken.
  • Keine Alert Rules.
Best Practices:
  • User Workload Monitoring nutzen.
  • ServiceMonitor/PodMonitor als Code.
  • Management-Endpunkte intern halten.
  • Label-Cardinality begrenzen.
  • JVM- und HTTP-Metriken aktivieren.
  • Business-Metriken sparsam und sinnvoll.
  • PromQL-Queries testen.
  • Alerts mit Runbooks verbinden.
Stolperfalle:

Ein einziges Label mit hoher Kardinalität kann ein gutes Metriksystem ruinieren.

Merksatz:

Prometheus liebt stabile Labels und hasst explodierende Kardinalität.

B6-K12-Q003 – PromQL, Alerts und SLO-orientiertes MonitoringAlerting ist Bereitschaftskommunikation, nicht Metrikdekoration.
Frage:

„Wie schreiben Sie gute Alerts für Java-Services auf OpenShift?“

Kurz erklärt:

PromQL fragt Zeitreihen ab. Alerts sollten SLO-orientiert sein: Fehlerrate, Latenz, Durchsatz, Sättigung und Verfügbarkeit statt nur CPU.

Musterantwort:

Gute Alerts sind symptomorientiert und handlungsrelevant. Ich alertiere nicht primär auf „CPU > 80 %“, sondern auf Nutzerwirkung: hohe Fehlerrate, erhöhte P99-Latenz, keine erfolgreichen Requests, Queue-Rückstau oder SLO-Budget-Verbrauch. CPU und Memory sind Diagnosemetriken, aber nicht immer gute Page-Alerts.

PromQL ermöglicht dafür Raten, Histogrammquantile und Aggregationen. Alerts brauchen for , Severity, Owner, Runbook und klare Routing-Regeln. Ohne Runbook ist ein Alert nur Lärm.

Senior-Antwort:

„Ein guter Alert sagt nicht nur, dass etwas ungewöhnlich ist. Er sagt, dass jemand jetzt sinnvoll handeln kann.“

Häufige Fehler:
  • Alert auf jede CPU-Spitze.
  • Kein for , dadurch Flapping.
  • Keine Runbooks.
  • Alerts ohne Owner.
  • Zu viele Severity-Pages.
  • Histogramme falsch aggregieren.
  • Durchschnittslatenz statt P95/P99.
  • Keine SLOs.
Best Practices:
  • SLO-orientierte Alerts.
  • Golden Signals nutzen.
  • for gegen Flapping.
  • Runbooks verlinken.
  • Owner und Service Labels.
  • Severity klar definieren.
  • Recording Rules für teure Queries.
  • Alert Reviews durchführen.
Stolperfalle:

CPU-Alerts sind oft Symptome. Nutzerwirkung sieht man besser über Latenz, Fehlerrate und Sättigung.

Merksatz:

Alerting ist Bereitschaftskommunikation, nicht Metrikdekoration.

B6-K12-Q004 – Logging: Strukturierte Logs, Aggregation und RetentionLogs müssen Menschen helfen und Maschinen lesen können.
Frage:

„Wie sollten Java-Anwendungen auf OpenShift loggen?“

Kurz erklärt:

OpenShift Logging sammelt, visualisiert, forwarded und speichert Logs. Anwendungen sollten strukturierte Logs nach stdout/stderr schreiben.

Musterantwort:

Java-Anwendungen sollten nach stdout/stderr loggen, idealerweise strukturiert als JSON. Logs sollten Service, Namespace, Pod, Version, Trace ID, Span ID, Request ID und relevante fachliche Kontextinformationen enthalten — aber keine Secrets, Tokens, Passwörter oder personenbezogene Daten ohne klare Rechtsgrundlage.

Red Hat beschreibt OpenShift Logging als Möglichkeit, Logdaten zu sammeln, zu visualisieren, weiterzuleiten und zu speichern, um Probleme zu diagnostizieren, Performance-Engpässe zu erkennen und Sicherheitsbedrohungen zu entdecken. Das funktioniert am besten, wenn Anwendungen strukturierte, maschinenlesbare Logs liefern.

Senior-Antwort:

„Logs sind Produktionsdaten. Ich logge strukturiert, korrelierbar und sparsam — niemals geheimnisvoll und niemals geheim.“

Häufige Fehler:
  • Logs in Dateien im Container.
  • Keine Trace ID.
  • Secrets im Log.
  • Debug-Level dauerhaft.
  • Multiline-Stacktraces unstrukturiert.
  • Zu hohe Log-Retention.
  • Logs als Metrikersatz missbrauchen.
  • Keine Log-Level-Governance.
Best Practices:
  • stdout/stderr.
  • JSON-Struktur.
  • Trace ID und Span ID.
  • Request ID.
  • Keine Secrets.
  • PII minimieren.
  • Log-Level dynamisch, aber kontrolliert.
  • Retention nach Zweck.
Stolperfalle:

Ein Secret im Log ist oft schlimmer als ein Secret im Pod, weil Logs breit kopiert, indexiert und langfristig gespeichert werden.

Merksatz:

Logs müssen Menschen helfen und Maschinen lesen können.

B6-K12-Q005 – Distributed Tracing und OpenTelemetryTraces zeigen Wege; Logs erzählen Details; Metriken zeigen Muster. 24h
Frage:

„Warum brauchen Microservices Distributed Tracing?“

Kurz erklärt:

Distributed Tracing zeigt den Pfad eines Requests über mehrere Services. OpenTelemetry standardisiert Instrumentierung, Sammlung und Export von Telemetriedaten.

Musterantwort:

In Microservices ist ein Request oft über mehrere Services, Datenbanken, Broker und externe APIs verteilt. Logs eines einzelnen Services zeigen nur einen Ausschnitt. Distributed Tracing zeigt, wo Zeit verbracht wurde und welcher Hop Fehler verursacht hat.

Red Hat beschreibt die Red Hat build of OpenTelemetry Dokumentation als Anleitung für Installation, Konfiguration und Troubleshooting des OpenTelemetry Operators und Collectors; der Collector wird über Receivers, Processors, Exporters, Connectors und Extensions konfiguriert. Für Java nutze ich OpenTelemetry-Instrumentierung, exportiere OTLP zum Collector und korreliere Trace IDs mit Logs.

Senior-Antwort:

„Tracing beantwortet nicht nur, dass ein Request langsam ist, sondern wo er langsam wurde.“

Häufige Fehler:
  • Traces ohne Service-Namen.
  • Keine Log-Trace-Korrelation.
  • 100 Prozent Sampling ohne Kostenplanung.
  • Collector ohne Resource Limits.
  • Kontext über Kafka/Async verloren.
  • Externe Calls nicht instrumentiert.
  • Sensitive Daten in Span Attributes.
Best Practices:
  • OpenTelemetry standardisieren.
  • Service-Namen konsistent.
  • Trace ID in Logs.
  • Sampling bewusst.
  • Collector hochverfügbar betreiben.
  • Batch und Memory Limiter.
  • Sensitive Attributes vermeiden.
  • HTTP, DB und Messaging instrumentieren.
Stolperfalle:

Tracing ohne Sampling-Strategie wird schnell zu teuer; Sampling ohne klare Ziele macht kritische Spuren unsichtbar.

Merksatz:

Traces zeigen Wege; Logs erzählen Details; Metriken zeigen Muster.

B6-K12-Q006 – JVM- und Spring-Boot-ObservabilityJVM-Observability macht Container-Symptome erklärbar.
Frage:

„Welche Metriken erwarten Sie von einem produktiven Java-Service auf OpenShift?“

Kurz erklärt:

Java- und Spring-Boot-Observability umfasst HTTP-, JVM-, GC-, Thread-, Connection-Pool-, Executor- und fachliche Metriken.

Musterantwort:

Ich erwarte technische und fachliche Metriken. Technisch: HTTP Request Count, Fehler, Latenz-Histogramme, JVM Heap/Non-Heap, GC-Pausen, Threads, Classloading, CPU, HikariCP Pool, Executor Queues und Downstream-Client-Metriken. Fachlich: Bestellungen erstellt, Zahlungen abgelehnt, Nachrichten verarbeitet, Queue Lag, Validierungsfehler.

Spring Boot mit Actuator und Micrometer ist dafür ein guter Standard. Wichtig ist, dass Metriken stabile Labels verwenden und keine IDs als Labels enthalten.

Senior-Antwort:

„Container-Memory sagt mir, dass Speicher steigt. JVM-Metriken sagen mir, warum.“

Häufige Fehler:
  • Nur Container CPU/Memory.
  • Keine HikariCP-Metriken.
  • Keine Downstream-Metriken.
  • IDs als Labels.
  • Management-Port extern erreichbar.
  • Keine Histogramme für Latenz.
  • Business-Metriken fehlen.
Best Practices:
  • Actuator standardisieren.
  • Management-Port intern.
  • Prometheus Endpoint schützen.
  • JVM-Metriken aktivieren.
  • Connection Pools beobachten.
  • Executor Queues messen.
  • Business-Metriken definieren.
  • Label-Cardinality begrenzen.
Stolperfalle:

Ohne Connection-Pool-Metriken wirken Datenbankprobleme oft wie zufällige HTTP-Latenz.

Merksatz:

JVM-Observability macht Container-Symptome erklärbar.

B6-K12-Q007 – Kubernetes Events, Pod-Zustände und Rollout-DiagnoseLogs sprechen für die App; Events sprechen für die Plattform.
Frage:

„Warum sind Kubernetes Events für Observability wichtig?“

Kurz erklärt:

Kubernetes Events erklären Plattformentscheidungen: Scheduling, Pulling, Mounting, Probe Failures, OOMKilled, FailedScheduling, BackOff und Rollout-Status.

Musterantwort:

Events erklären, was die Plattform mit einem Workload gemacht hat. Logs erklären, was die Anwendung sagt. Bei ImagePullBackOff, CrashLoopBackOff, FailedScheduling, Probe Failures, OOMKilled oder Mount-Problemen sind Events oft schneller und präziser als App-Logs.

Ein Senior prüft deshalb bei Rollout- oder Pod-Problemen immer: Deployment-Status, Pods, Events, Logs und vorherige Logs. Ohne Events fehlt die Plattformperspektive.

Senior-Antwort:

„Wenn der Container nie richtig startet, sind Events oft ehrlicher als Logs.“

Häufige Fehler:
  • Nur App-Logs prüfen.
  • Events ignorieren.
  • --previous vergessen.
  • Probe-Failures nicht lesen.
  • OOMKilled als Java-Exception suchen.
  • FailedScheduling mit App-Bug verwechseln.
  • Events nicht in Runbooks aufnehmen.
Best Practices:
  • Events immer prüfen.
  • Nach Zeit sortieren.
  • describe pod nutzen.
  • Vorherige Logs bei Crashloops.
  • Rollout-Status prüfen.
  • Probe-Events ernst nehmen.
  • Events mit Metriken korrelieren.
  • Runbooks standardisieren.
Stolperfalle:

Bei CrashLoopBackOff sind aktuelle Logs manchmal leer; --previous zeigt den letzten abgestürzten Versuch.

Merksatz:

Logs sprechen für die App; Events sprechen für die Plattform.

B6-K12-Q008 – Network Observability und Traffic Flow AnalyseWas man nicht sieht, kann man schlecht segmentieren.
Frage:

„Wozu dient Network Observability auf OpenShift?“

Kurz erklärt:

Network Observability macht Netzwerkflüsse sichtbar. Das hilft bei Security, Egress-Kontrolle, Policy-Design und Netzwerkdiagnose.

Musterantwort:

Network Observability hilft zu verstehen, welche Workloads miteinander kommunizieren, welche Egress-Ziele genutzt werden, wo viel Traffic entsteht und wie Netzwerkflüsse mit Policies oder Performanceproblemen zusammenhängen. Red Hat beschreibt, dass der Network Observability Operator Administratoren ermöglicht, Netzwerkverkehrsflüsse für OpenShift-Cluster zu beobachten und zu analysieren.

Das ist besonders wertvoll bei NetworkPolicy-Einführung, Egress-Governance, Incident-Diagnose und Kostenanalyse.

Senior-Antwort:

„Network Observability zeigt mir reale Kommunikation. Daraus werden bessere Policies als aus Vermutungen.“

Häufige Fehler:
  • Flow-Daten als vollständige App-Traces interpretieren.
  • Retention zu hoch.
  • Sensitive Ziele nicht klassifizieren.
  • Keine Verbindung zu Policy-Design.
  • Egress-Kosten ignorieren.
  • Flow-Daten ohne Owner.
  • Keine Sampling-Strategie.
Best Practices:
  • Network Observability für Policy-Einführung.
  • Top-Talker analysieren.
  • Egress-Ziele klassifizieren.
  • Denied Traffic beobachten.
  • Flow-Daten mit App-Metriken korrelieren.
  • Retention begrenzen.
  • Security- und Plattformteams einbinden.
  • Kommunikationsmatrix aus Flows validieren.
Stolperfalle:

Ein Flow zeigt, dass Kommunikation stattfindet, aber nicht, ob sie fachlich erlaubt oder gewünscht ist.

Merksatz:

Was man nicht sieht, kann man schlecht segmentieren.

B6-K12-Q009 – Observability-Kosten, Cardinality und RetentionAlles beobachten heißt nicht alles speichern.
Frage:

„Wie verhindern Sie, dass Observability zu teuer oder unbenutzbar wird?“

Kurz erklärt:

Observability erzeugt Daten. Ohne Cardinality-, Sampling- und Retention-Strategie wird sie teuer, langsam und schwer nutzbar.

Musterantwort:

Ich steuere Datenmenge und Datenqualität: stabile Metriklabels, keine IDs als Labels, sinnvolle Scrape-Intervalle, Recording Rules für teure Queries, strukturierte aber sparsame Logs, Log-Level-Governance, Trace Sampling, Attributfilterung und klare Retention pro Signal und Umgebung.

Observability ist ein Produkt mit Kostenmodell. Wenn alles dauerhaft und vollständig gespeichert wird, zahlen Teams für Daten, die sie im Incident nicht nutzen können.

Senior-Antwort:

„Observability-Daten müssen nützlich sein. Mehr Daten sind nicht automatisch mehr Wissen.“

Häufige Fehler:
  • userId als Label.
  • Vollständige URL als URI-Label.
  • Debug-Logs dauerhaft.
  • 100 Prozent Tracing überall.
  • Keine Retention pro Umgebung.
  • PromQL ohne Aggregationsverständnis.
  • Businessdaten in Span Attributes.
  • Keine Ownership für Dashboards.
Best Practices:
  • Label-Governance.
  • URI-Templating.
  • Scrape-Intervalle bewusst.
  • Recording Rules.
  • Log-Level-Standards.
  • Trace Sampling.
  • Retention nach Signaltyp.
  • Dashboards reviewen.
Stolperfalle:

Ein einziges dynamisches Label kann mehr Schaden verursachen als hundert normale Metriken.

Merksatz:

Alles beobachten heißt nicht alles speichern.

B6-K12-Q010 – Observability-Governance, Golden Dashboards und Anti-PatternsObservability ohne Ownership ist nur Telemetrie. 24h
Frage:

„Welche Observability-Anti-Patterns sehen Sie auf OpenShift?“

Kurz erklärt:

Observability-Governance standardisiert Metriken, Logs, Traces, Dashboards, Alerts, Runbooks, Ownership und Kostenkontrolle.

Musterantwort:

Typische Anti-Patterns sind: keine SLOs, keine Owner, Dashboards ohne Alerts, Alerts ohne Runbooks, nur Infrastrukturmetriken, keine Trace IDs in Logs, Debug-Logging in Produktion, High-Cardinality-Metriken, keine Retention-Strategie, kein Zusammenhang zwischen Logs/Metrics/Traces, keine Business-Metriken und Observability erst nach dem Incident nachrüsten.

Eine reife Plattform liefert Golden Dashboards, Standard-Alerts, Logging-Konventionen, Trace-Kontext, User-Workload-Monitoring, Kostenregeln und Self-Service-Diagnose. OpenShift Observability stellt die Plattformbausteine für Metriken, Logs, Traces und Events bereit.

Senior-Antwort:

„Observability ist ein Vertrag: Die Plattform liefert Signalinfrastruktur, das Team liefert sinnvolle Signale und Runbooks.“

Häufige Fehler:
  • Dashboard ohne Handlungswert.
  • Alerts ohne Runbook.
  • Kein SLO.
  • Kein Owner.
  • Teams verlassen sich auf Plattformmetriken.
  • Logs ohne Trace ID.
  • Trace-Daten ohne Sampling.
  • Kosten niemandem zugeordnet.
Best Practices:
  • Golden Dashboards.
  • SLO-basierte Alerts.
  • Runbook-Pflicht.
  • Owner Labels.
  • Logging-Konventionen.
  • Trace-Kontext standardisieren.
  • Retention-Richtlinien.
  • Observability Reviews.
Stolperfalle:

Ein schönes Dashboard ohne Alert, Owner und Runbook hilft im Incident oft zu spät.

Merksatz:

Observability ohne Ownership ist nur Telemetrie.

B6-K13 – Kapitel 13: Autoscaling, Ressourcenmanagement und Performance 10 Karten

B6-K13-Q001 – Requests, Limits und Kubernetes SchedulingDer Scheduler sieht Requests; die JVM spürt Limits. 24h
Frage:

„Was ist der Unterschied zwischen Requests und Limits in OpenShift?“

Kurz erklärt:

Requests reservieren Scheduling-Kapazität. Limits begrenzen maximale Nutzung. Ohne saubere Requests und Limits sind Scheduling, Autoscaling und Stabilität kaum kontrollierbar.

Musterantwort:

Requests beschreiben die Ressourcen, die ein Container mindestens benötigt und die der Scheduler für die Platzierung berücksichtigt. Limits definieren die Obergrenze, die ein Container nutzen darf. CPU-Limits können zu Throttling führen, Memory-Limits können bei Überschreitung zu OOMKilled führen. Ephemeral Storage kann ebenfalls über Requests und Limits gesteuert werden; Red Hat beschreibt, dass lokale ephemeral storage auf Pod- und Container-Ebene über Requests und Limits verwaltet werden kann.

Für Java ist besonders wichtig: Das Memory-Limit ist nicht gleich Heap. Heap, Metaspace, Direct Memory, Thread Stacks, Code Cache, GC-Strukturen und native Libraries teilen sich den Container-Speicher.

Senior-Antwort:

„Requests sind mein Kapazitätsvertrag mit dem Scheduler. Limits sind mein Sicherheitszaun. Für Java muss ich beide mit JVM-Speicherrealität verbinden.“

Häufige Fehler:
  • Keine Requests setzen.
  • Memory Limit gleich Heap setzen.
  • CPU Limit zu niedrig.
  • HPA auf CPU nutzen, aber keine Requests definieren.
  • Ephemeral Storage ignorieren.
  • Requests aus dem Bauch heraus wählen.
  • OOMKilled als Java-Exception suchen.
  • Ressourcen nie nachmessen.
Best Practices:
  • Requests aus Messdaten ableiten.
  • Memory-Limit mit JVM-Native-Anteilen planen.
  • CPU-Limits für latenzkritische Java-Services vorsichtig setzen.
  • HPA-kompatible Requests definieren.
  • Ephemeral Storage begrenzen.
  • Ressourcen regelmäßig überprüfen.
  • JVM- und Container-Metriken gemeinsam betrachten.
  • Standardgrößen pro Serviceklasse definieren.
Stolperfalle:

Ein Container mit 1Gi Memory Limit und -Xmx1g ist praktisch auf OOMKilled programmiert.

Merksatz:

Der Scheduler sieht Requests; die JVM spürt Limits.

B6-K13-Q002 – Horizontal Pod Autoscaler: CPU, Memory und Custom MetricsAutoscaling skaliert Kapazität nur, wenn die Architektur skalierbar ist.
Frage:

„Wie funktioniert der Horizontal Pod Autoscaler in OpenShift?“

Kurz erklärt:

HPA skaliert die Anzahl von Pods anhand von Metriken. CPU- und Memory-basierte Skalierung sind einfach, aber nicht für jeden Workload sinnvoll.

Musterantwort:

Der Horizontal Pod Autoscaler skaliert die Replica-Anzahl eines Deployments oder vergleichbaren Workloads zwischen minReplicas und maxReplicas . Er beobachtet Metriken wie CPU- oder Memory-Auslastung und erhöht oder reduziert Pods, wenn Zielwerte überschritten oder unterschritten werden. Red Hat beschreibt, dass HPA die Anzahl von Pods automatisch erhöht oder verringert, wenn ein CPU- oder Memory-Schwellenwert überschritten wird.

Wichtig ist: HPA skaliert Pods, nicht Abhängigkeiten. Wenn die Datenbank, der Broker oder ein externer Provider der Engpass ist, kann mehr Pod-Skalierung das Problem verschlimmern.

Senior-Antwort:

„HPA ist gut, wenn mehr Pods wirklich mehr Kapazität bedeuten. Wenn der Engpass downstream liegt, skaliert man nur den Druck.“

Häufige Fehler:
  • HPA ohne CPU Requests.
  • Memory als Skalierungsmetrik für nichtlineare Workloads.
  • maxReplicas ohne Downstream-Kapazität.
  • Zu schnelles Scale-down.
  • Readiness signalisiert zu früh.
  • Kein Load Test für HPA.
  • HPA als Ersatz für Performance-Tuning.
Best Practices:
  • HPA mit realistischen Requests.
  • Scale Behavior konfigurieren.
  • Readiness korrekt.
  • Downstream-Kapazität einplanen.
  • Custom Metrics für Queue-Länge oder Lag prüfen.
  • Load Tests mit Autoscaling.
  • MinReplicas für Basiskapazität.
  • MaxReplicas als Schutzgrenze.
Stolperfalle:

CPU-HPA ohne CPU Request ist fachlich wertlos, weil die Zielauslastung relativ zum Request berechnet wird.

Merksatz:

Autoscaling skaliert Kapazität nur, wenn die Architektur skalierbar ist.

B6-K13-Q003 – Vertical Pod Autoscaler: Empfehlungen und GrenzenHPA skaliert Anzahl; VPA skaliert Größe.
Frage:

„Wann verwenden Sie Vertical Pod Autoscaler statt HPA?“

Kurz erklärt:

VPA analysiert reale Nutzung und empfiehlt oder setzt CPU-/Memory-Requests. Für viele produktive Java-Workloads wird VPA zunächst im Empfehlungsmodus genutzt.

Musterantwort:

VPA hilft, richtige Requests und Limits aus realer Nutzung abzuleiten oder anzupassen. HPA skaliert horizontal die Pod-Anzahl. VPA skaliert vertikal die Ressourcengröße pro Pod. Für Java nutze ich VPA häufig zuerst im Empfehlungsmodus, um Requests zu kalibrieren.

OpenShift 4.21 Release Notes erwähnen VPA-Verbesserungen wie InPlaceOrRecreate , bei dem VPA versucht, Empfehlungen ohne Pod-Neuerstellung anzuwenden und bei Bedarf auf Recreate zurückfällt. Trotzdem muss man VPA vorsichtig einsetzen, weil Änderungen an Ressourcen JVM-Verhalten, Scheduling und Pod-Neustarts beeinflussen können.

Senior-Antwort:

„VPA ist ein hervorragendes Messwerkzeug. Als automatischer Eingriff braucht es deutlich mehr Governance.“

Häufige Fehler:
  • VPA Auto ohne Verständnis.
  • VPA und HPA auf CPU gleichzeitig unkoordiniert.
  • JVM-Speicherwirkung ignorieren.
  • Empfehlungen aus zu kurzer Zeit.
  • Keine Lastspitzen berücksichtigen.
  • Statefull Workloads unkontrolliert neu starten.
  • Requests automatisch ändern ohne GitOps-Abgleich.
Best Practices:
  • VPA zunächst im Empfehlungsmodus.
  • Empfehlungen mit Lastprofil vergleichen.
  • GitOps als Quelle für finale Requests.
  • JVM-Metriken prüfen.
  • HPA/VPA-Kombination bewusst designen.
  • Min/Max Policies setzen.
  • Rollout-Auswirkungen planen.
  • Empfehlungen regelmäßig reviewen.
Stolperfalle:

Wenn VPA das Memory-Limit ändert, ändert sich bei containerbewusster JVM auch das Heap-Verhalten.

Merksatz:

HPA skaliert Anzahl; VPA skaliert Größe.

B6-K13-Q004 – Cluster Autoscaler und Machine AutoscalerPod-Skalierung ist schneller als Node-Skalierung — aber beide brauchen Kapazitätslogik.
Frage:

„Was ist der Unterschied zwischen HPA und Cluster Autoscaler?“

Kurz erklärt:

Cluster Autoscaler erweitert oder reduziert Clusterkapazität. Machine Autoscaler skaliert konkrete MachineSets innerhalb definierter Grenzen.

Musterantwort:

HPA skaliert Pods. Cluster Autoscaler skaliert Clusterkapazität. Wenn HPA neue Pods erzeugt, diese aber wegen fehlender Ressourcen nicht schedulbar sind, kann Cluster Autoscaler zusätzliche Nodes bereitstellen. Red Hat beschreibt, dass der Cluster Autoscaler die Clustergröße abhängig von Deployment-Bedarf erhöht oder verringert, während der Machine Autoscaler die Anzahl der Maschinen in Compute Machine Sets anpasst. In älterer, aber weiterhin konzeptionell zutreffender Dokumentation beschreibt Red Hat außerdem, dass Cluster Autoscaler die Größe erhöht, wenn Pods wegen unzureichender Ressourcen nicht auf vorhandene Worker Nodes geplant werden können, und nicht über konfigurierte Limits hinaus skaliert.

Wichtig ist: Cluster Autoscaling reagiert auf Scheduling-Druck, nicht direkt auf HTTP-Traffic.

Senior-Antwort:

„HPA bestellt mehr Pods. Cluster Autoscaler bestellt mehr Platz für Pods. Beides hat unterschiedliche Reaktionszeiten.“

Häufige Fehler:
  • Cluster Autoscaler als sofortige Reaktion erwarten.
  • Zu niedrige MinReplicas.
  • MaxNodesTotal zu niedrig.
  • MachineAutoscaler nur in einer Zone.
  • DaemonSet-Overhead vergessen.
  • Große Java-Images verlangsamen Scale-up.
  • Scale-down entfernt Kapazität zu früh.
Best Practices:
  • HPA und Cluster Autoscaler gemeinsam testen.
  • Basiskapazität planen.
  • MachineAutoscaler pro Zone.
  • Limits bewusst setzen.
  • Scale-down konservativ.
  • Pending Pods monitoren.
  • Image-Pull-Zeiten optimieren.
  • Node-Provisioning-Zeit berücksichtigen.
Stolperfalle:

Wenn neue Nodes mehrere Minuten brauchen, hilft Cluster Autoscaler nicht gegen eine Lastspitze, die in Sekunden eskaliert.

Merksatz:

Pod-Skalierung ist schneller als Node-Skalierung — aber beide brauchen Kapazitätslogik.

B6-K13-Q005 – JVM im Container: Heap, Native Memory, GC und CPUContainer-Memory ist Prozessbudget, nicht Heapbudget. 24h
Frage:

„Wie dimensionieren Sie JVM-Speicher in OpenShift?“

Kurz erklärt:

Java-Performance im Container hängt stark von Memory-Limit, Heap-Anteil, Native Memory, Thread-Anzahl, GC und CPU-Throttling ab.

Musterantwort:

Ich dimensioniere nicht nur Heap. Ich starte vom Container-Memory-Limit und plane Anteile für Heap, Metaspace, Direct Buffers, Thread Stacks, Code Cache, GC-Strukturen, Agenten und native Libraries. MaxRAMPercentage kann sinnvoll sein, aber nicht blind. Bei 2 GiB Limit und 65 Prozent Heap bleiben rund 700 MiB für Nicht-Heap-Speicher.

Ich prüfe anschließend reale JVM-Metriken: Heap-Auslastung, GC-Pausen, Metaspace, Thread-Anzahl, Direct Memory und OOMKills. Container-Memory und JVM-Memory müssen gemeinsam betrachtet werden.

Senior-Antwort:

„Im Container ist Heap nur ein Mieter im Speicherhaus. Wenn er alles belegt, wirft Linux das ganze Haus raus.“

Häufige Fehler:
  • -Xmx gleich Memory Limit.
  • Native Memory ignorieren.
  • Zu viele Threads.
  • CPU-Limits zu eng.
  • GC-Metriken nicht beobachten.
  • APM-Agent-Speicher vergessen.
  • OOMKilled als App-Exception erwarten.
Best Practices:
  • Heap als Anteil, nicht als ganzes Limit.
  • Native Memory einplanen.
  • JVM-Metriken sammeln.
  • OOMKills alerten.
  • CPU-Throttling messen.
  • GC-Pausen beobachten.
  • Thread-Pools begrenzen.
  • APM-Agent overhead testen.
Stolperfalle:

Ein OOMKilled-Pod hat nicht zwingend einen Java-Stacktrace, weil der Kernel den Prozess beendet haben kann.

Merksatz:

Container-Memory ist Prozessbudget, nicht Heapbudget.

B6-K13-Q006 – CPU-Throttling, QoS-Klassen und Node OvercommitCPU-Limits sind Schutz, nicht Beschleunigung.
Frage:

„Warum kann ein CPU-Limit die Latenz eines Java-Services verschlechtern?“

Kurz erklärt:

CPU-Limits können Throttling erzeugen. QoS-Klassen beeinflussen Eviction-Priorität. Overcommit verbessert Auslastung, kann aber Latenz destabilisieren.

Musterantwort:

CPU-Limits setzen harte Grenzen. Wenn ein Container sein CPU-Kontingent verbraucht, kann er throttled werden. Das betrifft Request-Threads, GC, JIT und Hintergrundarbeit. Für latenzkritische Java-Services kann das P99 deutlich verschlechtern. Deshalb setzen manche Plattformen für bestimmte Workloads CPU-Requests, aber keine CPU-Limits, während Memory-Limits weiter gesetzt bleiben.

Das muss natürlich mit Namespace-Quotas, Fairness und Node-Kapazität abgestimmt werden. Ohne Governance kann fehlendes CPU-Limit andere Workloads beeinträchtigen.

Senior-Antwort:

„CPU-Limits schützen Fairness, aber sie können Latenz teuer machen. Für kritische Workloads entscheide ich das anhand von SLOs und Messdaten.“

Häufige Fehler:
  • CPU-Limit als Performancegarantie verstehen.
  • BestEffort-Pods in Produktion.
  • Keine Requests setzen.
  • CPU-Throttling nicht messen.
  • Latenzprobleme nur im Code suchen.
  • Alle Workloads gleich behandeln.
  • Overcommit ohne SLO-Bewertung.
Best Practices:
  • CPU-Throttling monitoren.
  • Für latenzkritische Services CPU-Limits prüfen.
  • Memory-Limits setzen.
  • Requests realistisch.
  • QoS bewusst wählen.
  • Workload-Klassen definieren.
  • Node Overcommit kontrollieren.
  • SLOs mit Ressourcenpolitik verbinden.
Stolperfalle:

Ein hoher CPU-Request ohne echtes Nutzungsmuster verschwendet Kapazität; ein zu enges CPU-Limit verschlechtert P99.

Merksatz:

CPU-Limits sind Schutz, nicht Beschleunigung.

B6-K13-Q007 – ResourceQuota, LimitRange und Multi-Tenant-KapazitätskontrolleMulti-Tenancy ohne Quotas ist Hoffnung, keine Plattformstrategie.
Frage:

„Warum braucht man ResourceQuota und LimitRange auf OpenShift?“

Kurz erklärt:

ResourceQuota begrenzt Gesamtressourcen eines Namespaces. LimitRange setzt Defaults, Minima und Maxima für einzelne Container oder Pods.

Musterantwort:

In einer Multi-Tenant-Plattform müssen Ressourcen fair und kontrolliert verteilt werden. ResourceQuota verhindert, dass ein Namespace unbegrenzt CPU, Memory, Storage oder Pod-Anzahl verbraucht. LimitRange verhindert, dass einzelne Container ohne Requests oder mit unrealistischen Werten laufen.

Red Hat beschreibt in älterer, aber konzeptionell weiterhin zutreffender Dokumentation, dass Container Requests und Limits für CPU, Memory und ephemeral Storage definieren können und Quotas diese Werte begrenzen können. In modernen OpenShift-Plattformen ist das weiterhin ein zentrales Kapazitäts- und Governance-Prinzip.

Senior-Antwort:

„Quotas sind nicht Bürokratie. Sie sind der Vertrag, dass ein Team nicht aus Versehen den gemeinsamen Cluster leerläuft.“

Häufige Fehler:
  • Keine Quotas in Shared Clustern.
  • LimitRange-Defaults zu klein.
  • Quotas nicht mit Teams abgestimmt.
  • HPA kann nicht skalieren wegen Quota.
  • Build- und Runtime-Ressourcen vermischen.
  • Fehlende ephemeral-storage-Quotas.
  • Quota-Fehler nicht in Runbooks.
Best Practices:
  • Quotas pro Environment und Team.
  • LimitRange-Defaults bewusst.
  • Serviceklassen definieren.
  • HPA-MaxReplicas mit Quota abstimmen.
  • Ephemeral Storage einbeziehen.
  • Quota-Auslastung monitoren.
  • Self-Service-Anzeige für Teams.
  • Ausnahmeprozess für Spitzenlast.
Stolperfalle:

Wenn HPA bis 50 Pods skalieren darf, die Quota aber nur 10 Pods oder zu wenig CPU erlaubt, bleibt Autoscaling Theorie.

Merksatz:

Multi-Tenancy ohne Quotas ist Hoffnung, keine Plattformstrategie.

B6-K13-Q008 – Startup, Readiness, Warmup und SkalierungsdynamikReadiness ist der Vertrag: Jetzt darf Traffic kommen.
Frage:

„Warum reicht es nicht, HPA zu konfigurieren, wenn Java-Pods langsam starten?“

Kurz erklärt:

Autoscaling ist nur so gut wie die Geschwindigkeit, mit der neue Pods wirklich bereit werden. Startup, Readiness, Warmup und JIT beeinflussen Skalierungsdynamik.

Musterantwort:

HPA kann neue Pods erzeugen, aber diese Pods liefern erst Kapazität, wenn sie scheduled, gestartet, initialisiert, warmed up und ready sind. Java-Apps brauchen häufig Klassenladen, Spring Context, JIT-Warmup, Cache-Aufbau, Connection-Pool-Aufbau und eventuell Schema-/Config-Checks. Wenn Readiness zu früh true wird, erhält ein kalter Pod Traffic und verschlechtert Latenz. Wenn Startup zu langsam ist, reagiert HPA zu spät.

Deshalb gehören Startup-Probe, Readiness, Warmup-Strategie, MinReplicas und Lasttests zusammen.

Senior-Antwort:

„Ein Pod ist nicht dann Kapazität, wenn er erstellt wurde. Er ist Kapazität, wenn er warm und ready ist.“

Häufige Fehler:
  • Readiness zu früh true.
  • Liveness prüft Datenbank.
  • Keine StartupProbe.
  • MinReplicas zu niedrig.
  • Zu große Images.
  • Cache-Warmup ohne Backpressure.
  • HPA-Lasttest ohne kalte Pods.
Best Practices:
  • StartupProbe für langsame Java-Starts.
  • Readiness nur bei echter Traffic-Bereitschaft.
  • Liveness minimal halten.
  • MinReplicas als Basiskapazität.
  • Warmup kontrollieren.
  • Image Pull optimieren.
  • JIT- und Cache-Verhalten testen.
  • Scale-out unter realer Last testen.
Stolperfalle:

Eine Liveness-Probe, die die Datenbank prüft, kann bei einem Datenbankproblem alle App-Pods neu starten und den Incident verschlimmern.

Merksatz:

Readiness ist der Vertrag: Jetzt darf Traffic kommen.

B6-K13-Q009 – Load Testing, Capacity Planning und SLOsCapacity Planning ist SLO plus Messung plus Kostenbewusstsein.
Frage:

„Wie testen Sie, ob Autoscaling wirklich funktioniert?“

Kurz erklärt:

Capacity Planning verbindet Lasttests, SLOs, Ressourcen, HPA, Downstreams und Kosten. Ohne Lasttest ist Autoscaling nur Vermutung.

Musterantwort:

Ich teste es unter realistischen Bedingungen: echte Containerlimits, echte JVM-Optionen, echte Probes, echte Downstream-Kapazität oder realistische Stubs, echte HPA-Konfiguration und beobachtete Metriken. Ich messe nicht nur Durchschnittslatenz, sondern P95/P99, Fehlerrate, Pod-Time-to-Ready, HPA-Reaktionszeit, CPU-Throttling, GC, DB-Pool, Queue-Lag und Kosten.

OpenShift-Skalierbarkeit hängt außerdem von Cluster- und Objektgrößen ab. Red Hat weist bei Performance- und Skalierungsempfehlungen darauf hin, dass Control-Plane-Ressourcen abhängig von Anzahl und Typ der Nodes und Objekte dimensioniert werden müssen und während Spitzen CPU- und Memory-Headroom wichtig ist.

Senior-Antwort:

„Autoscaling, das nie unter Last getestet wurde, ist ein Wunsch, kein Betriebsmodell.“

Häufige Fehler:
  • Nur Durchschnittslatenz messen.
  • Lasttest ohne HPA.
  • Stubs zu schnell.
  • Downstreams ignorieren.
  • Keine Soak Tests.
  • Keine Kostenanalyse.
  • Test mit anderen Limits als Prod.
  • JIT-Warmup ignorieren.
Best Practices:
  • SLOs vor Test definieren.
  • Realistische Limits.
  • HPA im Test aktiv.
  • P95/P99 messen.
  • Downstream-Kapazität messen.
  • Spike und Soak Tests.
  • JVM-Metriken erfassen.
  • Kosten pro RPS betrachten.
Stolperfalle:

Mehr Pods können die Fehlerrate erhöhen, wenn der eigentliche Engpass die Datenbank oder ein externer Provider ist.

Merksatz:

Capacity Planning ist SLO plus Messung plus Kostenbewusstsein.

B6-K13-Q010 – Performance-Governance, Kosten und Anti-PatternsPerformance-Governance skaliert Effizienz, nicht nur Pods. 24h
Frage:

„Welche Autoscaling- und Ressourcen-Anti-Patterns sehen Sie auf OpenShift?“

Kurz erklärt:

Performance-Governance verbindet Standards, Messungen, Kosten, SLOs und Plattformleitplanken. Anti-Patterns entstehen, wenn Teams Autoscaling als Ersatz für Architektur verstehen.

Musterantwort:

Häufige Anti-Patterns sind fehlende Requests, unrealistische Limits, HPA ohne sinnvolle Metriken, CPU-HPA ohne CPU Requests, maxReplicas ohne Downstream-Kapazität, Java-Heap gleich Containerlimit, CPU-Limits für latenzkritische Services ohne Messung, VPA Auto ohne Governance, Cluster Autoscaler als Sofortreaktion, keine Quotas und keine Lasttests.

Eine reife Plattform definiert Serviceklassen, Ressourcenvorlagen, HPA-Templates, JVM-Defaults, Quotas, Dashboards, Alerts, Kostenmodelle und Lasttestanforderungen. Red Hat stellt Performance- und Skalierungsempfehlungen für OpenShift bereit, einschließlich Cluster- und Control-Plane-Dimensionierung sowie Skalierungspraktiken.

Senior-Antwort:

„Autoscaling ist kein Freifahrtschein. Es braucht SLOs, Downstream-Kapazität, Ressourcenstandards und Kostenfeedback.“

Häufige Fehler:
  • Ressourcenwerte kopieren.
  • Keine Runtime Reviews.
  • Teams sehen Kosten nicht.
  • HPA-Metrik passt nicht.
  • Quotas fehlen.
  • Keine Downstream-Kapazitätsplanung.
  • Kein Alert auf Throttling/OOM.
  • Performance-Test ohne Prod-ähnliche Limits.
Best Practices:
  • Serviceklassen definieren.
  • Ressourcen-Templates bereitstellen.
  • HPA-Templates.
  • JVM-Defaults pro Klasse.
  • Runtime-Metriken reviewen.
  • Kosten sichtbar machen.
  • Quotas und LimitRanges.
  • Load-Test-Gates.
Stolperfalle:

Ein HPA kann einen schlechten Service nicht gesund skalieren, wenn der Engpass eine nicht skalierende Abhängigkeit ist.

Merksatz:

Performance-Governance skaliert Effizienz, nicht nur Pods.

B6-K14 – Kapitel 14: Upgrades, Day-2 Operations und Cluster Lifecycle 10 Karten

B6-K14-Q001 – Cluster Updates: CVO, Update Channels und Release ImagesCVO aktualisiert die Plattform; Betreiber verantworten den Change. 24h
Frage:

„Wie funktioniert ein OpenShift-Cluster-Update grundsätzlich?“

Kurz erklärt:

Der Cluster Version Operator orchestriert OpenShift-Updates. Update Channels bestimmen, welche Updatepfade angeboten werden. Release Images enthalten die gewünschte Plattformversion.

Musterantwort:

Ein OpenShift-Update wird durch den Cluster Version Operator orchestriert. Der CVO prüft verfügbare Updatepfade, nutzt ein Release Image für die Zielversion und reconciled Cluster Operators in Richtung des gewünschten Zustands. Red Hat beschreibt, dass der CVO die primäre Komponente ist, die den OpenShift-Updateprozess orchestriert.

Als Senior starte ich ein Update nicht blind. Ich prüfe vorher ClusterOperator-Zustand, verfügbare Updatepfade, Release Notes, bekannte Risiken, etcd-Backup, Kapazität, PodDisruptionBudgets, kritische Anwendungen, Operator-Kompatibilität und Wartungsfenster.

Senior-Antwort:

„Ein OpenShift-Update ist ein Plattform-Release. Ich behandle es wie ein produktionskritisches Change, nicht wie einen Paketmanager-Klick.“

Häufige Fehler:
  • Update trotz degraded ClusterOperators.
  • Kein etcd-Backup.
  • Keine Staging-Validierung.
  • Update Channel unbewusst ändern.
  • PDBs blockieren Node Drains.
  • Keine Kapazitätsreserve.
  • Anwendungsteams nicht informieren.
  • Operator-Kompatibilität ignorieren.
Best Practices:
  • Updates zuerst in Nicht-Prod.
  • ClusterOperators vorab prüfen.
  • Release Notes lesen.
  • etcd-Backup vor Update.
  • PDBs prüfen.
  • Wartungsfenster definieren.
  • Kapazitäts-Headroom bereitstellen.
  • Kritische Workloads beobachten.
Stolperfalle:

Ein grüner Updatepfad bedeutet nicht, dass Ihre Anwendungen updatebereit sind.

Merksatz:

CVO aktualisiert die Plattform; Betreiber verantworten den Change.

B6-K14-Q002 – Cluster Operators: Status, Degraded, Progressing und AvailableClusterOperator-Status ist Plattform-Vitalzeichen.
Frage:

„Wie interpretieren Sie ClusterOperator-Status in OpenShift?“

Kurz erklärt:

Cluster Operators betreiben zentrale OpenShift-Komponenten. Ihr Status zeigt, ob Plattformfunktionen verfügbar, im Update oder gestört sind.

Musterantwort:

Ich schaue auf Available , Progressing , Degraded und Upgradeable . Available=True bedeutet, dass der Operator seine Funktion bereitstellt. Progressing=True kann während Updates oder Reconciliation normal sein. Degraded=True weist auf ein Problem hin. Upgradeable=False kann Updates blockieren oder davor warnen.

Red Hat beschreibt Cluster Operators als Red-Hat-gelieferte Operators, die die architektonische Grundlage von OpenShift bilden, standardmäßig installiert sind und durch den CVO verwaltet werden.

Senior-Antwort:

„ClusterOperators sind die Organe des Clusters. Wenn eines degraded ist, spüren Anwendungen oft Symptome an ganz anderer Stelle.“

Häufige Fehler:
  • Nur Pod-Status prüfen.
  • Degraded Operator ignorieren.
  • Progressing während Update falsch interpretieren.
  • Upgradeable=False ignorieren.
  • Keine ClusterOperator-Alerts.
  • App-Fehler suchen, obwohl Plattformoperator gestört.
  • Conditions nicht lesen.
Best Practices:
  • ClusterOperator-Status regelmäßig prüfen.
  • Alerts auf Degraded.
  • Conditions und Messages lesen.
  • Operator-spezifische Runbooks.
  • Zusammenhang mit App-Symptomen herstellen.
  • Vor Updates alle Operators gesund.
  • Nach Updates Operators beobachten.
  • Historie dokumentieren.
Stolperfalle:

Ein Anwendungsausfall kann durch einen Plattformoperator verursacht werden, obwohl alle App-Pods grün aussehen.

Merksatz:

ClusterOperator-Status ist Plattform-Vitalzeichen.

B6-K14-Q003 – Machine Config Operator, Node Updates und RebootsMCO ändert Nodes; Nodes tragen Anwendungen.
Frage:

„Welche Rolle spielt der Machine Config Operator bei OpenShift Day-2 Operations?“

Kurz erklärt:

Der Machine Config Operator verwaltet Betriebssystem- und Node-Konfiguration. Änderungen können Node-Drain und Reboot auslösen.

Musterantwort:

Der Machine Config Operator, kurz MCO, verwaltet Betriebssystem- und Node-Konfigurationen über MachineConfigs und MachineConfigPools. Er überwacht Nodes und orchestriert Konfigurationsupdates. Red Hat beschreibt, dass der MCO alle Cluster-Nodes überwacht und deren Konfigurationsupdates orchestriert; der Machine Config Daemon läuft auf jedem Node.

Viele MCO-Änderungen führen zu Drain und Reboot. Deshalb ist MCO-Betrieb direkt relevant für Anwendungsverfügbarkeit.

Senior-Antwort:

„MachineConfig ist Node-Betrieb als Code. Jede Änderung kann echte Workload-Disruption bedeuten.“

Häufige Fehler:
  • MachineConfig-Änderungen als harmlos ansehen.
  • Keine Kapazität für Node-Drain.
  • PDBs blockieren Drain.
  • Single-Replica-Workloads auf rebootenden Nodes.
  • Manuelle Node-Änderungen erzeugen Drift.
  • Degraded MCP ignorieren.
  • MCO-Logs nicht prüfen.
Best Practices:
  • MachineConfig-Änderungen wie Changes behandeln.
  • MCP-Status überwachen.
  • maxUnavailable bewusst setzen.
  • Kapazitätsreserve einplanen.
  • PDBs vor Updates prüfen.
  • Manuelle Node-Änderungen vermeiden.
  • Node-Reboots in Runbooks.
  • Kritische Workloads redundant.
Stolperfalle:

Ein Node-Reboot ist kein Plattformdetail, wenn Ihre Anwendung nur eine Replica hat.

Merksatz:

MCO ändert Nodes; Nodes tragen Anwendungen.

B6-K14-Q004 – Configuration Drift, manuelle Änderungen und GitOps für PlattformDay-2-Betrieb braucht deklarierte Wahrheit.
Frage:

„Was ist Configuration Drift, und warum ist sie gefährlich?“

Kurz erklärt:

Configuration Drift entsteht, wenn tatsächlicher Clusterzustand vom gewünschten Zustand abweicht. Plattform-GitOps reduziert Drift und macht Änderungen auditierbar.

Musterantwort:

Configuration Drift bedeutet, dass der tatsächliche Zustand eines Clusters oder Nodes vom gewünschten, verwalteten Zustand abweicht. Red Hat beschreibt im MCO-Kontext, dass Drift entsteht, wenn der On-Disk-Zustand eines Nodes von der MachineConfig abweicht, zum Beispiel durch manuelle Änderungen an Dateien, systemd Units oder Permissions. Der MCO kann den Node und den MachineConfigPool dann auf Degraded setzen.

Gefährlich ist Drift, weil sie Updates blockieren, Fehler schwer reproduzierbar machen und im Incident unbekannte Zustände erzeugen kann. Deshalb sollten Plattformänderungen über GitOps, Change-Prozess und deklarative Manifeste laufen.

Senior-Antwort:

„Drift ist der Feind der Wiederholbarkeit. Was nicht als gewünschter Zustand existiert, kann ich nicht zuverlässig betreiben.“

Häufige Fehler:
  • oc edit in Produktion ohne Git-Nachzug.
  • SSH auf Nodes und manuelle Änderungen.
  • Operator-Subscriptions manuell ändern.
  • Drift als harmlos betrachten.
  • Keine Drift Alerts.
  • Hotfixes nicht dokumentieren.
  • Prod unterscheidet sich dauerhaft von Stage.
Best Practices:
  • Plattform-GitOps.
  • Manuelle Änderungen vermeiden.
  • Hotfixes sofort in Git zurückführen.
  • Drift Detection aktiv.
  • MCO-Degraded ernst nehmen.
  • Change-Prozess für Plattformressourcen.
  • Clustervergleiche zwischen Umgebungen.
  • Audit Logs nutzen.
Stolperfalle:

Ein schneller manueller Fix in Prod ist oft der Anfang eines langfristig unbekannten Systemzustands.

Merksatz:

Day-2-Betrieb braucht deklarierte Wahrheit.

B6-K14-Q005 – etcd Backup, Restore und Disaster RecoveryControl-Plane-Recovery und Application-Data-Recovery sind zwei verschiedene Probleme. 24h
Frage:

„Warum ist ein etcd-Backup vor einem OpenShift-Update wichtig?“

Kurz erklärt:

etcd speichert den Clusterzustand. etcd-Backups sind Grundlage für Control-Plane-Recovery, ersetzen aber keine Application-Backups.

Musterantwort:

etcd speichert den Zustand aller Kubernetes/OpenShift-Ressourcen. Vor einem Update ist ein etcd-Backup wichtig, weil ein Restore mit einem Backup aus derselben z-stream Version erfolgen muss. Red Hat weist ausdrücklich darauf hin, vor einem Update ein etcd-Backup zu erstellen, weil beim Restore ein etcd-Backup aus derselben z-stream Release verwendet werden muss.

Wichtig ist aber: etcd-Backup ist Cluster-State-Backup, nicht automatisch Backup der Daten in Datenbanken, PVCs oder externen Systemen. Application Data braucht eigene Backup- und Restore-Strategien.

Senior-Antwort:

„etcd bringt mir den Clusterzustand zurück. Es bringt mir nicht automatisch die fachlichen Daten zurück.“

Häufige Fehler:
  • etcd-Backup als vollständiges App-Backup verstehen.
  • Backup nie restoren.
  • Backup aus falscher Version nutzen wollen.
  • Backup unsicher speichern.
  • Secrets-Schutz ignorieren.
  • PV-Daten nicht sichern.
  • GitOps-Repos nicht in DR planen.
Best Practices:
  • etcd-Backup vor Updates.
  • Regelmäßige Backups.
  • Restore-Tests.
  • Backup-Version dokumentieren.
  • Sichere Off-Cluster-Speicherung.
  • Application-Daten separat sichern.
  • GitOps-Repos sichern.
  • DR-Runbooks pflegen.
Stolperfalle:

Ein Backup, das nie restore-getestet wurde, ist im Ernstfall nur eine Vermutung.

Merksatz:

Control-Plane-Recovery und Application-Data-Recovery sind zwei verschiedene Probleme.

B6-K14-Q006 – Application Readiness für Cluster-WartungUpdatefähigkeit beginnt in der Anwendung.
Frage:

„Wie machen Sie Java-Anwendungen wartungs- und updatefähig auf OpenShift?“

Kurz erklärt:

Anwendungen müssen Clusterwartung tolerieren: Node-Drain, Reboots, Pod-Neustarts, Rollouts und temporäre Kapazitätsverluste.

Musterantwort:

Ich stelle sicher, dass Anwendungen nicht an einzelne Pods oder Nodes gebunden sind. Sie brauchen mehrere Replicas, korrekte Readiness, graceful shutdown, PodDisruptionBudget, idempotente Verarbeitung, externe Sessions oder stateless Sessions, robuste Downstream-Reconnects und klare Timeout-/Retry-Strategien. Für Single-Replica- oder Stateful-Workloads braucht es bewusstes Wartungsfenster oder Operator-gesteuerte Verfügbarkeit.

Clusterupdates und MachineConfig-Änderungen können Nodes drainen und rebooten. Deshalb muss Anwendungshochverfügbarkeit mit Plattformwartung zusammenpassen.

Senior-Antwort:

„Clusterwartung ist normaler Betrieb. Wenn eine App das nicht toleriert, ist sie nicht cloud-native genug.“

Häufige Fehler:
  • Single Replica in Prod.
  • Kein PDB.
  • PDB zu strikt.
  • Readiness bleibt beim Shutdown true.
  • SIGTERM wird ignoriert.
  • Kafka-Nachrichten nicht idempotent.
  • Sessions im Pod-Speicher.
  • Keine Kapazität für Wartung.
Best Practices:
  • Mindestens zwei bis drei Replicas für kritische APIs.
  • PDB bewusst setzen.
  • Graceful shutdown testen.
  • Readiness beim Shutdown entfernen.
  • Idempotente Verarbeitung.
  • Externe Session Stores vermeiden oder bewusst einsetzen.
  • HPA-MinReplicas mit Wartungskapazität.
  • Wartung in Lasttests simulieren.
Stolperfalle:

Ein zu striktes PDB kann Node-Drains blockieren und damit Clusterupdates verzögern oder verhindern.

Merksatz:

Updatefähigkeit beginnt in der Anwendung.

B6-K14-Q007 – Operator Lifecycle Updates und Plattform-AbhängigkeitenOperatorupdates sind Betriebsänderungen mit API-Wirkung.
Frage:

„Warum sind Operator-Updates Day-2-kritisch?“

Kurz erklärt:

Neben Clusterupdates gibt es Operatorupdates. Operatoren können CRDs, Controllerlogik und gemanagte Workloads verändern.

Musterantwort:

Operatoren betreiben Plattform- oder Anwendungsdienste wie GitOps, Pipelines, Datenbanken, Kafka, Service Mesh, Zertifikate oder Storage. Ein Operator-Update kann CRDs, RBAC, Controllerlogik und gemanagte Workloads ändern. OLM kontrolliert Installation, Upgrade und RBAC von Operators im Cluster.

Deshalb behandle ich Operator-Updates wie Plattformchanges: Release Notes lesen, Staging testen, InstallPlan prüfen, CR-Kompatibilität prüfen, Backup/Restore bewerten und Anwendungsteams informieren.

Senior-Antwort:

„Operatoren sind Betreiber. Wenn ich den Betreiber aktualisiere, kann sich das betriebene System ändern.“

Häufige Fehler:
  • Operator Auto-Update in Prod.
  • InstallPlans nicht lesen.
  • CRD-Änderungen ignorieren.
  • Stateful-Operator ohne Backup.
  • Keine Staging-Tests.
  • Anwendungsteams nicht informieren.
  • Operator-Owner unklar.
Best Practices:
  • Operator-Owner definieren.
  • Manual Approval für kritische Operators.
  • Release Notes prüfen.
  • Staging zuerst.
  • InstallPlan analysieren.
  • CR-Kompatibilität prüfen.
  • Backup/Restore vor Stateful-Updates.
  • Wartungsfenster.
Stolperfalle:

Ein erfolgreich aktualisierter Operator sagt nichts darüber aus, ob alle Custom Resources danach gesund sind.

Merksatz:

Operatorupdates sind Betriebsänderungen mit API-Wirkung.

B6-K14-Q008 – Capacity, Certificates, Expiry und Routine OperationsRoutine ist Prävention in Betriebsform.
Frage:

„Welche regelmäßigen Betriebsaufgaben erwarten Sie auf einer OpenShift-Plattform?“

Kurz erklärt:

Day-2 Operations bestehen aus regelmäßigen Prüfungen: Kapazität, Zertifikate, Nodes, Operators, Quotas, Storage, Alerts, Backups und Updates.

Musterantwort:

Ich erwarte tägliche, wöchentliche und monatliche Routinen: ClusterOperator-Health, Node-Zustand, Alerts, Pending Pods, Quota- und Storage-Auslastung, Zertifikats- und CSR-Zustand, Backup-Validierung, Update-Planung, Operator-Versionen, Security-Audits, Kostenreviews und Rightsizing.

OpenShift ist stark operatorgesteuert, aber nicht betreiberlos. Automatisierung reduziert manuelle Arbeit, ersetzt aber nicht Operational Governance.

Senior-Antwort:

„Day-2-Betrieb heißt: Probleme erkennen, bevor sie Incidents werden.“

Häufige Fehler:
  • Nur reagieren, nie trendbasiert planen.
  • Alerts ohne Review.
  • Zertifikate vergessen.
  • Quotas erst im Deployment-Fehler sehen.
  • Backups nie prüfen.
  • Operator-Versionen veralten.
  • Keine Plattform-KPIs.
Best Practices:
  • Routine-Checklisten.
  • Automatisierte Reports.
  • Capacity Trends.
  • Certificate Expiry Alerts.
  • Quota-Dashboards.
  • Storage-Wachstum beobachten.
  • Backup-Validierung.
  • Operator-Review.
Stolperfalle:

OpenShift automatisiert viel, aber Automatisierung ohne Review kann Fehler nur schneller verbreiten.

Merksatz:

Routine ist Prävention in Betriebsform.

B6-K14-Q009 – Troubleshooting Cluster Updates und Stuck RolloutsUpdate-Diagnose beginnt bei ClusterVersion, nicht bei einzelnen Pods.
Frage:

„Wie debuggen Sie ein hängendes OpenShift-Update?“

Kurz erklärt:

Update-Probleme analysiert man über ClusterVersion, ClusterOperators, MachineConfigPools, Nodes, Events, Logs und must-gather.

Musterantwort:

Ich beginne mit oc describe clusterversion version , um Update-Historie, Zielversion und Conditions zu sehen. Red Hat dokumentiert, dass ClusterVersion-Historie initiale, Minor- und z-stream-Updates enthält und per Web Console oder oc describe clusterversions/version geprüft werden kann. Danach prüfe ich ClusterOperators, MachineConfigPools, Nodes, Events und relevante Operator-Logs. Wenn nötig, erstelle ich ein must-gather für tiefere Analyse.

Wichtig ist, nicht blind weiterzudrücken. Ein hängendes Update hat meist eine konkrete blockierende Condition: degraded Operator, stuck MCP, PDB blockiert Drain, fehlende Kapazität, Image Pull, Node-Problem oder API-Problem.

Senior-Antwort:

„Ein hängendes Update ist kein Mysterium. Es ist eine Kette von Conditions — man muss sie nur von oben nach unten lesen.“

Häufige Fehler:
  • Nur Web Console prüfen.
  • ClusterVersion Conditions ignorieren.
  • MCP nicht prüfen.
  • PDB-Blockaden übersehen.
  • Events nicht lesen.
  • Must-gather zu spät.
  • Workaround ohne Root Cause.
  • Update während degraded Zustand starten.
Best Practices:
  • Diagnose von oben nach unten.
  • ClusterVersion zuerst.
  • ClusterOperators danach.
  • MCP und Nodes prüfen.
  • Events nach Zeit sortieren.
  • PDBs prüfen.
  • must-gather früh bei komplexen Fällen.
  • Änderungen dokumentieren.
Stolperfalle:

PDBs schützen Anwendungen, können aber Node-Drains und damit Updates blockieren.

Merksatz:

Update-Diagnose beginnt bei ClusterVersion, nicht bei einzelnen Pods.

B6-K14-Q010 – Cluster Lifecycle Governance und Anti-PatternsCluster Lifecycle ist kontinuierliche Produktpflege. 24h
Frage:

„Welche Anti-Patterns sehen Sie im OpenShift Day-2-Betrieb?“

Kurz erklärt:

Cluster Lifecycle Governance definiert, wie Cluster erstellt, aktualisiert, betrieben, geprüft, gesichert und außer Betrieb genommen werden.

Musterantwort:

Typische Anti-Patterns sind: Updates nur im Notfall, kein regelmäßiger Update-Zyklus, kein Staging-Test, kein etcd-Backup, keine Restore-Tests, manuelle Node-Änderungen, Operator-Auto-Updates ohne Review, keine PDBs, Single-Replica-Workloads, fehlende Kapazitätsreserve, Drift zwischen Clustern, keine Runbooks, keine Plattform-SLOs und fehlende Kommunikation mit Anwendungsteams.

Eine reife Plattform hat Lifecycle Governance: Versionsinventar, Update-Cadence, Update Channels, Staging-vor-Prod, etcd-Backups, DR-Tests, Plattform-GitOps, Operator-Governance, Capacity Reviews und Post-Update-Reviews.

Senior-Antwort:

„Day-2-Betrieb ist kein Nachtrag zur Plattform. Er ist die Plattform.“

Häufige Fehler:
  • Cluster werden „vergessen“.
  • Keine Version Owner.
  • Updates zu selten.
  • Change-Fenster fehlen.
  • Keine DR-Übungen.
  • Support-Lifecycle ignorieren.
  • Anwendungsteams nicht einbinden.
  • Manuelle Änderungen nicht auditiert.
Best Practices:
  • Cluster-Inventar.
  • Regelmäßige Update-Cadence.
  • Staging-vor-Prod.
  • etcd-Backup vor Updates.
  • DR-Tests.
  • Plattform-GitOps.
  • Operator-Governance.
  • Post-Update-Checks.
Stolperfalle:

Ein Cluster, der nie aktualisiert wird, wirkt stabil — bis Security, Support oder Upgradepfade plötzlich zum Risiko werden.

Merksatz:

Cluster Lifecycle ist kontinuierliche Produktpflege.

B6-K15 – Kapitel 15: Java und Spring Boot auf OpenShift 10 Karten

B6-K15-Q001 – Deployment-Modell: Image, Deployment, Service und RouteDas Image liefert Code; das Deployment liefert Betriebsverhalten. 24h
Frage:

„Wie deployen Sie eine Spring-Boot-Anwendung produktionsreif auf OpenShift?“

Kurz erklärt:

Eine produktive Spring-Boot-Anwendung auf OpenShift besteht typischerweise aus einem unveränderlichen Image, einem Deployment, einem Service und optional einer Route für externen Zugriff.

Musterantwort:

Ich deploye nicht einfach ein JAR, sondern ein versioniertes, gescanntes und reproduzierbares Container Image. Das Deployment beschreibt Replicas, Ressourcen, Probes, SecurityContext, ConfigMaps, Secrets und ServiceAccount. Ein Service stellt stabile interne Erreichbarkeit bereit. Eine Route oder ein Ingress stellt externen Zugriff bereit, falls nötig.

Red Hat beschreibt Deployments als Objekte, die den gewünschten Zustand einer Anwendungskomponente als Pod-Template definieren; ReplicaSets halten dabei punktuelle Versionen dieses Pod-Templates. Für OpenShift-Anwendungen können bestehende Container Images aus Image Streams oder Registries verwendet werden.

Senior-Antwort:

„Auf OpenShift deploye ich keine Anwendung, sondern einen Betriebsvertrag: Image, Runtime, Ressourcen, Health, Netzwerk, Config und Observability.“

Häufige Fehler:
  • latest in Produktion.
  • Direktes Deployment ohne Service.
  • Management-Endpunkte öffentlich exponieren.
  • Single Replica für kritische APIs.
  • Keine Ressourcenangaben.
  • Keine Probes.
  • Image pro Umgebung neu bauen.
  • Konfiguration ins Image backen.
Best Practices:
  • Immutable Image.
  • Digest-basierte Promotion.
  • Deployment, Service und Route sauber trennen.
  • Management-Port intern halten.
  • Mehrere Replicas für kritische APIs.
  • Resources, Probes und SecurityContext definieren.
  • GitOps für Manifeste.
  • Rollout-Status beobachten.
Stolperfalle:

Eine Spring-Boot-App, die lokal startet, ist noch keine produktionsreife OpenShift-Anwendung.

Merksatz:

Das Image liefert Code; das Deployment liefert Betriebsverhalten.

B6-K15-Q002 – Container Images: OpenJDK 21, Layers, Rootless und Supply ChainEin gutes Image ist klein, sicher, reproduzierbar und rootless.
Frage:

„Wie bauen Sie ein gutes Java-21-Image für OpenShift?“

Kurz erklärt:

Ein gutes Java-Image ist klein, reproduzierbar, rootless-kompatibel, gescannt, signiert und nutzt eine unterstützte OpenJDK-21-Basis.

Musterantwort:

Ich verwende eine unterstützte OpenJDK-21-Runtime-Basis, kopiere nur das lauffähige Artefakt hinein, vermeide Root-Annahmen, schreibe Logs nach stdout, speichere keine Secrets im Image, setze containerbewusste JVM-Optionen und signiere beziehungsweise scanne das Image in CI. Red Hat stellt für Red Hat build of OpenJDK 21 eine Dokumentation bereit, die erklärt, wie Anwendungen in Container verpackt werden.

Für OpenShift ist Rootless-Kompatibilität besonders wichtig, weil restriktive Security Defaults verhindern sollen, dass normale Anwendungen unnötige Root- oder Host-Rechte benötigen.

Senior-Antwort:

„Ein Java-Container ist kein kleiner VM-Ersatz. Er ist ein signiertes, rootless lauffähiges Artefakt mit klarer Runtime-Semantik.“

Häufige Fehler:
  • Secrets ins Image kopieren.
  • Root als Runtime-User.
  • Build-Tools im Runtime-Image.
  • Riesige Images.
  • Kein Image Scan.
  • Keine Signatur.
  • JVM-Heap hart falsch setzen.
  • Dependencies aus unsicheren Quellen.
Best Practices:
  • Unterstützte OpenJDK-21-Basis.
  • Runtime-only Image.
  • Rootless-kompatibel.
  • Keine Secrets im Image.
  • Logs nach stdout.
  • Image scan und SBOM.
  • Image signieren.
  • Digest statt latest .
Stolperfalle:

Ein Image, das nur als Root funktioniert, ist für OpenShift-Multi-Tenancy ein Betriebsrisiko.

Merksatz:

Ein gutes Image ist klein, sicher, reproduzierbar und rootless.

B6-K15-Q003 – Build-Strategien: S2I, Dockerfile, BuildConfig und PipelinesDer Buildprozess ist Teil der Software Supply Chain.
Frage:

„Wann nutzen Sie S2I, Dockerfile oder OpenShift Pipelines für Java?“

Kurz erklärt:

Java-Anwendungen können über S2I, Dockerfile, BuildConfig oder Tekton/OpenShift Pipelines gebaut werden. Für Enterprise-Delivery ist Pipeline-basierte Orchestrierung meist am stärksten.

Musterantwort:

S2I ist gut für standardisierte Plattform-Builds und schnellen Einstieg. Dockerfile gibt maximale Kontrolle über Image-Aufbau, Layering und Runtime. OpenShift Pipelines ist sinnvoll, wenn Build, Test, Scan, SBOM, Signatur und GitOps-Promotion orchestriert werden sollen. Red Hat beschreibt S2I-Images als Runtime-Base-Images für Sprachen wie Java, in die Code eingefügt werden kann. Builds transformieren Source Code oder andere Eingaben typischerweise in ein lauffähiges Container Image; ein BuildConfig beschreibt dabei den Buildprozess.

Für produktive Enterprise-Plattformen bevorzuge ich meist Pipelines mit klaren Golden-Path-Tasks, weil dort Qualität und Supply-Chain-Security besser integrierbar sind.

Senior-Antwort:

„S2I ist Einstieg, Dockerfile ist Kontrolle, Pipeline ist Governance.“

Häufige Fehler:
  • Build-Tools im Runtime-Image.
  • Keine Tests im Buildflow.
  • S2I ohne Versionierung verstehen.
  • Dockerfile pro Team wild variieren.
  • Keine SBOM.
  • Kein Digest-Output.
  • Build und Deploy untrennbar koppeln.
Best Practices:
  • Build-Strategie standardisieren.
  • Tekton für Enterprise-CI/CD.
  • Dockerfile-Templates freigeben.
  • S2I für schnelle Standardfälle prüfen.
  • Tests vor Image-Promotion.
  • Scans und SBOM integrieren.
  • Build- und Runtime-Image trennen.
  • Digest als Ergebnis nutzen.
Stolperfalle:

Ein erfolgreicher Build ist kein vertrauenswürdiges Release, wenn Tests, Scans, SBOM und Promotion fehlen.

Merksatz:

Der Buildprozess ist Teil der Software Supply Chain.

B6-K15-Q004 – Spring Boot Configuration: ConfigMaps, Secrets und ProfilesSpring Boot liest flexibel; Senior-Teams validieren streng.
Frage:

„Wie konfigurieren Sie Spring Boot auf OpenShift sauber?“

Kurz erklärt:

Spring-Boot-Konfiguration sollte externalisiert, typisiert, validiert und rolloutfähig sein. ConfigMaps sind für nicht-sensitive Werte, Secrets für sensitive Werte.

Musterantwort:

Ich trenne Image und Konfiguration. Nicht-sensitive Werte kommen aus ConfigMaps, sensitive Werte aus Secrets oder einem externen Secret Store. In Spring Boot binde ich Konfiguration typisiert über @ConfigurationProperties und validiere sie beim Start. Red Hat beschreibt ConfigMaps als Mechanismus, um Container mit Konfigurationsdaten zu versorgen, während Container OpenShift-agnostisch bleiben.

Wichtig ist außerdem: Viele Spring-Boot-Werte werden beim Start gelesen. Wenn ConfigMap oder Secret geändert wird, braucht die Anwendung häufig einen kontrollierten Rollout.

Senior-Antwort:

„Konfiguration ist kein Nebensatz. Sie ist produktiver Systemzustand und braucht Typen, Validierung und Release-Disziplin.“

Häufige Fehler:
  • Secrets in ConfigMaps.
  • Config ins Image backen.
  • @Value überall verstreuen.
  • Keine Config-Validierung.
  • Defaults verdecken Prod-Fehler.
  • ConfigMap ändern ohne Rollout.
  • Secrets in Logs.
  • Profiles pro Umgebung neu bauen.
Best Practices:
  • Config externalisieren.
  • Secrets trennen.
  • External Secrets prüfen.
  • @ConfigurationProperties .
  • Bean Validation.
  • Config-Checksums.
  • Rollout bei Startzeit-Config.
  • Secret-Rotation planen.
Stolperfalle:

Eine geänderte Secret-Env-Var ändert nicht automatisch die laufende JVM.

Merksatz:

Spring Boot liest flexibel; Senior-Teams validieren streng.

B6-K15-Q005 – Health Checks: Startup, Readiness, Liveness und ActuatorLiveness ist nicht Readiness mit anderem Namen.
Frage:

„Wie konfigurieren Sie Health Checks für Spring Boot auf OpenShift?“

Kurz erklärt:

Startup, Readiness und Liveness beantworten unterschiedliche Fragen. Spring Boot Actuator kann passende Health-Gruppen bereitstellen.

Musterantwort:

Ich trenne Startup, Readiness und Liveness. Startup schützt langsame Initialisierung. Readiness entscheidet, ob der Pod Traffic bekommen darf. Liveness erkennt, ob der Prozess irreparabel hängt und neu gestartet werden soll. OpenShift bietet Health Checks, um ungesunde Container zu erkennen und zu behandeln.

Bei Spring Boot nutze ich Actuator-Probes und halte Management-Endpunkte intern. Liveness sollte keine harte Abhängigkeit wie Datenbank oder externen Provider prüfen, sonst kann ein Downstream-Problem alle App-Pods neu starten.

Senior-Antwort:

„Readiness schützt Nutzertraffic. Liveness schützt den Prozess. Wer das verwechselt, startet im Incident seine eigene App kaputt.“

Häufige Fehler:
  • Liveness prüft Datenbank.
  • Keine StartupProbe bei langsamer App.
  • Readiness zu früh true.
  • Management-Endpunkte öffentlich erreichbar.
  • Probe-Timeouts zu eng.
  • Actuator falsch exponiert.
  • Probe-Pfad nicht getestet.
Best Practices:
  • StartupProbe für langsame Java-Apps.
  • Readiness für Traffic-Bereitschaft.
  • Liveness minimal.
  • Management-Port intern.
  • Actuator-Probes nutzen.
  • Probe-Failures in Events prüfen.
  • Graceful shutdown mit Readiness koppeln.
  • Probes unter Last testen.
Stolperfalle:

Eine Liveness-Probe gegen die Datenbank kann einen Datenbankausfall in einen vollständigen App-Restart-Sturm verwandeln.

Merksatz:

Liveness ist nicht Readiness mit anderem Namen.

B6-K15-Q006 – JVM-Ressourcen: Memory, CPU, GC und Container-ErgonomieHeap ist wichtig, aber Container-Memory ist größer als Heap.
Frage:

„Wie dimensionieren Sie eine Spring-Boot-JVM auf OpenShift?“

Kurz erklärt:

Java im Container braucht realistische Memory- und CPU-Planung. Container-Memory ist Prozessbudget, nicht Heapbudget.

Musterantwort:

Ich starte mit Serviceklasse und Lastprofil: CPU-bound, IO-bound, latenzkritisch, Worker oder Batch. Dann setze ich Requests für Scheduling, Memory-Limits als Schutz und JVM-Heap als Anteil des Containerlimits. Heap darf nicht das gesamte Limit belegen, weil Metaspace, Direct Buffers, Thread Stacks, Code Cache, Agents und native Libraries ebenfalls Speicher brauchen.

CPU-Limits setze ich bei latenzkritischen Services vorsichtig oder gar nicht, wenn die Plattform-Governance das erlaubt, weil CPU-Throttling P99-Latenz verschlechtern kann. Memory-Limits bleiben wichtig, weil OOM sonst den Node gefährden kann.

Senior-Antwort:

„Bei Java auf OpenShift dimensioniere ich nicht den Heap, sondern den gesamten Prozess im Container.“

Häufige Fehler:
  • Heap gleich Containerlimit.
  • Keine JVM-Metriken.
  • CPU-Limits blind setzen.
  • Zu viele Threads.
  • Direct Memory ignorieren.
  • APM-Agent-Overhead vergessen.
  • OOMKilled im Java-Log suchen.
  • Requests aus Bauchgefühl setzen.
Best Practices:
  • Heap-Anteil bewusst wählen.
  • Native Memory einplanen.
  • JVM-Metriken aktivieren.
  • GC-Pausen alerten.
  • CPU-Throttling messen.
  • Thread-Pools begrenzen.
  • Connection-Pools dimensionieren.
  • Lasttests mit echten Limits.
Stolperfalle:

Ein OOMKilled-Pod kann ohne Java- OutOfMemoryError verschwinden, weil der Kernel den Prozess beendet.

Merksatz:

Heap ist wichtig, aber Container-Memory ist größer als Heap.

B6-K15-Q007 – Observability: Actuator, Micrometer, Logs und OpenTelemetryProduktionsreife Java-Services sind beobachtbar, nicht nur lauffähig.
Frage:

„Welche Observability erwarten Sie von einem Spring-Boot-Service auf OpenShift?“

Kurz erklärt:

Produktive Java-Observability kombiniert Actuator/Micrometer-Metriken, strukturierte Logs, Trace IDs und OpenTelemetry.

Musterantwort:

Ich erwarte HTTP-Metriken, JVM-Metriken, GC, Threads, Connection Pools, Executor-Metriken, fachliche Metriken, strukturierte JSON-Logs mit Trace ID, Distributed Tracing über OpenTelemetry und sinnvolle Alerts. OpenShift Observability umfasst laut Red Hat Echtzeit-Sichtbarkeit, Monitoring und Analyse von Metriken, Logs, Traces und Events.

Spring Boot Actuator und Micrometer sind Standardbausteine für Prometheus-Metriken. OpenTelemetry sorgt für verteilte Trace-Korrelation. Logs müssen nach stdout gehen und dürfen keine Secrets enthalten.

Senior-Antwort:

„Ich will im Incident vom Alert zum Trace, vom Trace zum Log und vom Log zur Version springen können.“

Häufige Fehler:
  • Nur Container-CPU monitoren.
  • Keine JVM-Metriken.
  • Logs ohne Trace ID.
  • Secrets in Logs.
  • Management-Port öffentlich.
  • User IDs als Metriklabels.
  • Kein Tracing über Messaging.
  • Kein Alert auf OOMKilled.
Best Practices:
  • Actuator standardisieren.
  • Management-Port intern.
  • ServiceMonitor als Code.
  • JSON-Logs.
  • Trace IDs in Logs.
  • OpenTelemetry nutzen.
  • JVM- und Business-Metriken.
  • High Cardinality vermeiden.
Stolperfalle:

Ohne Trace-ID-Korrelation sind Logs und Traces zwei getrennte Datensümpfe.

Merksatz:

Produktionsreife Java-Services sind beobachtbar, nicht nur lauffähig.

B6-K15-Q008 – Networking: Routes, Service Discovery, Timeouts und ResilienceJeder Remote Call braucht Grenzen.
Frage:

„Wie gestalten Sie Service-to-Service-Kommunikation für Spring Boot auf OpenShift?“

Kurz erklärt:

Java-Service-Kommunikation auf OpenShift braucht DNS, Services, Routes, NetworkPolicies, Timeouts, Retries und Resilience-Patterns.

Musterantwort:

Interne Services spreche ich über Kubernetes/OpenShift Service-DNS an, nicht über Pod-IPs. Cross-Namespace verwende ich qualifizierte Namen. Externe Kommunikation läuft über Routes, Egress, API Gateway oder externe Provider-Endpunkte. In Java setze ich immer Connect Timeout, Read Timeout, begrenzte Retries, Circuit Breaker oder Bulkheads, wenn nötig.

NetworkPolicies beschreiben, welche Kommunikation erlaubt ist. Die Anwendung muss damit zusammenpassen: Wenn ein Service Payment aufruft, muss diese Abhängigkeit in Config, NetworkPolicy, Observability und Runbook sichtbar sein.

Senior-Antwort:

„Service-to-Service ist kein bloßer URL-String. Es ist ein Vertrag aus DNS, Policy, Timeout, Retry, TLS und Observability.“

Häufige Fehler:
  • Pod-IPs hardcoden.
  • Keine Timeouts.
  • Unbegrenzte Retries.
  • Retry-Stürme bei Downstream-Ausfall.
  • NetworkPolicy-Abhängigkeiten vergessen.
  • DNS-Fehler als App-Bug behandeln.
  • Zu große Connection Pools pro Pod.
  • TLS-Konfiguration im Image verstecken.
Best Practices:
  • Service-DNS verwenden.
  • Cross-Namespace-FQDNs.
  • Timeouts immer setzen.
  • Retry-Budget begrenzen.
  • Circuit Breaker bei kritischen Downstreams.
  • Connection Pools dimensionieren.
  • NetworkPolicies als Kommunikationsvertrag.
  • Tracing für Downstream-Calls.
Stolperfalle:

Ohne Timeouts hängt ein Java-Thread länger als jedes Autoscaling helfen kann.

Merksatz:

Jeder Remote Call braucht Grenzen.

B6-K15-Q009 – State, Sessions, Datenbanken und MigrationenPods sind austauschbar; fachliche Daten nicht.
Frage:

„Wie gehen Sie mit Zustand in Spring-Boot-Anwendungen auf OpenShift um?“

Kurz erklärt:

Spring-Boot-Services sollten zustandsarm sein. Fachlicher Zustand gehört in Datenbanken, Object Storage oder spezialisierte Systeme. Migrationen müssen rolling-update-kompatibel sein.

Musterantwort:

Ich halte API-Pods möglichst stateless. HTTP-Sessions gehören nicht in den Pod-Speicher, wenn der Service horizontal skaliert. Fachlicher Zustand gehört in Datenbanken, Object Storage oder Messaging-Systeme. Temporäre Dateien gehören in emptyDir , nicht in das Container-Dateisystem. Persistente Dateisysteme nutze ich nur, wenn der Use Case wirklich ein POSIX-Dateisystem braucht.

Bei Datenbankmigrationen achte ich auf Rolling-Update-Kompatibilität. Flyway oder Liquibase sind sinnvoll, aber Migrationen müssen so gestaltet sein, dass alte und neue App-Versionen während des Rollouts gleichzeitig funktionieren.

Senior-Antwort:

„Stateless heißt nicht zustandsloses System. Es heißt: Der Pod ist nicht der Ort, an dem fachlicher Zustand lebt.“

Häufige Fehler:
  • Sessions im Pod-Speicher.
  • Uploads im Container-Dateisystem.
  • RWX-PVC als Integrationslösung.
  • Destruktive Migration vor App-Rollout.
  • Jeder Pod führt Migration gleichzeitig aus.
  • Connection Pool pro Pod zu groß.
  • Cache als System of Record.
  • Keine Backward Compatibility.
Best Practices:
  • Stateless API-Pods.
  • Externe Datenhaltung.
  • Object Storage für Blobs.
  • Migrationen backward-compatible.
  • Expand-Contract.
  • Migration-Locking prüfen.
  • HikariCP pro Replica dimensionieren.
  • Datenbankkapazität mit HPA abstimmen.
Stolperfalle:

Mehr Replicas lösen keine Datenbankgrenzen, wenn jeder Pod seinen eigenen großen Connection Pool öffnet.

Merksatz:

Pods sind austauschbar; fachliche Daten nicht.

B6-K15-Q010 – Produktionsreife, Golden Path und Anti-PatternsProduktionsreife muss der Standardweg sein, nicht ein Zusatzprojekt. 24h
Frage:

„Welche Anti-Patterns sehen Sie bei Spring Boot auf OpenShift?“

Kurz erklärt:

Ein Java-on-OpenShift-Golden-Path standardisiert produktionsreife Anforderungen, ohne jedes Team alles neu erfinden zu lassen.

Musterantwort:

Typische Anti-Patterns sind: latest -Images, Root-Container, Secrets im Image, fehlende Requests, Heap gleich Containerlimit, keine Probes, Liveness gegen Datenbank, keine Timeouts, unbegrenzte Retries, Session-State im Pod, Management-Endpunkte öffentlich, keine JVM-Metriken, keine Trace IDs, Config ohne Validierung, RWX-PVC als Allzwecklösung, Deployment direkt aus CI ohne GitOps und keine Rollback-Strategie.

Ein Golden Path verhindert diese Fehler durch Standards: Image-Template, Deployment-Template, Probes, Ressourcen, SecurityContext, Actuator, ServiceMonitor, Config-Kontrakt, HPA, PDB, GitOps und CI/CD-Gates.

Senior-Antwort:

„Der beste Golden Path ist der Weg des geringsten Widerstands — und trotzdem der sicherste Weg.“

Häufige Fehler:
  • Golden Path nur als Beispielrepo.
  • Zu starre Templates.
  • Keine Ownership.
  • Keine Aktualisierung.
  • Security nur dokumentiert.
  • Observability optional.
  • Teams kopieren alte YAMLs.
  • Ausnahmen ohne Review.
Best Practices:
  • Golden Path als Produkt.
  • Templates plus Dokumentation.
  • Self-Service.
  • Security by default.
  • Observability by default.
  • GitOps by default.
  • CI/CD-Gates.
  • Regelmäßige Updates.
Stolperfalle:

Wenn der sichere Standard schwerer ist als der unsichere Workaround, gewinnen Workarounds.

Merksatz:

Produktionsreife muss der Standardweg sein, nicht ein Zusatzprojekt.

B7 – Band 7: Testing Professional

B7-K01 – Kapitel 1: Testing Fundamentals 10 Karten

B7-K01-Q001 – Teststrategie und TestpyramideTeste so niedrig wie möglich und so realistisch wie nötig. 24h
Frage:

„Was verstehen Sie unter einer guten Teststrategie für ein Enterprise-Java-System?“

Kurz erklärt:

Die Testpyramide beschreibt ein gesundes Verhältnis zwischen schnellen Unit Tests, realistischeren Integrationstests und wenigen teuren End-to-End-Tests.

Musterantwort:

Eine gute Teststrategie ist risikobasiert. Ich teste nicht alles auf derselben Ebene. Fachliche Regeln, Randfälle und reine Logik gehören in schnelle Unit Tests. Persistenz, Messaging, HTTP-Clients, Security-Konfiguration und Framework-Integration gehören in Integrationstests. Kritische Geschäftsflüsse werden durch wenige, stabile End-to-End- oder Systemtests abgesichert.

Die Testpyramide ist dabei kein Dogma, sondern ein Kostenmodell. Je höher der Test, desto realistischer, aber auch langsamer, fragiler und teurer. Ein Senior achtet deshalb darauf, möglichst viele Fehler früh und billig zu finden, ohne die realen Integrationsrisiken zu ignorieren.

Senior-Antwort:

„Ich teste Logik niedrig, Integration realistisch und Geschäftsflüsse selektiv. Die Testpyramide ist für mich ein Kosten-Nutzen-Modell.“

Häufige Fehler:
  • Alle Tests als @SpringBootTest .
  • Zu viele fragile E2E-Tests.
  • Unit Tests ohne fachlichen Wert.
  • Mocks für alles.
  • Keine Integrationstests für Datenbank oder Messaging.
  • Testpyramide als starres Dogma.
  • Lange Testlaufzeiten ohne Priorisierung.
  • Keine klare Testverantwortung pro Risiko.
Best Practices:
  • Teststrategie risikobasiert definieren.
  • Domainlogik ohne Framework testen.
  • Integration mit echten Infrastrukturkomponenten testen.
  • Wenige, stabile E2E-Tests.
  • Contract Tests für Servicegrenzen.
  • Schnelle lokale Feedbackschleife.
  • Teststufen in CI/CD trennen.
  • Testdaten kontrolliert verwalten.
Stolperfalle:

Viele E2E-Tests wirken wie hohe Qualität, erzeugen aber oft langsames, fragiles Feedback und verdecken schlechte Architektur.

Merksatz:

Teste so niedrig wie möglich und so realistisch wie nötig.

B7-K01-Q002 – Unit Tests: schnelle Tests für fachliche LogikUnit Tests testen Verhalten, nicht Mechanik.
Frage:

„Was macht einen guten Unit Test aus?“

Kurz erklärt:

Unit Tests prüfen kleine, deterministische Einheiten ohne externe Infrastruktur. Sie sind schnell, präzise und ideal für fachliche Regeln.

Musterantwort:

Ein guter Unit Test ist schnell, deterministisch, isoliert und fachlich aussagekräftig. Er testet beobachtbares Verhalten, nicht interne Implementierungsdetails. Er braucht keine Datenbank, keinen Spring Context, kein Netzwerk und keinen Container. Er sollte klar zeigen, welches Verhalten erwartet wird und warum.

Für mich sind Unit Tests die beste Ebene für Domainlogik: Preisberechnung, Validierung, Zustandsübergänge, Policies, Aggregates und Fehlerfälle.

Senior-Antwort:

„Ein guter Unit Test ist ein schneller Vertrag über fachliches Verhalten. Wenn ich dafür Spring starten muss, ist es wahrscheinlich kein Unit Test mehr.“

Häufige Fehler:
  • @SpringBootTest für reine Logik.
  • Private Methoden direkt testen.
  • Zu viel Mocking.
  • Tests hängen von Systemzeit ab.
  • Tests hängen von Reihenfolge ab.
  • Assertions sind zu allgemein.
  • Testnamen sagen nichts aus.
  • Implementierungsdetails statt Verhalten testen.
Best Practices:
  • Verhalten testen.
  • Fachliche Namen verwenden.
  • Keine Infrastruktur.
  • Keine privaten Methoden testen.
  • Zeit über Clock injizieren.
  • Kleine Testdaten.
  • Klare Arrange-Act-Assert-Struktur.
  • Randfälle und Fehlerfälle prüfen.
Stolperfalle:

Unit Tests, die interne Implementierungsdetails prüfen, brechen bei Refactoring, obwohl das Verhalten korrekt bleibt.

Merksatz:

Unit Tests testen Verhalten, nicht Mechanik.

B7-K01-Q003 – Integrationstests: echte Zusammenarbeit prüfenIntegrationstests testen Vertrauen an Grenzen.
Frage:

„Wann schreiben Sie Integrationstests statt Unit Tests?“

Kurz erklärt:

Integrationstests prüfen, ob mehrere Komponenten korrekt zusammenarbeiten: Spring, JPA, Datenbank, Transaktionen, Messaging, HTTP oder Security.

Musterantwort:

Ich schreibe Integrationstests, wenn das Risiko im Zusammenspiel liegt. JPA-Mappings, SQL, Transaktionen, HTTP-Serialisierung, Security-Filter, Kafka-Consumer, REST-Clients oder Spring-Konfiguration kann man nicht sinnvoll nur mit Unit Tests absichern. Dort will ich echte Framework- und Infrastrukturinteraktion sehen.

Ein Integrationstest ist teurer als ein Unit Test, liefert aber Vertrauen an Systemgrenzen. Deshalb setze ich ihn gezielt ein.

Senior-Antwort:

„Integrationstests nutze ich dort, wo die Wahrheit im Zusammenspiel liegt — nicht dort, wo reine Logik schneller und klarer testbar ist.“

Häufige Fehler:
  • Integrationstest für reine Logik.
  • Zu viele vollständige Spring-Kontexte.
  • H2 statt produktionsnaher Datenbank bei SQL-spezifischem Verhalten.
  • Tests teilen Daten unkontrolliert.
  • Keine Transaktionsisolation.
  • Flaky Container-Starts.
  • Frameworkverhalten mocken statt testen.
Best Practices:
  • Integration gezielt testen.
  • Test Slices nutzen.
  • Testcontainers für produktionsnahe Infrastruktur.
  • Testdaten isolieren.
  • Kontext-Caching respektieren.
  • Keine unnötigen @DirtiesContext .
  • Datenbankmigrationen mitprüfen.
  • Realistische Serialisierung testen.
Stolperfalle:

Eine gemockte Datenbank beweist nicht, dass JPA-Mapping, SQL und Transaktionen funktionieren.

Merksatz:

Integrationstests testen Vertrauen an Grenzen.

B7-K01-Q004 – Test Doubles: Mock, Stub, Fake und SpyMocke Grenzen, nicht Gedanken.
Frage:

„Was ist der Unterschied zwischen Mock, Stub, Fake und Spy?“

Kurz erklärt:

Test Doubles ersetzen echte Abhängigkeiten im Test. Mocks prüfen Interaktionen, Stubs liefern Werte, Fakes haben einfache funktionierende Implementierungen, Spies beobachten echte Objekte.

Musterantwort:

Ein Stub liefert vorbereitete Antworten. Ein Mock wird typischerweise verwendet, um Interaktionen zu verifizieren. Ein Fake ist eine einfache, aber funktionierende Implementierung, zum Beispiel ein In-Memory-Repository. Ein Spy beobachtet ein echtes Objekt und erlaubt teilweise Verifikation oder Stubbing.

Ich verwende Test Doubles bewusst. Zu viele Mocks führen zu Tests, die Implementierungsdetails statt Verhalten prüfen. Fakes sind oft lesbarer, wenn eine Abhängigkeit einfach nachbildbar ist.

Senior-Antwort:

„Mocks sind Werkzeuge, keine Architekturstrategie. Wenn jeder Test zehn Mocks braucht, sagt mir das etwas über das Design.“

Häufige Fehler:
  • Alles mocken.
  • Interne Methoden verifizieren.
  • Mocks statt einfacher Fakes.
  • Tests brechen bei Refactoring.
  • Mocking von Value Objects.
  • Mockito für reine Daten.
  • Spring Beans mocken, obwohl Integration relevant ist.
Best Practices:
  • Verhalten vor Interaktion.
  • Fakes bei einfachen Ports.
  • Mocks für externe Effekte.
  • Stubs für vorbereitete Antworten.
  • Wenige Verifikationen.
  • Keine Value Objects mocken.
  • Keine privaten Methoden mocken.
  • Test Doubles fachlich benennen.
Stolperfalle:

Ein Test, der jede interne Interaktion verifiziert, schützt Implementierung — nicht Verhalten.

Merksatz:

Mocke Grenzen, nicht Gedanken.

B7-K01-Q005 – Testbarkeit und ArchitekturWas leicht testbar ist, ist oft auch leicht änderbar. 24h
Frage:

„Wie beeinflusst Architektur die Testbarkeit?“

Kurz erklärt:

Testbarkeit entsteht durch Entkopplung, Dependency Injection, kontrollierte Zeit, klare Ports, deterministische Logik und kleine Verantwortlichkeiten.

Musterantwort:

Architektur bestimmt, ob Tests einfach oder schmerzhaft sind. Wenn Logik stark an Frameworks, statische Methoden, Systemzeit, Datenbank, globale Zustände oder Netzwerk gekoppelt ist, werden Tests langsam und fragil. Wenn Logik über klare Ports, Dependency Injection und deterministische Eingaben entkoppelt ist, kann sie schnell und präzise getestet werden.

Testbarkeit ist deshalb ein Architektursignal. Schwer testbarer Code ist oft auch schwer änderbarer Code.

Senior-Antwort:

„Testbarkeit ist kein Testthema allein. Sie ist ein Architekturmerkmal.“

Häufige Fehler:
  • Systemzeit direkt verwenden.
  • Zufall direkt verwenden.
  • Datenbankzugriff in Domainlogik.
  • Statische Singleton-Abhängigkeiten.
  • Framework überall.
  • Große Services mit vielen Verantwortlichkeiten.
  • Seiteneffekte schwer kontrollierbar.
  • Tests brauchen komplexes Setup.
Best Practices:
  • Zeit injizieren.
  • Zufall injizieren.
  • Externe Effekte über Ports.
  • Domain ohne Framework halten.
  • Kleine Verantwortlichkeiten.
  • Konstruktorinjektion.
  • Keine globalen Zustände.
  • Deterministische Tests.
Stolperfalle:

Wenn ein einfacher Fachtest einen vollständigen ApplicationContext braucht, ist die fachliche Logik wahrscheinlich zu stark an Infrastruktur gekoppelt.

Merksatz:

Was leicht testbar ist, ist oft auch leicht änderbar.

B7-K01-Q006 – Risikobasiertes TestenTeste nach Schaden, nicht nach Zeilenzahl.
Frage:

„Wie entscheiden Sie, was wie intensiv getestet wird?“

Kurz erklärt:

Risikobasiertes Testen priorisiert Tests nach fachlichem Schaden, technischer Unsicherheit, Änderungshäufigkeit und Fehlerwahrscheinlichkeit.

Musterantwort:

Ich priorisiere nach Risiko. Kritische Zahlungslogik, Idempotenz, Security, Datenkonsistenz, Migrationen und externe Integrationen teste ich intensiver als reine Anzeigeformatierung. Die Testtiefe hängt von Business Impact, technischer Komplexität, Änderungshäufigkeit, Fehlerrate und Beobachtbarkeit ab.

Ein Senior versucht nicht, alles gleich stark zu testen. Er investiert Testaufwand dort, wo Fehler teuer sind.

Senior-Antwort:

„Coverage sagt mir, welcher Code ausgeführt wurde. Risiko sagt mir, welcher Fehler weh tut.“

Häufige Fehler:
  • Coverage als einziges Qualitätsziel.
  • Kritische Pfade nur oberflächlich testen.
  • Unkritische Getter intensiv testen.
  • Keine negativen Tests.
  • Keine Race-Condition-Tests bei Idempotenz.
  • Security erst manuell testen.
  • Testaufwand ohne Business-Kontext.
Best Practices:
  • Risiko pro Feature bewerten.
  • Kritische Pfade mehrfach absichern.
  • Negative Tests einplanen.
  • Nebenläufigkeit und Idempotenz testen.
  • Security und Datenkonsistenz priorisieren.
  • Coverage als Hilfsmetrik behandeln.
  • Risiko im Testplan dokumentieren.
  • Regression nach Incident ergänzen.
Stolperfalle:

Hohe Coverage kann trügerisch sein, wenn Assertions schwach sind oder kritische Randfälle fehlen.

Merksatz:

Teste nach Schaden, nicht nach Zeilenzahl.

B7-K01-Q007 – TestdatenmanagementGute Testdaten erzählen die Geschichte des Tests.
Frage:

„Wie organisieren Sie Testdaten in großen Java-Projekten?“

Kurz erklärt:

Testdaten sollten lesbar, minimal, kontrolliert und wartbar sein. Testdaten-Builder helfen, irrelevante Details zu verstecken.

Musterantwort:

Ich halte Testdaten so klein wie möglich und so aussagekräftig wie nötig. Für Domainobjekte nutze ich Testdaten-Builder oder Mother-Objekte, aber sparsam. Der Test soll zeigen, welche Daten fachlich relevant sind. Irrelevante Pflichtfelder sollten über Defaults kommen.

Für Integrationstests achte ich auf Isolation: Jeder Test bekommt eigene Daten oder räumt auf. In Datenbanktests vermeide ich versteckte Abhängigkeiten zwischen Tests.

Senior-Antwort:

„Testdaten sollen die Testabsicht sichtbar machen. Alles andere ist Rauschen.“

Häufige Fehler:
  • Riesige JSON-Fixtures.
  • Unklare globale Testdaten.
  • Tests hängen von Reihenfolge ab.
  • Zu viele irrelevante Felder im Test.
  • Builder verstecken fachlich relevante Werte.
  • Zufällige Daten ohne Reproduzierbarkeit.
  • Testdaten werden nicht bereinigt.
  • Test liest Daten aus Prod-ähnlichem Dump.
Best Practices:
  • Relevante Daten explizit.
  • Defaults über Builder.
  • Kleine Fixtures.
  • Deterministische IDs.
  • Keine Testreihenfolge-Abhängigkeit.
  • Daten pro Test isolieren.
  • Randfälle bewusst benennen.
  • Testdaten nah am Test halten.
Stolperfalle:

Zu generische Testdaten-Builder können fachlich wichtige Unterschiede verstecken.

Merksatz:

Gute Testdaten erzählen die Geschichte des Tests.

B7-K01-Q008 – Flaky Tests und DeterminismusEin Test, dem man nicht vertraut, testet nichts.
Frage:

„Wie gehen Sie mit flaky Tests um?“

Kurz erklärt:

Flaky Tests bestehen manchmal und scheitern manchmal ohne Codeänderung. Ursachen sind Zeit, Nebenläufigkeit, Reihenfolge, externe Systeme, Netzwerk, globale Zustände oder Race Conditions.

Musterantwort:

Flaky Tests werden nicht ignoriert und nicht dauerhaft wiederholt, sondern analysiert und repariert. Ich suche nach typischen Ursachen: Systemzeit, Sleeps, parallele Ausführung, Testreihenfolge, geteilte Daten, externe Services, unkontrollierte Asynchronität oder Race Conditions.

Ein flaky Test zerstört Vertrauen in die gesamte Pipeline. Wenn Teams lernen, rote Builds zu ignorieren, verliert CI/CD seinen Wert.

Senior-Antwort:

„Ein flaky Test ist kein Testproblem. Er ist ein Vertrauensproblem der Delivery-Pipeline.“

Häufige Fehler:
  • Flaky Tests ignorieren.
  • Rerun als Dauerlösung.
  • Thread.sleep statt Await.
  • Systemzeit direkt nutzen.
  • Geteilte Datenbankzustände.
  • Tests abhängig von Reihenfolge.
  • Externe APIs im CI-Test.
  • Statische Zustände nicht resetten.
Best Practices:
  • Zeit injizieren.
  • Awaitility oder klare Await-Mechanismen.
  • Daten isolieren.
  • Keine Testreihenfolge-Abhängigkeit.
  • Externe Systeme kontrollieren.
  • Flaky Tests mit Owner.
  • Quarantine nur temporär.
  • Race Conditions ernst nehmen.
Stolperfalle:

Thread.sleep(1000) macht Tests nicht stabil, sondern langsam und weiterhin zufällig.

Merksatz:

Ein Test, dem man nicht vertraut, testet nichts.

B7-K01-Q009 – Testabdeckung, Mutation Testing und AussagekraftCoverage misst Ausführung, nicht Vertrauen.
Frage:

„Wie bewerten Sie Code Coverage?“

Kurz erklärt:

Testabdeckung zeigt, welcher Code ausgeführt wurde. Sie sagt nicht automatisch, ob Tests gute Assertions haben. Mutation Testing prüft, ob Tests künstliche Codeänderungen erkennen.

Musterantwort:

Coverage ist eine hilfreiche Metrik, aber keine Qualitätsgarantie. Sie zeigt, welche Zeilen, Branches oder Methoden ausgeführt wurden. Sie zeigt nicht, ob Assertions sinnvoll sind oder kritische Randfälle geprüft wurden. Ich nutze Coverage als Warnsignal für ungetestete Bereiche, aber nicht als alleiniges Ziel.

Mutation Testing ist aussagekräftiger, weil es Code gezielt verändert und prüft, ob Tests diese Veränderung erkennen. Wenn Mutationen überleben, fehlen oft Assertions oder Randfälle.

Senior-Antwort:

„Coverage sagt mir, dass Code berührt wurde. Mutation Testing sagt mir eher, ob der Test etwas merken würde.“

Häufige Fehler:
  • Coverage-Ziel als Selbstzweck.
  • Getter/Setter testen für Prozentzahlen.
  • Keine Boundary Tests.
  • Assertions fehlen.
  • Nur Happy Path.
  • Kritische Logik ohne Mutation Testing.
  • Coverage von generiertem Code zählen.
  • Tests schreiben, die nichts prüfen.
Best Practices:
  • Coverage als Diagnose nutzen.
  • Branches und Randfälle prüfen.
  • Kritische Logik mit Mutation Testing.
  • Überlebende Mutationen analysieren.
  • Coverage-Ziele pro Modul differenzieren.
  • Generated Code ausschließen.
  • Assertions stark formulieren.
  • Risiko wichtiger als Prozentzahl.
Stolperfalle:

100 Prozent Coverage kann mit schwachen Assertions trotzdem fast wertlos sein.

Merksatz:

Coverage misst Ausführung, nicht Vertrauen.

B7-K01-Q010 – Senior-Teststrategie und Test GovernanceSenior Testing optimiert Vertrauen pro Feedbackzeit. 24h
Frage:

„Wie würden Sie als Senior oder Lead eine Teststrategie für ein großes Java-System definieren?“

Kurz erklärt:

Eine Senior-Teststrategie verbindet Risiko, Testebenen, CI/CD, Governance, Testdaten, Wartbarkeit, Flaky-Test-Management und Produktionsfeedback.

Musterantwort:

Ich beginne mit Risiken und Systemgrenzen. Welche Flows sind geschäftskritisch? Wo drohen Datenverlust, doppelte Zahlungen, Security-Probleme, Inkonsistenzen, Performanceverletzungen oder Integrationsbrüche? Danach ordne ich Testarten zu: Domainlogik in Unit Tests, Frameworkgrenzen in Slice Tests, Infrastruktur mit Testcontainers, Serviceverträge mit Contract Tests, kritische Journeys mit wenigen E2E-Tests, SLOs mit Performance Tests.

Dann definiere ich CI/CD-Gates: schnelle PR-Tests, realistischere Main-Branch-Tests, Release-Validierung und Smoke Tests nach Deployment. Zusätzlich braucht es Governance: Ownership für flaky Tests, Testdatenregeln, Laufzeitbudgets, Review von Testqualität und Regressionstests nach Incidents.

Senior-Antwort:

„Eine Teststrategie ist kein Toolkatalog. Sie ist eine bewusste Antwort auf Risiken, Kosten und Änderungsfrequenz.“

Häufige Fehler:
  • Tool zuerst, Risiko später.
  • Alles im PR laufen lassen.
  • Keine Ownership für Tests.
  • Flaky Tests tolerieren.
  • Coverage als einziges Ziel.
  • Keine Testdatenstrategie.
  • Keine Regression nach Incidents.
  • Testarchitektur nicht refactoren.
Best Practices:
  • Risiko zuerst.
  • Testebenen bewusst definieren.
  • PR-Feedback schnell halten.
  • Realistische Integration in Main.
  • E2E selektiv.
  • Flaky Tests ernst nehmen.
  • Testdatenstrategie.
  • Testlaufzeitbudget.
Stolperfalle:

Wenn alle Tests immer laufen müssen, werden wichtige Tests irgendwann deaktiviert oder umgangen.

Merksatz:

Senior Testing optimiert Vertrauen pro Feedbackzeit.

B7-K02 – Kapitel 2: JUnit 5 Professional 10 Karten

B7-K02-Q001 – JUnit Platform, Jupiter und VintageJUnit 5 ist Plattform plus Jupiter — nicht nur @Test . 24h
Frage:

„Was ist JUnit 5, und wie unterscheidet es sich architektonisch von JUnit 4?“

Kurz erklärt:

JUnit 5 besteht aus mehreren Bausteinen. Die Platform startet Tests, Jupiter ist das moderne Test- und Extension-Modell, Vintage dient zur Ausführung alter JUnit-3-/JUnit-4-Tests.

Musterantwort:

JUnit 5 ist modular aufgebaut. Die JUnit Platform ist die Laufzeit- und Integrationsschicht für IDEs, Build-Tools und TestEngines. JUnit Jupiter ist das moderne Programmier- und Extension-Modell für Tests. JUnit Vintage erlaubt, ältere JUnit-3- und JUnit-4-Tests über die Platform auszuführen, wenn man Migration braucht. Die offizielle JUnit-Dokumentation beschreibt Jupiter als Kombination aus Programmiermodell und Extension-Modell und die Platform als Grundlage für das Starten von Test-Frameworks auf der JVM.

Der große Unterschied zu JUnit 4 ist nicht nur Syntax. JUnit 5 ist stärker modularisiert, besitzt ein einheitliches Extension-Modell und ist besser auf moderne Tooling-, IDE- und Build-Integration ausgelegt.

Senior-Antwort:

„JUnit 5 ist nicht nur JUnit 4 mit neuen Annotationen. Es ist eine Plattform mit Jupiter als modernem Testmodell und einem sauberen Extension-Konzept.“

Häufige Fehler:
  • JUnit 5 nur als neue @Test -Annotation verstehen.
  • Vintage dauerhaft als Migrationskrücke behalten.
  • JUnit 4 und Jupiter-APIs vermischen.
  • Build-Tool nicht korrekt auf JUnit Platform konfigurieren.
  • Alte Rules ohne Extension-Migration weiterverwenden.
  • Testlaufzeitprobleme JUnit zuschreiben, obwohl Spring-Kontexte Ursache sind.
Best Practices:
  • Neue Tests mit Jupiter schreiben.
  • Vintage nur temporär für Migration.
  • JUnit-Version zentral verwalten.
  • Build-Tool sauber konfigurieren.
  • JUnit 4 Rules durch Extensions ersetzen.
  • Tests nach Fachlichkeit organisieren.
  • Reine Unit Tests ohne Framework halten.
  • Spring-Kontext nur gezielt starten.
Stolperfalle:

Wenn JUnit 4, Vintage, Jupiter und alte Rules unkontrolliert gemischt werden, wird die Testbasis schwer wartbar.

Merksatz:

JUnit 5 ist Plattform plus Jupiter — nicht nur @Test .

B7-K02-Q002 – Test Lifecycle: BeforeAll, BeforeEach, AfterEach und AfterAllJeder Test verdient eine eigene Welt.
Frage:

„Wie nutzen Sie Lifecycle-Callbacks in JUnit 5 sinnvoll?“

Kurz erklärt:

Lifecycle-Methoden steuern Testaufbau und Aufräumen. BeforeEach ist für frische Test-Fixtures wichtiger als BeforeAll , weil Testisolation zentral ist.

Musterantwort:

Ich nutze @BeforeEach , um pro Test eine frische Fixture aufzubauen. Das vermeidet Abhängigkeiten zwischen Tests. @AfterEach nutze ich, wenn Ressourcen aufgeräumt oder Nebenwirkungen zurückgesetzt werden müssen. @BeforeAll und @AfterAll verwende ich sparsam für teure, wirklich gemeinsam nutzbare Ressourcen.

Der wichtigste Punkt ist Testisolation. Tests dürfen nicht voneinander abhängen. Ein Shared State in @BeforeAll ist gefährlich, wenn er veränderlich ist.

Senior-Antwort:

„Lifecycle-Callbacks sind für Testisolation da, nicht für versteckte Magie. Jeder Test muss allein verständlich und allein ausführbar sein.“

Häufige Fehler:
  • Mutable State in @BeforeAll .
  • Tests abhängig von Reihenfolge.
  • Zu viel Setup für jeden Test.
  • Setup versteckt Testabsicht.
  • @AfterEach kompensiert schlechtes Design.
  • @DirtiesContext in vielen Spring-Tests.
  • Lifecycle-Methoden mit fachlicher Logik überladen.
Best Practices:
  • Frische Fixture pro Test.
  • Shared Ressourcen nur unveränderlich oder kontrolliert.
  • Testsetup klein halten.
  • Testdaten nah am Test.
  • Keine Reihenfolge-Abhängigkeit.
  • Teure Infrastruktur bewusst kapseln.
  • Spring-Kontext-Caching respektieren.
  • Cleanup deterministisch.
Stolperfalle:

Ein grüner Testlauf kann zufällig sein, wenn Tests stillschweigend vom Zustand vorheriger Tests abhängen.

Merksatz:

Jeder Test verdient eine eigene Welt.

B7-K02-Q003 – Assertions, assertAll und Exception TestsStarke Assertions machen Tests wertvoll.
Frage:

„Wie schreiben Sie aussagekräftige Assertions in JUnit 5?“

Kurz erklärt:

Assertions beschreiben erwartetes Verhalten. assertAll gruppiert zusammengehörige Assertions, assertThrows prüft Fehlerfälle.

Musterantwort:

Assertions müssen fachliche Erwartungen ausdrücken. Ich prüfe nicht nur, dass ein Objekt „nicht null“ ist, sondern relevante Felder, Zustände und Fehlerfälle. assertAll nutze ich, wenn mehrere Eigenschaften desselben Ergebnisses zusammengehören. assertThrows nutze ich für erwartete Exceptions und prüfe bei fachlichen Fehlern auch Message oder Typinformationen.

Wichtig ist, nicht die Implementierung zu testen. Assertions sollten das beobachtbare Verhalten prüfen.

Senior-Antwort:

„Eine Assertion ist eine fachliche Aussage. Je präziser sie ist, desto schneller verstehe ich den Fehler.“

Häufige Fehler:
  • Nur assertNotNull .
  • Zu viele irrelevante Assertions.
  • Exception-Typ zu breit.
  • Fehlerpfade nicht testen.
  • Implementierungsdetails prüfen.
  • Mehrere unabhängige Verhaltensweisen in einem Test.
  • Assertions ohne fachlichen Namen.
Best Practices:
  • Fachliches Verhalten prüfen.
  • Relevante Felder assertieren.
  • Fehlerfälle explizit testen.
  • assertAll für zusammengehörige Eigenschaften.
  • Exception-Typ präzise prüfen.
  • Randfälle abdecken.
  • Assertions lesbar halten.
  • Bei komplexen Objekten spezialisierte Assertion-Helfer nutzen.
Stolperfalle:

Ein Test mit assertNotNull(result) kann grün sein, obwohl fast alles fachlich falsch ist.

Merksatz:

Starke Assertions machen Tests wertvoll.

B7-K02-Q004 – Parameterized Tests und DatenquellenGleiche Regel, viele Beispiele — Parameterized Test.
Frage:

„Wann verwenden Sie Parameterized Tests in JUnit 5?“

Kurz erklärt:

Parameterized Tests führen dieselbe Testlogik mit mehreren Eingaben aus. Sie eignen sich für Validierung, Randfälle, Mapping-Regeln und tabellarische Fachlogik.

Musterantwort:

Ich verwende Parameterized Tests, wenn dieselbe Regel für mehrere Eingabewerte geprüft werden soll: Validierung, Boundary Values, Mapping, Formatierung oder Regelmatrizen. Dadurch vermeide ich Copy-Paste und mache Testfälle tabellarisch sichtbar.

JUnit unterstützt Parameterized Tests über Jupiter Params, zum Beispiel mit @ValueSource , @NullAndEmptySource , @CsvSource , @EnumSource oder @MethodSource . Die JUnit-Dokumentation beschreibt Parameterized Tests als Teil des Jupiter-Testmodells.

Senior-Antwort:

„Parameterized Tests sind stark, wenn eine Regel viele Beispiele braucht. Sie sind schwach, wenn man unterschiedliche Geschichten in eine Tabelle presst.“

Häufige Fehler:
  • Zu viele Fälle in einem unlesbaren Parameter-Test.
  • Unterschiedliche Erwartungen in schwer lesbarer CSV.
  • Komplexe Fachlogik in @MethodSource .
  • Massendaten ohne Testabsicht.
  • Parameterized Tests für unabhängige Verhaltensweisen.
  • Testnamen ohne Parameteranzeige.
  • Nullfälle vergessen.
Best Practices:
  • Gleiche Logik, mehrere Daten.
  • Testnamen mit Parametern.
  • @MethodSource für komplexe Objekte.
  • CSV sparsam und lesbar.
  • Randfälle sichtbar machen.
  • Null- und Empty-Fälle bewusst testen.
  • Nicht zu viele Cases in einen Test pressen.
  • Fachliche Gruppierung statt Datensalat.
Stolperfalle:

Eine riesige CSV im Test ist oft schwerer zu verstehen als mehrere fachlich benannte Tests.

Merksatz:

Gleiche Regel, viele Beispiele — Parameterized Test.

B7-K02-Q005 – Nested Tests und fachliche StrukturKontext sichtbar machen, nicht Struktur simulieren.
Frage:

„Wann verwenden Sie Nested Tests in JUnit 5?“

Kurz erklärt:

@Nested strukturiert Tests nach Kontexten. Das verbessert Lesbarkeit bei Zustandsautomaten, Policies und komplexer Fachlogik.

Musterantwort:

Ich verwende Nested Tests, wenn eine Klasse mehrere fachliche Kontexte hat: aktives Abo, gekündigtes Abo, gesperrtes Konto, leere Bestellung, autorisierte Zahlung. Dadurch wird die Testklasse wie eine Spezifikation lesbar. Jeder Nested-Kontext bündelt Setup und Erwartungen für eine Situation.

Ich nutze @Nested nicht, um beliebig tiefe Ordnerstrukturen im Code nachzubauen. Es soll fachliche Lesbarkeit erhöhen.

Senior-Antwort:

„Nested Tests nutze ich, wenn die Fachlichkeit Kontexte hat. Der Test soll sich wie eine Spezifikation lesen.“

Häufige Fehler:
  • Technische statt fachliche Gruppierung.
  • Zu tiefe Verschachtelung.
  • Gemeinsamer mutable State.
  • Setup über mehrere Ebenen schwer nachvollziehbar.
  • Testnamen wiederholen Kontext unnötig.
  • Nested Tests als Ersatz für kleine Testklassen missbrauchen.
Best Practices:
  • Nach fachlichem Kontext gruppieren.
  • Maximal wenige Ebenen.
  • Kontextnamen sprechend.
  • Setup nah am Kontext.
  • Kein komplexer Shared State.
  • Tests weiterhin einzeln verständlich.
  • Zustandsautomaten damit strukturieren.
  • Lesbarkeit vor cleverer Struktur.
Stolperfalle:

Zu viele Nested-Ebenen machen Tests schwerer lesbar als eine flache Testklasse.

Merksatz:

Kontext sichtbar machen, nicht Struktur simulieren.

B7-K02-Q006 – Dynamic Tests und TestFactoryDynamik braucht klare Namen.
Frage:

„Was sind Dynamic Tests in JUnit 5, und wann würden Sie sie verwenden?“

Kurz erklärt:

Dynamic Tests werden zur Laufzeit erzeugt. Sie eignen sich für dynamisch geladene Beispiele, generierte Testfälle oder externe Spezifikationsdaten.

Musterantwort:

Dynamic Tests werden nicht statisch als @Test -Methoden deklariert, sondern über eine @TestFactory zur Laufzeit erzeugt. Ich verwende sie, wenn Testfälle aus einer Datenquelle, Spezifikation oder Konfiguration entstehen. Für einfache Datenvarianten sind Parameterized Tests meist lesbarer.

Dynamic Tests sind mächtig, aber sie sollten nicht zu versteckter Testgenerierung führen. Der Testreport muss weiterhin verständlich bleiben.

Senior-Antwort:

„Dynamic Tests nutze ich, wenn Testfälle wirklich dynamisch sind. Für normale Tabellen ist ein Parameterized Test oft klarer.“

Häufige Fehler:
  • Dynamic Tests für einfache Parameterfälle.
  • Schlechte Testnamen.
  • Externe Datenquelle instabil.
  • Zu viele generierte Tests.
  • Testlogik schwer nachvollziehbar.
  • Fehlerreport unverständlich.
  • Lifecycle-Annahmen wie bei normalen Tests.
Best Practices:
  • Parameterized Tests bevorzugen, wenn ausreichend.
  • Dynamic Tests für echte dynamische Quellen.
  • Namen aussagekräftig generieren.
  • Datenquellen versionieren.
  • Testanzahl kontrollieren.
  • Fehlerfälle gut reportbar machen.
  • Nicht zu viel Logik in TestFactory.
  • Testdaten validieren.
Stolperfalle:

Dynamisch erzeugte Tests ohne sprechende Namen sind im CI-Report schwer zu debuggen.

Merksatz:

Dynamik braucht klare Namen.

B7-K02-Q007 – Tags, Test Suites und CI/CD-SelektionNicht jeder Test gehört in jeden Pipeline-Schritt.
Frage:

„Wie nutzen Sie JUnit Tags in einer CI/CD-Pipeline?“

Kurz erklärt:

Tags markieren Tests für gezielte Ausführung. CI/CD kann dadurch schnelle PR-Tests, Integrationstests, Smoke Tests und Release-Gates trennen.

Musterantwort:

Ich nutze Tags, um Tests nach Zweck und Kosten zu selektieren: unit , integration , contract , smoke , e2e , slow , database , kafka . In Pull Requests laufen schnelle Tests. Auf dem Main Branch laufen realistischere Integrationstests. Vor Releases laufen Smoke-, Contract- oder E2E-Tests. Nach Deployment laufen Smoke- und Health-Checks.

Tags sind aber nur sinnvoll, wenn sie standardisiert sind. Wenn jedes Team eigene Tags erfindet, wird CI/CD unübersichtlich.

Senior-Antwort:

„Tags sind Pipeline-Verträge. Sie sagen nicht nur, was ein Test ist, sondern wann sein Feedback gebraucht wird.“

Häufige Fehler:
  • Tags ohne Teamstandard.
  • Langsame Tests nicht markiert.
  • Tests falsch klassifiziert.
  • PR-Pipeline führt alles aus und wird langsam.
  • Release-Gates laufen lokal nie.
  • Tags ersetzen sauberes Testdesign.
  • Flaky Tests werden nur ausgeschlossen statt repariert.
Best Practices:
  • Tag-Konvention definieren.
  • Tags nach Testzweck und Kosten.
  • PR-Feedback kurz halten.
  • Langsame Tests in spätere Stufen.
  • Smoke Tests separat.
  • Flaky Tests nicht dauerhaft wegfiltern.
  • CI-Konfiguration versionieren.
  • Testlaufzeiten messen.
Stolperfalle:

Wenn Tests nur aus Laufzeitgründen als slow markiert und nie ausgeführt werden, hat man Qualität ausgelagert statt organisiert.

Merksatz:

Nicht jeder Test gehört in jeden Pipeline-Schritt.

B7-K02-Q008 – Extensions und Extension ModelExtensions erweitern Testinfrastruktur, nicht Testabsicht.
Frage:

„Was ist das Extension Model von JUnit 5?“

Kurz erklärt:

Extensions erweitern JUnit Jupiter über definierte Extension Points. Sie ersetzen viele JUnit-4-Rules- und Runner-Anwendungsfälle.

Musterantwort:

JUnit Jupiter hat ein einheitliches Extension Model. Extensions können sich an verschiedenen Punkten des Test-Lifecycles einklinken: vor oder nach Tests, bei Parameterauflösung, Exception Handling, Testinstanz-Erstellung oder bedingter Ausführung. Die offizielle JUnit-Dokumentation beschreibt Jupiter als Programmier- und Extension-Modell für Tests und Extensions.

Ich nutze Extensions für Querschnittsfunktionen in Tests: Testcontainers-Management, temporäre Ressourcen, Timing, Parameterinjektion, Custom Conditions oder Frameworkintegration. Spring nutzt dieses Modell ebenfalls über die SpringExtension.

Senior-Antwort:

„Extensions sind stark für Testinfrastruktur. Fachliche Testlogik gehört weiterhin sichtbar in den Test.“

Häufige Fehler:
  • Extensions verstecken zu viel Logik.
  • Globale Extension mit Seiteneffekten.
  • Teure Ressourcen pro Test starten.
  • State im falschen Scope speichern.
  • Extensions statt einfacher Helper.
  • Fehler schwer debugbar machen.
  • Testabsicht verschleiern.
Best Practices:
  • Extensions für Querschnittsfunktionen.
  • Testabsicht sichtbar halten.
  • Scope bewusst wählen.
  • Zustand über ExtensionContext Store.
  • Eigene Meta-Annotations prüfen.
  • Keine fachliche Logik in Extensions.
  • Performance messen.
  • Extensions dokumentieren.
Stolperfalle:

Eine Extension, die zu viel automatisch macht, kann Tests grün erscheinen lassen, obwohl niemand mehr versteht, warum.

Merksatz:

Extensions erweitern Testinfrastruktur, nicht Testabsicht.

B7-K02-Q009 – Assumptions, Conditional Tests und Environment-spezifische TestsSkip ist nicht grün — Skip ist nicht geprüft.
Frage:

„Wann verwenden Sie Assumptions oder bedingte Tests in JUnit 5?“

Kurz erklärt:

Assumptions und Conditional Tests steuern, ob Tests unter bestimmten Bedingungen ausgeführt oder übersprungen werden.

Musterantwort:

Ich verwende Assumptions, wenn ein Test nur unter bestimmten Umgebungsbedingungen sinnvoll ist: externe Provider aktiviert, Docker verfügbar, bestimmtes Betriebssystem, bestimmte Umgebungsvariable. Wenn die Annahme nicht erfüllt ist, wird der Test übersprungen, nicht als Fehler gewertet.

Wichtig ist, diese Mechanismen sparsam zu nutzen. Wenn zu viele Tests bedingt übersprungen werden, entsteht ein falsches Sicherheitsgefühl. CI/CD muss klar zeigen, welche Tests gelaufen sind und welche nicht.

Senior-Antwort:

„Ein übersprungener Test ist kein bestandener Test. CI muss sichtbar machen, was wirklich geprüft wurde.“

Häufige Fehler:
  • Kritische Tests standardmäßig skippen.
  • Skips im CI nicht sichtbar machen.
  • Environment-Variablen uneinheitlich.
  • Assumptions statt Testselektion über Tags.
  • Tests lokal grün, weil sie nie liefen.
  • Bedingte Tests ohne Pipeline-Dokumentation.
Best Practices:
  • Assumptions sparsam.
  • Skipped Tests sichtbar reporten.
  • Tags für Pipeline-Selektion bevorzugen.
  • Conditions klar dokumentieren.
  • Kritische Tests in definierter Stufe ausführen.
  • Keine stillen Skips für Kernqualität.
  • Environment-Abhängigkeiten minimieren.
  • CI-Profile versionieren.
Stolperfalle:

Bedingte Tests können Qualität vortäuschen, wenn sie in den wichtigen Pipelines nie laufen.

Merksatz:

Skip ist nicht grün — Skip ist nicht geprüft.

B7-K02-Q010 – JUnit 5 Best Practices und Anti-PatternsJUnit ist das Werkzeug; Testlesbarkeit ist das Ziel. 24h
Frage:

„Welche JUnit-5-Anti-Patterns sehen Sie in Enterprise-Projekten?“

Kurz erklärt:

Professionelle JUnit-5-Tests sind lesbar, deterministisch, isoliert, schnell, fachlich aussagekräftig und CI/CD-fähig.

Musterantwort:

Häufige Anti-Patterns sind: @SpringBootTest für alles, schwache Assertions, Testreihenfolge-Abhängigkeiten, Thread.sleep , Systemzeit direkt, globale mutable Fixtures, @Disabled ohne Ticket, riesige Testdaten, Mocks für Value Objects, Interaktionsverifikation statt Verhalten, langsame PR-Suites und Tests, die nur Coverage erhöhen.

JUnit 5 liefert viele professionelle Werkzeuge, aber gute Tests entstehen durch Disziplin: klare Namen, kleine Fixtures, starke Assertions, gezielte Parameterisierung, sinnvolle Tags, Extensions nur für Infrastruktur und konsequente Flaky-Test-Behebung.

Senior-Antwort:

„JUnit 5 gibt mir Werkzeuge. Qualität entsteht durch Testdesign, Konventionen und Disziplin.“

Häufige Fehler:
  • @SpringBootTest überall.
  • Schwache Assertions.
  • Testreihenfolge-Abhängigkeit.
  • Sleeps statt Await.
  • Disabled Tests ohne Owner.
  • Statische mutable Fixtures.
  • Zufällige Testdaten ohne Reproduzierbarkeit.
  • Zu viel Mocking.
Best Practices:
  • Fachliche Testnamen.
  • Arrange-Act-Assert.
  • Eine Verhaltensaussage pro Test.
  • Starke Assertions.
  • Deterministische Zeit.
  • Keine Shared Mutable State.
  • Parameterized Tests für Tabellen.
  • Tags für Pipeline-Stufen.
Stolperfalle:

Mehr JUnit-Features bedeuten nicht automatisch bessere Tests. Zu viel Mechanik kann Testabsicht verdecken.

Merksatz:

JUnit ist das Werkzeug; Testlesbarkeit ist das Ziel.

B7-K03 – Kapitel 3: Mockito und Test Doubles 10 Karten

B7-K03-Q001 – Mock, Stub, Fake und Spy richtig unterscheidenMocke Grenzen, nicht Fachobjekte. 24h
Frage:

„Was ist der Unterschied zwischen Mock, Stub, Fake und Spy?“

Kurz erklärt:

Ein Stub liefert Antworten, ein Mock verifiziert Interaktionen, ein Fake ist eine einfache funktionierende Implementierung, ein Spy überwacht ein echtes Objekt.

Musterantwort:

Ein Stub liefert vorbereitete Antworten. Ein Mock wird typischerweise verwendet, um Interaktionen zu verifizieren. Ein Fake ist eine einfache, lauffähige Ersatzimplementierung, zum Beispiel ein In-Memory-Repository. Ein Spy ist ein echtes Objekt, dessen Aufrufe beobachtet oder teilweise überschrieben werden können.

Mockito ist vor allem für Mocks, Stubs, Spies, Verification und Stubbing gedacht. Die offizielle Mockito-Seite beschreibt Mockito als Framework für saubere, lesbare Tests und klare Verification-Fehler.

Als Senior unterscheide ich bewusst: Für fachliche Logik bevorzuge ich oft Fakes oder reine Java-Objekte. Für externe Effekte und Ports sind Mocks oder Stubs sinnvoll. Zu viel Mocking ist ein Designsignal.

Senior-Antwort:

„Ich verwende Mockito an Systemgrenzen. Wenn jeder Test zehn Mocks braucht, prüfe ich zuerst das Design.“

Häufige Fehler:
  • Jeden Collaborator mocken.
  • Value Objects mocken.
  • Fakes und Mocks begrifflich verwechseln.
  • Interaktionen statt Verhalten testen.
  • Mockito verwenden, obwohl ein einfacher Fake lesbarer wäre.
  • Integration mit Datenbank mocken und dadurch SQL-Risiken ignorieren.
  • Zu viele technische Details im Test verifizieren.
Best Practices:
  • Fachliches Verhalten zuerst testen.
  • Externe Effekte über Ports mocken oder stubben.
  • Fakes für einfache Speicher- oder Regelersatzimplementierungen.
  • Keine Value Objects mocken.
  • Nicht private Methoden mocken.
  • Interaktionen nur verifizieren, wenn sie fachlich relevant sind.
  • Test Doubles bewusst benennen.
  • Integration dort real testen, wo Frameworkverhalten kritisch ist.
Stolperfalle:

Ein Mock beweist nicht, dass eine Integration funktioniert. Er beweist nur, dass der Code mit einer simulierten Erwartung umgehen kann.

Merksatz:

Mocke Grenzen, nicht Fachobjekte.

B7-K03-Q002 – Mockito-Grundlagen: mock, when, thenReturn und verifyStubbe nur, was gebraucht wird; verifiziere nur, was zählt.
Frage:

„Wie verwenden Sie Mockito in einem einfachen Unit Test?“

Kurz erklärt:

Mockito erstellt Mocks, stubbt Rückgabewerte und verifiziert Aufrufe. Der typische Ablauf ist Arrange, Act, Verify/Assert.

Musterantwort:

Ich erzeuge Mocks für externe Abhängigkeiten, stubbe notwendige Rückgaben mit when(...).thenReturn(...) , führe das Verhalten des zu testenden Objekts aus und verifiziere relevante Interaktionen mit verify(...) . Mockito unterstützt genau diese Kernaufgaben: Mock-Erzeugung, Stubbing und Verification.

Wichtig ist, nicht jede interne Interaktion zu prüfen. Ich verifiziere nur Effekte, die zum beobachtbaren Verhalten gehören, zum Beispiel „E-Mail wurde gesendet“ oder „Event wurde publiziert“.

Senior-Antwort:

„Ich verifiziere nicht, wie der Service denkt. Ich verifiziere, welche fachlich relevanten Effekte er nach außen erzeugt.“

Häufige Fehler:
  • Jede Methode verifizieren.
  • verifyNoMoreInteractions() überall.
  • Mocks für triviale Datenobjekte.
  • Stubbing ohne Nutzung.
  • Interaktionsreihenfolge ohne fachlichen Grund prüfen.
  • Rückgabewert und Interaktion doppelt ohne Mehrwert prüfen.
  • Tests stark an Implementierung koppeln.
Best Practices:
  • Nur externe Effekte mocken.
  • Nur relevante Interaktionen verifizieren.
  • Verhalten vor Implementierung.
  • Stubbing minimal halten.
  • Testnamen fachlich formulieren.
  • Keine unnötigen verifyNoMoreInteractions() .
  • Assertions und Verification bewusst kombinieren.
  • Mocks lokal halten, wenn sie nur in einem Test gebraucht werden.
Stolperfalle:

verifyNoMoreInteractions() kann Refactoring unnötig erschweren, wenn es ohne fachlichen Grund eingesetzt wird.

Merksatz:

Stubbe nur, was gebraucht wird; verifiziere nur, was zählt.

B7-K03-Q003 – MockitoExtension, @Mock, @InjectMocks und @CaptorAnnotationen sollen Tests vereinfachen, nicht verstecken.
Frage:

„Wie integrieren Sie Mockito sauber mit JUnit 5?“

Kurz erklärt:

MockitoExtension integriert Mockito in JUnit Jupiter. @Mock erzeugt Mocks, @Captor erzeugt ArgumentCaptors, @InjectMocks kann Abhängigkeiten in das zu testende Objekt injizieren.

Musterantwort:

In JUnit 5 verwende ich @ExtendWith(MockitoExtension.class) , wenn ich Mockito-Annotationen wie @Mock , @Captor oder @InjectMocks nutzen möchte. Das ist die Jupiter-Integration von Mockito. Die JUnit-Dokumentation beschreibt Jupiter als Programmier- und Extension-Modell; Mockito nutzt dieses Modell über eine Extension.

Ich bin bei @InjectMocks vorsichtig. Für einfache Fälle ist es bequem, aber explizite Konstruktorerzeugung macht Tests oft klarer. Gerade bei Senior-Tests will ich sehen, welche Abhängigkeiten wirklich gebraucht werden.

Senior-Antwort:

„MockitoExtension ist für Unit Tests. Wenn ich dafür Spring starte, sollte ich einen konkreten Integrationsgrund haben.“

Häufige Fehler:
  • @InjectMocks blind verwenden.
  • Testobjekt versteckt erzeugen lassen.
  • Zu viele Mocks als Felder.
  • MockitoExtension und SpringExtension ohne Grund mischen.
  • Mocks über Testmethoden hinweg wiederverwenden.
  • Annotationen wichtiger nehmen als Testlesbarkeit.
Best Practices:
  • MockitoExtension für Mockito-Annotationen.
  • Explizite Konstruktorerzeugung bevorzugen, wenn lesbarer.
  • @Captor für ArgumentCaptor.
  • Wenige Mocks pro Testklasse.
  • Mocks nicht unnötig global machen.
  • Spring-Kontext nicht starten, wenn Mockito reicht.
  • Testabhängigkeiten sichtbar halten.
Stolperfalle:

@InjectMocks kann Tests bequem machen, aber auch verschleiern, welche Abhängigkeiten wirklich Teil des Szenarios sind.

Merksatz:

Annotationen sollen Tests vereinfachen, nicht verstecken.

B7-K03-Q004 – ArgumentCaptor und fachliche ArgumentprüfungCapture, wenn der Inhalt zählt.
Frage:

„Wann verwenden Sie ArgumentCaptor in Mockito?“

Kurz erklärt:

ArgumentCaptor erfasst Argumente eines Mock-Aufrufs, damit sie anschließend fachlich geprüft werden können.

Musterantwort:

Ich verwende ArgumentCaptor, wenn ich nicht nur wissen will, dass eine Methode aufgerufen wurde, sondern welches fachliche Objekt übergeben wurde. Typische Beispiele sind Events, Audit Logs, Commands, E-Mails oder Persistenzobjekte.

Die Mockito-Dokumentation beschreibt ArgumentCaptor als Mechanismus, um Argumentwerte für weitere Assertions zu erfassen. Sie empfiehlt ArgumentCaptor besonders im Verification-Kontext und warnt davor, ihn für Stubbing zu verwenden, weil das Lesbarkeit und Fehlerlokalisierung verschlechtern kann.

Senior-Antwort:

„ArgumentCaptor nutze ich, wenn der Wert des ausgehenden Arguments der eigentliche Vertrag ist.“

Häufige Fehler:
  • Captor für Stubbing verwenden.
  • Captor, obwohl equals reicht.
  • Zu viele technische Felder prüfen.
  • Implementierungsdetails statt Eventvertrag prüfen.
  • Captured Value nicht assertieren.
  • Mehrere Captors machen Test schwer lesbar.
  • Fachliche Assertions fehlen.
Best Practices:
  • Captor für Verification verwenden.
  • Fachliche Payload prüfen.
  • Bei einfachen Objekten equals nutzen.
  • getAllValues() bei mehreren Aufrufen.
  • Captor nahe am Verify-Block halten.
  • Events, Commands und Audit-Einträge damit prüfen.
  • Nicht als Ersatz für Contract Tests missverstehen.
Stolperfalle:

Ein Captor ohne starke Assertions ist nur komplizierteres verify .

Merksatz:

Capture, wenn der Inhalt zählt.

B7-K03-Q005 – ArgumentMatcher, eq, any und präzises StubbingMatch so präzise wie nötig und so locker wie vertretbar.
Frage:

„Wie verwenden Sie ArgumentMatcher in Mockito sinnvoll?“

Kurz erklärt:

ArgumentMatcher steuern, welche Aufrufe zu einem Stub oder Verify passen. Matcher müssen präzise genug sein, damit Tests nicht versehentlich falsches Verhalten erlauben.

Musterantwort:

Ich verwende Matcher wie eq , any , isNull oder argThat , wenn exakte Objektgleichheit nicht sinnvoll oder zu streng ist. Dabei achte ich darauf, nicht zu breit zu matchen. any() überall macht Tests schwach, weil fast jeder Aufruf passt.

Mockito empfiehlt für normale Argumentprüfung grundsätzlich natürliche Java-Gleichheit über equals , weil das Tests sauber und einfach hält. ArgumentCaptor oder Matcher sind dann sinnvoll, wenn spezielle Prüfung nötig ist.

Senior-Antwort:

„Matcher sind ein Präzisionswerkzeug. Zu breit und der Test sagt nichts; zu eng und er bricht beim Refactoring.“

Häufige Fehler:
  • any() für alle Argumente.
  • Matcher und konkrete Werte falsch mischen.
  • Komplexe Lambda-Matcher.
  • Matcher prüfen Implementierungsdetails.
  • Zu strikte Objektidentität.
  • Stubbing passt zufällig nicht.
  • Test wird grün, obwohl falsche Argumente übergeben werden.
Best Practices:
  • equals bevorzugen, wenn möglich.
  • eq() für relevante feste Werte.
  • any() nur für irrelevante Argumente.
  • argThat() sparsam.
  • Fachliche Matcher benennen.
  • Komplexe Prüfungen lieber mit ArgumentCaptor.
  • Stubbing präzise genug halten.
  • Matcher-Lesbarkeit reviewen.
Stolperfalle:

any() macht Tests bequem, aber oft blind für falsche Parameter.

Merksatz:

Match so präzise wie nötig und so locker wie vertretbar.

B7-K03-Q006 – Verification: times, never, order und InteraktionsverträgeVerifiziere Verträge, nicht Schritte.
Frage:

„Wann verifizieren Sie Interaktionen mit Mockito?“

Kurz erklärt:

Verification prüft, ob relevante Interaktionen stattgefunden haben. Reihenfolge sollte nur geprüft werden, wenn sie fachlich relevant ist.

Musterantwort:

Ich verifiziere Interaktionen, wenn der ausgehende Effekt das beobachtbare Verhalten ist: Event publiziert, E-Mail gesendet, Audit geschrieben, Command weitergegeben. Ich verifiziere keine internen Hilfsaufrufe. times , never , verifyNoInteractions und InOrder nutze ich nur, wenn Anzahl, Ausbleiben oder Reihenfolge fachlich relevant sind.

Mockito ist auf Verification ausgelegt und liefert klare Verification-Fehler. Aber zu viel Verification koppelt Tests an Implementierung.

Senior-Antwort:

„Ich verifiziere Interaktionen nur, wenn sie die Grenze meines Systems berühren oder fachlich relevant sind.“

Häufige Fehler:
  • Jede interne Methode verifizieren.
  • Reihenfolge ohne fachlichen Grund prüfen.
  • times(1) überall, obwohl Default.
  • verifyNoMoreInteractions() pauschal.
  • Fehlende Assertions auf Rückgabewerte.
  • Ausnahmen im Test verschlucken.
  • Negative Tests ohne klare Erwartung.
Best Practices:
  • Externe Effekte verifizieren.
  • Rückgabewerte per Assertions.
  • Reihenfolge nur bei fachlicher Bedeutung.
  • Negative Interaktionen gezielt prüfen.
  • times(1) nur wenn Lesbarkeit hilft.
  • never() für wichtige Nicht-Effekte.
  • Keine Implementierungsdetails prüfen.
  • Captor für Payload-Verifikation nutzen.
Stolperfalle:

Je mehr Reihenfolge ein Test erzwingt, desto weniger refactoringfreundlich wird er.

Merksatz:

Verifiziere Verträge, nicht Schritte.

B7-K03-Q007 – Strict Stubbing und unnötige StubsJeder Stub braucht einen Grund.
Frage:

„Was ist Strict Stubbing in Mockito, und warum ist es sinnvoll?“

Kurz erklärt:

Strict Stubbing hilft, unnötige oder falsche Stubs zu erkennen. Das verbessert Testsauberkeit und Fehlerlokalisierung.

Musterantwort:

Strict Stubbing bedeutet, dass Mockito unnötige oder problematische Stubbings erkennt. Wenn ein Test Rückgaben stubbt, die nie verwendet werden, ist das oft ein Hinweis auf toten Testcode, falsches Setup oder verändertes Verhalten. Mockito dokumentiert Strictness als Konfiguration, die Stubbing und Verification beeinflusst; Strict Stubbing wurde eingeführt, um saubere Tests zu fördern und unnötige Stubbings zu erkennen.

Ich sehe Strict Stubbing als Testhygiene. Lenient Stubbing verwende ich nur gezielt, wenn es einen klaren Grund gibt — nicht als Standard, um Warnungen zu verstecken.

Senior-Antwort:

„Ein Stub, der nicht gebraucht wird, ist Rauschen. Strict Stubbing hilft, Tests als Spezifikation sauber zu halten.“

Häufige Fehler:
  • Viele Stubs im @BeforeEach , obwohl einzelne Tests sie nicht brauchen.
  • Lenient überall.
  • Warnungen ignorieren.
  • Setup aus alten Tests kopieren.
  • Stubs für nicht relevante Pfade.
  • Strictness deaktivieren statt Test aufzuräumen.
  • Testdaten und Stubs vermischen.
Best Practices:
  • Stubbing nahe am Test.
  • Nur benötigte Stubs.
  • Shared Setup klein halten.
  • Strict Stubbing akzeptieren.
  • Lenient nur begründet.
  • Unused Stubs entfernen.
  • Testsetup regelmäßig refactoren.
  • Jeder Stub sollte Testabsicht unterstützen.
Stolperfalle:

lenient() ist kein Reinigungsmittel. Es ist eine Ausnahme für besondere Fälle.

Merksatz:

Jeder Stub braucht einen Grund.

B7-K03-Q008 – Spies und Partial MockingSpy ist ein Skalpell, kein Hammer.
Frage:

„Wann würden Sie einen Spy verwenden — und wann vermeiden?“

Kurz erklärt:

Ein Spy ist ein echtes Objekt, das von Mockito überwacht wird. Partial Mocking kann nützlich sein, ist aber oft ein Designgeruch.

Musterantwort:

Ein Spy kann sinnvoll sein, wenn ein echtes Objekt verwendet werden soll, aber einzelne Interaktionen beobachtet oder in Legacy-Code teilweise überschrieben werden müssen. Ich verwende Spies sparsam. In neuem Code sind Spies häufig ein Hinweis, dass Verantwortlichkeiten schlecht getrennt sind.

Wenn ich eine private oder interne Methode teilweise mocken möchte, ist das meist ein Refactoring-Signal. Besser ist es, die Abhängigkeit als Port herauszuziehen und sauber zu stubben oder zu fake-en.

Senior-Antwort:

„Spies sind manchmal pragmatisch, aber selten schön. In neuem Code frage ich zuerst, warum ich überhaupt teilweise mocken muss.“

Häufige Fehler:
  • Spy für normale Unit Tests.
  • Private Methoden indirekt spy-en.
  • Echte Seiteneffekte auslösen.
  • Partial Mocking statt Refactoring.
  • Komplexe Stubs auf echten Objekten.
  • Test wird schwer lesbar.
  • Spy statt klarer Port-Abhängigkeit.
Best Practices:
  • Spies sparsam einsetzen.
  • Rückgabewerte bevorzugt normal testen.
  • Partial Mocking als Refactoring-Signal sehen.
  • Keine IO-lastigen Spies.
  • Bei Spies Stubbing vorsichtig.
  • Legacy-Ausnahmen dokumentieren.
  • Neue Architektur über Ports testbar machen.
  • Verhalten statt interne Calls prüfen.
Stolperfalle:

Ein Spy kann echte Methoden ausführen, während man glaubt, nur zu stubben.

Merksatz:

Spy ist ein Skalpell, kein Hammer.

B7-K03-Q009 – Mockito mit Spring Boot: @MockBean, Unit Test oder Slice Test?Mockito für Logik, Spring Tests für Spring-Risiken.
Frage:

„Wann verwenden Sie Mockito in Spring-Boot-Tests?“

Kurz erklärt:

Mockito kann reine Unit Tests unterstützen. In Spring Boot sollte man klar unterscheiden: Mockito ohne Spring, Spring Slice Test mit Mock Bean oder vollständiger Integrationstest.

Musterantwort:

Für reine Fach- oder Application-Service-Logik verwende ich Mockito ohne Spring Context. Das ist schnell und klar. Für Controller-Slices kann ich Abhängigkeiten mocken, wenn ich nur MVC-Mapping, Validation oder HTTP-Verhalten testen möchte. Für JPA, Security, Transactions oder echte Bean-Konfiguration brauche ich Integrationstests statt Mocks.

Der Hauptfehler ist, @SpringBootTest mit vielen Mock Beans als Standard zu verwenden. Dann zahlt man den Preis eines Integrationstests, bekommt aber oft nur die Aussagekraft eines Unit Tests.

Senior-Antwort:

„Wenn ich Spring starte und dann alles mocke, habe ich oft die schlechteste Kombination aus langsam und wenig realistisch.“

Häufige Fehler:
  • @SpringBootTest für Unit Tests.
  • Zu viele Mock Beans.
  • Spring-Kontext testen, aber zentrale Beans mocken.
  • Mockito und Integration vermischen.
  • Controller-Tests ohne HTTP-Assertions.
  • Repository mocken statt JPA testen.
  • Security mocken statt Security-Konfiguration testen.
Best Practices:
  • Ohne Spring testen, wenn möglich.
  • Slice Tests für Frameworkgrenzen.
  • @SpringBootTest gezielt.
  • Mock Beans nur für angrenzende Ports.
  • Integration real testen, wo Framework relevant ist.
  • Testlaufzeiten messen.
  • Kontext-Caching respektieren.
  • Keine unnötigen @DirtiesContext .
Stolperfalle:

Ein Test mit Spring Context und Mock Beans ist nicht automatisch ein Integrationstest.

Merksatz:

Mockito für Logik, Spring Tests für Spring-Risiken.

B7-K03-Q010 – Mockito-Anti-Patterns und Senior-TestdesignMockito soll Tests vereinfachen, nicht Designprobleme verstecken. 24h
Frage:

„Welche Mockito-Anti-Patterns sehen Sie in Enterprise-Projekten?“

Kurz erklärt:

Mockito-Anti-Patterns entstehen, wenn Mocking als Ersatz für gutes Design oder realistische Integrationstests verwendet wird.

Musterantwort:

Die häufigsten Anti-Patterns sind: alles mocken, Value Objects mocken, private oder interne Abläufe indirekt verifizieren, any() überall, verifyNoMoreInteractions() überall, lenient() als Standard, Spies auf Business-Services, @SpringBootTest mit vielen Mock Beans, ArgumentCaptor ohne Assertions und Integrationstests durch Mocks ersetzen.

Mockito ist ein gutes Werkzeug für Ports und externe Effekte. Es ist kein Ersatz für Architektur, Integrationstests oder fachliche Assertions. Mockito selbst betont lesbare Tests und saubere Verification-Fehler als Stärke. Die Dokumentation zu ArgumentCaptor empfiehlt Captor für Verification, nicht für Stubbing, weil sonst Lesbarkeit und Fehlerlokalisierung leiden können.

Senior-Antwort:

„Mockito ist dann gut eingesetzt, wenn der Test fachlich lesbar bleibt und nur die echten Systemgrenzen simuliert.“

Häufige Fehler:
  • Test liest sich wie Implementierungsskript.
  • Mehr Mock-Setup als Fachszenario.
  • Keine echten Assertions.
  • Keine Integrationstests.
  • any() verdeckt falsche Parameter.
  • Strict Stubbing deaktivieren.
  • Spies statt Refactoring.
  • Spring-Kontext mit Mock-Orgie.
Best Practices:
  • Verhalten statt Implementierung testen.
  • Ports mocken, Werte echt verwenden.
  • Fakes prüfen.
  • Integration real testen.
  • Strict Stubbing nutzen.
  • Captor mit starken Assertions.
  • Matcher präzise.
  • Spies vermeiden.
Stolperfalle:

Viele Mocks sind oft kein Zeichen guter Isolation, sondern ein Hinweis auf zu viele Verantwortlichkeiten.

Merksatz:

Mockito soll Tests vereinfachen, nicht Designprobleme verstecken.

B7-K04 – Kapitel 4: Spring Boot Testing 10 Karten

B7-K04-Q001 – Spring Boot Teststrategie: Unit, Slice und Full ContextSo wenig Spring wie möglich, so viel Spring wie nötig. 24h
Frage:

„Wie entscheiden Sie, ob ein Spring-Boot-Test ein Unit Test, Slice Test oder @SpringBootTest sein sollte?“

Kurz erklärt:

Spring Boot Testing beginnt mit der richtigen Testebene: reine Unit Tests ohne Spring, Slice Tests für Frameworkgrenzen und @SpringBootTest für vollständige Anwendungskontexte.

Musterantwort:

Ich starte mit dem Risiko. Wenn reine Fachlogik getestet wird, verwende ich JUnit und Mockito ohne Spring. Wenn ich eine bestimmte Frameworkschicht prüfen will, nutze ich einen Slice Test, etwa @WebMvcTest für Controller oder @DataJpaTest für JPA. Wenn ich das Zusammenspiel des gesamten ApplicationContext, Auto-Configuration, Properties oder mehrere Schichten prüfen will, nutze ich gezielt @SpringBootTest .

Spring Boot bietet @SpringBootTest , wenn Spring-Boot-Features benötigt werden, und daneben fokussierte Annotationen für bestimmte Application Slices. Die Kunst ist, nicht aus Gewohnheit immer den größten Test zu wählen.

Senior-Antwort:

„Ich starte Spring nur, wenn Spring Teil des Risikos ist.“

Häufige Fehler:
  • @SpringBootTest für reine Logik.
  • Zu viele Mock Beans im Full Context.
  • Repository mocken statt JPA testen.
  • Controller ohne HTTP-Vertrag testen.
  • Slices und Full Context vermischen.
  • Testebene nicht nach Risiko wählen.
  • Langsame PR-Suite durch unnötige Contextstarts.
Best Practices:
  • Testebene nach Risiko wählen.
  • Domainlogik ohne Spring testen.
  • Slices für Frameworkgrenzen nutzen.
  • Full Context gezielt einsetzen.
  • Context Caching respektieren.
  • Testlaufzeiten messen.
  • Integration realistisch testen.
  • Mocking nicht mit Integration verwechseln.
Stolperfalle:

Ein langsamer @SpringBootTest mit vielen Mocks ist oft weder guter Unit Test noch guter Integrationstest.

Merksatz:

So wenig Spring wie möglich, so viel Spring wie nötig.

B7-K04-Q002 – @SpringBootTest und WebEnvironmentFull Context testet Boot-Realität — teuer, aber wertvoll.
Frage:

„Wann verwenden Sie @SpringBootTest , und welche Rolle spielt webEnvironment ?“

Kurz erklärt:

@SpringBootTest startet einen vollständigen Spring-Boot-Testkontext. Mit webEnvironment steuert man, ob und wie ein Webserver gestartet wird.

Musterantwort:

Ich verwende @SpringBootTest , wenn ich den vollständigen Spring-Boot-Kontext testen möchte: Auto-Configuration, Bean Wiring, Properties, Profile, Security-Konfiguration oder das Zusammenspiel mehrerer Schichten. Das Attribut webEnvironment steuert, ob kein Webserver, ein Mock-Web-Environment, ein definierter Port oder ein zufälliger Port verwendet wird. Die Spring-Boot-API-Dokumentation beschreibt, dass @SpringBootTest verschiedene webEnvironment -Modi unterstützt und bei Tests mit vollständig laufendem Webserver auch TestRestTemplate oder WebTestClient registrieren kann.

Für echte HTTP-Tests bevorzuge ich RANDOM_PORT , damit Tests portkonfliktfrei in CI laufen.

Senior-Antwort:

„ @SpringBootTest ist mein Reality Check für Boot-Wiring — nicht mein Standardhammer für jede Klasse.“

Häufige Fehler:
  • @SpringBootTest überall.
  • DEFINED_PORT in CI.
  • Full Context mit unnötigen Mock Beans.
  • Keine klare Testabsicht.
  • Startup-Test ohne relevante Properties.
  • Webserver starten, obwohl MockMvc reicht.
  • Testdaten teilen sich globalen Zustand.
Best Practices:
  • Full Context gezielt einsetzen.
  • RANDOM_PORT für echte HTTP-Tests.
  • NONE für nicht-web Startup-Kontexte.
  • Properties explizit setzen.
  • Kontextstarts minimieren.
  • Smoke Tests klein halten.
  • Full-Context-Tests in passende Pipeline-Stufe.
  • Testcontainers für externe Infrastruktur.
Stolperfalle:

Ein grüner Unit Test sagt nichts darüber aus, ob der Spring ApplicationContext überhaupt startet.

Merksatz:

Full Context testet Boot-Realität — teuer, aber wertvoll.

B7-K04-Q003 – Test Slices: @WebMvcTest, @DataJpaTest und fokussierte KontexteTeste den Slice, nicht die ganze Welt.
Frage:

„Was sind Spring Boot Test Slices, und wann nutzen Sie sie?“

Kurz erklärt:

Test Slices laden nur relevante Teile des ApplicationContext. Dadurch sind sie schneller und fokussierter als vollständige Spring-Boot-Tests.

Musterantwort:

Test Slices sind fokussierte Spring-Boot-Testkontexte für bestimmte Anwendungsschichten. @WebMvcTest lädt MVC-relevante Komponenten, @DataJpaTest JPA-relevante Komponenten, weitere Slices decken JSON, JDBC, REST-Clients und andere Bereiche ab. Spring Boot dokumentiert eine Liste solcher Test-Slice-Annotationen und der Auto-Configurations, die sie importieren.

Ich nutze Slices, wenn ich ein Frameworkverhalten gezielt testen will, ohne den gesamten ApplicationContext zu starten. Das verbessert Laufzeit und Testpräzision.

Senior-Antwort:

„Ein Slice Test ist ein Mikroskop. Wenn ich alles hineinlade, verliere ich seine Stärke.“

Häufige Fehler:
  • Slice erwartet alle Beans.
  • Services im MVC-Test mit echter Logik testen.
  • Repository im Web-Slice verwenden.
  • Zu viele Imports machen Slice zum Full Context.
  • Security Filter unbewusst deaktivieren.
  • HTTP-Verhalten nicht assertieren.
  • Test Slice ohne klaren Fokus.
Best Practices:
  • Slice nach Frameworkrisiko wählen.
  • Nur relevante Schicht testen.
  • Angrenzende Ports mocken.
  • HTTP-Vertrag im Web-Slice prüfen.
  • JPA im Data-Slice real prüfen.
  • Slice nicht mit Imports überladen.
  • Testlaufzeit beobachten.
  • Slices in PR-Pipeline nutzen.
Stolperfalle:

Wenn ein @WebMvcTest mit vielen @Import s repariert wird, ist oft die Testgrenze falsch gewählt.

Merksatz:

Teste den Slice, nicht die ganze Welt.

B7-K04-Q004 – @MockitoBean, @MockitoSpyBean und Bean OverridesMock im Context nur, wenn der Context selbst das Testziel ist.
Frage:

„Was ist der Unterschied zwischen Mockito-Mocks in Unit Tests und @MockitoBean in Spring Tests?“

Kurz erklärt:

@MockitoBean und @MockitoSpyBean überschreiben Beans im Test-ApplicationContext mit Mockito-Mock oder Spy.

Musterantwort:

Ein normaler Mockito-Mock ist ein Objekt in einem Unit Test und braucht keinen Spring Context. @MockitoBean ersetzt dagegen eine Bean im Spring-Test-ApplicationContext durch einen Mockito-Mock. @MockitoSpyBean ersetzt sie durch einen Spy. Die Spring-Framework-Dokumentation beschreibt, dass @MockitoBean und @MockitoSpyBean verwendet werden können, um eine Bean im Test-ApplicationContext durch einen Mockito Mock oder Spy zu überschreiben.

Ich nutze @MockitoBean , wenn ich einen Spring Slice oder Kontext testen will und eine angrenzende Bean ersetzen muss. Für reine Unit Tests nutze ich normale Mockito-Mocks ohne Spring.

Senior-Antwort:

„ @MockitoBean ist kein Unit-Test-Werkzeug. Es ist ein Spring-Kontext-Werkzeug zum Ersetzen einer Bean an einer Testgrenze.“

Häufige Fehler:
  • @MockitoBean in Tests ohne Springbedarf.
  • Zu viele Bean Overrides.
  • Full Context mit halber Anwendung gemockt.
  • Spy auf komplexe Beans.
  • Mock-Konfiguration in vielen Klassen unterschiedlich.
  • Integrationstest wird durch Mock zum Scheintest.
  • Context Caching unbewusst zerstört.
Best Practices:
  • Plain Mockito für Unit Tests.
  • @MockitoBean nur in Spring-Tests.
  • Bean Overrides sparsam.
  • Angrenzende Ports ersetzen.
  • Kontextkonfigurationen standardisieren.
  • Full Integration nicht wegmocken.
  • Spy selten verwenden.
  • Testlaufzeit messen.
Stolperfalle:

Wenn fast alle Beans gemockt sind, testet der Spring Context kaum noch echte Integration.

Merksatz:

Mock im Context nur, wenn der Context selbst das Testziel ist.

B7-K04-Q005 – ApplicationContext Caching, @DirtiesContext und TestlaufzeitSpring-Testperformance ist Context-Ökonomie. 24h
Frage:

„Warum werden Spring-Boot-Tests langsam, und wie optimieren Sie sie?“

Kurz erklärt:

Spring TestContext cached ApplicationContexts. Gleiche Kontextkonfigurationen können wiederverwendet werden. Zu viele Varianten machen Tests langsam.

Musterantwort:

Spring-Boot-Tests werden langsam, wenn viele unterschiedliche ApplicationContexts gestartet werden. Unterschiedliche Properties, Profiles, Mock Beans, Imports oder @DirtiesContext können Context-Reuse verhindern. Spring Boot Testunterstützung baut auf Spring TestContext auf und stellt Testmodule und Auto-Configuration bereit.

Ich optimiere, indem ich reine Logik ohne Spring teste, Slices nutze, Kontextkonfiguration standardisiere, @DirtiesContext vermeide, Mock Beans reduziere und Testlaufzeit sichtbar messe.

Senior-Antwort:

„In großen Spring-Test-Suites optimiert man nicht einzelne Assertions, sondern die Anzahl eindeutiger ApplicationContexts.“

Häufige Fehler:
  • @DirtiesContext überall.
  • Jede Testklasse eigene Properties.
  • Viele unterschiedliche Profiles.
  • Unterschiedliche Mock Beans pro Klasse.
  • Full Context für Unit Tests.
  • Unnötige @Import s.
  • Testdaten über globale Beans verändern.
  • Laufzeit nicht messen.
Best Practices:
  • Kontextvarianten reduzieren.
  • Testkonfiguration standardisieren.
  • @DirtiesContext nur begründet.
  • Slices nutzen.
  • Unit Tests ohne Spring.
  • Gemeinsame Testprofile.
  • Mock Beans sparsam.
  • Kontextstartzeit sichtbar machen.
Stolperfalle:

@DirtiesContext löst kurzfristig Zustandsprobleme und erzeugt langfristig Laufzeitprobleme.

Merksatz:

Spring-Testperformance ist Context-Ökonomie.

B7-K04-Q006 – Profiles, Test Properties und DynamicPropertySourceKonfiguration ist Teil des Testszenarios.
Frage:

„Wie konfigurieren Sie Spring-Boot-Tests sauber?“

Kurz erklärt:

Test Properties und Profiles steuern Testkonfiguration. @DynamicPropertySource ist besonders nützlich für dynamische Werte wie Testcontainer-Ports.

Musterantwort:

Ich verwende test-spezifische Properties für stabile Testwerte und @DynamicPropertySource für dynamische Werte, etwa Host und Port eines Testcontainers. Profiles nutze ich sparsam, weil viele Profile Context-Varianten erzeugen können. Spring Boot dokumentiert, dass Test-Properties unter anderem über Testannotations und @DynamicPropertySource in die Property-Reihenfolge einfließen.

Wichtig ist: Tests sollen explizit zeigen, welche Konfiguration relevant ist. Versteckte Defaults und zufällige Umgebungswerte machen Tests fragil.

Senior-Antwort:

„Testkonfiguration muss explizit sein. Versteckte Defaults sind der Feind reproduzierbarer Tests.“

Häufige Fehler:
  • Zu viele Testprofile.
  • Properties pro Klasse wild variieren.
  • Produktive Fehler durch Testdefaults verdecken.
  • DynamicPropertySource für statische Werte missbrauchen.
  • Environment-Variablen unkontrolliert nutzen.
  • Secrets in Testproperties committen.
  • Test und Prod Config driften auseinander.
Best Practices:
  • Testproperties explizit.
  • @DynamicPropertySource für dynamische Infrastruktur.
  • Profiles sparsam.
  • Gemeinsame Testkonfiguration.
  • Keine Secrets im Git.
  • Prod-ähnliche kritische Properties testen.
  • Context Caching beachten.
  • Config-Validation testen.
Stolperfalle:

Ein Test kann grün sein, weil er eine bequeme Testproperty nutzt, die in Produktion nie existiert.

Merksatz:

Konfiguration ist Teil des Testszenarios.

B7-K04-Q007 – TestConfiguration, Import und gezielte Test BeansTest Beans sind Testinfrastruktur, nicht Ersatzproduktion.
Frage:

„Wann verwenden Sie @TestConfiguration in Spring-Boot-Tests?“

Kurz erklärt:

@TestConfiguration und @Import erlauben gezielte Testbeans. Sie sind oft besser als große Testkontexte oder pauschale Mocks.

Musterantwort:

Ich nutze @TestConfiguration , wenn ich für Tests gezielt Beans bereitstellen oder ersetzen möchte, zum Beispiel einen festen Clock , einen Fake-Client, einen Test-Mapper oder eine leichte Infrastrukturkomponente. Im Unterschied zu Mockito-Mocks liefert eine TestConfiguration echte Bean-Implementierungen, die oft stabiler und fachlicher sind.

Ich verwende sie sparsam, weil zusätzliche Imports den Testkontext verändern und Context Caching beeinflussen können.

Senior-Antwort:

„TestConfiguration ist ideal, wenn ich echtes Spring-Wiring testen will, aber eine kontrollierte Test-Bean brauche.“

Häufige Fehler:
  • TestConfiguration als zweites Produktionssystem.
  • Zu viele individuelle Konfigurationen.
  • Test Beans verdecken Produktionsfehler.
  • Full Boot Context trotz kleiner Bean-Anforderungen.
  • Fakes mit anderer Semantik als echte Beans.
  • Unklare Imports.
  • Context Caching verschlechtern.
Best Practices:
  • Test Beans gezielt.
  • Feste Clock als Bean.
  • Fakes statt Mocks, wenn fachlicher.
  • Kleine Kontexte bevorzugen.
  • Produktionskonfiguration nicht verschmutzen.
  • Imports dokumentieren.
  • Context-Varianten begrenzen.
  • TestConfiguration wiederverwenden, wenn sinnvoll.
Stolperfalle:

Eine TestConfiguration, die Produktionslogik ersetzt, kann Tests grün machen und echte Wiring-Probleme verstecken.

Merksatz:

Test Beans sind Testinfrastruktur, nicht Ersatzproduktion.

B7-K04-Q008 – Actuator, Health und Observability TestsHealth Tests prüfen Betriebsfähigkeit, nicht nur Codepfade.
Frage:

„Wie testen Sie Actuator Health und Observability-Konfiguration in Spring Boot?“

Kurz erklärt:

Actuator-Tests prüfen Health Indicators, Probe-Konfiguration, Management-Endpunkte und Observability-Konfiguration.

Musterantwort:

Ich teste eigene HealthIndicator-Logik als normalen Unit Test. Zusätzlich habe ich wenige Spring-Kontext- oder HTTP-Smoke-Tests, die prüfen, ob Actuator-Konfiguration, Health Groups, Probes und Management-Endpunkte korrekt verdrahtet sind. Spring Boot Testunterstützung bietet Utilities und Annotationen für Anwendungstests, und @SpringBootTest eignet sich für Boot-Kontextvalidierung.

Wichtig ist, Liveness und Readiness fachlich sauber zu trennen. Ein HealthIndicator für externe Provider sollte nicht automatisch bedeuten, dass Liveness fehlschlägt und Pods neu gestartet werden.

Senior-Antwort:

„Actuator ist nicht nur ein Endpoint. Es ist ein Betriebsvertrag zwischen Anwendung und Plattform.“

Häufige Fehler:
  • HealthIndicator nur im Full Context testen.
  • Liveness prüft externe Systeme.
  • Health Check ist langsam.
  • Sensitive Details im Health Response.
  • Management Endpoint öffentlich.
  • Probes passen nicht zur Actuator-Konfiguration.
  • Kein Test für Downstream-Ausfall.
Best Practices:
  • HealthIndicator-Logik unit testen.
  • Actuator-Konfiguration mit Context-Test prüfen.
  • Health-Endpunkte per Smoke-Test validieren.
  • Liveness minimal halten.
  • Readiness für Traffic-Bereitschaft.
  • Sensitive Details begrenzen.
  • Timeouts für Health Checks.
  • Probes mit Deployment abstimmen.
Stolperfalle:

Ein Health Check, der zu viele Downstreams synchron prüft, kann selbst zum Incident-Verstärker werden.

Merksatz:

Health Tests prüfen Betriebsfähigkeit, nicht nur Codepfade.

B7-K04-Q009 – Spring Security im Spring-Boot-TestkontextSecurity ohne Negativtests ist Hoffnung.
Frage:

„Wie testen Sie Spring Security in Spring-Boot-Anwendungen?“

Kurz erklärt:

Security sollte auf mehreren Ebenen getestet werden: Policy-Logik, Method Security, Web Security, JWT/OAuth2-Mapping und negative Zugriffsfälle.

Musterantwort:

Ich trenne fachliche Autorisierungslogik von Frameworkintegration. Eine Policy wie „Benutzer darf nur eigenes Konto sehen“ teste ich als reinen Unit Test. Method Security, Security Filter Chain, CSRF, CORS, OAuth2/JWT und Rollenmapping teste ich mit Spring-Security-Testunterstützung in passenden Slice- oder Integrationstests.

Der wichtigste Punkt: Security Tests brauchen negative Fälle. Ein Test, der nur erfolgreichen Zugriff prüft, beweist nicht, dass verbotener Zugriff blockiert wird.

Senior-Antwort:

„Security teste ich nicht nur darauf, dass berechtigte Nutzer durchkommen, sondern vor allem darauf, dass falsche Nutzer draußen bleiben.“

Häufige Fehler:
  • Security in Tests deaktivieren.
  • Nur Happy Path.
  • Rollenmapping nicht testen.
  • Method Security ohne Proxy testen.
  • Policylogik in SpEL verstecken.
  • JWT-Claims hart falsch annehmen.
  • Keine 401/403-Tests.
  • Controller-Tests ohne Security Filter.
Best Practices:
  • Policylogik extrahieren.
  • Negative Tests.
  • 401 und 403 unterscheiden.
  • Method Security mit Spring testen.
  • Web Security mit MockMvc/HTTP testen.
  • JWT-Claims realistisch.
  • Security nicht pauschal deaktivieren.
  • Testdaten für Rollen und Scopes standardisieren.
Stolperfalle:

Security in Controller-Tests zu deaktivieren macht Tests schneller, aber oft auch wertlos für reale HTTP-Grenzen.

Merksatz:

Security ohne Negativtests ist Hoffnung.

B7-K04-Q010 – Spring Boot Testing Anti-Patterns und Enterprise Best PracticesSpring Boot Tests brauchen Kontextdisziplin. 24h
Frage:

„Welche Spring-Boot-Testing-Anti-Patterns sehen Sie in Enterprise-Projekten?“

Kurz erklärt:

Spring Boot Testing wird professionell, wenn Testebenen, Context Caching, Slices, Integration, Security und CI/CD bewusst gesteuert werden.

Musterantwort:

Die häufigsten Anti-Patterns sind: @SpringBootTest für alles, Full Context mit vielen Mock Beans, @DirtiesContext überall, viele individuelle Testproperties, Profil-Wildwuchs, überladene Slices, gemockte Repositories statt echter JPA-Tests, deaktivierte Security, fehlende negative Security-Tests, keine Actuator-/Probe-Tests, flaky Datenbankzustände und Testcontainers ohne Lifecycle-Strategie.

Spring Boot bietet starke Testunterstützung mit Testmodulen, @SpringBootTest und fokussierten Slices. Senior-Kompetenz bedeutet, diese Werkzeuge strategisch einzusetzen, nicht überall den größten Kontext zu starten.

Senior-Antwort:

„Spring Boot Testing ist Kontextmanagement. Wer jeden Test mit voller Realität startet, bezahlt Realität auch bei jeder Kleinigkeit.“

Häufige Fehler:
  • Tool statt Risiko entscheidet Testart.
  • Contextstarts nicht messen.
  • Mock Beans überall.
  • Testprofile unkontrolliert.
  • Integration durch Mocking ersetzen.
  • Keine Smoke Tests.
  • Kein Testdatenkonzept.
  • Flaky Tests tolerieren.
Best Practices:
  • Teststrategie nach Risiko.
  • Unit Tests ohne Spring.
  • Slices für Frameworkgrenzen.
  • Full Context gezielt.
  • Testcontainers für echte Infrastruktur.
  • Context-Varianten reduzieren.
  • @DirtiesContext vermeiden.
  • Security nicht pauschal deaktivieren.
Stolperfalle:

Ein Test kann langsam und trotzdem unrealistisch sein, wenn der Full Context mit Mocks ausgehöhlt wurde.

Merksatz:

Spring Boot Tests brauchen Kontextdisziplin.

B7-K05 – Kapitel 5: REST API Testing 10 Karten

B7-K05-Q001 – REST-Teststrategie: Controller, HTTP-Vertrag und SystemgrenzenREST API Tests prüfen Verträge, nicht Controller-Interna. 24h
Frage:

„Wie testen Sie REST APIs in einer Spring-Boot-Anwendung professionell?“

Kurz erklärt:

REST API Testing prüft den HTTP-Vertrag, nicht nur Java-Methoden. Controller-Tests, Integrationstests, Contract Tests und Client Tests decken unterschiedliche Risiken ab.

Musterantwort:

Ich teste REST APIs auf mehreren Ebenen. Controller-Slice-Tests prüfen Mapping, Validation, JSON und Statuscodes schnell mit MockMvc. Full-HTTP-Tests mit zufälligem Port prüfen den echten HTTP-Stack, Filter, Security und Boot-Wiring. Contract Tests sichern die Kompatibilität zwischen Consumer und Provider. Client-Tests prüfen, ob unsere ausgehenden REST-Clients mit Fehlern, Timeouts, Headern und Serialisierung korrekt umgehen.

MockMvc eignet sich besonders für Spring-MVC-Tests ohne laufenden Server, weil es Spring-MVC-Request-Handling über Mock-Requests und Mock-Responses ausführt. WebTestClient kann dagegen sowohl für End-to-End-HTTP-Tests als auch für Tests ohne laufenden Server verwendet werden.

Senior-Antwort:

„Ich teste REST nicht als Java-Methode, sondern als Vertrag über HTTP.“

Häufige Fehler:
  • Controller-Methode direkt aufrufen und HTTP-Vertrag ignorieren.
  • Nur Happy Path testen.
  • Statuscodes nicht prüfen.
  • JSON-Feldnamen nicht prüfen.
  • Validation nicht testen.
  • Fehlerformat nicht standardisieren.
  • Security in Tests deaktivieren.
  • Contract Tests durch E2E ersetzen.
Best Practices:
  • HTTP-Vertrag explizit testen.
  • Statuscodes und Header prüfen.
  • Request-Validation testen.
  • Fehlerfälle testen.
  • JSON-Serialisierung prüfen.
  • Security-Negativfälle testen.
  • Contract Tests für öffentliche APIs.
  • REST-Clients separat testen.
Stolperfalle:

Ein Controller-Unit-Test kann grün sein, obwohl JSON-Binding, Validation oder Exception Mapping in der echten API kaputt sind.

Merksatz:

REST API Tests prüfen Verträge, nicht Controller-Interna.

B7-K05-Q002 – MockMvc: Controller, Request Mapping und Response AssertionsMockMvc testet Spring MVC ohne Server, aber mit HTTP-Denken.
Frage:

„Was testet MockMvc genau, und warum ist es für Spring MVC nützlich?“

Kurz erklärt:

MockMvc testet Spring MVC serverseitig ohne laufenden Server. Es eignet sich hervorragend für Controller, Mapping, Validation, JSON und Error Handling.

Musterantwort:

MockMvc testet Spring MVC serverseitig. Es führt das vollständige Spring-MVC-Request-Handling aus, verwendet aber Mock-Request- und Mock-Response-Objekte statt eines laufenden Servers. Die Spring-Dokumentation beschreibt genau diesen Ansatz: volle Spring-MVC-Verarbeitung ohne laufenden Server.

Dadurch kann ich Controller-Mapping, Request Body Binding, Bean Validation, MessageConverter, Exception Handling, Header, Statuscodes und JSON-Responses schnell testen. Es ist deutlich realistischer als ein direkter Methodenaufruf, aber schneller als ein Full-HTTP-Test mit echtem Server.

Senior-Antwort:

„MockMvc ist mein Werkzeug für den MVC-Vertrag: Request rein, HTTP-Antwort raus.“

Häufige Fehler:
  • Nur Statuscode prüfen.
  • Content-Type vergessen.
  • Location Header nicht prüfen.
  • Service-Logik im Controller-Test testen.
  • JSON-Struktur nicht prüfen.
  • Validation nicht testen.
  • Security Filter unbewusst deaktivieren.
  • Zu viele Imports im @WebMvcTest .
Best Practices:
  • Statuscode prüfen.
  • Header prüfen.
  • Content-Type prüfen.
  • JSON-Felder prüfen.
  • Request Validation testen.
  • Fehlerfälle testen.
  • Service als Grenze mocken.
  • Controller dünn halten.
Stolperfalle:

Ein MockMvc-Test ohne Assertions auf Body und Header ist oft nur ein oberflächlicher Smoke-Test.

Merksatz:

MockMvc testet Spring MVC ohne Server, aber mit HTTP-Denken.

B7-K05-Q003 – Request Validation und FehlerfälleNegative REST-Tests sind Grenzschutz.
Frage:

„Wie testen Sie Request Validation in REST APIs?“

Kurz erklärt:

REST-Tests müssen ungültige Requests prüfen. Validation-Fehler sollen korrekte Statuscodes liefern und die Fachlogik nicht aufrufen.

Musterantwort:

Ich sende bewusst ungültige JSON-Requests über MockMvc oder WebTestClient und erwarte typischerweise 400 Bad Request. Zusätzlich prüfe ich, dass die Service-Schicht nicht aufgerufen wird. Dadurch teste ich nicht nur Bean Validation, sondern auch, dass ungültige Eingaben an der API-Grenze gestoppt werden.

Request Validation ist ein Grenzschutz. Wenn sie kaputt ist, gelangen ungültige Daten tiefer ins System.

Senior-Antwort:

„Validation-Tests prüfen, dass schlechte Daten an der Grenze sterben und nicht im Kernsystem weiterleben.“

Häufige Fehler:
  • Nur gültige Requests testen.
  • @Valid vergessen.
  • Service wird trotz invalidem Request aufgerufen.
  • Fehlerformat nicht prüfen.
  • Required Fields nicht testen.
  • Boundary Values nicht testen.
  • Validation nur in Unit Tests prüfen.
  • JSON-Parsing-Fehler nicht testen.
Best Practices:
  • Ungültige Requests explizit testen.
  • Boundary Values prüfen.
  • Pflichtfelder testen.
  • Service-Nichtaufruf prüfen.
  • Fehlerformat standardisieren.
  • Feldfehler lesbar zurückgeben.
  • Validation nahe an API-Grenze.
  • Fachliche Validierung separat testen.
Stolperfalle:

Wenn der Service bei invalidem Request trotzdem aufgerufen wird, ist die API-Grenze undicht.

Merksatz:

Negative REST-Tests sind Grenzschutz.

B7-K05-Q004 – Problem Details und konsistente FehlerverträgeFehlerantworten sind API-Verträge.
Frage:

„Wie testen Sie konsistente Fehlerantworten in Spring REST APIs?“

Kurz erklärt:

Problem Details standardisieren Fehlerantworten. Tests sollten Status, Content-Type, type, title, detail und fachliche Zusatzfelder prüfen.

Musterantwort:

Ich teste Fehlerantworten genauso wie Erfolgsantworten als Vertrag. Bei Spring 6 und Spring Boot 3 nutze ich häufig ProblemDetail . Spring dokumentiert ProblemDetail als Repräsentation eines RFC-9457-Problem-Details mit Standardfeldern und nicht-standardisierten Zusatzfeldern. Außerdem beschreibt Spring MVC ErrorResponse als Vertrag für HTTP-Fehlerdetails inklusive Status, Header und Body im RFC-9457-Format.

Im Test prüfe ich nicht nur 404, sondern auch Content-Type, type , title , status , detail und relevante Extensions wie orderId oder errorCode .

Senior-Antwort:

„Ein Fehler ist kein Sonderfall außerhalb des Vertrags. Für Clients ist er oft der wichtigste Teil des Vertrags.“

Häufige Fehler:
  • Nur Statuscode testen.
  • Fehlerbody ignorieren.
  • Unterschiedliche Fehlerformate.
  • Stacktraces im Fehlerbody.
  • Interne Exception-Namen exposen.
  • Keine Fehlercodes.
  • Validation-Fehler nicht standardisieren.
  • Fehlerformat nicht in Contract Tests aufnehmen.
Best Practices:
  • Problem Details standardisieren.
  • Status und Body prüfen.
  • Content-Type prüfen.
  • Stabile type -Werte.
  • Keine internen Details leaken.
  • Fachliche Fehlercodes nutzen.
  • Validation-Fehler konsistent.
  • Fehlerverträge in Contract Tests.
Stolperfalle:

Ein konsistenter 2xx-Vertrag hilft wenig, wenn Fehlerantworten chaotisch und nicht parsebar sind.

Merksatz:

Fehlerantworten sind API-Verträge.

B7-K05-Q005 – WebTestClient: Fluent API für HTTP- und Web-Layer-TestsWebTestClient testet HTTP aus Client-Perspektive.
Frage:

„Wann verwenden Sie WebTestClient statt MockMvc?“

Kurz erklärt:

WebTestClient bietet eine fluent API für Request-Ausführung und Response-Assertions. Er kann gegen echte Server oder gebundene Controller/Kontexte laufen.

Musterantwort:

WebTestClient ist besonders nützlich, wenn ich eine fluent, clientartige API für HTTP-Tests möchte oder WebFlux teste. Er kann echte Server über HTTP testen und auch ohne laufenden Server an Spring MVC oder WebFlux gebunden werden. Die Spring-Dokumentation beschreibt, dass WebTestClient für End-to-End-HTTP-Tests verwendet werden kann und auch Spring MVC und WebFlux ohne laufenden Server testen kann.

In Spring MVC-Projekten ist MockMvc weiterhin sehr stark. WebTestClient kann aber eine einheitliche API über verschiedene Testarten bieten.

Senior-Antwort:

„WebTestClient ist praktisch, wenn ich HTTP wie ein Client testen will — mit oder ohne echten Server.“

Häufige Fehler:
  • WebTestClient nur als WebFlux-Werkzeug verstehen.
  • Full-HTTP-Test verwenden, obwohl Slice reicht.
  • Body nicht prüfen.
  • Keine Header prüfen.
  • Bind-Modus nicht bewusst wählen.
  • Reactive Timing nicht beachten.
  • Security-Kontext unklar.
Best Practices:
  • Testmodus bewusst wählen.
  • Status, Header und Body prüfen.
  • Für WebFlux bevorzugen.
  • Für Full-HTTP-Smoke-Tests nutzen.
  • MockMvc bei MVC-Slices weiterhin erwägen.
  • API über HTTP-Vertrag testen.
  • Response Assertions fachlich halten.
  • Nicht jeden Test als echten Server-Test ausführen.
Stolperfalle:

Das Werkzeug ist weniger wichtig als der Modus: ohne Server, mit Context oder echter HTTP-Stack.

Merksatz:

WebTestClient testet HTTP aus Client-Perspektive.

B7-K05-Q006 – REST Client Testing: RestClient, RestTemplate, WireMock und Mock Web ServerREST Clients brauchen HTTP-realistische Tests.
Frage:

„Wie testen Sie ausgehende REST Clients in Spring Boot?“

Kurz erklärt:

REST Clients sollten mit realistischem HTTP-Verhalten getestet werden: Statuscodes, JSON, Header, Fehler, Timeouts und Netzwerkprobleme.

Musterantwort:

Ich teste REST Clients nicht nur durch Mocking der Java-Methode. Ich will wissen, ob der Client echten HTTP-Vertrag korrekt erzeugt und verarbeitet: Pfad, Methode, Header, Body, Statuscodes, Fehlerbody, Timeouts und Deserialisierung. Die Spring-Dokumentation empfiehlt für Code mit RestClient oder RestTemplate Mock-Webserver wie OkHttp MockWebServer oder WireMock, weil diese HTTP-Anfragen wie ein regulärer Server annehmen und dadurch dieselbe Client-Konfiguration wie in Produktion getestet werden kann.

Für Spring-Boot-Slices kann @RestClientTest relevant sein; die API-Dokumentation beschreibt, dass diese Annotation auf Beans fokussiert, die RestTemplateBuilder oder RestClient.Builder verwenden, und relevante Rest-Client-Test-Auto-Configuration aktiviert.

Senior-Antwort:

„Einen REST Client teste ich als HTTP-Client, nicht als gemockte Java-Funktion.“

Häufige Fehler:
  • REST Client komplett mocken.
  • Pfad und Header nie testen.
  • Kein Test für 4xx/5xx.
  • Keine Timeout-Tests.
  • Produktionsclient anders konfiguriert als Testclient.
  • JSON-Felder nicht prüfen.
  • Retry-Verhalten nicht testen.
  • Externe echte Provider im CI direkt aufrufen.
Best Practices:
  • Kritische Clients mit Mock-Webserver testen.
  • Pfad, Methode und Header prüfen.
  • Request Body prüfen.
  • Erfolgs- und Fehlerfälle testen.
  • Timeouts konfigurieren und testen.
  • Retry-Budget testen.
  • Auth Header prüfen.
  • Deserialisierung real testen.
Stolperfalle:

Wenn der Client im Test nicht denselben Builder, dieselben Timeouts und dieselben Interceptors nutzt wie Produktion, testet man eine andere Anwendung.

Merksatz:

REST Clients brauchen HTTP-realistische Tests.

B7-K05-Q007 – Security Testing: 401, 403, Rollen und ScopesAuthentifiziert heißt nicht autorisiert.
Frage:

„Wie testen Sie Security-Grenzen einer REST API?“

Kurz erklärt:

REST Security Tests müssen positive und negative Fälle prüfen: unauthenticated, authenticated-but-forbidden und authorized.

Musterantwort:

Ich teste mindestens drei Fälle: ohne Authentifizierung erwarte ich 401, mit falschen Rollen oder Scopes erwarte ich 403, mit korrekter Berechtigung erwarte ich den fachlichen Erfolg. Zusätzlich teste ich fachliche Autorisierung wie Tenant- oder Ownership-Regeln.

Wichtig ist, Security in REST-Tests nicht pauschal zu deaktivieren. Sonst testet man eine API, die in Produktion so nicht existiert.

Senior-Antwort:

„Security-Tests sind vor allem Negativtests. Der wichtigste Test ist oft der, der 403 erwartet.“

Häufige Fehler:
  • Nur erfolgreichen Zugriff testen.
  • Security Filter deaktivieren.
  • 401 und 403 verwechseln.
  • Scopes nicht testen.
  • Business-Authorization nicht testen.
  • JWT Claims unrealistisch mocken.
  • Controller direkt aufrufen.
  • Keine Regressionstests für Security-Bugs.
Best Practices:
  • 401 testen.
  • 403 testen.
  • Erfolgsfall testen.
  • Rollen und Scopes realistisch.
  • Tenant- und Ownership-Regeln testen.
  • Security-Konfiguration einbeziehen.
  • Method Security separat prüfen.
  • Negative Tests als Pflicht.
Stolperfalle:

Ein Controller-Test ohne Security Filter kann eine API grün testen, die in Produktion nicht erreichbar oder zu offen ist.

Merksatz:

Authentifiziert heißt nicht autorisiert.

B7-K05-Q008 – OpenAPI, Dokumentation und API-KompatibilitätDokumentation ohne Verifikation ist Wunschdenken.
Frage:

„Wie stellen Sie sicher, dass eine REST API rückwärtskompatibel bleibt?“

Kurz erklärt:

REST API Tests sollten API-Dokumentation und Kompatibilität absichern. OpenAPI, Contract Tests und Regressionstests verhindern Breaking Changes.

Musterantwort:

Ich definiere API-Kompatibilitätsregeln und sichere sie mit Tests. Dazu gehören Contract Tests, OpenAPI-Diff-Prüfungen, Regressionstests für Statuscodes und Fehlerformate sowie Consumer-driven Contracts für kritische Consumer. Dokumentation darf nicht nur generiert werden, sondern muss gegen Verhalten validiert werden.

Spring Boot kann in Tests mit Spring REST Docs integriert werden; die Spring-Boot-Dokumentation beschreibt @AutoConfigureRestDocs , das den MockMvc-Bean für Spring REST Docs anpasst. Das ist ein testgetriebener Ansatz: Dokumentation entsteht aus getesteten Requests und Responses.

Senior-Antwort:

„Eine API ist ein Produktvertrag. Breaking Changes brauchen denselben Respekt wie Datenbankmigrationen.“

Häufige Fehler:
  • Dokumentation manuell veraltet.
  • Breaking Changes ohne Tests.
  • Fehlerformat nicht dokumentiert.
  • OpenAPI nur generieren, nie prüfen.
  • Consumer nicht einbeziehen.
  • Enum-Erweiterungen als harmlos annehmen.
  • Statuscode-Änderungen unterschätzen.
Best Practices:
  • API-Kompatibilitätsregeln definieren.
  • OpenAPI-Diff in CI.
  • Contract Tests für kritische Consumer.
  • Fehlerverträge dokumentieren.
  • Beispiele testen.
  • Dokumentation aus Tests generieren.
  • Versionierungsstrategie definieren.
  • Deprecation-Prozess.
Stolperfalle:

Ein neues Pflichtfeld im Request ist für bestehende Clients ein Breaking Change, auch wenn der Servercode sauber aussieht.

Merksatz:

Dokumentation ohne Verifikation ist Wunschdenken.

B7-K05-Q009 – Idempotenz, Retries und Nebenwirkungen testenRetries passieren — Duplikate dürfen nicht passieren.
Frage:

„Wie testen Sie Idempotenz bei REST APIs?“

Kurz erklärt:

Idempotenztests prüfen, dass wiederholte Requests keine doppelten fachlichen Nebenwirkungen erzeugen.

Musterantwort:

Ich teste wiederholte Requests mit demselben Idempotency-Key. Der erste Request erzeugt die Ressource. Der zweite Request mit gleichem Key und gleichem Payload darf keine zweite Zahlung, Bestellung oder Buchung erzeugen. Er sollte dieselbe fachliche Antwort oder einen klar definierten Status liefern. Außerdem teste ich denselben Key mit anderem Payload als Fehlerfall.

Idempotenztests sind besonders wichtig bei POST-Endpunkten mit Zahlungs-, Buchungs- oder Bestellnebenwirkungen, weil Clients bei Timeouts häufig retryen.

Senior-Antwort:

„Idempotenz teste ich nicht über Statuscodes allein, sondern über ausbleibende doppelte Nebenwirkungen.“

Häufige Fehler:
  • Nur ersten Request testen.
  • Retry-Verhalten ignorieren.
  • Idempotency-Key nicht verpflichtend.
  • Gleicher Key mit anderem Payload erlaubt.
  • Keine Unique Constraints.
  • Keine Concurrency Tests.
  • Fehlerantwort nicht definiert.
  • Idempotenz nur im Cache speichern.
Best Practices:
  • Wiederholte Requests testen.
  • Gleicher Key, gleicher Payload.
  • Gleicher Key, anderer Payload.
  • Missing Key testen.
  • Side Effects zählen.
  • Datenbankconstraints nutzen.
  • Race Conditions testen.
  • Retry-Szenarien simulieren.
Stolperfalle:

Ein Retry-Test ist wertlos, wenn er nicht prüft, ob wirklich nur ein fachlicher Effekt entstanden ist.

Merksatz:

Retries passieren — Duplikate dürfen nicht passieren.

B7-K05-Q010 – REST API Testing Anti-Patterns und Enterprise Best PracticesREST-Qualität ist Vertragsqualität. 24h
Frage:

„Welche REST API Testing Anti-Patterns sehen Sie in Enterprise-Projekten?“

Kurz erklärt:

Professionelles REST API Testing prüft den Vertrag vollständig: Erfolg, Fehler, Security, Kompatibilität, Clients und Nebenwirkungen.

Musterantwort:

Typische Anti-Patterns sind direkte Controller-Methodenaufrufe als einziger Test, nur Happy Path, nur Statuscode ohne Body/Header, keine Validation-Tests, kein Fehlervertrag, Security in Tests deaktiviert, keine 401/403-Abgrenzung, REST Clients nur als Java-Mock, keine Timeout-Tests, OpenAPI ohne Verifikation, zu viele fragile E2E-Tests statt Contract Tests und keine Idempotenztests für kritische POSTs.

Professionelles REST Testing nutzt MockMvc oder WebTestClient für schnelle Web-Layer-Tests, echte HTTP-Tests für kritische Boot-/Security-Pfade, Contract Tests für Kompatibilität und Mock-Webserver für ausgehende REST Clients. MockMvc testet Spring MVC ohne laufenden Server, WebTestClient kann End-to-End-HTTP und serverlose Web-Layer-Tests abdecken, und Mock-Webserver helfen, echte Client-Konfiguration zu testen.

Senior-Antwort:

„Eine gute REST-Teststrategie prüft nicht nur, dass Endpunkte antworten, sondern dass Verträge stabil bleiben.“

Häufige Fehler:
  • Controller direkt testen.
  • HTTP-Vertrag nicht prüfen.
  • Fehlerfälle ignorieren.
  • Security abschalten.
  • Clients nicht realistisch testen.
  • Contract Tests fehlen.
  • OpenAPI driftet.
  • E2E-Suite wird flaky.
Best Practices:
  • REST als Vertrag testen.
  • MockMvc/WebTestClient für Web-Layer.
  • Problem Details testen.
  • Security-Negativtests.
  • REST Clients mit Mock-Webserver.
  • Contract Tests für Consumer.
  • OpenAPI-Diff nutzen.
  • Idempotenz testen.
Stolperfalle:

Viele E2E-Tests ersetzen keine Contract Tests. Sie finden Fehler spät, langsam und oft flaky.

Merksatz:

REST-Qualität ist Vertragsqualität.

B7-K06 – Kapitel 6: Persistence Testing 10 Karten

B7-K06-Q001 – Persistenz-Teststrategie: Unit, Repository, Integration und MigrationMocke Fachports, aber teste Persistenzverträge realistisch. 24h
Frage:

„Wie bauen Sie eine sinnvolle Persistenz-Teststrategie für eine Spring-Boot-Anwendung auf?“

Kurz erklärt:

Persistence Testing braucht mehrere Ebenen: Domainlogik ohne Datenbank, Repository-Slices für JPA/Spring Data, echte Datenbankintegration für SQL-Realität und Migrationstests für Schema-Evolution.

Musterantwort:

Ich trenne Fachlogik von Persistenzrisiken. Domainregeln teste ich ohne Datenbank. Repositories, JPQL, Entity Mapping, Constraints und Transaktionen teste ich mit @DataJpaTest . Wenn SQL-Dialekt, Migrationen, Indexe, Locking oder Datenbankfunktionen relevant sind, nutze ich eine echte Datenbank über Testcontainers.

Der zentrale Punkt ist Realismus an der richtigen Stelle. Ein Mock-Repository beweist nicht, dass JPA-Mapping, Query, Constraint oder Migration funktionieren. Gleichzeitig sollte reine Fachlogik nicht durch eine Datenbank verlangsamt werden.

Senior-Antwort:

„Ich mocke Repositories nicht, wenn das Risiko im Mapping, SQL oder Transaktionsverhalten liegt.“

Häufige Fehler:
  • Repository mocken und JPA-Risiken ignorieren.
  • Alles als Full @SpringBootTest .
  • H2 verwenden, obwohl PostgreSQL-spezifisches SQL genutzt wird.
  • Migrationen nicht testen.
  • Constraints nicht prüfen.
  • Lazy Loading nur zufällig testen.
  • Keine negativen Persistenztests.
  • Datenbanktests mit riesigen Fixtures überladen.
Best Practices:
  • Domainlogik ohne Datenbank testen.
  • Repository-Methoden mit JPA testen.
  • Produktionsnahe Datenbank für SQL-spezifische Risiken.
  • Migrationen in Tests laufen lassen.
  • Constraints bewusst testen.
  • Flush erzwingen, wenn nötig.
  • Testdaten klein halten.
  • Query Count für kritische Pfade prüfen.
Stolperfalle:

Ein grüner Unit Test mit Mock-Repository sagt nichts darüber aus, ob die Query in der echten Datenbank funktioniert.

Merksatz:

Mocke Fachports, aber teste Persistenzverträge realistisch.

B7-K06-Q002 – @DataJpaTest und Repository-SlicesRepository-Tests testen Queries, nicht Controller.
Frage:

„Was testet @DataJpaTest , und wann verwenden Sie es?“

Kurz erklärt:

@DataJpaTest lädt einen fokussierten Persistenzkontext für JPA-Tests. Es eignet sich für Repository-Methoden, Queries, Entity Mapping und Transaktionsverhalten.

Musterantwort:

@DataJpaTest verwende ich, wenn ich JPA-Repositories, Entity-Mapping, Derived Queries, JPQL, Projections oder Transaktionsverhalten testen will. Es lädt keinen vollständigen Spring-Boot-Kontext, sondern einen fokussierten JPA-Slice. Spring Boot dokumentiert Test Slices und zeigt, dass @DataJpaTest JPA-, Hibernate-, DataSource-, Transaction- und TestDatabase-Auto-Configuration importiert.

Der Vorteil ist Fokus und Geschwindigkeit. Der Nachteil ist: Wenn eine In-Memory-Datenbank genutzt wird, kann das Verhalten von Produktion abweichen. Für SQL-spezifische Risiken kombiniere ich @DataJpaTest mit Testcontainers.

Senior-Antwort:

„ @DataJpaTest ist mein Mikroskop für JPA. Ich nutze es, um Persistenzverträge zu prüfen, nicht um die ganze Anwendung zu testen.“

Häufige Fehler:
  • Service-Logik in @DataJpaTest .
  • Annahme, dass alle Beans geladen werden.
  • H2-Verhalten mit PostgreSQL gleichsetzen.
  • Keine Sortierung testen.
  • Query nur auf Existenz prüfen.
  • Testdaten zu groß.
  • Fehlender Flush bei Constraint-Tests.
  • Lazy Loading zufällig offen lassen.
Best Practices:
  • Repository-Verhalten fokussiert testen.
  • Kleine Testdaten.
  • Filter, Sortierung und Pagination prüfen.
  • SQL-spezifische Tests mit echter DB.
  • Flush erzwingen, wenn Constraints relevant sind.
  • Keine Web-/Service-Logik im Slice.
  • Derived Queries und JPQL abdecken.
  • Projections separat prüfen.
Stolperfalle:

Ein @DataJpaTest mit vielen Imports und Services ist oft ein verkappter Integrationstest mit falscher Grenze.

Merksatz:

Repository-Tests testen Queries, nicht Controller.

B7-K06-Q003 – Produktionsnahe Datenbanktests mit TestcontainersDatenbankrealismus kostet Zeit, spart aber Produktionsüberraschungen.
Frage:

„Wann verwenden Sie Testcontainers für Persistenztests?“

Kurz erklärt:

Testcontainers erlaubt Tests gegen echte Datenbanken wie PostgreSQL. Dadurch werden Dialekt, Constraints, Migrationsverhalten und SQL-Realität besser abgedeckt als mit H2.

Musterantwort:

Ich verwende Testcontainers, wenn Datenbankrealismus wichtig ist: PostgreSQL-spezifisches SQL, JSONB, UUID, Indexe, Constraints, Locking, Migrationen, Query-Pläne oder Unterschiede zu H2. Testcontainers stellt ein PostgreSQL-Modul bereit, mit dem eine PostgreSQL-Containerinstanz aus Java-Tests gestartet werden kann.

Spring Boot unterstützt Testcontainers-Integration über Testunterstützung und dynamische Properties. In modernen Boot-Projekten kann @ServiceConnection die Verbindung zu unterstützten Testcontainers vereinfachen. Der wichtige Senior-Punkt ist: Nicht jeder Test braucht einen Container, aber kritische Persistenzrisiken sollten nicht gegen eine unrealistische Datenbank getestet werden.

Senior-Antwort:

„Wenn mein Produktionsproblem PostgreSQL-spezifisch ist, teste ich nicht gegen eine Fantasie-Datenbank.“

Häufige Fehler:
  • Testcontainer pro Testmethode starten.
  • H2 für PostgreSQL-spezifische Features.
  • Container-Version nicht pinnen.
  • Migrations nicht ausführen.
  • Testdaten nicht isolieren.
  • Externe lokale Datenbank verwenden.
  • Docker-Verfügbarkeit im CI nicht planen.
  • Testcontainers für jeden trivialen Query-Test verwenden.
Best Practices:
  • Produktionsdatenbanktyp verwenden.
  • Container-Version bewusst wählen.
  • Dynamische Properties sauber registrieren.
  • Container-Lifecycle optimieren.
  • Migrationen mitlaufen lassen.
  • Testdaten pro Test isolieren.
  • Kritische Queries gegen echte DB.
  • CI-Docker-Fähigkeit sicherstellen.
Stolperfalle:

H2 grün bedeutet nicht PostgreSQL grün.

Merksatz:

Datenbankrealismus kostet Zeit, spart aber Produktionsüberraschungen.

B7-K06-Q004 – Transaktionale Tests, Rollback und FlushOhne Flush sieht die Datenbank manche Fehler nie.
Frage:

„Warum müssen Persistenztests manchmal explizit flush() aufrufen?“

Kurz erklärt:

Viele Persistenzfehler werden erst beim Flush oder Commit sichtbar. Transaktionale Tests rollen häufig zurück, aber Rollback ersetzt keine bewusste Prüfung von Constraints.

Musterantwort:

JPA schreibt Änderungen nicht zwingend sofort in die Datenbank. Hibernate arbeitet mit Persistence Context und Flush. Constraint-Verletzungen, SQL-Fehler oder Datenbankchecks werden oft erst beim Flush oder Commit sichtbar. Wenn ein Test nur save() aufruft und danach endet, kann er grün sein, obwohl ein echter Commit scheitern würde.

Spring Test unterstützt transaktionale Tests; Tests können in einer Transaktion laufen und am Ende zurückgerollt werden. Rollback ist praktisch für Isolation, aber ich muss trotzdem bewusst flushen, wenn ich Datenbankverhalten prüfen will.

Senior-Antwort:

„Bei Persistenztests ist save() nicht die Wahrheit. Die Wahrheit beginnt oft erst bei flush() oder Commit.“

Häufige Fehler:
  • Constraint-Test ohne Flush.
  • Rollback mit Validierung verwechseln.
  • Datenbankfehler erst beim Deployment sehen.
  • Tests lesen aus Persistence Context statt aus DB.
  • Dirty Checking nicht verstehen.
  • Commit-Verhalten nie testen.
  • Zu breite Exception prüfen.
Best Practices:
  • Flush bei Constraint-Tests.
  • Persistence Context ggf. clearen.
  • Commit-nahe Tests für kritische Fälle.
  • Rollback für Isolation nutzen.
  • Transaktionsgrenzen bewusst testen.
  • Datenbankconstraints nicht nur Bean Validation testen.
  • Exceptions präzise prüfen, soweit portabel.
  • Nicht unnötig überall flushen.
Stolperfalle:

Ein Test kann grün sein, weil die Datenbank den fehlerhaften Zustand noch gar nicht gesehen hat.

Merksatz:

Ohne Flush sieht die Datenbank manche Fehler nie.

B7-K06-Q005 – Flyway und Liquibase Migration TestingMigrationen müssen laufen, bevor Produktion sie sieht.
Frage:

„Wie testen Sie Datenbankmigrationen in Spring Boot?“

Kurz erklärt:

Migrationstests prüfen, ob Datenbankschema und Anwendung zusammenpassen. Flyway oder Liquibase sollten in Integrationstests gegen eine echte oder produktionsnahe Datenbank laufen.

Musterantwort:

Ich lasse Migrationen in Tests wirklich laufen und kombiniere sie mit Schema-Validation. Das Ziel ist zu prüfen, ob das migrierte Schema zur Anwendung passt. Für PostgreSQL-spezifische Migrationen nutze ich PostgreSQL-Testcontainers statt H2. Zusätzlich teste ich kritische Migrationspfade: neue Constraints, Indizes, Spalten mit Defaults, Datenmigrationen und Backward Compatibility bei Rolling Deployments.

Spring Boot testet Persistenz über fokussierte Slices oder Full Contexts; wenn Migration und Anwendungskontext zusammen validiert werden sollen, ist ein gezielter Integrationstest sinnvoll. Testcontainers stellt dafür echte Datenbanken bereit.

Senior-Antwort:

„Migrationen sind produktiver Code. Ich teste sie gegen die Datenbank, auf der sie später laufen.“

Häufige Fehler:
  • Migrationen in Tests deaktivieren.
  • Hibernate erzeugt Testschema automatisch.
  • H2 statt Produktionsdatenbank für Dialekt-SQL.
  • Keine Tests für Datenmigrationen.
  • Destruktive Migrationen ohne Expand-Contract.
  • Defaults und Not-Null-Constraints nicht testen.
  • Indexe nie prüfen.
  • Rollout-Kompatibilität ignorieren.
Best Practices:
  • Migrationen in Integrationstests ausführen.
  • Produktionsnahen Datenbanktyp verwenden.
  • Hibernate Validate aktivieren.
  • Datenmigrationen gezielt testen.
  • Expand-Contract für Rolling Updates.
  • Not-Null und Default-Werte prüfen.
  • Migrationen klein halten.
  • Keine manuelle DB-Änderung außerhalb Migration.
Stolperfalle:

Wenn Hibernate im Test das Schema automatisch erzeugt, testen Sie nicht Ihre Migrationen.

Merksatz:

Migrationen müssen laufen, bevor Produktion sie sieht.

B7-K06-Q006 – N+1-Erkennung und Fetching TestsDaten korrekt laden reicht nicht — man muss sie auch mit vertretbarer Query-Anzahl laden.
Frage:

„Wie testen Sie, dass ein Repository kein N+1-Problem erzeugt?“

Kurz erklärt:

N+1-Probleme entstehen, wenn für eine Liste von Entities zusätzliche Queries pro Entity ausgeführt werden. Tests können Query-Anzahl und Fetching-Strategien prüfen.

Musterantwort:

Ich baue ein Szenario mit mehreren Parent-Entities und Children, führe den relevanten Query aus und greife auf die benötigten Associations zu. Dann prüfe ich die Anzahl der ausgeführten SQL-Statements über Hibernate Statistics oder ein SQL-Counting-Tool. Hibernate beschreibt Fetching als Prozess, Daten aus der Datenbank verfügbar zu machen, und betont, dass Fetch-Tuning eine große Performancewirkung haben kann.

Der Test sollte nicht nur „Daten vorhanden“ prüfen, sondern das Performance-Verhalten der Query. Kritische Listen- und Suchendpunkte sollten solche Tests haben.

Senior-Antwort:

„N+1 teste ich nicht mit Bauchgefühl, sondern mit mehreren Datensätzen und Query Count.“

Häufige Fehler:
  • Nur mit einem Datensatz testen.
  • Lazy Loading in Transaktion versteckt Problem.
  • Query Count nie prüfen.
  • EntityGraph vergessen.
  • Join Fetch mit Pagination falsch nutzen.
  • N+1 erst im Lasttest finden.
  • Performance-Test ohne realistische Datenform.
  • Open Session in View kaschiert Designproblem.
Best Practices:
  • Mehrere Parent-Entities testen.
  • Association-Zugriff im Test auslösen.
  • Query Count messen.
  • EntityGraph oder DTO-Queries nutzen.
  • Kritische Listenendpunkte absichern.
  • Pagination und Fetching bewusst kombinieren.
  • SQL-Logs in Diagnose nutzen.
  • Performance-Regressionstests für Hotspots.
Stolperfalle:

Ein Test mit nur einer Order kann kein N+1-Problem sichtbar machen.

Merksatz:

Daten korrekt laden reicht nicht — man muss sie auch mit vertretbarer Query-Anzahl laden.

B7-K06-Q007 – Optimistic Locking und konkurrierende UpdatesConcurrency muss man provozieren, nicht nur behaupten.
Frage:

„Wie testen Sie Optimistic Locking in JPA?“

Kurz erklärt:

Optimistic Locking verhindert verlorene Updates durch Versionsprüfung. Tests müssen konkurrierende Transaktionen oder stale Entities simulieren.

Musterantwort:

Ich verwende eine Entity mit @Version , lade dieselbe Zeile in zwei getrennten Transaktionen oder Persistence Contexts, ändere und speichere zuerst eine Version, und versuche danach die stale Version zu speichern. Der zweite Update muss fehlschlagen. Wichtig ist, echte Transaktionsgrenzen zu verwenden und Flush oder Commit auszulösen.

Optimistic Locking ist keine reine Unit-Test-Frage. Es ist ein Zusammenspiel aus Entity-Version, Persistence Context, SQL-Update und Datenbankzustand.

Senior-Antwort:

„Optimistic Locking teste ich mit echten Transaktionsgrenzen. Sonst teste ich nur meine Annahme, nicht den Konflikt.“

Häufige Fehler:
  • Locking in einer einzigen Transaktion testen.
  • Kein Flush auslösen.
  • @Version vergessen.
  • Konflikt fachlich nicht behandeln.
  • Exception-Typ zu datenbankspezifisch erwarten.
  • Testdaten nicht isolieren.
  • Nebenläufigkeit nur theoretisch betrachten.
  • Retry ohne Idempotenz.
Best Practices:
  • @Version für konkurrierende Aggregate.
  • Getrennte Transaktionen testen.
  • Flush/Commit auslösen.
  • Konfliktfall explizit testen.
  • Fachliche Fehlerantwort definieren.
  • Retry nur kontrolliert.
  • Idempotenz beachten.
  • Hohe Konfliktrate architektonisch analysieren.
Stolperfalle:

Ein Locking-Test ohne getrennte Persistence Contexts ist oft kein echter Locking-Test.

Merksatz:

Concurrency muss man provozieren, nicht nur behaupten.

B7-K06-Q008 – Testdaten, Fixtures und DatenisolationTestdaten sind Teil der Testlesbarkeit.
Frage:

„Wie organisieren Sie Testdaten für Persistenztests?“

Kurz erklärt:

Persistenztests brauchen kleine, isolierte und fachlich lesbare Testdaten. Builder helfen, relevante Werte sichtbar zu machen und irrelevante Pflichtfelder zu kapseln.

Musterantwort:

Ich halte Persistenz-Testdaten klein und isoliert. Jeder Test erzeugt seine relevanten Daten selbst. Für Entities nutze ich Testdaten-Builder, damit Pflichtfelder valide Defaults haben und fachlich relevante Unterschiede sichtbar bleiben. Ich vermeide globale SQL-Dumps und Reihenfolgeabhängigkeiten.

Datenisolation ist entscheidend. Ob durch Rollback, Cleanup, Testcontainer-Reset oder eindeutige IDs: Tests dürfen nicht voneinander abhängen.

Senior-Antwort:

„Persistenz-Testdaten sollen genau das Szenario zeigen — nicht die ganze Produktionswelt nachbauen.“

Häufige Fehler:
  • Riesige SQL-Fixtures.
  • Globale Testdaten.
  • Tests hängen von Reihenfolge ab.
  • Zufällige Daten ohne Debuggability.
  • Builder verstecken relevante Werte.
  • Cleanup unvollständig.
  • Persistenztests verwenden Prod-Dumps.
  • Personenbezogene Daten in Tests.
Best Practices:
  • Kleine Daten pro Test.
  • Relevante Werte explizit.
  • Builder für Defaults.
  • Eindeutige IDs.
  • Keine Reihenfolgeabhängigkeit.
  • Rollback oder Cleanup sauber wählen.
  • Daten nahe am Test.
  • Beziehungen gezielt aufbauen.
Stolperfalle:

Globale Testdaten machen Tests bequem, bis der erste parallele CI-Lauf zufällig rot wird.

Merksatz:

Testdaten sind Teil der Testlesbarkeit.

B7-K06-Q009 – Native Queries, Projections und Datenbank-spezifische FeaturesJe datenbankspezifischer der Query, desto realistischer muss der Test sein.
Frage:

„Wie testen Sie native Queries und Spring Data Projections?“

Kurz erklärt:

Native Queries und Projections sind besonders testwürdig, weil sie direkt vom Datenbankschema, SQL-Dialekt und Alias-Mapping abhängen.

Musterantwort:

Native Queries teste ich gegen eine produktionsnahe Datenbank, weil SQL-Dialekt, Funktionen, Datentypen und Alias-Mapping datenbankspezifisch sind. Bei Projections prüfe ich, ob Aliase korrekt auf Getter gemappt werden und ob Typen wie UUID, BigDecimal oder Instant sauber konvertiert werden.

Hibernate ist für Mapping und Query/Retrieval zuständig, aber native SQL verlässt teilweise die Portabilitätsebene von JPQL. Deshalb ist hier Realismus wichtiger als bei einfachen Derived Queries.

Senior-Antwort:

„Native SQL teste ich gegen die Datenbank, deren Dialekt ich verwende. Alles andere ist Optimismus.“

Häufige Fehler:
  • Native Query nur gegen H2 testen.
  • Projection-Aliase nicht prüfen.
  • Aggregationen ohne mehrere Datensätze.
  • Stornierte oder gelöschte Daten nicht testen.
  • Typkonvertierung ignorieren.
  • Sortierung nicht prüfen.
  • Query-Performance nie betrachten.
  • Native SQL mit Schema-Drift nicht absichern.
Best Practices:
  • Native Queries gegen echte DB.
  • Projection-Felder explizit assertieren.
  • Aliase stabil benennen.
  • Aggregationsfälle realistisch.
  • Negative Daten einschließen.
  • Sortierung testen.
  • Typen prüfen.
  • Migration und Query zusammen testen.
Stolperfalle:

Ein Projection-Test, der nur result.size() prüft, erkennt keine kaputten Aliase.

Merksatz:

Je datenbankspezifischer der Query, desto realistischer muss der Test sein.

B7-K06-Q010 – Persistence Testing Anti-Patterns und Enterprise Best PracticesPersistenztests brauchen Realität mit Maß. 24h
Frage:

„Welche Anti-Patterns sehen Sie bei Persistenztests in Java/Spring-Projekten?“

Kurz erklärt:

Professionelles Persistence Testing balanciert Geschwindigkeit, Realismus und Risiko. Anti-Patterns entstehen, wenn Tests entweder zu weit von der echten Datenbank entfernt oder zu schwergewichtig sind.

Musterantwort:

Häufige Anti-Patterns sind gemockte Repositories für Query-Verhalten, H2 für PostgreSQL-spezifisches SQL, deaktivierte Migrationen, Hibernate-generiertes Testschema statt Flyway/Liquibase, Constraint-Tests ohne Flush, keine N+1-Erkennung, keine Locking-Tests, riesige globale Fixtures, native Queries ohne Feldassertions und Full-Context-Tests für einfache Repository-Fälle.

Professionelles Persistence Testing nutzt Unit Tests für Domainlogik, @DataJpaTest für JPA-Slices, Testcontainers für produktionsnahe Datenbankrealität und gezielte Performance-Regressionstests für kritische Fetching- und Query-Pfade.

Senior-Antwort:

„Persistenztests sind dort realistisch, wo Datenbankrealität zählt, und dort schlank, wo reine Logik reicht.“

Häufige Fehler:
  • Persistenzrisiko unterschätzen.
  • Datenbanktests zu spät in CI.
  • Testdaten nicht isoliert.
  • Keine negativen Tests.
  • Keine Performance-Hotspot-Tests.
  • Alles mit Full Context.
  • Keine Schema-Validation.
  • SQL-Logs nie betrachten.
Best Practices:
  • Risikoorientierte Persistenztests.
  • @DataJpaTest für Repository-Slices.
  • Testcontainers für echte DB-Risiken.
  • Migrationen ausführen.
  • Schema validieren.
  • Flush bewusst.
  • Query Count prüfen.
  • Testdaten klein und isoliert.
Stolperfalle:

Eine schnelle Persistenztestsuite ist wertlos, wenn sie die echte Datenbank nie berührt.

Merksatz:

Persistenztests brauchen Realität mit Maß.

B7-K07 – Kapitel 7: Testcontainers Professional 10 Karten

B7-K07-Q001 – Grundlagen: Warum Testcontainers und wann nicht?Nicht jeder Test braucht Container — aber echte Infrastrukturfehler brauchen echte Infrastru… 24h
Frage:

„Wann würden Sie Testcontainers in einem Java-/Spring-Boot-Projekt einsetzen?“

Kurz erklärt:

Testcontainers startet echte Infrastruktur für Integrationstests. Es ist sinnvoll, wenn das Risiko in Datenbank-, Broker-, Cache-, Object-Storage- oder HTTP-Integrationen liegt.

Musterantwort:

Ich setze Testcontainers ein, wenn die echte Infrastruktur Teil des Risikos ist. Bei PostgreSQL-spezifischem SQL, Flyway-Migrationen, Kafka-Consumer-Verhalten, Redis-TTLs, S3-Integration oder WireMock-HTTP-Verträgen reicht ein Mock oft nicht aus. Testcontainers liefert dafür wegwerfbare, echte Services in Docker-Containern. Die offizielle Testcontainers-Seite beschreibt genau diesen Zweck: kurzlebige, leichtgewichtige Instanzen von Datenbanken, Message Brokern und anderen Services für Tests.

Ich nutze Testcontainers aber nicht für reine Domainlogik. Ein Rabattalgorithmus, ein Value Object oder eine Policy braucht keinen Container. Testcontainers ist ein Werkzeug für Integrationsrealismus, nicht für alles.

Senior-Antwort:

„Ich verwende Testcontainers dort, wo Mocks lügen würden und echte Infrastruktur Teil des Vertrags ist.“

Häufige Fehler:
  • Testcontainers für reine Unit Tests.
  • Alles gegen Container testen.
  • Container pro Testmethode starten.
  • H2 weiter für PostgreSQL-spezifische Features nutzen.
  • Container-Version nicht pinnen.
  • Testdaten nicht isolieren.
  • CI-Docker-Verfügbarkeit ignorieren.
  • Testcontainers als Ersatz für Contract Tests missverstehen.
Best Practices:
  • Testcontainers für echte Integrationsrisiken.
  • Unit Tests weiterhin ohne Container.
  • Container-Versionen bewusst setzen.
  • Container-Lifecycle optimieren.
  • Testdaten isolieren.
  • Migrationen mitlaufen lassen.
  • CI-Fähigkeit sicherstellen.
  • Container-Logs bei Fehlern verfügbar machen.
Stolperfalle:

Ein Test gegen H2 kann grün sein, obwohl PostgreSQL wegen Dialekt, Constraint oder Typkonvertierung scheitert.

Merksatz:

Nicht jeder Test braucht Container — aber echte Infrastrukturfehler brauchen echte Infrastrukturtests.

B7-K07-Q002 – JUnit 5 Integration und Container LifecycleContainer-Lifecycle ist Testarchitektur.
Frage:

„Wie funktioniert der Container-Lifecycle mit JUnit 5 und Testcontainers?“

Kurz erklärt:

Die JUnit-Jupiter-Extension von Testcontainers kann Container automatisch starten und stoppen. Statische Container werden für die Testklasse geteilt, Instanzcontainer pro Testmethode.

Musterantwort:

Testcontainers bietet eine JUnit-Jupiter-Extension. Mit @Testcontainers wird die Extension aktiviert, mit @Container werden Containerfelder erkannt und verwaltet. Die offizielle Dokumentation beschreibt zwei Modi: Container können pro Testmethode neu gestartet oder zwischen allen Methoden einer Testklasse geteilt werden.

Ein statisches Containerfeld wird typischerweise einmal für die Testklasse gestartet. Ein nicht-statisches Containerfeld wird pro Testinstanz beziehungsweise Testmethode verwaltet. Als Senior entscheide ich bewusst zwischen Isolation und Laufzeit. Für Datenbanken nutze ich oft einen geteilten Container mit sauberer Datenisolation pro Test.

Senior-Antwort:

„Lifecycle ist der Trade-off zwischen Isolation und Feedbackzeit. Ich wähle ihn bewusst, nicht zufällig.“

Häufige Fehler:
  • Container pro Testmethode ohne Grund.
  • Statischer Container ohne Datenisolation.
  • @Container und manuelles start() falsch mischen.
  • Testreihenfolge-Abhängigkeiten.
  • Containerlogs nicht sichern.
  • Lifecycle nicht verstanden.
  • CI-Laufzeit explodiert.
Best Practices:
  • Lifecycle bewusst wählen.
  • Statische Container für teure Infrastruktur.
  • Daten pro Test isolieren.
  • Keine Testreihenfolge-Abhängigkeit.
  • Manuelles Lifecycle-Management nur begründet.
  • Container-Versionen pinnen.
  • Logs bei Fehlern verfügbar machen.
  • Laufzeit messen.
Stolperfalle:

Ein geteilter Container ohne Datenisolation macht Tests schnell — und irgendwann flaky.

Merksatz:

Container-Lifecycle ist Testarchitektur.

B7-K07-Q003 – Spring Boot Integration: @ServiceConnection und DynamicPropertySourceContainer laufen lassen reicht nicht — die Anwendung muss sie korrekt finden.
Frage:

„Wie verbinden Sie Spring Boot Tests mit Testcontainers?“

Kurz erklärt:

Spring Boot kann Testcontainers-Verbindungen automatisch oder dynamisch konfigurieren. @ServiceConnection vereinfacht unterstützte Container, @DynamicPropertySource ist flexibel für beliebige Properties.

Musterantwort:

Es gibt zwei typische Wege. Bei unterstützten Containern kann ich @ServiceConnection nutzen, damit Spring Boot passende Connection Details automatisch bereitstellt. Die Spring-Boot-Dokumentation beschreibt Testcontainers-Unterstützung und zeigt, dass Container mit @ServiceConnection als Service-Verbindungen für Tests verwendet werden können.

Für flexible oder eigene Container verwende ich @DynamicPropertySource , um dynamische Werte wie JDBC-URL, Host, Port oder Credentials in den Spring Environment einzutragen. Das ist besonders nützlich, wenn Containerports erst zur Laufzeit bekannt sind.

Senior-Antwort:

„Die eigentliche Integration ist nicht nur der Containerstart, sondern die korrekte Übergabe dynamischer Connection Details an Spring.“

Häufige Fehler:
  • Feste Ports konfigurieren.
  • Container startet nach ApplicationContext.
  • DynamicPropertySource nicht statisch.
  • Properties falsch benannt.
  • Unterschiedliche Testproperties pro Klasse.
  • @ServiceConnection für nicht unterstützte Fälle erwarten.
  • Testcontainer und Spring Context Lifecycle nicht abstimmen.
Best Practices:
  • @ServiceConnection für unterstützte Standardservices.
  • @DynamicPropertySource für eigene Properties.
  • Keine festen Hostports.
  • Container vor Context-Start verfügbar machen.
  • Gemeinsame Basisklassen prüfen.
  • Context-Varianten reduzieren.
  • Properties fachlich benennen.
  • Container-Konfiguration versionieren.
Stolperfalle:

Ein Container kann laufen, während Spring trotzdem gegen die falsche URL verbindet.

Merksatz:

Container laufen lassen reicht nicht — die Anwendung muss sie korrekt finden.

B7-K07-Q004 – PostgreSQL, Migrationen und produktionsnahe DatenbanktestsProduktionsdatenbankrisiken brauchen Produktionsdatenbanknähe.
Frage:

„Warum ist PostgreSQL-Testcontainers oft besser als H2 für Spring Data JPA Tests?“

Kurz erklärt:

PostgreSQL-Testcontainers ermöglichen realistische Tests für Migrationen, Constraints, native Queries, Datentypen und Dialektverhalten.

Musterantwort:

H2 kann für einfache Repository-Tests ausreichen, aber es ist nicht PostgreSQL. PostgreSQL hat andere Datentypen, Dialektregeln, Locking-Semantik, Constraints, JSONB, UUID-Verhalten, Indexe und SQL-Funktionen. Wenn diese Dinge relevant sind, teste ich gegen PostgreSQL. Testcontainers stellt ein PostgreSQL-Modul bereit, mit dem eine PostgreSQL-Containerinstanz aus Java-Tests gestartet werden kann.

Besonders wichtig ist das für Flyway- oder Liquibase-Migrationen. Wenn Hibernate im Test das Schema automatisch erzeugt, teste ich nicht meine Migrationen. Mit PostgreSQL-Testcontainers kann CI früh erkennen, ob Migrationen und Anwendungsmapping zusammenpassen.

Senior-Antwort:

„Ich teste nicht gegen eine bequemere Datenbank, wenn mein Produktionsrisiko im echten Dialekt liegt.“

Häufige Fehler:
  • H2 für native PostgreSQL Queries.
  • Flyway in Tests deaktiviert.
  • ddl-auto=create-drop statt Migration.
  • Container-Version nicht zur Produktion passend.
  • Keine Constraint-Tests.
  • Keine Migration-Validation.
  • Testdaten nicht isoliert.
  • PostgreSQL-spezifische Features ungetestet.
Best Practices:
  • Produktionsdatenbanktyp verwenden.
  • PostgreSQL-Version bewusst wählen.
  • Migrationen im Test ausführen.
  • Hibernate Validate nutzen.
  • Native Queries gegen echte DB.
  • JSONB und UUID real testen.
  • Constraints mit Flush prüfen.
  • Container-Lifecycle optimieren.
Stolperfalle:

ddl-auto=create kann Migrationen komplett umgehen und ein falsches Sicherheitsgefühl erzeugen.

Merksatz:

Produktionsdatenbankrisiken brauchen Produktionsdatenbanknähe.

B7-K07-Q005 – Kafka, Redis und Multi-Service-IntegrationstestsTeste Integrationspfade, nicht Infrastruktursammlungen. 24h
Frage:

„Wie testen Sie Kafka- oder Redis-Integrationen mit Testcontainers?“

Kurz erklärt:

Testcontainers kann mehrere Services gleichzeitig starten. Multi-Service-Tests sollten aber gezielt kritische Integrationspfade prüfen, nicht die gesamte Produktionslandschaft nachbauen.

Musterantwort:

Ich starte echte Kafka- oder Redis-Container und teste einen klar abgegrenzten Integrationspfad. Bei Kafka prüfe ich Producer, Consumer, Serialisierung, Topic-Konfiguration, Retry- oder DLT-Verhalten. Bei Redis prüfe ich Cache-Schlüssel, TTL, Serialisierung und Invalidierung. Testcontainers unterstützt viele Service-Arten; die offizielle Testcontainers-Seite nennt unter anderem Datenbanken und Message Broker als typische kurzlebige Testinstanzen.

Wichtig ist, Multi-Service-Tests klein zu halten. Ich teste nicht die gesamte Produktionslandschaft, sondern einen fachlich kritischen Pfad mit deterministischen Testdaten und klaren Timeouts.

Senior-Antwort:

„Multi-Service-Tests sind wertvoll, wenn sie einen kritischen Pfad prüfen — gefährlich, wenn sie die halbe Produktion nachbauen.“

Häufige Fehler:
  • Ganze Systemlandschaft nachbauen.
  • Keine klaren Timeouts.
  • Thread.sleep statt Await.
  • Topics zwischen Tests teilen.
  • Redis Keys kollidieren.
  • Consumer-Gruppen nicht isoliert.
  • Tests abhängig von Reihenfolge.
  • Containerstarts in jeder Testmethode.
Best Practices:
  • Einen Integrationspfad pro Test.
  • Eindeutige Topics oder Key-Prefixes.
  • Bounded Await.
  • Keine harten Sleeps.
  • Serialisierung real testen.
  • DLT/Retry gezielt testen.
  • TTL und Cache-Invalidierung prüfen.
  • Container-Lifecycle optimieren.
Stolperfalle:

Asynchrone Tests ohne klare Await-Bedingung sind fast immer zukünftige flaky Tests.

Merksatz:

Teste Integrationspfade, nicht Infrastruktursammlungen.

B7-K07-Q006 – Netzwerke, Ports und Service Discovery zwischen ContainernNetzwerkadresse hängt vom Beobachter ab.
Frage:

„Wie funktioniert Networking bei Testcontainers?“

Kurz erklärt:

Testcontainers unterscheidet zwischen Zugriff aus der Test-JVM auf Container und Kommunikation zwischen Containern. Dynamische Ports und Netzwerke müssen bewusst genutzt werden.

Musterantwort:

Aus der Test-JVM greife ich über container.getHost() und container.getMappedPort(...) zu. Ich verlasse mich nicht auf feste Hostports. Für Container-zu-Container-Kommunikation nutze ich ein gemeinsames Testcontainers-Network und Network Aliases. Testcontainers wartet standardmäßig auf den ersten exponierten Port eines Containers, um einfache Erreichbarkeit zu prüfen.

Der wichtigste Fehler ist, Host- und Container-Netzwerksicht zu verwechseln. localhost im Testprozess ist nicht dasselbe wie localhost in einem anderen Container.

Senior-Antwort:

„Bei Testcontainers muss ich immer fragen: Wer spricht mit wem — Test-JVM zum Container oder Container zu Container?“

Häufige Fehler:
  • Feste Hostports.
  • localhost im Container falsch verstehen.
  • Container-zu-Container-Kommunikation über Hostport.
  • Keine Network Aliases.
  • Tests brechen bei Parallelisierung.
  • Portwerte vor Containerstart lesen.
  • Kubernetes-Service-DNS mit Docker-Netzwerk verwechseln.
Best Practices:
  • Dynamische Ports nutzen.
  • getHost() und getMappedPort() verwenden.
  • Shared Network für Container-zu-Container.
  • Network Aliases für stabile Namen.
  • Keine festen Ports in CI.
  • Properties nach Containerstart registrieren.
  • Netzwerktopologie einfach halten.
  • Testnetzwerke pro Testgruppe sauber verwalten.
Stolperfalle:

localhost ist eine Perspektive, keine universelle Adresse.

Merksatz:

Netzwerkadresse hängt vom Beobachter ab.

B7-K07-Q007 – Wait Strategies, Startup Checks und stabile BereitschaftWarte auf Ready, nicht auf Sekunden.
Frage:

„Warum sind Wait Strategies in Testcontainers wichtig?“

Kurz erklärt:

Wait Strategies definieren, wann ein Container wirklich testbereit ist. „Port offen“ bedeutet nicht immer „Service bereit“.

Musterantwort:

Container können laufen, obwohl der Service im Container noch nicht bereit ist. Testcontainers wartet standardmäßig bis zu 60 Sekunden darauf, dass der erste gemappte Netzwerkport lauscht. Für viele Services reicht das nicht. Dann nutze ich HTTP-, Log-, Healthcheck- oder eigene Wait Strategies.

Senior-Regel: Tests sollen auf Bereitschaft warten, nicht schlafen. Thread.sleep ist langsam und flaky. Eine Wait Strategy beschreibt die echte Bedingung, unter der der Test beginnen darf.

Senior-Antwort:

„Ein stabiler Integrationstest wartet auf eine Bedingung, nicht auf eine Vermutung.“

Häufige Fehler:
  • Thread.sleep zur Bereitschaft.
  • Nur Port offen prüfen.
  • Zu kurze Timeouts.
  • Zu lange Timeouts.
  • Falsche Logmeldung.
  • Service startet, aber Schema nicht bereit.
  • Race Conditions bei Async-Initialisierung.
  • Readiness nicht fachlich definiert.
Best Practices:
  • Bereitschaft konkret definieren.
  • HTTP Wait für HTTP-Services.
  • Log Wait nur bei stabilen Logs.
  • Modulspezifische Waits bevorzugen.
  • Keine Sleeps.
  • Startup Timeout bewusst setzen.
  • Fehlerlogs sammeln.
  • Langsame Starts analysieren.
Stolperfalle:

Ein offener Port beweist nicht, dass ein Service fachlich bereit ist.

Merksatz:

Warte auf Ready, nicht auf Sekunden.

B7-K07-Q008 – Reuse, Singleton Container Pattern und CI/CD-LaufzeitSchnell ist gut; reproduzierbar ist Pflicht.
Frage:

„Wie optimieren Sie Testcontainers-Laufzeit, ohne Tests flaky zu machen?“

Kurz erklärt:

Container-Wiederverwendung kann lokale Feedbackzeit verbessern, birgt aber Risiken durch veralteten Zustand. In CI ist Reproduzierbarkeit oft wichtiger als maximale Wiederverwendung.

Musterantwort:

Ich optimiere zuerst Lifecycle und Scope: schwere Container statisch pro Klasse oder über ein bewusstes Singleton-Pattern, Testdaten isoliert, Schema-Cleanup oder eindeutige Daten pro Test. Die Testcontainers-Dokumentation erklärt manuelle Lifecycle-Kontrolle über start() und stop() sowie AutoCloseable-Verhalten. Für JUnit 5 beschreibt die Dokumentation automatische Lifecycle-Verwaltung über die Jupiter-Extension.

Reusable Containers können lokal hilfreich sein, aber ich verwende sie vorsichtig. Wiederverwendung mit altem Zustand kann Tests verfälschen. In CI bevorzuge ich reproduzierbare, frische Umgebungen oder kontrollierte statische Container pro Suite.

Senior-Antwort:

„Ich optimiere Containerstarts, aber niemals auf Kosten von reproduzierbaren Tests.“

Häufige Fehler:
  • Reuse in CI ohne Datenreset.
  • Globaler Container mit verstecktem Zustand.
  • Container pro Methode für schwere Dienste.
  • Singleton Pattern falsch mit Extension mischen.
  • Keine Datenisolation.
  • Testreihenfolge-Abhängigkeit.
  • Laufzeitprobleme durch Abschalten von Tests lösen.
  • Image-Pulls nicht cachen.
Best Practices:
  • Lifecycle bewusst standardisieren.
  • Statische Container für teure Services.
  • Daten pro Test isolieren.
  • Reuse lokal optional.
  • CI reproduzierbar halten.
  • Images pinnen und cachen.
  • Integrationsteststufen trennen.
  • Testlaufzeiten messen.
Stolperfalle:

Ein wiederverwendeter Container mit altem Zustand kann Fehler genauso gut verstecken wie ein Mock.

Merksatz:

Schnell ist gut; reproduzierbar ist Pflicht.

B7-K07-Q009 – CI/CD, Docker-Umgebungen und OpenShift-nahe TestsTestcontainers testet Abhängigkeiten; Deployment-Smoke-Tests testen die Plattformrealität.
Frage:

„Was müssen Sie beachten, wenn Testcontainers in CI/CD läuft?“

Kurz erklärt:

Testcontainers braucht in CI eine funktionierende Container-Runtime, ausreichende Ressourcen, Image-Zugriff, Logs und klare Pipeline-Stufung.

Musterantwort:

CI muss Docker beziehungsweise eine kompatible Container-Umgebung bereitstellen. Testcontainers führt Startup Checks aus, um zu prüfen, ob die Umgebung geeignet ist. Die Dokumentation beschreibt solche Startup Checks, etwa Docker-Version, mountbare Dateien und Zugriff auf exponierte Ports.

Ich trenne Unit- und Integrationstests, cache Images, pinne Container-Versionen, archiviere Logs, kontrolliere Parallelisierung und plane Ressourcen. Wichtig ist auch: Testcontainers validiert containerisierte Abhängigkeiten im Test, ersetzt aber keine echten OpenShift-Deployment-Smoke-Tests gegen Routes, ConfigMaps, Secrets, NetworkPolicies und Probes.

Senior-Antwort:

„Testcontainers braucht eine ernst gemeinte CI-Umgebung. Docker nebenbei irgendwie verfügbar zu machen reicht nicht.“

Häufige Fehler:
  • Docker im CI nicht verfügbar.
  • Integrationstests in Unit-Stage versteckt.
  • Kein Image Cache.
  • Zu wenig Runner-Ressourcen.
  • Feste Ports in parallelen Jobs.
  • Logs bei Fehlern nicht archiviert.
  • Secrets in Logs.
  • Testcontainers mit OpenShift-Validierung verwechseln.
Best Practices:
  • Docker-fähige Runner bereitstellen.
  • Unit und Integration trennen.
  • Images pinnen.
  • Image Pull Cache nutzen.
  • Ressourcen planen.
  • Parallelisierung kontrollieren.
  • Containerlogs sichern.
  • Fail-fast bei Startup-Problemen.
Stolperfalle:

Ein grüner Testcontainers-Test bedeutet nicht, dass die Anwendung auf OpenShift korrekt geroutet, konfiguriert oder berechtigt ist.

Merksatz:

Testcontainers testet Abhängigkeiten; Deployment-Smoke-Tests testen die Plattformrealität.

B7-K07-Q010 – Anti-Patterns, Governance und Enterprise Best PracticesTestcontainers braucht Strategie, nicht nur Docker. 24h
Frage:

„Welche Testcontainers-Anti-Patterns sehen Sie in Enterprise-Projekten?“

Kurz erklärt:

Testcontainers braucht Governance: welche Services, welche Images, welcher Lifecycle, welche CI-Stufe, welche Isolation und welche Laufzeitbudgets.

Musterantwort:

Typische Anti-Patterns sind Container für reine Unit Tests, schwere Container pro Testmethode, feste Ports, latest -Images, fehlende Wait Strategies, Thread.sleep , geteilter Zustand ohne Cleanup, Docker-unfähige CI-Runner, keine Logs bei Fehlern, unkontrollierte Parallelisierung und der Versuch, die gesamte Produktionslandschaft in Tests nachzubauen.

Professionelle Nutzung bedeutet Standards: gepinnte Images, definierte Lifecycle-Patterns, Datenisolation, Wait Strategies, CI-Stufung, Image-Caching, Logs und klare Regeln, wann Testcontainers eingesetzt wird. Testcontainers stellt die Infrastruktur bereit; die Teststrategie entscheidet, wo sie sinnvoll ist.

Senior-Antwort:

„Testcontainers gibt uns echte Abhängigkeiten. Governance sorgt dafür, dass sie nicht zu echten Problemen in der Pipeline werden.“

Häufige Fehler:
  • Keine Testcontainers-Regeln.
  • Jedes Team baut eigene Patterns.
  • Containerstarts unkontrolliert.
  • Keine Datenisolation.
  • Keine Wait Strategies.
  • Keine CI-Ressourcenplanung.
  • latest -Images.
  • Logs fehlen bei Failures.
Best Practices:
  • Testcontainers-Golden-Path.
  • Images pinnen.
  • Lifecycle-Patterns definieren.
  • Wait Strategies vorschreiben.
  • Datenisolation standardisieren.
  • Unit- und Integrationstests trennen.
  • Containerlogs sichern.
  • Laufzeitbudgets definieren.
Stolperfalle:

Ohne Standards wird Testcontainers schnell vom Qualitätsgewinn zum Laufzeit- und Flakiness-Problem.

Merksatz:

Testcontainers braucht Strategie, nicht nur Docker.

B7-K08 – Kapitel 8: Kafka und Messaging Testing 10 Karten

B7-K08-Q001 – Kafka-Teststrategie: Producer, Consumer, Contract und SystemflussKafka-Testing ist Vertrags- und Zustandsprüfung, nicht nur Sendebestätigung. 24h
Frage:

„Wie würden Sie eine Teststrategie für Kafka-basierte Spring-Boot-Services aufbauen?“

Kurz erklärt:

Kafka-Testing braucht mehrere Ebenen. Producer, Consumer, Event-Vertrag, Broker-Integration und fachlicher End-to-End-Fluss prüfen unterschiedliche Risiken.

Musterantwort:

Ich trenne Producer-, Consumer-, Contract- und Integrationstests. Producer-Tests prüfen, ob das richtige Topic, der richtige Key, Header und Payload gesendet werden. Consumer-Tests prüfen Deserialisierung, fachliche Verarbeitung, Idempotenz, Fehler- und Retry-Verhalten. Contract Tests sichern Event-Schema und semantische Kompatibilität zwischen Producer und Consumer. Integrationstests mit echter Kafka-Infrastruktur prüfen Broker-Verhalten, Offsets, Consumer Groups und DLT-Flows.

Spring Kafka stellt Testunterstützung über spring-kafka-test bereit, und bei Spring Boot sollte der Boot-Test-Starter für Kafka-Tests verwendet werden. Für produktionsnähere Tests nutze ich häufig Testcontainers, weil das Kafka-Modul echte Kafka-Container starten und verwalten kann.

Senior-Antwort:

„Kafka teste ich nicht wie einen Methodenaufruf. Ich teste Event-Verträge, Zustandsübergänge, Offsets, Idempotenz und Fehlerpfade.“

Häufige Fehler:
  • Kafka nur mit Mocks testen.
  • Keine Event-Contract-Tests.
  • Consumer-Idempotenz nicht testen.
  • DLT-Verhalten ignorieren.
  • Keine negativen Tests für Poison Messages.
  • Offsets nicht verstehen.
  • Tests mit Thread.sleep .
  • Topics zwischen Tests teilen.
Best Practices:
  • Producer und Consumer getrennt testen.
  • Event-Verträge versionieren.
  • Broker-Integration realistisch testen.
  • Testcontainers für produktionsnahe Kafka-Flows.
  • Eindeutige Topics oder Consumer Groups.
  • Await statt Sleep.
  • Idempotenz immer testen.
  • Retry und DLT testen.
Stolperfalle:

Ein gemockter KafkaTemplate beweist nicht, dass ein Consumer das Event später deserialisieren und korrekt verarbeiten kann.

Merksatz:

Kafka-Testing ist Vertrags- und Zustandsprüfung, nicht nur Sendebestätigung.

B7-K08-Q002 – Producer Tests: KafkaTemplate, Key, Header und PayloadTeste das ganze Event, nicht nur den Methodenaufruf.
Frage:

„Wie testen Sie einen Kafka Producer in Spring Boot?“

Kurz erklärt:

Producer-Tests prüfen, dass ein Service das richtige Event mit korrektem Key, Topic, Headern und Payload erzeugt.

Musterantwort:

Ich teste die Producer-Logik zunächst ohne Broker, indem ich KafkaTemplate mocke und die gesendete Message capturiere. Dabei prüfe ich Topic, Key, Header und Payload. Das ist schnell und zeigt, ob der Event-Vertrag korrekt erzeugt wird. Zusätzlich brauche ich mindestens einen Integrationstest mit echtem Kafka, der Serialisierung und Broker-Kommunikation prüft.

Spring Kafka stellt KafkaTemplate als High-Level-Abstraktion für das Senden von Kafka-Nachrichten bereit. Ein Mock-Test deckt aber nicht die echte Serializer-Konfiguration, Topic-Erreichbarkeit oder Broker-Kommunikation ab.

Senior-Antwort:

„Ein Kafka-Event besteht nicht nur aus Payload. Key, Topic und Header sind genauso Teil des Vertrags.“

Häufige Fehler:
  • Nur send() verifizieren.
  • Key nicht prüfen.
  • Header nicht prüfen.
  • Topic-Name hardcodiert und ungetestet.
  • Payload nur teilweise prüfen.
  • Zeit nicht deterministisch.
  • Keine Serializer-Tests.
  • Integrationstest mit echtem Broker fehlt.
Best Practices:
  • Payload vollständig fachlich prüfen.
  • Key bewusst testen.
  • Topic aus Konfiguration testen.
  • Header-Konventionen testen.
  • Zeit über Clock .
  • Correlation ID und Tenant prüfen.
  • Serializer-Integration ergänzen.
  • Contract Test für Event-Schema.
Stolperfalle:

Ein Producer-Test, der nur send() prüft, kann grün sein, obwohl Key oder Header falsch sind.

Merksatz:

Teste das ganze Event, nicht nur den Methodenaufruf.

B7-K08-Q003 – Consumer Tests: Listener-Logik, Deserialisierung und Side EffectsListener dünn, Handler testbar.
Frage:

„Wie testen Sie einen Kafka Consumer sinnvoll?“

Kurz erklärt:

Consumer-Tests sollten Listener-Technik und fachliche Verarbeitung trennen. Handler-Logik kann schnell ohne Broker getestet werden; Listener-Integration braucht Kafka-nahe Tests.

Musterantwort:

Ich trenne den Kafka Listener von der fachlichen Handler-Logik. Die Handler-Logik teste ich als schnellen Unit Test. Dort prüfe ich Side Effects wie Datenbankupdate, Event-Publishing oder Idempotenz. Zusätzlich teste ich mit echter Kafka-Infrastruktur, ob der Listener Nachrichten korrekt deserialisiert, konsumiert und an den Handler weitergibt.

Spring Kafka unterstützt message-driven POJOs, also Listener-Methoden, die Nachrichten aus Kafka verarbeiten. Für Tests stellt Spring Kafka eigene Testunterstützung bereit, während Testcontainers echte Kafka-Container starten kann.

Senior-Antwort:

„Der Listener ist Infrastruktur. Die fachliche Verarbeitung gehört in einen Handler, den ich ohne Broker testen kann.“

Häufige Fehler:
  • Fachlogik direkt in Listener-Methode.
  • Jeder Consumer-Test startet Kafka.
  • Kein Test für Deserialisierung.
  • Side Effects nur indirekt prüfen.
  • Keine Idempotenztests.
  • Fehlerpfade ignorieren.
  • Consumer Group-Konfiguration nicht testen.
  • Async-Verarbeitung mit Sleep testen.
Best Practices:
  • Listener dünn halten.
  • Handler separat testen.
  • Broker-Integration gezielt.
  • Deserialisierung real testen.
  • Side Effects assertieren.
  • Idempotenz testen.
  • Fehlerpfade testen.
  • Await statt Sleep.
Stolperfalle:

Wenn die ganze Fachlogik in der Listener-Methode steckt, braucht jeder kleine Fachtest einen Kafka-Test.

Merksatz:

Listener dünn, Handler testbar.

B7-K08-Q004 – Testcontainers Kafka: echter Broker für IntegrationstestsBroker-Risiken brauchen Broker-Tests.
Frage:

„Wann verwenden Sie Testcontainers Kafka statt Embedded Kafka oder Mocks?“

Kurz erklärt:

Testcontainers Kafka startet einen echten Kafka-Broker für Integrationstests. Damit können Producer, Consumer, Serialization, Topics und Offset-Verhalten realistischer geprüft werden.

Musterantwort:

Ich verwende Testcontainers Kafka, wenn ich möglichst produktionsnahes Broker-Verhalten testen will: echte Kafka-Container, echte Netzwerkkommunikation, echte Serializer, Consumer Groups, Offset-Verhalten und Integration mit Spring Boot. Das Testcontainers-Kafka-Modul kann Kafka-Container automatisch instanziieren und verwalten; es unterstützt unter anderem KafkaContainer für apache/kafka und apache/kafka-native .

Embedded Kafka ist für manche Tests bequem und schnell, aber Testcontainers ist oft näher an containerisierten Produktionsumgebungen. Spring Kafka dokumentiert Embedded-Kafka-Testunterstützung; seit Kafka 4.0 ist dabei nur noch EmbeddedKafkaKraftBroker verfügbar.

Senior-Antwort:

„Für kritische Kafka-Flows will ich mindestens einen Test, der wirklich über einen Broker geht.“

Häufige Fehler:
  • Kafka-Container pro Testmethode.
  • Feste Topic-Namen ohne Isolation.
  • Consumer Group wiederverwendet.
  • Keine Await-Bedingung.
  • Thread.sleep .
  • Auto-Topic-Annahmen unklar.
  • Serializer nicht produktionsnah.
  • Test prüft nur Senden, nicht Verarbeitung.
Best Practices:
  • Kafka-Testcontainer für echte Integration.
  • Topics pro Test eindeutig oder bereinigt.
  • Consumer Groups eindeutig.
  • Bootstrap Servers dynamisch konfigurieren.
  • Await statt Sleep.
  • Side Effects prüfen.
  • Serializer real nutzen.
  • Container-Lifecycle optimieren.
Stolperfalle:

Ein Integrationstest, der nur produziert, aber keinen Consumer-Side-Effect prüft, beweist wenig über den Messaging-Flow.

Merksatz:

Broker-Risiken brauchen Broker-Tests.

B7-K08-Q005 – Offsets, Acknowledgement und Delivery Semantics testenAt-least-once heißt: Duplikate sind kein Fehlerfall, sondern Systemrealität. 24h
Frage:

„Wie testen Sie Offset-Handling und Delivery Semantics bei Kafka-Consumern?“

Kurz erklärt:

Kafka-Delivery-Semantics hängen stark davon ab, wann Offsets committet werden und ob Verarbeitung idempotent ist. Tests müssen Duplikate und Fehlerfälle prüfen.

Musterantwort:

Ich teste nicht nur den Happy Path. Ich simuliere Verarbeitungsfehler, Consumer-Neustart, Duplikate und wiederholte Zustellung. Für viele Systeme ist at-least-once realistisch: Eine Nachricht kann erneut verarbeitet werden. Deshalb muss der Consumer idempotent sein. Eine gute Teststrategie prüft, dass ein doppelt zugestelltes Event keinen doppelten fachlichen Effekt erzeugt.

Kafka speichert Event-Streams dauerhaft und Consumer verarbeiten Events aus diesen Streams. Die konkrete Delivery-Semantik entsteht aus Producer-, Broker- und Consumer-Konfiguration sowie Offset-Commit-Verhalten. In Consumer-Dokumentation wird betont, dass korrektes Offset-Management entscheidend für Message-Delivery-Garantien ist.

Senior-Antwort:

„Bei Kafka rechne ich mit Duplikaten. Meine Tests müssen beweisen, dass Duplikate fachlich harmlos sind.“

Häufige Fehler:
  • Duplikate nicht testen.
  • Offset-Commit nicht verstehen.
  • Auto-Commit blind nutzen.
  • Idempotenz nur behaupten.
  • Fachlicher Effekt wird doppelt erzeugt.
  • Retry erzeugt Duplikate.
  • Fehlerfall committet trotzdem.
  • Keine Tests für Consumer Restart.
Best Practices:
  • Duplicate Events testen.
  • Idempotenz über Event-ID oder fachlichen Key.
  • Unique Constraints nutzen.
  • Fehler vor und nach Side Effect simulieren.
  • Ack-Modus bewusst konfigurieren.
  • Retry-Verhalten testen.
  • Offset-Management dokumentieren.
  • Consumer-Neustart testen.
Stolperfalle:

Ein Consumer, der im Happy Path funktioniert, kann unter Retry-Bedingungen doppelte Side Effects erzeugen.

Merksatz:

At-least-once heißt: Duplikate sind kein Fehlerfall, sondern Systemrealität.

B7-K08-Q006 – Retry, Dead Letter Topic und Poison Message TestsNicht jede Nachricht verdient einen Retry.
Frage:

„Wie testen Sie Retry und Dead Letter Topics bei Kafka-Consumern?“

Kurz erklärt:

Retry- und DLT-Tests prüfen, wie Consumer mit transienten und permanenten Fehlern umgehen. Poison Messages dürfen Consumer nicht dauerhaft blockieren.

Musterantwort:

Ich unterscheide transiente und permanente Fehler. Transiente Fehler sollten begrenzt und mit Backoff wiederholt werden. Permanente Fehler oder Poison Messages sollten nach klaren Regeln in ein Dead Letter Topic oder einen Fehlerpfad gelangen. Tests müssen prüfen, dass nicht endlos retried wird, dass Payload und relevante Header erhalten bleiben und dass Alerts oder Metriken ausgelöst werden.

Spring Kafka bietet Testunterstützung für Kafka-Anwendungen und Listener-Tests. Für echte DLT-Flows nutze ich häufig einen Kafka-Testcontainer, weil ich dann Topics, Consumer und Fehlerpfad real prüfen kann.

Senior-Antwort:

„Retry ist für temporäre Probleme. Permanente Fehler brauchen DLT, Transparenz und einen Betriebsprozess.“

Häufige Fehler:
  • Alle Fehler gleich behandeln.
  • Unbegrenzte Retries.
  • Permanente Fehler retried.
  • DLT-Payload verliert Header.
  • Keine Tests für Deserialisierungsfehler.
  • Keine Observability für DLT.
  • Poison Message blockiert Partition.
  • DLT wird nie konsumiert oder überwacht.
Best Practices:
  • Fehler klassifizieren.
  • Transient vs permanent unterscheiden.
  • Retry begrenzen.
  • Backoff konfigurieren.
  • DLT testen.
  • Originalpayload erhalten.
  • Header und Fehlerursache erhalten.
  • DLT-Metriken alerten.
Stolperfalle:

Unbegrenzter Retry macht aus einer schlechten Nachricht einen dauerhaften Incident.

Merksatz:

Nicht jede Nachricht verdient einen Retry.

B7-K08-Q007 – Event Contract Testing und Schema EvolutionEvent-Verträge altern im Log — teste auch die Vergangenheit.
Frage:

„Wie testen Sie Kafka Event Contracts und Schema Evolution?“

Kurz erklärt:

Event Contract Tests stellen sicher, dass Producer und Consumer dasselbe Event verstehen. Schema Evolution muss rückwärtskompatibel geplant und getestet werden.

Musterantwort:

Ich behandle Events als öffentliche Verträge. Ich teste, dass Consumer Beispielpayloads des Producers deserialisieren können und dass Producer keine Breaking Changes einführen. Dazu gehören Pflichtfelder, Typen, Enum-Semantik, Header, Key-Semantik und Versionierung. Bei Avro, Protobuf oder JSON Schema nutze ich Schema-Registry-Kompatibilitätsprüfungen; bei JSON ohne Registry brauche ich mindestens Contract-Beispiele, JSON-Schema-Validierung oder Consumer-driven Contracts.

Kafka speichert Event-Streams dauerhaft. Das macht Schema Evolution besonders kritisch: Alte Events können noch lange gelesen oder erneut verarbeitet werden. Ein Consumer muss also nicht nur neue Events verstehen, sondern oft auch ältere Versionen.

Senior-Antwort:

„Ein Event ist nicht nur ein DTO. Es ist ein langlebiger Vertrag im Log.“

Häufige Fehler:
  • Events ohne Versionierung.
  • Felder umbenennen.
  • Required Fields entfernen.
  • Enum-Werte semantisch ändern.
  • Keine Consumer-Contract-Tests.
  • Alte Events nicht testen.
  • Header-Vertrag ignorieren.
  • Key-Semantik ändern.
Best Practices:
  • Events als öffentliche Verträge behandeln.
  • Beispielpayloads versionieren.
  • Alte Eventversionen testen.
  • Additive Änderungen bevorzugen.
  • Unknown Fields bewusst konfigurieren.
  • Schema Registry nutzen, wenn passend.
  • Header und Key mitvertraglich behandeln.
  • Consumer-driven Contracts für kritische Flows.
Stolperfalle:

Ein Event-Feld umzubenennen ist kein internes Refactoring, sondern ein Breaking Change für Consumer.

Merksatz:

Event-Verträge altern im Log — teste auch die Vergangenheit.

B7-K08-Q008 – Outbox Pattern Testing und transaktionale KonsistenzOutbox testet nicht nur Events, sondern Transaktionswahrheit.
Frage:

„Wie testen Sie das Outbox Pattern in Kafka-Systemen?“

Kurz erklärt:

Outbox-Tests prüfen, dass Datenbankänderung und Event-Erzeugung atomar zusammen gespeichert werden und spätere Veröffentlichung robust und idempotent ist.

Musterantwort:

Ich teste zuerst die Transaktion: Wenn eine Order gespeichert wird, muss das Outbox-Event in derselben Datenbanktransaktion gespeichert werden. Wenn die Transaktion fehlschlägt, darf weder Order noch Outbox-Event committen. Danach teste ich den Publisher: Unveröffentlichte Outbox-Events werden an Kafka gesendet und erst danach als veröffentlicht markiert. Bei Publish-Fehlern bleiben sie retrybar.

Das Outbox Pattern adressiert ein klassisches Problem: Datenbankupdate und Kafka-Publish sind nicht automatisch atomar. Tests müssen genau diese Konsistenzgrenze absichern.

Senior-Antwort:

„Outbox teste ich an der Konsistenzgrenze: Datenbankzustand und Eventabsicht müssen gemeinsam committen.“

Häufige Fehler:
  • Event direkt nach DB-Commit senden ohne Fallback.
  • Outbox-Insert nicht in derselben Transaktion.
  • Publisher markiert vor erfolgreichem Publish.
  • Keine Retry-Tests.
  • Keine Idempotenz bei Consumer.
  • Outbox wächst unbegrenzt.
  • Keine Monitoring-Metriken.
  • Duplicate Publish nicht berücksichtigt.
Best Practices:
  • Domainänderung und Outbox atomar.
  • Publisher separat testen.
  • Publish-Fehler retrybar.
  • Mark published erst nach Erfolg.
  • Idempotente Consumer.
  • Outbox-Cleanup planen.
  • Monitoring für Lag und Fehler.
  • Batch-Größe kontrollieren.
Stolperfalle:

Ein Kafka-Publish innerhalb eines Service-Methods ist nicht automatisch konsistent mit dem Datenbank-Commit.

Merksatz:

Outbox testet nicht nur Events, sondern Transaktionswahrheit.

B7-K08-Q009 – Asynchrone Tests: Awaitility, Timeouts und FlakinessAsynchrone Tests brauchen Zustandsbedingungen, keine Schlafpausen.
Frage:

„Wie verhindern Sie flaky Tests bei Kafka-Integrationstests?“

Kurz erklärt:

Kafka-Tests sind oft asynchron. Stabile Tests warten auf klare Bedingungen mit Timeout, statt feste Sleeps zu verwenden.

Musterantwort:

Ich verwende keine festen Sleeps. Stattdessen warte ich auf eine fachliche Bedingung: Datensatz existiert, Event wurde konsumiert, DLT enthält Nachricht, Offset wurde committed oder Metrik ist sichtbar. Das Warten ist begrenzt und liefert bei Timeout eine aussagekräftige Fehlermeldung.

Testcontainers-Guides für Kafka-Listener-Tests zeigen ebenfalls die Kombination aus Kafka-Testcontainer und Awaitility für asynchrone Verarbeitung. Das Muster ist wichtig: Kafka ist asynchron, Tests müssen eventual consistency sauber beobachten.

Senior-Antwort:

„Ich warte nicht auf Zeit. Ich warte auf Zustand.“

Häufige Fehler:
  • Feste Sleeps.
  • Unbegrenztes Warten.
  • Zu kurze Timeouts.
  • Sofortige Assertions nach Send.
  • Geteilte Topics.
  • Geteilte Consumer Groups.
  • Keine Correlation IDs.
  • Timeout ohne Diagnose.
Best Practices:
  • Await auf fachliche Bedingung.
  • Timeout immer begrenzen.
  • Poll-Intervalle bewusst wählen.
  • Eindeutige IDs.
  • Eindeutige Consumer Groups.
  • Testdaten isolieren.
  • Logs und Consumer Lag bei Fehlern prüfen.
  • DLT und Retry getrennt testen.
Stolperfalle:

Thread.sleep macht einen Kafka-Test nicht deterministisch, sondern nur langsamer.

Merksatz:

Asynchrone Tests brauchen Zustandsbedingungen, keine Schlafpausen.

B7-K08-Q010 – Kafka Testing Anti-Patterns und Enterprise Best PracticesKafka-Systeme scheitern selten am Senden — sie scheitern an Verträgen, Wiederholung und Fehl… 24h
Frage:

„Welche Kafka-Testing-Anti-Patterns sehen Sie in Enterprise-Projekten?“

Kurz erklärt:

Professionelles Kafka Testing kombiniert schnelle Handler- und Producer-Tests, Contract Tests, brokernahe Integrationstests, Idempotenztests, DLT-Tests und Observability.

Musterantwort:

Typische Anti-Patterns sind: nur KafkaTemplate mocken, keine Consumer-Tests, keine Event-Contract-Tests, Fachlogik direkt im Listener, Thread.sleep , geteilte Topics und Consumer Groups, keine Idempotenztests, keine DLT-Tests, ungetestete Retries, ignorierte Schema Evolution, Key und Header nicht prüfen und alles nur über wenige fragile E2E-Tests absichern.

Professionell ist eine geschichtete Strategie: Handler ohne Broker testen, Producer-Nachrichten capturen, Event-Verträge prüfen, kritische Flows mit Kafka-Testcontainers real testen und DLT, Retry, Idempotenz und Observability als Betriebsverträge behandeln. Kafka ist eine Event-Streaming-Plattform für dauerhafte Event-Streams und deren Verarbeitung; diese Langlebigkeit macht Contract- und Evolutionsfragen besonders wichtig.

Senior-Antwort:

„Kafka-Tests müssen beweisen, dass Events verstanden, wiederholt, fehlerhaft und historisch korrekt verarbeitet werden.“

Häufige Fehler:
  • Kafka als Queue mit Methodenaufruf-Denken behandeln.
  • Kein Vertrag für Events.
  • Keine alten Eventversionen testen.
  • Doppelte Verarbeitung nicht testen.
  • Poison Messages ignorieren.
  • Consumer-Konfiguration nicht testen.
  • Keine eindeutigen Test-Topics.
  • Keine Betriebsmetriken.
Best Practices:
  • Events als Verträge.
  • Listener dünn.
  • Handler schnell testen.
  • Producer Key/Header/Payload testen.
  • Schema Evolution absichern.
  • Kafka-Testcontainers für kritische Flows.
  • Await statt Sleep.
  • Idempotenz erzwingen und testen.
Stolperfalle:

Ein System kann im Happy Path Events verarbeiten und trotzdem bei Retry, Schemaänderung oder Poison Message versagen.

Merksatz:

Kafka-Systeme scheitern selten am Senden — sie scheitern an Verträgen, Wiederholung und Fehlerbehandlung.

B7-K09 – Kapitel 9: Security Testing 10 Karten

B7-K09-Q001 – Security-Teststrategie: Authentifizierung, Autorisierung und AngriffsflächenSecurity Testing beginnt dort, wo Zugriff verweigert werden muss. 24h
Frage:

„Wie bauen Sie eine Security-Teststrategie für eine Spring-Boot-Anwendung auf?“

Kurz erklärt:

Security Testing braucht mehrere Ebenen: reine Policy-Unit-Tests, Web-Security-Tests, Method-Security-Tests, OAuth2/JWT-Tests, negative Zugriffstests und betriebliche Sicherheitsprüfungen.

Musterantwort:

Ich trenne fachliche Autorisierungslogik von Frameworkintegration. Rollen-, Scope-, Tenant- und Ownership-Regeln teste ich zuerst als reine Unit Tests gegen Policy-Klassen. Danach teste ich Spring Security Integration: Web-Endpunkte über MockMvc oder WebTestClient, Method Security über Spring-Proxies, OAuth2/JWT-Claim-Mapping über Security-Testunterstützung und kritische Endpunkte mit echten HTTP-Smoke-Tests.

Spring Security bietet Authentifizierung, Autorisierung und Schutz gegen häufige Angriffe. Für Tests stellt Spring Security eigene Testunterstützung bereit. Entscheidend ist: Ich teste nicht nur erlaubte Zugriffe, sondern vor allem verbotene Zugriffe.

Senior-Antwort:

„Security teste ich in Schichten: fachliche Policy schnell und isoliert, Frameworkintegration realistisch und vor allem mit Negativfällen.“

Häufige Fehler:
  • Nur Happy Path testen.
  • Security in Tests deaktivieren.
  • 401 und 403 verwechseln.
  • Tenant-Grenzen nicht testen.
  • Fachliche Autorisierung in SpEL verstecken.
  • Method Security ohne Proxy testen.
  • JWT-Claim-Mapping ignorieren.
  • Keine Tests für fehlende oder falsche Scopes.
Best Practices:
  • Policylogik extrahieren.
  • Negative Tests verpflichtend machen.
  • 401 und 403 separat testen.
  • Tenant- und Ownership-Regeln testen.
  • Method Security über Spring-Proxies prüfen.
  • JWT-Claim-Mapping testen.
  • CSRF/CORS bewusst testen.
  • Security nicht pauschal deaktivieren.
Stolperfalle:

Ein grüner Test mit deaktivierter Security sagt nichts über die echte Produktionssicherheit aus.

Merksatz:

Security Testing beginnt dort, wo Zugriff verweigert werden muss.

B7-K09-Q002 – MockMvc Security Tests: 401, 403 und erlaubter ZugriffEin Security-Test ohne Forbidden-Fall ist halb blind.
Frage:

„Wie testen Sie gesicherte REST-Endpunkte mit MockMvc?“

Kurz erklärt:

MockMvc-Security-Tests prüfen HTTP-Security-Grenzen: unauthenticated, authenticated-but-forbidden und authorized.

Musterantwort:

Ich teste mindestens drei Fälle: ohne Authentifizierung erwarte ich 401, mit Authentifizierung aber ohne passende Berechtigung erwarte ich 403, und mit passender Berechtigung erwarte ich den fachlichen Erfolg. Spring Security bietet MockMvc-Testunterstützung, mit der man Nutzer oder Security-Kontexte an Requests binden kann. Die Dokumentation zeigt beispielsweise, wie ein Request mit einem User über user(...) ausgeführt wird.

Wichtig ist, dass diese Tests über die HTTP-Schicht laufen. Ein direkter Controller-Aufruf prüft keine Filterkette und keine echte Security-Grenze.

Senior-Antwort:

„Für jeden geschützten Endpunkt will ich wissen: Wer ist anonym, wer ist verboten, wer ist erlaubt?“

Häufige Fehler:
  • Nur 200 testen.
  • 401 und 403 nicht unterscheiden.
  • Security Filter deaktivieren.
  • Ungültige Authorities falsch simulieren.
  • Method Security nicht aktivieren.
  • JSON-Response nicht prüfen.
  • Keine negativen Fälle für Rollen und Scopes.
  • Testdaten ohne Tenant-Kontext.
Best Practices:
  • 401 testen.
  • 403 testen.
  • Erfolgsfall testen.
  • Rollen und Scopes realistisch benennen.
  • SecurityContext bewusst setzen.
  • HTTP-Vertrag mitprüfen.
  • Kritische Endpunkte vollständig abdecken.
  • Tenant- und Ownership-Regeln ergänzen.
Stolperfalle:

Ein Test, der nur einen berechtigten Nutzer prüft, sagt nichts darüber, ob unberechtigte Nutzer blockiert werden.

Merksatz:

Ein Security-Test ohne Forbidden-Fall ist halb blind.

B7-K09-Q003 – Method Security: @PreAuthorize und fachliche PoliciesMethod Security testet man über den Proxy, Policylogik über reine Unit Tests.
Frage:

„Wie testen Sie @PreAuthorize und Method Security korrekt?“

Kurz erklärt:

Method Security muss über Spring-Proxies getestet werden. Die fachliche Policy sollte zusätzlich als reine Java-Logik getestet werden.

Musterantwort:

Ich teste die fachliche Policy separat als Unit Test. Die @PreAuthorize -Integration teste ich mit Spring-Kontext, aktivierter Method Security und Security-Testkontext, zum Beispiel mit @WithMockUser . Spring Security dokumentiert eigene Testunterstützung für method-based security.

Wichtig ist: Ein direkter Aufruf einer Methode auf einem manuell erzeugten Objekt testet keine Method Security. Method Security funktioniert über Spring-Proxies. Deshalb muss der Test die Bean aus dem Spring-Kontext verwenden.

Senior-Antwort:

„Komplexe Autorisierung gehört in eine Policy. @PreAuthorize sollte diese Policy aufrufen, nicht die gesamte Fachlogik enthalten.“

Häufige Fehler:
  • Service mit new erzeugen.
  • Method Security nicht aktivieren.
  • Nur erlaubten Fall testen.
  • SpEL mit komplexer Fachlogik überladen.
  • Policy nicht separat testen.
  • Self Invocation ignorieren.
  • Falsche Authorities im Test.
  • AccessDenied nicht prüfen.
Best Practices:
  • Policylogik extrahieren.
  • Policy unit-testen.
  • @PreAuthorize über Spring-Bean testen.
  • Denied-Fälle testen.
  • @EnableMethodSecurity bewusst aktivieren.
  • SpEL schlank halten.
  • Self-Invocation vermeiden.
  • Security-Kontext realistisch setzen.
Stolperfalle:

Method Security greift nicht, wenn man die Klasse im Test mit new erzeugt.

Merksatz:

Method Security testet man über den Proxy, Policylogik über reine Unit Tests.

B7-K09-Q004 – OAuth2 Resource Server und JWT Claim MappingJWT-Claims sind Sicherheitsdaten, keine beliebigen DTO-Felder.
Frage:

„Wie testen Sie OAuth2 Resource Server und JWT-basierte Autorisierung?“

Kurz erklärt:

JWT-Security-Tests müssen Claim Mapping, Scopes, Authorities, Issuer, Audience und negative Tokenfälle prüfen.

Musterantwort:

Ich teste zwei Ebenen. Erstens teste ich Claim- und Authority-Mapping als Unit Test: Welche Claims werden zu welchen Spring Authorities? Zweitens teste ich REST-Endpunkte mit simulierten JWTs und prüfe 401, 403 und 200. Spring Security dokumentiert Resource Server JWT-Architektur: JwtAuthenticationProvider nutzt einen JwtDecoder und einen JwtAuthenticationConverter , um ein JWT zu authentifizieren.

Wichtig ist, realistische Claims zu verwenden. Viele Sicherheitslücken entstehen, weil Tests andere Claim-Namen, Scopes oder Tenant-Informationen verwenden als der Identity Provider in Produktion.

Senior-Antwort:

„JWT-Tests sind nicht nur Token-Mocks. Sie prüfen, ob der Identity-Provider-Vertrag korrekt in Autorisierungsentscheidungen übersetzt wird.“

Häufige Fehler:
  • Claim-Namen falsch annehmen.
  • Scopes nicht testen.
  • Tenant-Claim ignorieren.
  • Nur Happy Path.
  • Audience/Issuer nicht prüfen.
  • Expired Token nicht testen.
  • Test-JWTs zu unrealistisch.
  • Roles und Scopes vermischen.
Best Practices:
  • Claim Mapping unit-testen.
  • REST-Endpunkte mit JWT testen.
  • 401/403/200-Matrix.
  • Realistische Claims.
  • Tenant und Subject prüfen.
  • Issuer und Audience validieren.
  • Expiration testen.
  • Authority-Konvention dokumentieren.
Stolperfalle:

Ein Test mit künstlichen Claims kann grün sein, obwohl echte Tokens aus dem Identity Provider anders aussehen.

Merksatz:

JWT-Claims sind Sicherheitsdaten, keine beliebigen DTO-Felder.

B7-K09-Q005 – CSRF, CORS und Browser-nahe SicherheitsgrenzenBrowser-Sicherheit testet man über Browser-nahe HTTP-Grenzen. 24h
Frage:

„Wie testen Sie CSRF und CORS in Spring Security?“

Kurz erklärt:

CSRF-Tests prüfen, ob zustandsverändernde Browser-Requests korrekt geschützt werden. CORS-Tests prüfen, ob erlaubte Origins kontrolliert sind.

Musterantwort:

Bei CSRF teste ich zustandsverändernde Methoden wie POST, PUT, PATCH und DELETE einmal ohne Token und einmal mit gültigem Token. Spring Security dokumentiert, dass bei nicht sicheren HTTP-Methoden und aktivem CSRF-Schutz ein gültiger CSRF-Token im Request enthalten sein muss; für MockMvc gibt es dafür den csrf() RequestPostProcessor.

CORS teste ich über Origin-, Preflight- und Header-Verhalten. Besonders wichtig ist, nicht versehentlich * mit Credentials oder zu breite Origins zu erlauben. CSRF und CORS sind browsernahe Sicherheitsgrenzen und sollten nicht pauschal deaktiviert werden, ohne Architekturgrund.

Senior-Antwort:

„CSRF teste ich dort, wo Browser, Cookies und zustandsverändernde Requests zusammenkommen.“

Häufige Fehler:
  • CSRF in Tests deaktivieren.
  • Nur GET-Endpunkte testen.
  • POST ohne CSRF fälschlich erlauben.
  • CORS zu breit konfigurieren.
  • Credentials mit Wildcard-Origin.
  • Browser- und API-Clients nicht unterscheiden.
  • Security-Filter im Test nicht aktiv.
  • CSRF und Authentifizierung verwechseln.
Best Practices:
  • Nicht sichere Methoden testen.
  • Ohne CSRF Token 403 erwarten.
  • Mit CSRF Token Erfolg erwarten.
  • Invalid Token testen.
  • CORS Origins explizit.
  • Preflight testen.
  • Browser-Architektur berücksichtigen.
  • CSRF-Deaktivierung dokumentieren.
Stolperfalle:

CSRF in Tests abzuschalten, weil POST sonst 403 liefert, beseitigt nicht das Problem, sondern den Test.

Merksatz:

Browser-Sicherheit testet man über Browser-nahe HTTP-Grenzen.

B7-K09-Q006 – Negative Authorization Tests: Tenant, Owner und Object-Level AccessAuthentifiziert heißt nicht objektberechtigt.
Frage:

„Wie testen Sie Object-Level Authorization und Tenant-Isolation?“

Kurz erklärt:

Object-Level Authorization schützt konkrete Ressourcen. Tests müssen prüfen, dass Nutzer nicht auf fremde Objekte, fremde Tenants oder fremde Besitzer zugreifen können.

Musterantwort:

Ich teste nicht nur Rollen, sondern konkrete Ressourcenzugriffe. Ein Nutzer mit Self-Scope darf nur eigene Ressourcen sehen. Ein Admin darf vielleicht alle Ressourcen im Tenant sehen, aber nicht tenant-übergreifend. Deshalb teste ich falscher Owner, falscher Tenant, fehlender Scope und korrekter Zugriff.

Diese Tests schreibe ich bevorzugt zuerst als schnelle Policy-Unit-Tests. Zusätzlich teste ich REST- oder Method-Security-Grenzen, damit die Policy wirklich aufgerufen wird.

Senior-Antwort:

„Rollen sagen, was jemand grundsätzlich darf. Object-Level Authorization sagt, ob er dieses konkrete Objekt sehen darf.“

Häufige Fehler:
  • Nur Rollen testen.
  • Objektbesitz nicht testen.
  • Tenant-Grenzen nicht testen.
  • Admin-Rechte tenant-übergreifend erlauben.
  • Policy nicht zentralisieren.
  • REST-Endpunkt prüft ID, Repository liefert aber fremde Daten.
  • Query-Filter fehlen.
  • Fehlerfall gibt zu viele Details preis.
Best Practices:
  • Tenant zuerst prüfen.
  • Owner-Regeln explizit testen.
  • Negative Matrix pro Ressource.
  • Policy zentralisieren.
  • Query-Ebene absichern.
  • 403 oder 404 bewusst wählen.
  • ID-Manipulation testen.
  • Admin-Rechte begrenzen.
Stolperfalle:

Ein Nutzer mit gültigem Token ist nicht automatisch berechtigt, jede ID im Pfad zu lesen.

Merksatz:

Authentifiziert heißt nicht objektberechtigt.

B7-K09-Q007 – Secrets, Konfiguration und Information Leakage testenWas nie nach außen darf, muss auch in Fehlerfällen getestet werden.
Frage:

„Wie testen Sie sicherheitsrelevante Konfiguration und Information Leakage?“

Kurz erklärt:

Security Tests sollten prüfen, dass Secrets nicht fehlen, nicht geloggt und nicht in Fehlerantworten, Actuator-Ausgaben oder Testreports geleakt werden.

Musterantwort:

Ich teste Configuration Properties mit Validierung, damit fehlende Secrets oder URLs früh auffallen. Gleichzeitig prüfe ich Fehlerantworten darauf, dass keine Stacktraces, Tokens, Secrets, SQL-Details oder interne Klassennamen nach außen gelangen. Spring Boot sichert Webanwendungen standardmäßig, wenn Spring Security auf dem Classpath liegt, einschließlich /error . Das ersetzt aber nicht eigene Tests für Fehlerverträge und sensible Daten.

Besonders wichtig sind Actuator, Logs und Exception Handling. Ein System kann funktional korrekt sein und trotzdem durch Fehlermeldungen oder Logs sensible Informationen offenlegen.

Senior-Antwort:

„Security endet nicht bei Zugriffskontrolle. Auch Fehlermeldungen und Logs sind Angriffsflächen.“

Häufige Fehler:
  • Secrets in Testproperties committen.
  • Authorization Header loggen.
  • Stacktrace im Response.
  • Interne Exception Message an Client geben.
  • Actuator Details öffentlich.
  • Config Validation fehlt.
  • Fehlerantwort enthält SQL-Details.
  • Testreports enthalten Tokens.
Best Practices:
  • Configuration Properties validieren.
  • Secrets nie hardcoden.
  • Fehlerantworten standardisieren.
  • Keine internen Details exposen.
  • Logs maskieren.
  • Authorization Header redigieren.
  • Actuator absichern.
  • Negative Fehlerpfade testen.
Stolperfalle:

Ein sauberer 500-Status ist nicht sicher, wenn der Body Stacktrace, SQL oder Secret enthält.

Merksatz:

Was nie nach außen darf, muss auch in Fehlerfällen getestet werden.

B7-K09-Q008 – Audit, Security Events und NachvollziehbarkeitWas sicherheitsrelevant ist, muss nachvollziehbar sein.
Frage:

„Wie testen Sie Security Auditing?“

Kurz erklärt:

Security Audit Tests prüfen, ob sicherheitsrelevante Ereignisse nachvollziehbar, vollständig und ohne sensible Daten aufgezeichnet werden.

Musterantwort:

Ich teste, dass relevante Security-Events erzeugt werden: Login-Fehler, Access Denied, Privilege Changes, Token-Probleme, Admin-Aktionen, Secret-Rotation oder Policy-Verletzungen. Der Test prüft nicht nur, dass irgendein Log geschrieben wurde, sondern User, Aktion, Ressource, Grund, Zeitpunkt und Correlation ID.

Gleichzeitig prüfe ich, dass Audit-Daten keine Secrets, Tokens oder unnötigen personenbezogenen Details enthalten. Audit ist ein Betriebs- und Compliance-Vertrag.

Senior-Antwort:

„Audit ist nicht nur Logging. Es ist ein nachvollziehbarer Sicherheitsvertrag für Betrieb und Compliance.“

Häufige Fehler:
  • Audit gar nicht testen.
  • Nur Textlog prüfen.
  • Secrets im Audit.
  • Keine Correlation ID.
  • Kein Grund für Access Denied.
  • Audit nicht tenantfähig.
  • Zeit nicht deterministisch.
  • Audit-Fehler blockiert falsche Flows.
Best Practices:
  • Audit-Events fachlich modellieren.
  • Access Denied auditieren.
  • Admin-Aktionen auditieren.
  • Keine Secrets loggen.
  • Zeit über Clock .
  • Correlation ID prüfen.
  • Audit-Fehlerstrategie definieren.
  • Audit-Format versionieren.
Stolperfalle:

Ein Audit-Log, das Tokens oder Secrets enthält, ist selbst ein Sicherheitsrisiko.

Merksatz:

Was sicherheitsrelevant ist, muss nachvollziehbar sein.

B7-K09-Q009 – Security Regression, SAST, Dependency und Container ScanningSecurity Testing ist Pipeline- und Lebenszyklusdisziplin.
Frage:

„Wie integrieren Sie Security Testing in CI/CD?“

Kurz erklärt:

Security Testing umfasst nicht nur JUnit-Tests. CI/CD sollte Regressionstests, SAST, Dependency Scanning, Secret Scanning, Container Scanning und Smoke Tests kombinieren.

Musterantwort:

Ich kombiniere automatisierte Security-Regressionstests mit technischen Security-Gates. In Pull Requests laufen Policy-, REST- und Method-Security-Tests sowie Dependency- und Secret-Scanning. Auf Main oder Release laufen tiefere Integrationstests, Container-Image-Scanning, SBOM-Erzeugung und Smoke Tests. Kritisch ist: Jeder Security-Fix erzeugt einen Regressionstest oder eine automatisierte Kontrolle.

Spring Security deckt Authentifizierung, Autorisierung und Schutz gegen häufige Angriffe ab. Aber Frameworkschutz ersetzt keine CI/CD-Governance gegen verwundbare Dependencies, geleakte Secrets oder falsche Deployment-Konfiguration.

Senior-Antwort:

„Ein Security-Fix ohne Regressionstest ist nur eine Hoffnung, dass derselbe Fehler nicht zurückkommt.“

Häufige Fehler:
  • Security nur manuell testen.
  • Penetration-Test-Funde ohne Regressionstest schließen.
  • Dependency-Scans ignorieren.
  • Secrets in Testlogs.
  • Container Images nicht scannen.
  • Security Gates zu langsam und werden deaktiviert.
  • Kein Prozess für CVE-Triage.
  • Keine Runtime-Smoke-Tests.
Best Practices:
  • Security Regression Tests.
  • Secret Scanning.
  • Dependency Scanning.
  • Container Image Scanning.
  • SAST sinnvoll konfigurieren.
  • SBOM erzeugen.
  • CVE-Triage-Prozess.
  • Findings in Tests übersetzen.
Stolperfalle:

Security-Scanning ohne Triage-Prozess erzeugt Warnrauschen statt Sicherheit.

Merksatz:

Security Testing ist Pipeline- und Lebenszyklusdisziplin.

B7-K09-Q010 – Security Testing Anti-Patterns und Enterprise Best PracticesSecurity-Tests müssen Verbote beweisen. 24h
Frage:

„Welche Security-Testing-Anti-Patterns sehen Sie in Spring-Boot-Projekten?“

Kurz erklärt:

Professionelles Security Testing kombiniert negative Autorisierungstests, echte Frameworkintegration, Policy-Unit-Tests, JWT-Tests, CSRF/CORS-Tests, Audit, Secret-Schutz und CI/CD-Governance.

Musterantwort:

Die häufigsten Anti-Patterns sind: Security im Test deaktivieren, nur Happy Path testen, 401 und 403 nicht unterscheiden, keine Tenant-/Owner-Negativtests, Method Security mit new statt Spring-Proxy testen, unrealistische JWT-Claims, CSRF deaktivieren, CORS zu breit erlauben, Actuator offen lassen, Secrets in Testproperties, Stacktraces in Fehlerantworten, keine Audit-Tests und Security-Fixes ohne Regressionstests.

Spring Security stellt starke Mechanismen für Authentifizierung, Autorisierung und Schutz bereit. Spring Security Test unterstützt die Prüfung dieser Mechanismen im Test. Aber ein Framework kann nicht wissen, welche fachlichen Objekt- und Tenant-Grenzen Ihr System braucht. Diese Regeln müssen modelliert und getestet werden.

Senior-Antwort:

„Security Testing ist nicht der Beweis, dass der richtige Nutzer hinein darf. Es ist der Beweis, dass alle falschen Nutzer draußen bleiben.“

Häufige Fehler:
  • Security-Konfiguration nur manuell testen.
  • Negative Tests fehlen.
  • Test-JWTs passen nicht zur Realität.
  • Policylogik in Controllern verstreut.
  • CORS/CSRF nicht verstanden.
  • Audit fehlt.
  • Secrets leaken in Tests.
  • Security-Fixes nicht regressionsgesichert.
Best Practices:
  • Security-Testmatrix definieren.
  • Negative Tests als Pflicht.
  • Policylogik zentralisieren.
  • Policy schnell unit-testen.
  • Web- und Method-Security real prüfen.
  • JWT-Claims realistisch.
  • CSRF/CORS bewusst testen.
  • Actuator absichern.
Stolperfalle:

Security im Test abzuschalten ist oft der Anfang einer Test-Suite, die Produktionsrisiken systematisch übersieht.

Merksatz:

Security-Tests müssen Verbote beweisen.

B7-K10 – Kapitel 10: Contract Testing 10 Karten

B7-K10-Q001 – Grundlagen: Consumer, Provider und ContractContract Tests schützen Integrationsverträge vor unbeabsichtigten Breaking Changes. 24h
Frage:

„Was ist Contract Testing, und welches Problem löst es?“

Kurz erklärt:

Contract Testing prüft die Kompatibilität zwischen Consumer und Provider. Der Contract beschreibt konkrete Erwartungen an eine API oder Message.

Musterantwort:

Contract Testing prüft, ob ein Provider die Erwartungen seiner Consumer erfüllt. Bei REST bedeutet das: Wenn der Consumer einen bestimmten Request sendet, muss der Provider mit Status, Headern und Body so antworten, wie vereinbart. Bei Messaging bedeutet es: Ein publiziertes Event muss von Consumern weiterhin verstanden werden.

Das Problem ist, dass Microservices unabhängig deployt werden. Ohne Contract Tests merkt man Breaking Changes oft erst in späten End-to-End-Tests oder in Produktion. Spring Cloud Contract unterstützt consumer-driven und producer-driven Contract Testing. Pact ist ein code-first Consumer-Driven-Contract-Testing-Werkzeug, bei dem Contracts während Consumer-Tests erzeugt werden.

Senior-Antwort:

„Contract Testing prüft nicht, ob das ganze System läuft. Es prüft, ob zwei Services denselben Integrationsvertrag verstehen.“

Häufige Fehler:
  • Contract Tests mit vollständigen E2E-Tests verwechseln.
  • Nur Happy Path contract-testen.
  • Fehlerformate nicht contract-testen.
  • Contracts zu breit formulieren.
  • Provider-Verifikation nicht in CI ausführen.
  • Consumer-Erwartungen nicht versionieren.
  • Contract Tests ohne fachliche Ownership.
  • Contract Tests als Ersatz für Security Tests missverstehen.
Best Practices:
  • Contracts klein und konkret halten.
  • Consumer-Erwartungen explizit machen.
  • Provider-Verifikation automatisieren.
  • Fehlerfälle contract-testen.
  • Contracts versionieren.
  • Breaking Changes früh blockieren.
  • Contract Tests in CI/CD integrieren.
  • E2E-Tests gezielt reduzieren, nicht blind ersetzen.
Stolperfalle:

Ein Provider-Test ohne Consumer-Erwartung beweist nicht, dass echte Consumer kompatibel bleiben.

Merksatz:

Contract Tests schützen Integrationsverträge vor unbeabsichtigten Breaking Changes.

B7-K10-Q002 – Consumer-Driven Contracts mit PactConsumer Contracts sollten Erwartungen beschreiben, nicht Provider-Implementierung einfrieren.
Frage:

„Wie funktioniert Consumer-Driven Contract Testing mit Pact?“

Kurz erklärt:

Bei Consumer-Driven Contract Testing formuliert der Consumer seine Erwartungen. Der Provider verifiziert später, dass er diese Erwartungen erfüllt.

Musterantwort:

Bei Pact schreibt der Consumer einen Test gegen einen Mock Provider. Dieser Test beschreibt, welchen Request der Consumer sendet und welche Response er erwartet. Dabei entsteht ein Pact-Contract. Der Provider verifiziert später diesen Contract gegen seine echte Implementierung. Pact beschreibt diesen Ansatz als code-first Consumer-Driven Contract Testing; Contracts entstehen während automatisierter Consumer-Tests.

Der entscheidende Vorteil ist: Der Provider wird nur auf Verhalten geprüft, das echte Consumer tatsächlich nutzen. Pact dokumentiert ausdrücklich, dass nur die von Consumer genutzten Teile der Kommunikation getestet werden, sodass nicht genutztes Provider-Verhalten freier geändert werden kann.

Senior-Antwort:

„Consumer-driven Contracts drehen die Perspektive: Nicht der Provider sagt, was theoretisch möglich ist, sondern der Consumer sagt, was er wirklich braucht.“

Häufige Fehler:
  • Consumer beschreibt mehr Felder als er braucht.
  • Provider-Verifikation läuft nicht in CI.
  • Pact-Dateien werden nicht veröffentlicht.
  • Contracts werden manuell gepflegt.
  • Provider ignoriert fehlgeschlagene Verification.
  • Consumer testet keine Fehlerfälle.
  • Testdaten im Provider passen nicht zu Contract States.
  • Pact wird als vollständiger E2E-Test missverstanden.
Best Practices:
  • Nur genutzte Felder contract-testen.
  • Consumer-Tests nah am API-Client.
  • Provider-Verifikation automatisieren.
  • Pact Broker oder sauberes Artefaktmanagement.
  • Provider States klar definieren.
  • Fehlerfälle mit Contracts abdecken.
  • Contracts reviewen.
  • Breaking Changes blockieren.
Stolperfalle:

Wenn Consumer jedes Response-Feld contract-testen, entstehen unnötige Breaking Changes bei Provider-Refactorings.

Merksatz:

Consumer Contracts sollten Erwartungen beschreiben, nicht Provider-Implementierung einfrieren.

B7-K10-Q003 – Spring Cloud Contract: Producer- und Consumer-VerifikationSpring Cloud Contract macht Verträge ausführbar und Stubs reproduzierbar.
Frage:

„Wie funktioniert Spring Cloud Contract?“

Kurz erklärt:

Spring Cloud Contract beschreibt Contracts in DSL-Dateien und kann daraus Provider-Tests sowie Consumer-Stubs generieren.

Musterantwort:

Spring Cloud Contract ermöglicht consumer-driven und producer-driven Contract Testing. Es verschiebt TDD auf Architekturebene, indem Contracts zwischen Services als ausführbare Spezifikationen behandelt werden. Aus Contract-Definitionen können Provider-Verifikationstests und Stubs für Consumer entstehen.

Der typische Ablauf ist: Contract definieren, Provider-Test generieren und ausführen, Stubs veröffentlichen, Consumer gegen diese Stubs testen. Spring Cloud Contract unterstützt außerdem Messaging-Verifikation, nicht nur REST.

Senior-Antwort:

„Spring Cloud Contract macht Integrationsverträge ausführbar. Der Contract ist nicht Dokumentation neben dem Code, sondern Teil des Build-Vertrags.“

Häufige Fehler:
  • Contracts nicht versionieren.
  • Base Test Class bereitet unrealistische Daten vor.
  • Generated Tests laufen nicht in CI.
  • Stubs werden nicht veröffentlicht.
  • Consumer nutzt veraltete Stubs.
  • Contract beschreibt Implementierungsdetails.
  • Fehlerfälle fehlen.
  • Messaging-Contracts werden ignoriert.
Best Practices:
  • Contracts als Build-Artefakte behandeln.
  • Provider-Verifikation in CI.
  • Consumer-Stubs versionieren.
  • Base Test Classes klein halten.
  • Provider States realistisch.
  • Fehlerfälle ergänzen.
  • Messaging-Contracts nutzen.
  • Contracts reviewen.
Stolperfalle:

Wenn die Base Test Class im Provider-Test Daten erzeugt, die es in realen Zuständen nie geben kann, wird der Contract wertlos.

Merksatz:

Spring Cloud Contract macht Verträge ausführbar und Stubs reproduzierbar.

B7-K10-Q004 – Provider States und realistische TestdatenProvider State ist die Bühne, auf der der Contract überprüft wird.
Frage:

„Was sind Provider States, und warum sind sie für Contract Tests wichtig?“

Kurz erklärt:

Provider States definieren den Zustand, den der Provider erfüllen muss, damit ein Contract sinnvoll verifiziert werden kann.

Musterantwort:

Provider States beschreiben die Vorbedingung eines Provider-Tests. Ein Contract wie „Customer existiert und ist ACTIVE“ braucht einen Provider-Zustand, der genau diesen Datensatz bereitstellt. Ohne Provider State testet man zufällige Daten oder hängt von globalem Testzustand ab.

Bei Pact sind Provider States zentral, weil Consumer Erwartungen formulieren, die der Provider in einem passenden Zustand verifizieren muss. Bei Spring Cloud Contract übernimmt häufig die Base Test Class die Vorbereitung solcher Zustände. Wichtig ist: Provider States müssen realistisch und minimal sein.

Senior-Antwort:

„Ein Contract braucht einen klaren Zustand. Sonst testet man nicht Kompatibilität, sondern Zufall.“

Häufige Fehler:
  • Keine Provider States.
  • Globale Testdaten.
  • Provider State zu breit.
  • Provider State unrealistisch.
  • Consumer erwartet Daten, die Provider nie erzeugen kann.
  • Fehlerzustände fehlen.
  • Provider State abhängig von Testreihenfolge.
  • Testdaten werden nicht bereinigt.
Best Practices:
  • Provider States minimal halten.
  • Fachlich benennen.
  • Positive und negative States.
  • Daten pro Contract isolieren.
  • Keine globalen Dumps.
  • Testdaten realistisch.
  • Provider State Setup versionieren.
  • Fehlerzustände contract-testen.
Stolperfalle:

Provider States, die nur für den Test gebaut sind und real nie auftreten, erzeugen falsches Vertrauen.

Merksatz:

Provider State ist die Bühne, auf der der Contract überprüft wird.

B7-K10-Q005 – REST Contract Testing: Status, Header, Body und FehlerformatContract Tests sollten genau genug sein, aber nicht enger als der Consumer-Vertrag. 24h
Frage:

„Was gehört in einen guten REST Contract?“

Kurz erklärt:

REST Contracts sollten den tatsächlichen Integrationsvertrag prüfen: Statuscode, Content-Type, relevante Header, Body-Felder und Fehlerformat.

Musterantwort:

Ein guter REST Contract beschreibt nur das Verhalten, das der Consumer braucht: Methode, Pfad, relevante Query-Parameter, benötigte Header, Request Body, erwarteter Status, Content-Type und relevante Response-Felder. Fehlerfälle gehören ebenfalls dazu, vor allem standardisierte Fehlerformate wie Problem Details.

Wichtig ist, nicht zu viel zu spezifizieren. Wenn ein Consumer ein Feld nicht nutzt, sollte es nicht unnötig Teil seines Contracts sein. Pact betont als Vorteil, dass nur die Kommunikationsteile getestet werden, die Consumer tatsächlich verwenden.

Senior-Antwort:

„Ein Contract soll den Consumer schützen, nicht jede interne Provider-Entscheidung einfrieren.“

Häufige Fehler:
  • Nur 200-Fälle testen.
  • Fehlerformat vergessen.
  • Irrelevante Felder spezifizieren.
  • Interne Header contract-testen.
  • Response Body komplett einfrieren.
  • Content-Type ignorieren.
  • Query-Parameter nicht testen.
  • Security-Anforderungen nicht berücksichtigen.
Best Practices:
  • Nur genutzte Felder spezifizieren.
  • Fehlerfälle aufnehmen.
  • Content-Type prüfen.
  • Relevante Header prüfen.
  • Optionale Felder bewusst behandeln.
  • Problem Details contract-testen.
  • Security-Kontext berücksichtigen.
  • Contract und OpenAPI abgleichen.
Stolperfalle:

Ein Contract, der den kompletten Response Body unnötig festnagelt, macht additive Änderungen schwerer als nötig.

Merksatz:

Contract Tests sollten genau genug sein, aber nicht enger als der Consumer-Vertrag.

B7-K10-Q006 – Messaging Contract Testing: Events, Header und Schema EvolutionEvents sind langlebige Verträge, keine internen DTOs.
Frage:

„Wie testen Sie Contracts für Kafka- oder Messaging-Events?“

Kurz erklärt:

Messaging Contracts prüfen Event-Payload, Header, Key-Semantik, Topic-Konventionen und Schema Evolution.

Musterantwort:

Ich behandle Events als langlebige Verträge. Ein Messaging Contract beschreibt nicht nur Payload, sondern auch Topic oder Channel, Key, Header, Event-Typ, Schema-Version, Correlation ID und semantische Bedeutung. Spring Cloud Contract unterstützt auch Messaging-Verifikation und beschreibt, dass Anwendungen verifiziert werden können, die Messaging als Kommunikationsmittel verwenden.

Wichtig ist Schema Evolution. Events bleiben im Log und können später reprocessed werden. Deshalb teste ich alte und neue Versionen. Additive Felder sind meist weniger riskant als Umbenennungen, Typänderungen oder geänderte Enum-Semantik.

Senior-Antwort:

„Ein Kafka-Event ist ein Contract im Log. Header, Key und Payload gehören zusammen.“

Häufige Fehler:
  • Nur Payload testen.
  • Header ignorieren.
  • Key-Semantik nicht contract-testen.
  • Schema-Version fehlt.
  • Alte Events nicht testen.
  • Event-Enum-Werte brechen Consumer.
  • Topic-Name nicht versioniert.
  • Broker-Test ersetzt Contract Test.
Best Practices:
  • Payload und Header testen.
  • Key-Semantik dokumentieren.
  • Schema-Version aufnehmen.
  • Alte Eventversionen prüfen.
  • Additive Evolution bevorzugen.
  • Breaking Changes über neue Topics.
  • Consumer-driven Message Contracts.
  • Contract Tests plus Broker-Integration.
Stolperfalle:

Ein Event-Payload kann formal gültig sein und trotzdem semantisch inkompatibel, wenn Key oder Header geändert wurden.

Merksatz:

Events sind langlebige Verträge, keine internen DTOs.

B7-K10-Q007 – Contract Broker, Versionierung und CI/CD GatesContracts brauchen Versionen, sonst fehlt der Deployment-Kontext.
Frage:

„Wie integrieren Sie Contract Testing in CI/CD?“

Kurz erklärt:

Ein Contract Broker oder Artefaktrepository verwaltet Contracts, Versionen und Verifikationsergebnisse. CI/CD nutzt diese Informationen als Deployment Gate.

Musterantwort:

Consumer-Tests erzeugen Contracts und veröffentlichen sie mit Consumer-Version. Provider-Pipelines holen relevante Contracts und verifizieren sie gegen den Provider. Verifikationsergebnisse werden gespeichert. Vor Deployments prüft ein Gate, ob die geplante Consumer-Provider-Kombination kompatibel ist.

Pact nutzt häufig einen Pact Broker für Contract-Austausch und Verifikationsergebnisse. Pact dokumentiert den Consumer-Driven-Ansatz, bei dem Consumer-Tests Contracts erzeugen, die Provider anschließend bestätigen müssen. Spring Cloud Contract unterstützt Stubs und Contract-Flows, einschließlich Varianten mit externen Repositories oder Artefaktablagen.

Senior-Antwort:

„Contract Testing wird erst mit Versionierung und Deployment Gates wirklich wertvoll.“

Häufige Fehler:
  • Contracts ohne Version.
  • Provider-Verifikation nicht gespeichert.
  • Deployment ohne Compatibility Gate.
  • Alte nicht mehr aktive Consumer blockieren alles.
  • Branch-Contracts mit Main-Contracts vermischen.
  • Keine Environment-Metadaten.
  • Manuelle Freigabe statt automatischer Gates.
  • Contract-Artefakte nicht reproduzierbar.
Best Practices:
  • Contracts versionieren.
  • Verifikationsergebnisse speichern.
  • Consumer und Provider Versionen verbinden.
  • Deployment Gates automatisieren.
  • Branch- und Environment-Metadaten nutzen.
  • Alte Consumer deprecaten.
  • Contract-Artefakte unveränderlich.
  • Provider-Verifikation in PR und Main.
Stolperfalle:

Ein Contract im Git ohne Verifikationsergebnis sagt nicht, welche Provider-Version tatsächlich kompatibel ist.

Merksatz:

Contracts brauchen Versionen, sonst fehlt der Deployment-Kontext.

B7-K10-Q008 – Contract Testing vs OpenAPI, REST Docs und E2EDokumentation beschreibt Verträge; Contract Tests verifizieren sie consumer-spezifisch.
Frage:

„Wie unterscheiden sich Contract Testing, OpenAPI, REST Docs und E2E-Tests?“

Kurz erklärt:

OpenAPI, REST Docs, Contract Tests und E2E-Tests lösen unterschiedliche Probleme. Sie sollten kombiniert, nicht verwechselt werden.

Musterantwort:

OpenAPI beschreibt die API-Oberfläche und eignet sich für Dokumentation, Review und Schema-Diff. Spring REST Docs erzeugt Dokumentation aus Tests. Contract Testing prüft, ob konkrete Consumer-Erwartungen vom Provider erfüllt werden. E2E-Tests prüfen wenige komplette Geschäftsflüsse in einer realen oder realitätsnahen Umgebung.

Spring Cloud Contract kann unter anderem Provider- und Consumer-Contract-Testing unterstützen. Pact betont, dass nur die Teile der Kommunikation getestet werden, die Consumer tatsächlich verwenden. Das ist ein anderer Fokus als eine vollständige OpenAPI-Beschreibung.

Senior-Antwort:

„OpenAPI sagt, was die API sein soll. Contract Tests beweisen, was Consumer wirklich brauchen und Provider wirklich liefern.“

Häufige Fehler:
  • OpenAPI als Contract-Test-Ersatz.
  • E2E als einzige Kompatibilitätsprüfung.
  • REST Docs ohne Consumer-Erwartung.
  • Zu viele redundante Tests.
  • Contract Tests ohne Dokumentation.
  • API-Dokumentation driftet.
  • Breaking Changes nur manuell reviewen.
  • Kein Deployment Gate.
Best Practices:
  • OpenAPI für API-Beschreibung.
  • REST Docs für getestete Beispiele.
  • Contract Tests für Consumer-Kompatibilität.
  • E2E für kritische Journeys.
  • Redundanz bewusst reduzieren.
  • OpenAPI-Diff in CI.
  • Contract Gates für Deployments.
  • Fehlerformate überall konsistent.
Stolperfalle:

Eine valide OpenAPI-Spezifikation beweist nicht automatisch, dass ein bestimmter Consumer mit dem Provider kompatibel bleibt.

Merksatz:

Dokumentation beschreibt Verträge; Contract Tests verifizieren sie consumer-spezifisch.

B7-K10-Q009 – Breaking Changes, Deprecation und VersionierungKompatibilität wird an aktiven Consumern gemessen.
Frage:

„Wie gehen Sie mit Breaking Changes in APIs oder Events um?“

Kurz erklärt:

Contract Tests helfen, Breaking Changes früh zu erkennen und Deprecation-Prozesse kontrolliert durchzuführen.

Musterantwort:

Ich behandle API- und Event-Verträge als versionierte Produkte. Entfernen, Umbenennen, Typänderungen, Statuscodeänderungen oder geänderte Semantik sind potenziell breaking. Contract Tests zeigen, welche Consumer betroffen sind. Danach folgt ein Deprecation-Prozess: parallel unterstützen, Consumer migrieren, Verifikationsergebnisse prüfen und erst dann entfernen.

Contract Testing ist hier wertvoll, weil es nicht nur theoretisch sagt, dass ein Feld existiert, sondern zeigt, welcher Consumer es tatsächlich erwartet. Pacts consumer-driven Ansatz testet gezielt die genutzten Kommunikationsteile.

Senior-Antwort:

„Ein Breaking Change ist nicht, was der Provider für breaking hält, sondern was einen aktiven Consumer bricht.“

Häufige Fehler:
  • Feld umbenennen als internes Refactoring behandeln.
  • Optionales Feld plötzlich required machen.
  • Fehlerformat ändern.
  • Alte Consumer ignorieren.
  • Keine Deprecation-Kommunikation.
  • Contracts nicht mit aktiven Deployments verknüpfen.
  • Event-Semantik ändern.
  • Versionierung nur im Pfad, nicht im Prozess.
Best Practices:
  • Additive Änderungen bevorzugen.
  • Contracts als Usage-Signal nutzen.
  • Deprecation dokumentieren.
  • Alte und neue Form parallel unterstützen.
  • Aktive Consumer identifizieren.
  • Provider-Verifikation vor Merge.
  • Deployment Gates nutzen.
  • Event-Versionierung planen.
Stolperfalle:

Ein Feld zu entfernen, das in OpenAPI als optional markiert ist, kann trotzdem breaking sein, wenn ein Consumer es real nutzt.

Merksatz:

Kompatibilität wird an aktiven Consumern gemessen.

B7-K10-Q010 – Contract Testing Anti-Patterns und Enterprise Best PracticesContract Tests brauchen Governance, sonst werden sie zu veralteter Dokumentation. 24h
Frage:

„Welche Contract-Testing-Anti-Patterns sehen Sie in Enterprise-Projekten?“

Kurz erklärt:

Contract Testing funktioniert nur mit CI/CD-Integration, Versionierung, realistischer Provider-Verifikation, guter Ownership und bewusster Begrenzung der Contracts.

Musterantwort:

Typische Anti-Patterns sind manuell gepflegte Contracts ohne Verifikation, Provider-Verifikation außerhalb von CI, Over-Specification kompletter Responses, nur Happy Path, keine Fehlerverträge, keine Message Contracts, fehlende Versionierung, kein Broker oder Artefaktmanagement, kein Deployment Gate, veraltete Consumer als dauerhafte Blocker und die Annahme, Contract Tests könnten Security-, Performance- oder echte Infrastrukturtests vollständig ersetzen.

Spring Cloud Contract unterstützt consumer-driven und producer-driven Contract Testing. Pact unterstützt code-first Consumer-Driven Contracts. Beide Werkzeuge sind aber nur so gut wie die Teststrategie, CI/CD-Integration und Contract-Governance darum herum.

Senior-Antwort:

„Contract Testing ist kein einzelner Test. Es ist ein Kompatibilitätsprozess zwischen Teams.“

Häufige Fehler:
  • Contracts ohne Ownership.
  • Contracts ohne Provider-Verifikation.
  • Contracts ohne Version.
  • Keine Fehlercontracts.
  • Keine Message Contracts.
  • Zu breite Contracts.
  • Keine Deprecation-Regeln.
  • Contract Broker wird nicht gepflegt.
Best Practices:
  • Contract Ownership definieren.
  • Contracts automatisiert erzeugen oder verifizieren.
  • Provider-Verifikation blockierend in CI.
  • Versionierung und Metadaten.
  • Deployment Gates.
  • Fehlerfälle aufnehmen.
  • Messaging-Contracts aufnehmen.
  • Over-Specification vermeiden.
Stolperfalle:

Contracts ohne Deployment Gate zeigen nur, dass etwas inkompatibel ist — sie verhindern es aber nicht.

Merksatz:

Contract Tests brauchen Governance, sonst werden sie zu veralteter Dokumentation.

B7-K11 – Kapitel 11: Performance Testing 10 Karten

B7-K11-Q001 – Performance-Teststrategie: Micro, Load, Stress, Soak und RegressionOhne Ziel ist jede Performance-Messung nur eine Zahl. 24h
Frage:

„Wie bauen Sie eine professionelle Performance-Teststrategie für eine Java-/Spring-Boot-Anwendung auf?“

Kurz erklärt:

Performance Testing besteht aus mehreren Testarten. Microbenchmarks, Load Tests, Stress Tests, Spike Tests, Soak Tests und Regressionstests beantworten unterschiedliche Fragen.

Musterantwort:

Ich beginne mit Zielen: Welche SLOs gelten für Latenz, Durchsatz, Fehlerrate und Ressourcenverbrauch? Danach wähle ich die passende Testart. Microbenchmarks mit JMH nutze ich für kleine, isolierte Codepfade. Load Tests prüfen erwartete Produktionslast. Stress Tests finden die Belastungsgrenze. Spike Tests prüfen plötzliche Lastsprünge. Soak Tests finden Speicherlecks, Pool-Leaks und langsame Ressourcenerschöpfung. Performance-Regressionstests schützen kritische Pfade gegen schleichende Verschlechterung.

JMH ist der OpenJDK-Harness zum Erstellen, Ausführen und Analysieren von JVM-Benchmarks. Für Systemlasttests nutze ich Werkzeuge wie Gatling oder k6, weil sie Workload-Modelle, virtuelle Nutzer und hohe Lastprofile unterstützen.

Senior-Antwort:

„Performance Testing beginnt nicht mit einem Tool, sondern mit einem messbaren Ziel.“

Häufige Fehler:
  • Nur Durchschnittslatenz messen.
  • Keine SLOs definieren.
  • Unrealistische Testdaten.
  • Kein Warmup.
  • Keine Baseline.
  • Load Test ohne Metriken.
  • Microbenchmark mit Systemtest verwechseln.
  • Performance erst kurz vor Release testen.
Best Practices:
  • SLOs vor Tests definieren.
  • p95 und p99 messen.
  • Fehlerrate mitmessen.
  • Ressourcenverbrauch messen.
  • Realistische Daten und Payloads.
  • Warmup berücksichtigen.
  • Baselines versionieren.
  • Kritische Pfade automatisiert testen.
Stolperfalle:

Ein einzelner Durchschnittswert kann ein katastrophales p99-Verhalten vollständig verbergen.

Merksatz:

Ohne Ziel ist jede Performance-Messung nur eine Zahl.

B7-K11-Q002 – JMH Microbenchmarks und JVM-FallenJMH misst kleine Wahrheiten — keine ganze Produktion.
Frage:

„Warum sollte man Java-Microbenchmarks mit JMH schreiben statt mit System.nanoTime() ?“

Kurz erklärt:

JMH ist das Standardwerkzeug für JVM-Microbenchmarks. Es hilft, typische JVM-Benchmark-Fallen wie Warmup, JIT-Effekte und Dead-Code-Elimination zu reduzieren.

Musterantwort:

Weil die JVM stark optimiert. JIT, Warmup, Dead-Code-Elimination, Constant Folding, Escape Analysis und GC können naive Messungen verfälschen. JMH wurde genau dafür entwickelt: Es ist ein OpenJDK-Harness für JVM-Benchmarks. Die JMH-Dokumentation betont außerdem, dass Samples und Annotation-Javadocs wichtig sind und dass Benchmarks trotz Harness reviewt werden sollten, weil JMH nicht automatisch alle Benchmark-Fallen verhindert.

Als Senior nutze ich JMH für kleine, heiße Codepfade. Ich extrapoliere Microbenchmark-Ergebnisse aber nicht blind auf das Gesamtsystem.

Senior-Antwort:

„Ein Microbenchmark beantwortet eine sehr kleine Frage. Er darf nie allein die Systemarchitektur entscheiden.“

Häufige Fehler:
  • System.nanoTime() -Schleifen.
  • Kein Warmup.
  • Ergebnis wird nicht verwendet.
  • Unrealistische Konstanten.
  • Zu kleine Messdauer.
  • GC und Allocation ignorieren.
  • Microbenchmark als Systemperformance interpretieren.
  • Benchmark nicht reviewen.
Best Practices:
  • JMH verwenden.
  • Warmup konfigurieren.
  • Mehrere Forks.
  • Realistische Inputs.
  • Ergebnis nicht wegoptimieren lassen.
  • Allocation mitbetrachten.
  • Benchmark-Code reviewen.
  • Baselines speichern.
Stolperfalle:

Ein Microbenchmark kann eine Implementierung gewinnen lassen, die im echten System durch IO, Cache, Allocation oder Branch-Verhalten schlechter ist.

Merksatz:

JMH misst kleine Wahrheiten — keine ganze Produktion.

B7-K11-Q003 – Load Testing mit Gatling und k6Traffic erzeugen ist einfach; realistische Last modellieren ist schwer.
Frage:

„Wie entwerfen Sie einen sinnvollen Load Test für eine Spring-Boot-REST-API?“

Kurz erklärt:

Load Tests simulieren realistische Nutzer- oder Requestlast. Gatling und k6 sind typische Werkzeuge für API- und Systemlasttests.

Musterantwort:

Ich beginne mit einem realistischen Workload-Modell: Welche Endpunkte, welche Payloads, welche Datenverteilung, welche Authentifizierung, welche RPS, welche Ramp-up-Zeit und welche SLOs? Danach definiere ich Checks für Statuscodes, Fehlerquote und Latenzpercentile. Gatling dokumentiert Load-Testing-Konzepte wie Workload-Modelle, virtuelle Nutzer und Metrikanalyse. k6 ist für hohe Lasttests wie Spike, Stress und Soak Tests optimiert.

Wichtig ist: Ein Load Test ohne realistische Daten und ohne Metriken aus der Anwendung ist nur Traffic-Erzeugung.

Senior-Antwort:

„Ein Lasttest ist nur so gut wie sein Lastmodell.“

Häufige Fehler:
  • Nur einen Endpoint testen.
  • Keine Ramp-up-Phase.
  • Unrealistische Payloads.
  • Kein Authentifizierungsmodell.
  • Testdaten werden gecacht und unrealistisch schnell.
  • Fehlerquote ignorieren.
  • Nur Client-Metriken betrachten.
  • Anwendungsmesswerte nicht korrelieren.
Best Practices:
  • Produktionsnahe Workloads.
  • Realistische Datenverteilung.
  • Ramp-up und Steady State.
  • Thresholds definieren.
  • Status und Body prüfen.
  • Fehlerquote messen.
  • Anwendung und Infrastruktur beobachten.
  • Load Generator dimensionieren.
Stolperfalle:

Ein Load Test mit immer demselben Datensatz misst oft Cache-Performance, nicht Systemperformance.

Merksatz:

Traffic erzeugen ist einfach; realistische Last modellieren ist schwer.

B7-K11-Q004 – Latenz, Percentiles und SLO-basierte AssertionsPerformance lebt in der Verteilung, nicht im Durchschnitt.
Frage:

„Warum reichen Durchschnittslatenzen in Performance Tests nicht aus?“

Kurz erklärt:

Performance Tests sollten Percentiles und Fehlerraten auswerten. Durchschnittswerte sind für SLOs meist zu schwach.

Musterantwort:

Durchschnittslatenz kann Tail-Latency verstecken. Wenn 95 Prozent der Requests schnell sind, aber 5 Prozent sehr langsam, kann der Durchschnitt akzeptabel aussehen, obwohl viele Nutzer schlechte Erfahrung haben. Deshalb definiere ich SLOs über p95, p99 und Fehlerrate. Gatling dokumentiert in seinen Load-Testing-Konzepten ebenfalls, dass Mean und Standard Deviation irreführend sein können.

Als Senior schaue ich immer gemeinsam auf Latenzpercentile, Durchsatz, Fehlerrate und Ressourcen. Ein p99 ohne Fehlerquote und Lastniveau ist nicht aussagekräftig.

Senior-Antwort:

„Durchschnitt ist angenehm für Präsentationen, aber Percentiles entscheiden über Nutzererfahrung.“

Häufige Fehler:
  • Nur Durchschnitt messen.
  • p99 ohne Sample-Größe interpretieren.
  • Fehlerhafte Requests aus Latenz herausrechnen.
  • Lastniveau nicht angeben.
  • Warmup mitmessen.
  • Keine Baseline.
  • Percentiles verschiedener Systeme falsch aggregieren.
  • Fehlerrate ignorieren.
Best Practices:
  • p95 und p99 messen.
  • Fehlerrate immer mitbewerten.
  • Lastniveau dokumentieren.
  • Warmup getrennt.
  • SLOs als Thresholds.
  • Tail-Latency analysieren.
  • Ressourcenmetriken korrelieren.
  • Sample-Größe beachten.
Stolperfalle:

Ein guter Durchschnitt kann ein schlechtes p99 kaschieren.

Merksatz:

Performance lebt in der Verteilung, nicht im Durchschnitt.

B7-K11-Q005 – Spring Boot Observability während Performance TestsPerformance ohne Observability ist Raten unter Last. 24h
Frage:

„Welche Metriken beobachten Sie während eines Performance Tests einer Spring-Boot-Anwendung?“

Kurz erklärt:

Performance Tests brauchen Observability. Ohne Metriken, Logs und Traces sieht man nur, dass etwas langsam ist, aber nicht warum.

Musterantwort:

Ich beobachte HTTP-Latenz und Fehlerrate, JVM-Heap, GC, Threads, CPU, Container-Memory, Datenbankpool, Query-Latenz, Downstream-Latenz, Cache-Hit-Rate und Queue-Lag. Spring Boot Actuator stellt Auto-Configuration für Micrometer bereit, eine Metrik-Fassade für viele Monitoring-Systeme. Micrometer beschreibt sich als Instrumentation Library, die Observability aus Spring-Boot-Anwendungen unterstützt.

Als Senior verlasse ich mich nicht nur auf Load-Generator-Metriken. Ich korreliere Client-Latenz mit Server-, JVM-, Datenbank- und Infrastrukturmetriken.

Senior-Antwort:

„Ein Load Test ohne Observability sagt mir, dass es brennt. Metriken und Traces sagen mir, wo.“

Häufige Fehler:
  • Nur Load-Generator-Metriken.
  • Keine JVM-Metriken.
  • Keine Datenbankmetriken.
  • Keine Custom Metrics.
  • Logs fehlen.
  • Traces fehlen.
  • Testumgebung nicht beobachtbar.
  • Metriken mit hoher Cardinality.
Best Practices:
  • Actuator und Micrometer aktivieren.
  • Golden Signals beobachten.
  • JVM- und GC-Metriken erfassen.
  • Datenbankpool beobachten.
  • Downstream-Latenz messen.
  • Business-Timer definieren.
  • Metrik-Cardinality begrenzen.
  • Dashboards vor Test vorbereiten.
Stolperfalle:

Wenn die Testumgebung keine Produktionsmetriken hat, testet man Performance blind.

Merksatz:

Performance ohne Observability ist Raten unter Last.

B7-K11-Q006 – Datenbank-Performance, Query Count und Connection PoolsViele API-Latenzen sind SQL-Latenzen mit HTTP-Headern.
Frage:

„Wie testen und analysieren Sie Datenbank-Performance in einer Spring-Boot-Anwendung?“

Kurz erklärt:

Viele Java-Performanceprobleme sind Datenbankprobleme. Query Count, Indexe, Connection Pools und Transaktionsdauer müssen explizit getestet und beobachtet werden.

Musterantwort:

Ich prüfe kritische Repository- und Servicepfade auf Query Count, N+1, Indexnutzung, Pagination, Locking und Connection-Pool-Verhalten. In Integrationstests kann ich Hibernate Statistics oder SQL-Zähler nutzen, um Query-Regressions zu erkennen. Unter Last beobachte ich zusätzlich Datenbanklatenz, HikariCP-Wartezeiten, aktive Connections, Slow Queries und Lock Waits.

Performanceprobleme entstehen oft nicht durch Java-Code selbst, sondern durch zu viele Queries, falsche Fetching-Strategien oder gesättigte Connection Pools.

Senior-Antwort:

„Bei Spring-Boot-Performance schaue ich früh auf die Datenbank. Dort verstecken sich viele p99-Probleme.“

Häufige Fehler:
  • Nur kleine Datenmengen testen.
  • Query Count ignorieren.
  • H2 statt produktionsnaher Datenbank.
  • Keine Indexprüfung.
  • Connection Pool zu groß einstellen.
  • Transaktionen zu lange halten.
  • Pagination mit Fetch Join falsch nutzen.
  • Ergebnislisten unbegrenzt laden.
Best Practices:
  • Kritische Queries testen.
  • Query Count absichern.
  • Realistische Datenmengen.
  • Produktionsnahe Datenbank.
  • Indexe prüfen.
  • Pagination erzwingen.
  • Connection Pool beobachten.
  • Transaktionen kurz halten.
Stolperfalle:

Ein Test mit zehn Datensätzen beweist keine Performance für zehn Millionen Datensätze.

Merksatz:

Viele API-Latenzen sind SQL-Latenzen mit HTTP-Headern.

B7-K11-Q007 – JVM, GC, Allocation und Speicherverhalten testenSpeicherperformance ist Allocation, Retention und Limit — nicht nur Heapgröße.
Frage:

„Wie analysieren Sie Speicher- und GC-Probleme in Java-Performance-Tests?“

Kurz erklärt:

JVM-Performance wird stark durch Allocation, GC, Heap-Größe, Native Memory und Containerlimits beeinflusst.

Musterantwort:

Ich beobachte Allocation Rate, Heap-Verlauf, GC-Pausen, GC-Frequenz, Old-Gen-Wachstum, Direct Memory, Thread-Anzahl und Container-Memory. Unter Last prüfe ich, ob Speicher nach Warmup stabil bleibt oder kontinuierlich wächst. Bei Microbenchmarks kann ich Allocation pro Operation messen, bei Systemtests brauche ich Actuator-, JVM- und Container-Metriken.

Spring Boot Actuator liefert über Micrometer viele JVM- und Anwendungsmesswerte an Monitoring-Systeme. Entscheidend ist, GC nicht isoliert zu betrachten. Häufig ist hoher GC-Druck nur Symptom von übermäßiger Allocation.

Senior-Antwort:

„GC ist selten die Ursache. Meist ist GC die Rechnung für zu viel Allocation oder falsche Retention.“

Häufige Fehler:
  • Nur Heap betrachten.
  • Native Memory ignorieren.
  • Zu kurze Tests.
  • GC-Pausen ohne Allocation analysieren.
  • Große Responses vollständig im Speicher bauen.
  • Unbegrenzte Caches.
  • Thread-Leaks ignorieren.
  • Containerlimit nicht beachten.
Best Practices:
  • Allocation messen.
  • GC-Metriken beobachten.
  • Soak Tests für Leaks.
  • Heap nach Warmup vergleichen.
  • Caches begrenzen.
  • Streaming für große Daten.
  • Direct Memory beachten.
  • Containerlimits realistisch setzen.
Stolperfalle:

Ein 10-Minuten-Load-Test findet nicht zuverlässig ein Speicherleck, das nach 8 Stunden kritisch wird.

Merksatz:

Speicherperformance ist Allocation, Retention und Limit — nicht nur Heapgröße.

B7-K11-Q008 – Concurrency, Thread Pools, Virtual Threads und BackpressureConcurrency braucht Grenzen, sonst wird Parallelität zu Stau.
Frage:

„Wie testen Sie Concurrency- und Thread-Pool-Verhalten unter Last?“

Kurz erklärt:

Concurrency-Performance-Tests prüfen Thread-Pools, Virtual Threads, Backpressure, Timeouts und Verhalten bei langsamen Downstreams.

Musterantwort:

Ich simuliere langsame Downstreams, hohe Parallelität und gesättigte Pools. Dann prüfe ich Latenz, Fehlerrate, Queue-Größen, aktive Threads, Rejections, Timeouts und Ressourcenverbrauch. Wichtig ist, ob das System kontrolliert degradiert oder unkontrolliert wartet.

Virtual Threads erhöhen die Skalierbarkeit blockierender IO-Pfade, ersetzen aber keine Backpressure. Wenn ein Downstream nur 50 parallele Requests verträgt, dürfen 5.000 virtuelle Threads ihn nicht ungebremst überlasten. Deshalb teste ich Bulkheads, Timeouts und Limits.

Senior-Antwort:

„Mehr Threads sind keine Kapazität. Kapazität sitzt oft in Datenbank, Downstream und Locks.“

Häufige Fehler:
  • Unbegrenzte Executor-Queues.
  • Keine Timeouts.
  • Retries ohne Budget.
  • Virtual Threads als Kapazitätsersatz missverstehen.
  • Datenbankpool ignorieren.
  • Downstream-Limits ignorieren.
  • Nur Happy-Path-Last testen.
  • Keine Slow-Dependency-Tests.
Best Practices:
  • Timeouts überall.
  • Bounded Queues.
  • Bulkheads.
  • Retry-Budget.
  • Slow-Downstream-Tests.
  • Thread- und Pool-Metriken.
  • Rejections bewusst behandeln.
  • Virtual Threads mit Limits kombinieren.
Stolperfalle:

Virtual Threads können ein System schneller an externe Grenzen bringen, wenn Backpressure fehlt.

Merksatz:

Concurrency braucht Grenzen, sonst wird Parallelität zu Stau.

B7-K11-Q009 – Performance Regression in CI/CDPerformance, die nicht regressionsgesichert ist, verschlechtert sich leise.
Frage:

„Wie integrieren Sie Performance Regression Tests in CI/CD?“

Kurz erklärt:

Performance Regression Testing schützt kritische Pfade vor schleichender Verschlechterung. CI/CD braucht Baselines, Budgets und reproduzierbare Umgebungen.

Musterantwort:

Ich trenne schnelle und teure Performance-Checks. JMH-Benchmarks für kritische Hotspots können regelmäßig laufen. API-Load-Tests laufen in separaten Stages, Nightly oder vor Releases. Wichtig sind Baselines, feste Umgebung, Versionierung der Ergebnisse, klare Budgets und automatische Gates.

JMH-Dokumentation warnt davor, Benchmarks blind zu vertrauen; Benchmarks sollten reviewt werden. Für Systemlasttests müssen Umgebung und Lastmodell stabil sein. Sonst blockiert CI wegen Messrauschen oder übersieht echte Regressionen.

Senior-Antwort:

„Performance Regression ist ein Qualitätsproblem wie ein funktionaler Bug — nur schwerer zu sehen.“

Häufige Fehler:
  • Performance nur manuell testen.
  • Keine Baseline.
  • Instabile Testumgebung.
  • Zu harte Gates.
  • Zu weiche Gates.
  • Warmup ignorieren.
  • Ergebnisse nicht speichern.
  • Keine Trendanalyse.
Best Practices:
  • Kritische Pfade auswählen.
  • Baselines speichern.
  • SLO-Budgets definieren.
  • Umgebung kontrollieren.
  • Warmup berücksichtigen.
  • Ergebnisse versionieren.
  • Trends beobachten.
  • Gates risikobasiert.
Stolperfalle:

Ein Performance Gate ohne stabile Umgebung erzeugt Misstrauen und wird irgendwann ausgeschaltet.

Merksatz:

Performance, die nicht regressionsgesichert ist, verschlechtert sich leise.

B7-K11-Q010 – Performance Testing Anti-Patterns und Enterprise Best PracticesPerformance Testing muss messbar, realistisch und handlungsfähig sein. 24h
Frage:

„Welche Performance-Testing-Anti-Patterns sehen Sie in Enterprise-Projekten?“

Kurz erklärt:

Professionelles Performance Testing ist zielbasiert, realistisch, beobachtbar und regressionsgesichert. Anti-Patterns entstehen durch unrealistische Last, falsche Metriken und fehlende Baselines.

Musterantwort:

Die häufigsten Anti-Patterns sind: keine SLOs, nur Durchschnittslatenz, keine Fehlerrate, unrealistische Testdaten, kein Warmup, keine Observability, keine Datenbankmetriken, keine GC-Metriken, falsche Infrastruktur, Load Generator als Engpass, Microbenchmarks als Systembeweis, keine Baselines und keine Regression Gates.

Professionell ist Performance Testing nur, wenn Lastmodell, Datenmodell, Infrastrukturmodell und Erfolgskriterien klar sind. Tools wie JMH, Gatling, k6, Actuator und Micrometer helfen, aber sie ersetzen kein Testdesign. JMH unterstützt JVM-Benchmarks, Gatling dokumentiert Load-Testing-Konzepte und Spring Boot Actuator integriert Micrometer-Metriken.

Senior-Antwort:

„Performance Testing ist nicht der letzte Test vor Release. Es ist ein kontinuierlicher Vertrag zwischen Architektur, Code und Betrieb.“

Häufige Fehler:
  • Performance spät testen.
  • Keine reproduzierbare Umgebung.
  • Keine Baseline.
  • Keine Thresholds.
  • Keine p99-Analyse.
  • Downstreams mocken, obwohl sie Bottleneck sind.
  • Testdaten zu klein.
  • Logging-Level unrealistisch.
Best Practices:
  • SLOs definieren.
  • Lastmodell dokumentieren.
  • Realistische Daten.
  • Warmup trennen.
  • Percentiles messen.
  • Fehlerquote messen.
  • Observability vollständig.
  • Datenbank und Downstreams einbeziehen.
Stolperfalle:

Ein Performance-Test ohne Produktionsnähe und ohne Observability erzeugt Zahlen, aber keine Erkenntnis.

Merksatz:

Performance Testing muss messbar, realistisch und handlungsfähig sein.

B7-K12 – Kapitel 12: Testarchitektur und Testing Governance 10 Karten

B7-K12-Q001 – Teststrategie als ArchitekturentscheidungTestarchitektur ist Risikomanagement mit Feedbackschleifen. 24h
Frage:

„Wie würden Sie eine Teststrategie für ein großes Spring-Boot-Microservice-System definieren?“

Kurz erklärt:

Teststrategie ist eine Architekturentscheidung. Sie legt fest, welche Risiken auf welcher Ebene getestet werden und welche Feedbackzeit akzeptabel ist.

Musterantwort:

Ich beginne mit Risiken und Feedbackzeiten. Reine Fachlogik teste ich ohne Framework. Web-, JPA-, Security- und REST-Client-Grenzen teste ich mit fokussierten Slices. Echte Infrastruktur wie PostgreSQL, Kafka oder Redis teste ich mit Testcontainers. API- und Event-Kompatibilität sichere ich mit Contract Tests. Wenige kritische End-to-End- und Smoke-Tests prüfen das Deployment und die Plattformrealität.

Die Testpyramide ist dabei eine gute Heuristik: viele schnelle, niedrige Tests und weniger breite, teurere Tests. In modernen Systemen ergänze ich sie aber um Contract Tests, Security Tests, Performance Regression und Observability Checks.

Senior-Antwort:

„Ich wähle die Testebene nach Risiko, nicht nach persönlicher Vorliebe oder Framework-Gewohnheit.“

Häufige Fehler:
  • Teststrategie nach Tool statt Risiko.
  • Alles mit @SpringBootTest .
  • Nur Unit Tests ohne Integrationsrisiken.
  • Zu viele E2E-Tests.
  • Keine Contract Tests.
  • Keine Security-Negativtests.
  • Keine Performance-Baselines.
  • Keine klare Ownership.
Best Practices:
  • Risiken explizit machen.
  • Testebene bewusst wählen.
  • Unit Tests frameworkfrei halten.
  • Slices für Frameworkgrenzen nutzen.
  • Testcontainers für echte Infrastruktur.
  • Contract Tests für Kompatibilität.
  • E2E auf kritische Journeys begrenzen.
  • Testlaufzeiten messen.
Stolperfalle:

Eine große Testsuite kann gleichzeitig langsam und risikoblind sein.

Merksatz:

Testarchitektur ist Risikomanagement mit Feedbackschleifen.

B7-K12-Q002 – Testpyramide, Testdiamant und moderne TestportfoliosNicht die Form der Pyramide zählt, sondern das Verhältnis von Feedbackwert zu Testkosten.
Frage:

„Ist die Testpyramide heute noch sinnvoll?“

Kurz erklärt:

Die Testpyramide ist eine Heuristik, keine starre Regel. Moderne Backend-Systeme brauchen zusätzlich Contract-, Security-, Performance- und Observability-Tests.

Musterantwort:

Ja, als Heuristik. Die Grundidee bleibt wertvoll: viele schnelle, fokussierte Tests und weniger breite, teure Tests. Fowler beschreibt die Testpyramide als Modell, um automatisierte Tests verschiedener Granularität auszubalancieren. Gleichzeitig ist moderne Testarchitektur differenzierter. Google beschreibt die Testpyramide weiterhin als kanonische Heuristik, diskutiert aber auch weiterentwickelte Modelle für moderne Test-Suiten.

In Backend-Systemen denke ich deshalb eher in Testportfolios: Unit, Slice, Integration, Contract, Security, Performance und Smoke. Die Form ist weniger wichtig als Feedbackqualität, Kosten und Risikoabdeckung.

Senior-Antwort:

„Die Testpyramide ist kein Kunstwerk, das perfekt aussehen muss. Sie ist eine Kosten-Nutzen-Heuristik.“

Häufige Fehler:
  • Testpyramide dogmatisch anwenden.
  • Unit Tests ohne Integrationsrisikoabdeckung.
  • Zu viele E2E-Tests.
  • Keine Contract-Ebene.
  • Performance und Security aus Portfolio ausklammern.
  • Testanzahl statt Testwert messen.
  • Breite Tests für triviale Logik.
  • Flaky Tests als unvermeidlich akzeptieren.
Best Practices:
  • Pyramide als Heuristik nutzen.
  • Risikoportfolio statt starre Form.
  • Breite Tests begründen.
  • E2E auf kritische Journeys.
  • Contract Tests für Kompatibilität.
  • Security und Performance als eigene Dimensionen.
  • Testkosten sichtbar machen.
  • Testwert regelmäßig reviewen.
Stolperfalle:

Viele Unit Tests kompensieren keine fehlenden Integrations- oder Contract-Tests.

Merksatz:

Nicht die Form der Pyramide zählt, sondern das Verhältnis von Feedbackwert zu Testkosten.

B7-K12-Q003 – Test Naming, Struktur und LesbarkeitTests dokumentieren Verhalten — nicht Implementierung.
Frage:

„Wie benennen und strukturieren Sie Tests in großen Java-Projekten?“

Kurz erklärt:

Tests sind Dokumentation des erwarteten Verhaltens. Gute Namen, klare Struktur und fachliche Assertions erhöhen Wartbarkeit.

Musterantwort:

Ich benenne Tests nach Verhalten und Erwartung, nicht nach Implementierungsdetails. Ein guter Testname beantwortet: Unter welcher Bedingung passiert welches erwartete Verhalten? Die Struktur folgt häufig Given-When-Then oder Arrange-Act-Assert. JUnit Jupiter unterstützt moderne Teststrukturierung unter anderem mit verschachtelten Tests und Lifecycle-Konzepten, die im User Guide beschrieben werden.

Wichtig ist, dass ein Test beim Fehlschlag direkt verständlich macht, welches Verhalten verletzt wurde.

Senior-Antwort:

„Ein Test ist ein ausführbares Beispiel. Wenn er nicht lesbar ist, verliert er die Hälfte seines Werts.“

Häufige Fehler:
  • Namen wie test1 .
  • Methodennamen statt Verhalten.
  • Zu viel Setup.
  • Relevante Daten in Fixtures versteckt.
  • Mehrere Verhalten in einem Test.
  • Assertions auf interne Details.
  • Copy-Paste-Tests.
  • Fehlermeldungen nicht hilfreich.
Best Practices:
  • Verhalten im Testnamen.
  • Eine zentrale Erwartung pro Test.
  • Setup minimal.
  • Relevante Werte sichtbar.
  • Builder für irrelevante Defaults.
  • Nested Tests für Kontexte.
  • Fachliche Assertions.
  • Fehlermeldungen prüfen.
Stolperfalle:

Zu viel gemeinsames Setup macht einzelne Tests kurz, aber schwer verständlich.

Merksatz:

Tests dokumentieren Verhalten — nicht Implementierung.

B7-K12-Q004 – Testdatenarchitektur und deterministische TestsDeterministische Testdaten sind die Grundlage vertrauenswürdiger Tests.
Frage:

„Wie organisieren Sie Testdaten in einer großen Enterprise-Test-Suite?“

Kurz erklärt:

Deterministische Testdaten machen Tests reproduzierbar. Gute Testdatenarchitektur vermeidet globale Fixtures, echte personenbezogene Daten und versteckte Abhängigkeiten.

Musterantwort:

Ich unterscheide Testdaten nach Ebene. Unit Tests nutzen Builder oder Object Mothers mit sichtbaren relevanten Werten. Integrationstests erzeugen ihre Daten pro Test oder pro Provider State. Datenbanktests verwenden eindeutige IDs, Rollback, Cleanup oder isolierte Schemas. Zeit wird über Clock kontrolliert. Keine Tests sollten von aktueller Systemzeit, Testreihenfolge oder globalem Datenzustand abhängen.

Determinismus ist entscheidend. Ein Test, der nur manchmal fehlschlägt, zerstört Vertrauen in die Suite.

Senior-Antwort:

„Testdaten sind nicht Nebensache. Sie entscheiden, ob Tests reproduzierbar, lesbar und parallelisierbar sind.“

Häufige Fehler:
  • Produktionsdaten in Tests.
  • Aktuelle Zeit direkt nutzen.
  • Globale Fixtures.
  • Testreihenfolge-Abhängigkeiten.
  • Unkontrollierte Zufallsdaten.
  • Zu große SQL-Dumps.
  • Builder verstecken relevante Werte.
  • Testdaten werden nie bereinigt.
Best Practices:
  • Clock injizieren.
  • Zeitzonen explizit.
  • Testdaten klein halten.
  • Relevante Werte sichtbar.
  • Builder für Defaults.
  • Eindeutige IDs.
  • Datenisolierung pro Test.
  • Keine echten personenbezogenen Daten.
Stolperfalle:

Globale Fixtures sparen Setup-Zeit beim Schreiben, kosten aber Debugging-Zeit über Jahre.

Merksatz:

Deterministische Testdaten sind die Grundlage vertrauenswürdiger Tests.

B7-K12-Q005 – Flaky Tests erkennen, klassifizieren und eliminierenFlakiness ist technischer Schuldenstand mit direktem CI-Zins.
Frage:

„Wie gehen Sie mit flaky Tests in einer großen CI/CD-Pipeline um?“

Kurz erklärt:

Flaky Tests sind Tests, die ohne Codeänderung mal grün und mal rot sind. Sie müssen priorisiert behoben werden, weil sie Vertrauen in die gesamte Suite zerstören.

Musterantwort:

Ich behandle flaky Tests als Qualitätsbug. Zuerst klassifiziere ich Ursache: Zeit, geteilte Daten, Parallelisierung, externe Infrastruktur, asynchrone Verarbeitung oder Zufall. Dann wird der Test stabilisiert, quarantined oder entfernt, aber nicht dauerhaft ignoriert. Retry ist höchstens ein Diagnosewerkzeug, keine Lösung.

JUnit bietet Plattform- und Ausführungsfeatures für moderne Test-Suiten. Gerade wenn Parallelisierung, Tags oder verschiedene Teststufen genutzt werden, muss Flakiness aktiv gemessen und mit Ownership versehen werden.

Senior-Antwort:

„Ein flaky Test ist kein nerviger Test. Er ist ein Angriff auf das Vertrauen in die Pipeline.“

Häufige Fehler:
  • Flaky Tests ignorieren.
  • Retry als Lösung.
  • Test dauerhaft deaktivieren ohne Ticket.
  • Keine Ownership.
  • Asynchrone Tests mit Sleep.
  • Geteilte Testdaten.
  • Testreihenfolge-Abhängigkeit.
  • Externe Services direkt nutzen.
Best Practices:
  • Flaky Tests als Bug behandeln.
  • Ursache klassifizieren.
  • Ownership zuweisen.
  • Quarantine nur temporär.
  • Await statt Sleep.
  • Daten isolieren.
  • Zeit kontrollieren.
  • Parallelisierung absichern.
Stolperfalle:

Retries machen flaky Tests seltener sichtbar, aber nicht stabiler.

Merksatz:

Flakiness ist technischer Schuldenstand mit direktem CI-Zins.

B7-K12-Q006 – Testlaufzeit, Parallelisierung und CI/CD-StufungCI/CD-Stufung ist Risikosteuerung über Zeit.
Frage:

„Wie organisieren Sie Testlaufzeit und CI/CD-Stufen in einer großen Java-Codebasis?“

Kurz erklärt:

Nicht alle Tests müssen in jeder Pipeline-Stufe laufen. Tags, Profile und CI/CD-Stages steuern Feedbackzeit und Risikoabdeckung.

Musterantwort:

Ich teile Tests nach Laufzeit und Risiko. Pull Requests brauchen schnelle, zuverlässige Tests. Main-Branch-Pipelines dürfen mehr Integration enthalten. Nightly- oder Release-Stages können Performance, Soak und größere Contract-Matrizen ausführen. JUnit unterstützt moderne Testausführung, einschließlich Features für das Schreiben, Ausführen und Strukturieren von Tests.

Wichtig ist, dass langsame Tests nicht einfach weggelassen werden, sondern in passende Stufen wandern. Gleichzeitig darf die PR-Pipeline nicht so langsam werden, dass Entwickler sie umgehen.

Senior-Antwort:

„Nicht jeder Test gehört in jede Pipeline. Aber jedes wichtige Risiko braucht eine Pipeline-Stufe.“

Häufige Fehler:
  • Alle Tests in PR.
  • Zu wenige Tests in PR.
  • Langsame Tests deaktivieren.
  • Tags ohne Governance.
  • Keine Laufzeitbudgets.
  • Parallelisierung ohne Isolation.
  • Testcontainers unkontrolliert parallel.
  • E2E blockiert jeden kleinen Merge.
Best Practices:
  • Testlaufzeit messen.
  • Tags standardisieren.
  • PR-Budget definieren.
  • Stufen nach Risiko.
  • Langsame Tests begründen.
  • Integrationstests separat.
  • Parallelisierung vorsichtig.
  • Nightly-Fehler mit Ownership.
Stolperfalle:

Eine schnelle PR-Pipeline ist gefährlich, wenn sie kritische Integrationsrisiken nirgendwo anders prüft.

Merksatz:

CI/CD-Stufung ist Risikosteuerung über Zeit.

B7-K12-Q007 – Code Coverage, Mutation Testing und AussagekraftCoverage misst Ausführung, nicht Vertrauen.
Frage:

„Wie bewerten Sie Code Coverage in einer Enterprise-Teststrategie?“

Kurz erklärt:

Coverage zeigt, was ausgeführt wurde, nicht ob richtig geprüft wurde. Mutation Testing kann Assertion-Qualität besser sichtbar machen.

Musterantwort:

Coverage ist ein nützliches Signal, aber kein Qualitätsbeweis. 90 Prozent Line Coverage können schlechte Tests bedeuten, wenn Assertions schwach sind. Ich nutze Coverage, um ungetestete Bereiche sichtbar zu machen, aber nicht als alleinige Zielmetrik. Für kritische Logik ist Mutation Testing wertvoller, weil es prüft, ob Tests fehlschlagen, wenn Produktionscode absichtlich verändert wird.

Als Governance-Regel ist mir wichtiger: Sind kritische Risiken abgedeckt? Gibt es negative Tests? Gibt es Contract-, Security- und Integrationstests? Coverage allein beantwortet das nicht.

Senior-Antwort:

„Coverage sagt mir, wo Licht war. Sie sagt mir nicht, ob jemand genau hingesehen hat.“

Häufige Fehler:
  • Coverage als Qualitätsziel.
  • Tests ohne Assertions.
  • Nur Happy Path.
  • Coverage über generierten Code.
  • Kritische Logik ohne Mutation Testing.
  • Coverage-Schwelle blind erhöhen.
  • Integrationstests in Coverage falsch interpretieren.
  • Security- und Contract-Risiken nicht abbilden.
Best Practices:
  • Coverage als Signal, nicht Ziel.
  • Branch Coverage für Logik beachten.
  • Kritische Module mutation-testen.
  • Coverage-Deltas reviewen.
  • Generated Code ausschließen.
  • Assertion-Qualität prüfen.
  • Negative Tests verlangen.
  • Risikoabdeckung dokumentieren.
Stolperfalle:

Eine Coverage-Schwelle kann zu vielen Tests führen, die Code ausführen, aber Verhalten nicht schützen.

Merksatz:

Coverage misst Ausführung, nicht Vertrauen.

B7-K12-Q008 – Test Ownership, Standards und Review-KulturTesting Governance ist Ownership plus Enablement.
Frage:

„Wie etablieren Sie Testing Governance über mehrere Teams hinweg?“

Kurz erklärt:

Testing Governance braucht klare Ownership, Standards und Review-Kultur. Tests gehören nicht „QA“, sondern dem Team, das Verhalten liefert.

Musterantwort:

Ich definiere Standards, aber lasse Ownership bei den Feature-Teams. Teams besitzen ihre Tests, Testdaten, Flaky-Fixes und Integrationsverträge. Platform Teams liefern Werkzeuge: CI-Templates, Testcontainers-Utilities, Contract Broker, Security Scans und Dashboards. Architektur oder QA Enablement sorgt für Leitplanken, Reviews und Metriken.

Governance darf nicht Bürokratie sein. Sie muss Teams schneller und sicherer machen.

Senior-Antwort:

„Tests gehören dem Team, das das Verhalten liefert. Governance liefert Leitplanken, nicht Ausreden.“

Häufige Fehler:
  • Tests gehören niemandem.
  • QA als nachgelagerter Gatekeeper.
  • Standards ohne Tooling.
  • Tooling ohne Standards.
  • Flaky Tests ohne Owner.
  • Code Reviews ignorieren Tests.
  • Contract Ownership unklar.
  • Security-Tests nicht verpflichtend.
Best Practices:
  • Feature-Team-Ownership.
  • Golden Paths.
  • Review-Checklisten.
  • Testtemplates.
  • Gemeinsame Basisklassen vorsichtig.
  • Flaky-Test-Owner.
  • Contract Ownership.
  • Security-Testmatrix.
Stolperfalle:

Wenn Tests als QA-Aufgabe gelten, entstehen Qualitätssilos und langsame Feedbackschleifen.

Merksatz:

Testing Governance ist Ownership plus Enablement.

B7-K12-Q009 – Legacy-Test-Suiten refactorenTest-Suiten sind Code — sie brauchen Refactoring.
Frage:

„Wie würden Sie eine langsame und flaky Legacy-Test-Suite modernisieren?“

Kurz erklärt:

Legacy-Test-Suiten müssen wie Produktionscode refactored werden. Ziel ist nicht nur weniger Laufzeit, sondern mehr Aussagekraft und Vertrauen.

Musterantwort:

Ich starte mit Messung: Laufzeit pro Test, Flaky Rate, häufige Failures, Spring Context Count, E2E-Anteil und deaktivierte Tests. Dann klassifiziere ich Tests nach Ebene und Risiko. Breite @SpringBootTest werden, wo möglich, in Unit-, Slice- oder Integrationstests zerlegt. Flaky Tests werden priorisiert. Globale Fixtures werden durch isolierte Testdaten ersetzt. Redundante E2E-Tests werden durch Contract- oder Integrationstests ersetzt.

Wichtig ist, nicht blind Tests zu löschen. Ich frage: Welches Risiko deckt dieser Test ab? Gibt es eine billigere, stabilere Testebene für dasselbe Risiko?

Senior-Antwort:

„Ich lösche keine Tests, weil sie langsam sind. Ich ersetze teure Tests durch billigere Tests mit gleicher oder besserer Risikoabdeckung.“

Häufige Fehler:
  • Blind Tests löschen.
  • Nur Laufzeit optimieren.
  • Risikoabdeckung verlieren.
  • Flaky Tests ignorieren.
  • Keine Baseline-Messung.
  • Kein Owner.
  • Refactoring ohne CI-Stufung.
  • Legacy-Fixtures behalten.
Best Practices:
  • Erst messen.
  • Tests klassifizieren.
  • Risiko pro Test verstehen.
  • Breite Tests zerlegen.
  • Redundanz entfernen.
  • Flaky Tests fixen.
  • Testdaten isolieren.
  • Contract Tests einführen.
Stolperfalle:

Eine schnellere Testsuite ist kein Fortschritt, wenn sie weniger wichtige Risiken abdeckt.

Merksatz:

Test-Suiten sind Code — sie brauchen Refactoring.

B7-K12-Q010 – Enterprise Testing Governance Anti-Patterns und Best PracticesTesting Governance ist skalierte Testarchitektur. 24h
Frage:

„Welche Testing-Governance-Anti-Patterns sehen Sie in großen Java-Organisationen?“

Kurz erklärt:

Testing Governance macht Testqualität wiederholbar. Sie kombiniert Strategie, Standards, Ownership, CI/CD-Gates, Metriken und Kultur.

Musterantwort:

Typische Anti-Patterns sind: keine explizite Teststrategie, Testpyramide als Dogma, alle Tests in einer Pipeline-Stufe, keine Ownership, tolerierte Flaky Tests, Coverage als einzige Qualitätsmetrik, keine Contract Gates, keine Security-Negativmatrix, keine Performance-Baselines, globale Testdaten, zu viele E2E-Tests, weggemockte Integrationsrisiken und Standards, die nur in Wiki-Seiten stehen.

Eine gute Governance ist risikobasiert, automatisiert und teamfähig. Sie definiert Golden Paths, aber lässt Teams Verantwortung. Sie misst Laufzeit, Flakiness, Vertragkompatibilität, Sicherheitsabdeckung und Performance-Trends.

Senior-Antwort:

„Gute Governance automatisiert gute Entscheidungen und macht schlechte Muster sichtbar.“

Häufige Fehler:
  • Governance nur als Dokument.
  • Keine Automatisierung.
  • Keine Ownership.
  • Metriken ohne Konsequenz.
  • Zu starre Standards.
  • Teams umgehen Gates.
  • Keine Legacy-Modernisierung.
  • Security und Performance fehlen.
Best Practices:
  • Governance als Enablement.
  • Standards automatisieren.
  • Golden Paths anbieten.
  • Team Ownership.
  • Flaky Tests mit SLA.
  • CI/CD-Gates risikobasiert.
  • Regelmäßige Portfolio-Reviews.
  • Coverage nicht isoliert bewerten.
Stolperfalle:

Ein Wiki mit Teststandards ist keine Governance, solange Build, Templates und Reviews diese Standards nicht unterstützen.

Merksatz:

Testing Governance ist skalierte Testarchitektur.

Glossar zusammengeführt: Anhang H und die frühere Erweiterung sind ein Glossar. Du kannst nach Themen filtern oder den A-Z-Index nutzen.
A-Z-Index aller Glossarbegriffe (259)
AbstraktionActuatorADRAggregateAlert FatigueAllocation HotspotAllocation RateAnti-Corruption LayerAPI-VertragApplicationContextApplicationRunnerArchitecture DriftArchitecture GovernanceArrayListAssertionAt-Least-OnceAtomic OperationAuthenticationAuthorizationAuto-ConfigurationBack-offBackpressureBackward CompatibilityBeanBean ScopeBean Validation IntegrationBeanPostProcessorBlameless PostmortemBlue-Green DeploymentBOLABounded ContextBreaking ChangeBuild vs BuyBuildConfigBytecodeCanary DeploymentCanonical ConstructorCharacterization TestChecked ExceptionClassLoaderCognitive LoadCohesionCommandLineRunnerCommit OffsetCompact ConstructorCompacted TopicCompletableFutureConcurrentHashMapConditional BeanConfigMapConfiguration PropertiesConnection PoolConsumer GroupConsumer LagContract TestControllerConway’s LawCorrelation IDCORSCORS PreflightCouplingCovering IndexCPU SaturationCSRFCSRF TokenCursor PaginationDDDDead Letter TopicDeadlockDecision LogDefensive CopyDeoptimizationDependency InjectionDeprecationDirect BufferDirty CheckingDistributed MonolithDominator TreeDTOE2E TestEncapsulationEntityEntityGraphEnvironmentequals/hashCodeError BudgetEscape AnalysisEventEvent ContractEvent EnvelopeEventual ConsistencyEvolutionary ArchitectureExactly-Once SemanticsException TranslationExecutorServiceExhaustivenessExpand-Contract MigrationFail-Fast IteratorFakeFetch JoinFinaler MerksatzFitness FunctionFixtureFlaky TestFlushFlywayForeign KeyGarbage CollectionGC PauseGitOpsGiven-When-ThenGolden Master TestGolden PathHappens-BeforeHashMapHeapHeap DumpHorizontal Pod AutoscalerIdempotenzImageStreamImmutabilityIndex SelectivityIntegrationstestInterfaceInvariantJava RecordJFRJITJWKSJWTKafka PartitionKey RotationLazy LoadingLeast PrivilegeLinkedHashMapLiquibaseLiveness ProbeLoad TestLock ContentionLogMessage OrderingMetaspaceMethod SecurityMetricMicroserviceMockModularer MonolithModularisierungmTLSMTTRMutation TestingNative MemoryNetworkPolicyNull Object PatternOAuth2 Resource ServerOffsetOperatorOptimistic LockingOptionalOutbox Patternp95p99PaginationParallel StreamPattern MatchingPECSPerformance RegressionPersistence ContextPessimistic LockingPlatform EngineeringPod Disruption BudgetPoison MessagePrimitive ObsessionProblemDetailProfileProperty SourceProxyQuery PlanRace ConditionRate LimitingRaw TypeRBACRead ModelReadiness ProbeRebalancingReconciliationRefresh TokenRequest TimeoutResource LimitsRESTRestClientRetained SizeRetry BudgetRetry-AfterRolling UpdateRouteRunbookSafepointSchema EvolutionScopeSealed ClassSecretSecret RotationSecurity Regression TestSecurityContextConstraintsSelf InvocationSensitive Data ExposureServiceAccountShallow SizeSide EffectSLISlice TestSLOSnapshot TestSoak TestSOLIDSpanSpike TestStackStarterStartup ProbeStop-the-WorldStrangler Fig PatternStreamStress TestStructured ConcurrencyStubSwitch ExpressionTail-LatencyTeam TopologiesTechnical DebtTemporal CouplingTenant IsolationTest Data BuilderTest DiamondTest PyramidTest SmellTestcontainersTesting GovernanceText BlockThread DumpThread StarvationToken ExpirationTombstone EventTraceTrade-offTransaction Boundarytry-with-resourcesType ErasureUnchecked ExceptionUnique ConstraintValidationValue ObjectVertical Pod AutoscalerVirtual ThreadvolatileWarmupWebClientWildcard

AH – Anhang H: Glossar komplett (inkl. Erweiterung)

Dieses Glossar fasst zentrale Begriffe aus dem Senior Java Interview Masterbook kompakt zusammen.

Es ist als Nachschlagewerk für Interviews, Wiederholung und schnelle Begriffsklärung gedacht. Die Erklärungen sind bewusst kurz, aber seniororientiert formuliert: nicht nur „was ist es?“, sondern auch „warum ist es relevant?“.


1. Java Core

Abstraktion

Abstraktion bedeutet, unwichtige Details zu verbergen und eine fachlich oder technisch sinnvolle Schnittstelle bereitzustellen.

Senior-Fokus:
Abstraktion ist gut, wenn sie Änderbarkeit erhöht.
Sie ist schlecht, wenn sie Komplexität ohne echten Nutzen erzeugt.

API-Vertrag

Ein API-Vertrag beschreibt, was Nutzer einer Schnittstelle erwarten dürfen: Eingaben, Ausgaben, Fehler, Semantik, Stabilität und Kompatibilität.

Senior-Fokus:
Ein Vertrag umfasst auch Fehlerfälle, Security, Versionierung und Rückwärtskompatibilität.

Defensive Copy

Eine defensive Kopie schützt internen Zustand vor externer Mutation.

Beispiel:
Eine immutable Klasse kopiert eingehende Collections,
statt Referenzen direkt zu speichern.

equals/hashCode

equals definiert logische Gleichheit. hashCode unterstützt Hash-basierte Collections.

Senior-Fokus:
equals/hashCode müssen zusammenpassen.
Mutable Felder in hashCode sind gefährlich.

Immutability

Immutability bedeutet, dass ein Objekt nach seiner Erzeugung nicht mehr verändert werden kann.

Vorteile:
- bessere Thread-Safety
- bessere Testbarkeit
- klare Value Objects
- weniger Nebenwirkungen

Interface

Ein Interface beschreibt einen Vertrag ohne konkrete Implementierung.

Senior-Fokus:
Interfaces sind sinnvoll an Architekturgrenzen,
nicht automatisch für jede Klasse.

Java Record

Ein Record ist eine kompakte Java-Syntax für unveränderliche Datenobjekte mit automatisch erzeugten Accessors, equals, hashCode und toString.

Senior-Fokus:
Gut für DTOs und Value Objects.
Nicht ideal für mutable JPA-Entities.

Pattern Matching

Pattern Matching kombiniert Typprüfung und Typbindung, zum Beispiel bei instanceof oder switch.

Senior-Fokus:
Besonders stark zusammen mit sealed Klassen
für geschlossene fachliche Typmodelle.

Sealed Class

Eine sealed Class oder ein sealed Interface beschränkt, welche Typen davon erben dürfen.

Senior-Fokus:
Hilfreich für fachlich geschlossene Varianten,
zum Beispiel PaymentStatus oder Command-Hierarchien.

Value Object

Ein Value Object wird über seine Werte definiert, nicht über eine technische Identität.

Eigenschaften:
- immutable
- validiert Invarianten
- equals/hashCode nach Wert
- keine eigene Lebenszyklusidentität

2. Collections und Generics

ArrayList

Eine listenartige Collection mit schnellem indexbasiertem Zugriff.

Senior-Fokus:
Meist bessere Default-Wahl als LinkedList wegen Speicherlokalität.

ConcurrentHashMap

Eine thread-sichere Map für nebenläufige Zugriffe.

Senior-Fokus:
Einzeloperationen sind thread-safe.
Zusammengesetzte Fachtransaktionen müssen trotzdem bewusst geschützt werden.

Fail-Fast Iterator

Ein Iterator, der bei konkurrierender struktureller Änderung typischerweise ConcurrentModificationException wirft.

Senior-Fokus:
Fail-fast ist Diagnosehilfe, keine Nebenläufigkeitsgarantie.

HashMap

Eine Map, die Keys über hashCode und equals organisiert.

Senior-Fokus:
Keine Ordnung garantieren.
Keine mutable Keys verwenden.
Nicht thread-safe.

LinkedHashMap

Eine Map, die Einfügereihenfolge oder Zugriffreihenfolge erhalten kann.

Senior-Fokus:
Nützlich für deterministische Ausgabe oder einfache LRU-nahe Strukturen.

PECS

Producer Extends, Consumer Super.

Bedeutung:
- ? extends T, wenn gelesen/produziert wird
- ? super T, wenn geschrieben/konsumiert wird

Raw Type

Ein generischer Typ ohne Typparameter, zum Beispiel List statt List<String>.

Senior-Fokus:
Raw Types umgehen Typsicherheit und sollten vermieden werden.

Type Erasure

Generische Typinformationen werden zur Laufzeit weitgehend gelöscht.

Konsequenzen:
- kein new T()
- keine generischen primitiven Typen
- Reflection/Serialization bewusst betrachten

Wildcard

Ein Platzhaltertyp wie ? extends Number oder ? super String.

Senior-Fokus:
Wildcards verbessern flexible APIs,
können aber Lesbarkeit verschlechtern, wenn übernutzt.

3. Streams, Optional und Exceptions

Checked Exception

Eine Exception, die der Compiler zur Behandlung oder Deklaration erzwingt.

Senior-Fokus:
Nützlich für explizite Fehlerverträge,
aber problematisch, wenn sie technische Details durch alle Schichten leitet.

Exception Translation

Übersetzung technischer Exceptions in fachlich oder architektonisch passende Fehler.

Beispiel:
SQL-Fehler -> RepositoryException oder Conflict-Fehler im API-Vertrag.

Optional

Ein Container für optional vorhandene Rückgabewerte.

Senior-Fokus:
Gut als Rückgabetyp.
Nicht ideal als Entity-Feld oder pauschaler Null-Ersatz.

Parallel Stream

Ein Stream, der Verarbeitung parallelisiert.

Senior-Fokus:
Nicht blind verwenden.
Besonders riskant bei IO, Seiteneffekten und gemeinsamem Zustand.

Side Effect

Eine Nebenwirkung ist eine Änderung außerhalb des direkten Rückgabewerts einer Funktion.

Senior-Fokus:
Streams und funktionale Pipelines sollten möglichst nebenwirkungsarm bleiben.

Stream

Eine deklarative API zur Verarbeitung von Datenfolgen.

Senior-Fokus:
Gut für Transformationen.
Schlecht für versteckte Seiteneffekte und überkomplexe Kontrollflüsse.

try-with-resources

Java-Konstrukt zum automatischen Schließen von Ressourcen.

Senior-Fokus:
Verhindert Resource Leaks bei IO, JDBC, Streams und Clients.

Unchecked Exception

Eine Runtime Exception, die nicht explizit deklariert werden muss.

Senior-Fokus:
Geeignet für Programmierfehler und viele technische Fehler,
aber nicht als Entschuldigung für unklare Fehlerverträge.

4. JVM und Performance

Allocation Rate

Die Menge an Speicher, die pro Zeit oder Operation neu alloziert wird.

Senior-Fokus:
Hohe Allocation Rate erzeugt GC-Druck und kann p99-Latenzen verschlechtern.

Bytecode

Zwischencode, den der Java-Compiler erzeugt und die JVM ausführt oder kompiliert.

Senior-Fokus:
Wichtig für JVM-Verständnis, Proxies, Instrumentation und Performance.

ClassLoader

Komponente, die Klassen in die JVM lädt.

Senior-Fokus:
Relevant für App-Server, Plugins, Isolation und ClassLoader-Leaks.

Deoptimization

Rücknahme von JIT-Optimierungen, wenn Annahmen nicht mehr gelten.

Senior-Fokus:
Performance kann sich zur Laufzeit ändern.
Warmup und Profiling sind wichtig.

Escape Analysis

JVM-Optimierung, die erkennt, ob ein Objekt den lokalen Kontext verlässt.

Senior-Fokus:
Kann Allocation reduzieren oder entfernen.
Beeinflusst Microbenchmarks.

Garbage Collection

Automatische Speicherbereinigung unerreichbarer Objekte.

Senior-Fokus:
GC-Probleme sind oft Symptome von Allocation,
Retention oder Speicherlimits.

Heap

Speicherbereich für Objekte, die von der Garbage Collection verwaltet werden.

Senior-Fokus:
Heap ist nicht gesamter Prozessspeicher.
Native Memory, Metaspace und Thread Stacks zählen zusätzlich.

JFR

Java Flight Recorder zeichnet JVM-Laufzeitereignisse auf.

Senior-Fokus:
Sehr wertvoll für CPU, Allocation, GC, Locks, Threads und IO-Analyse.

JIT

Just-in-Time Compiler der JVM, der häufig ausgeführten Bytecode optimiert in Maschinencode übersetzt.

Senior-Fokus:
Warmup, Inlining, Profiling und Deoptimization beeinflussen Performance.

Metaspace

Nativer Speicherbereich für Klassenmetadaten.

Senior-Fokus:
ClassLoader-Leaks können Metaspace-Wachstum verursachen.

Safepoint

Punkt, an dem die JVM Threads sicher koordinieren oder anhalten kann.

Senior-Fokus:
Safepoint-Pausen können Tail-Latency beeinflussen.

Stack

Thread-lokaler Speicherbereich für Methodenaufrufe und lokale Variablen.

Senior-Fokus:
Jeder Thread hat einen Stack.
Viele Platform Threads verbrauchen entsprechend Speicher.

Warmup

Phase, in der JVM, JIT, Caches und Anwendung ihren stabilen Laufzeitzustand erreichen.

Senior-Fokus:
Benchmarks und Load Tests ohne Warmup sind oft irreführend.

5. Concurrency

Atomic Operation

Eine Operation, die unteilbar ausgeführt wird.

Senior-Fokus:
AtomicInteger löst Zählerprobleme,
aber nicht automatisch komplexe Invarianten.

Backpressure

Mechanismus, um Last zu begrenzen, wenn ein System nicht mehr aufnehmen kann.

Senior-Fokus:
Verhindert unbounded queues, Thread-Explosion und Downstream-Überlastung.

CompletableFuture

Java-API für asynchrone Berechnungen und Komposition.

Senior-Fokus:
Executor, Fehlerbehandlung, Timeouts und Cancellation bewusst steuern.

Deadlock

Situation, in der Threads gegenseitig auf Ressourcen warten und nicht weiterkommen.

Senior-Fokus:
Lock-Reihenfolge, Timeout und Thread Dumps sind zentrale Diagnosemittel.

ExecutorService

Abstraktion zur Ausführung von Aufgaben in Threads.

Senior-Fokus:
Thread-Pools brauchen Grenzen, Namen, Metriken und Shutdown-Strategie.

Happens-Before

Regel des Java Memory Model für Sichtbarkeit und Ordnung.

Senior-Fokus:
Ohne happens-before können Threads veraltete oder unerwartete Werte sehen.

Race Condition

Fehler, der vom zeitlichen Ablauf mehrerer Threads abhängt.

Senior-Fokus:
Tests allein beweisen Race-Freiheit selten.
Design mit weniger shared mutable state ist besser.

Structured Concurrency

Ansatz, zusammengehörige nebenläufige Aufgaben als strukturierte Einheit zu behandeln.

Senior-Fokus:
Verbessert Cancellation, Fehlerbehandlung und Lesbarkeit.

Virtual Thread

Leichtgewichtiger JVM-Thread für skalierbare blockierende IO.

Senior-Fokus:
Virtual Threads ersetzen keine Datenbankverbindungen,
Downstream-Kapazität oder Backpressure.

volatile

Keyword für Sichtbarkeit und Ordnungsregeln bei Variablenzugriffen.

Senior-Fokus:
volatile macht zusammengesetzte Operationen nicht atomar.

6. Spring und Spring Boot

Actuator

Spring-Boot-Modul für Health, Metrics, Info und Betriebsendpunkte.

Senior-Fokus:
Wichtig für OpenShift-Probes und Observability.
Muss abgesichert werden.

ApplicationContext

Spring Container, der Beans verwaltet.

Senior-Fokus:
In Tests kann Context-Breite Laufzeit stark beeinflussen.

Auto-Configuration

Spring-Boot-Mechanismus, der Beans abhängig von Classpath, Properties und Conditions automatisch konfiguriert.

Senior-Fokus:
Produktiv, aber nur sicher, wenn man Conditions und Back-off versteht.

Bean

Ein von Spring verwaltetes Objekt.

Senior-Fokus:
Nicht jedes Objekt muss eine Bean sein.
Domainobjekte bleiben oft besser frameworkfrei.

BeanPostProcessor

Lifecycle-Hook, mit dem Spring Beans vor oder nach Initialisierung verändern kann.

Senior-Fokus:
Viele Proxy- und Infrastrukturfeatures basieren darauf.

Configuration Properties

Typsichere Bindung externer Konfiguration an Java-Objekte.

Senior-Fokus:
Mit Validierung nutzen, damit Konfigurationsfehler beim Start sichtbar werden.

Dependency Injection

Abhängigkeiten werden von außen bereitgestellt.

Senior-Fokus:
Constructor Injection macht Abhängigkeiten explizit und testbar.

Profile

Spring-Mechanismus zur Aktivierung kontextabhängiger Beans oder Konfiguration.

Senior-Fokus:
Profile nicht als chaotischen Ersatz für saubere Konfigurationsstrategie missbrauchen.

Proxy

Objekt, das Aufrufe abfängt und Zusatzlogik ausführt.

Beispiele:
- Transaktionen
- Security
- AOP
- Caching

Self Invocation

Direkter Methodenaufruf innerhalb derselben Klasse, der Spring-Proxies umgeht.

Senior-Fokus:
Typische Ursache für unerwartet fehlende Transaktionen oder Security.

Starter

Spring-Boot-Abhängigkeit, die typische Libraries und Auto-Configuration bündelt.

Senior-Fokus:
Starter beschleunigen Setup,
aber Classpath bestimmt Verhalten.

7. REST und Web

Controller

Spring-MVC-Komponente, die HTTP-Requests behandelt.

Senior-Fokus:
Controller sollten API-Vertrag orchestrieren,
nicht komplexe Fachlogik enthalten.

CORS

Cross-Origin Resource Sharing steuert Browserzugriffe von anderen Origins.

Senior-Fokus:
CORS ist Browser-Sicherheit, nicht Server-zu-Server-Auth.

CSRF

Cross-Site Request Forgery: Angriff, bei dem Browser ungewollt zustandsverändernde Requests senden.

Senior-Fokus:
Relevant bei Browser, Cookies und zustandsverändernden Requests.

DTO

Data Transfer Object für API- oder Integrationsgrenzen.

Senior-Fokus:
DTOs trennen externe Verträge vom internen Modell.

Idempotenz

Mehrfache Ausführung hat denselben Effekt wie einmalige Ausführung.

Senior-Fokus:
Kritisch für Retries, Payments, Messaging und Distributed Systems.

ProblemDetail

Strukturierte HTTP-Fehlerantwort.

Senior-Fokus:
Fehlerformate sind API-Verträge und sollten getestet werden.

REST

Architekturstil für ressourcenorientierte HTTP-Schnittstellen.

Senior-Fokus:
REST ist nicht nur JSON über HTTP.
HTTP-Semantik, Statuscodes, Idempotenz und Caching zählen.

Validation

Prüfung von Eingaben gegen Regeln und Constraints.

Senior-Fokus:
API-Validation und Domain-Invarianten unterscheiden.

8. Persistence

Dirty Checking

JPA erkennt Änderungen an managed Entities automatisch.

Senior-Fokus:
Bequem, aber kann unerwartete Updates und Performancekosten verursachen.

Entity

Objekt mit eigener Identität und Lebenszyklus.

Senior-Fokus:
Entity-Gleichheit ist schwieriger als Value-Object-Gleichheit.

EntityGraph

JPA-Mechanismus zur Definition, welche Beziehungen geladen werden sollen.

Senior-Fokus:
Hilft gegen N+1, wenn gezielt eingesetzt.

Fetch Join

JPQL-Technik, um Beziehungen in einem Query mitzuladen.

Senior-Fokus:
Nützlich gegen N+1,
aber bei Pagination und großen Graphen vorsichtig.

Flush

Synchronisiert Persistence Context mit der Datenbank.

Senior-Fokus:
In Tests wichtig, um echte DB-Constraints sichtbar zu machen.

Lazy Loading

Beziehungen werden erst bei Zugriff geladen.

Senior-Fokus:
Kann N+1 und LazyInitializationException verursachen.

Optimistic Locking

Konflikterkennung über Versionen.

Senior-Fokus:
Gut bei niedriger Konfliktrate.
Konflikte müssen fachlich behandelt werden.

Persistence Context

JPA-Kontext für managed Entities, Identity Map und Dirty Checking.

Senior-Fokus:
Kann Tests grün machen, obwohl Datenbankzustand nicht geprüft wurde.

Pessimistic Locking

Aktives Sperren von Datenbankzeilen.

Senior-Fokus:
Kann nötig sein, reduziert aber Durchsatz und erhöht Deadlock-Risiko.

Transaction Boundary

Grenze, innerhalb der Operationen atomar committen oder rollbacken.

Senior-Fokus:
Sollte fachliche Use Cases und Aggregate-Grenzen widerspiegeln.

9. Security

Authentication

Feststellung, wer ein Nutzer oder Service ist.

Senior-Fokus:
Authentifiziert bedeutet noch nicht autorisiert.

Authorization

Entscheidung, was ein Nutzer oder Service tun darf.

Senior-Fokus:
Rollen/Scopes reichen oft nicht für Objekt- und Tenant-Regeln.

BOLA

Broken Object Level Authorization: Zugriff auf fremde Ressourcen durch ID-Manipulation.

Senior-Fokus:
Mit falscher ID, falschem Owner und falschem Tenant testen.

JWT

JSON Web Token für übertragbare Claims.

Senior-Fokus:
Claim Mapping, Audience, Issuer, Expiration und Scopes testen.

Least Privilege

Minimal notwendige Rechte vergeben.

Senior-Fokus:
Gilt für Nutzer, Services, Datenbanken, CI/CD und Plattform.

Method Security

Absicherung von Methoden, zum Beispiel mit @PreAuthorize.

Senior-Fokus:
Über Spring-Proxies testen.
Policylogik nicht in SpEL verstecken.

OAuth2 Resource Server

Service, der Access Tokens validiert und geschützte Ressourcen bereitstellt.

Senior-Fokus:
Scopes und Claims müssen korrekt in Authorities übersetzt werden.

RBAC

Role-Based Access Control.

Senior-Fokus:
Gut für grobe Rechte.
Für feine fachliche Regeln oft mit Policies kombinieren.

Scope

Berechtigung im OAuth2-Kontext, etwa payment:create.

Senior-Fokus:
Scopes sind technische Berechtigungen,
ersetzen aber keine fachliche Objektprüfung.

Tenant Isolation

Trennung von Daten und Zugriffen zwischen Mandanten.

Senior-Fokus:
Tenant-Isolation ist Sicherheitsgrenze,
nicht nur Filterbedingung.

10. Architektur

ADR

Architecture Decision Record.

Nutzen:
Dokumentiert Kontext, Entscheidung, Alternativen und Konsequenzen.

Aggregate

DDD-Konsistenzgrenze mit Aggregate Root.

Senior-Fokus:
Aggregate sollten Invarianten schützen und nicht zu groß werden.

Anti-Corruption Layer

Schicht, die ein eigenes Modell vor fremden Modellen schützt.

Senior-Fokus:
Wichtig bei Legacy-Systemen, externen APIs und Kontextintegration.

Bounded Context

Fachliche Grenze, innerhalb der ein Modell eindeutig gilt.

Senior-Fokus:
Grundlage für DDD, Microservice-Schnitt und Teamgrenzen.

Coupling

Grad der Abhängigkeit zwischen Modulen.

Senior-Fokus:
Nicht jede Kopplung ist schlecht.
Unbewusste Kopplung ist gefährlich.

DDD

Domain-Driven Design.

Senior-Fokus:
DDD beginnt mit Sprache, Grenzen und fachlicher Komplexität,
nicht mit Repository-Klassen.

Distributed Monolith

Mehrere Services, die trotz Verteilung eng gemeinsam geändert und deployed werden müssen.

Senior-Fokus:
Häufig durch synchrone Ketten, gemeinsame Datenbanken und fehlende Ownership.

Microservice

Unabhängig deploybare fachliche Serviceeinheit mit eigener Verantwortung.

Senior-Fokus:
Microservices brauchen Teamautonomie, Datenownership und Observability.

Modularer Monolith

Eine monolithische Anwendung mit klaren internen Modulgrenzen.

Senior-Fokus:
Oft bessere Ausgangsarchitektur als voreilige Microservices.

SOLID

Fünf Designprinzipien für wartbare objektorientierte Software.

Senior-Fokus:
SOLID ist Heuristik, kein Dogma.

11. Kafka und Messaging

At-Least-Once

Nachrichten werden mindestens einmal zugestellt, Duplikate sind möglich.

Senior-Fokus:
Consumer müssen idempotent sein.

Consumer Group

Gruppe von Kafka-Consumern, die Partitionen gemeinsam verarbeitet.

Senior-Fokus:
Skalierung ist durch Partitionen begrenzt.

Consumer Lag

Abstand zwischen produziertem und verarbeitetem Offset.

Senior-Fokus:
Lag im Kontext von Last, Partitionen und Verarbeitungslatenz bewerten.

Dead Letter Topic

Topic für Nachrichten, die nicht verarbeitet werden konnten.

Senior-Fokus:
DLT braucht Monitoring, Ownership und Reprocessing-Prozess.

Event

Fakt über etwas, das in der Vergangenheit passiert ist.

Senior-Fokus:
Events sind Verträge und sollten nicht wie interne DTOs behandelt werden.

Eventual Consistency

Konsistenz stellt sich verzögert ein.

Senior-Fokus:
Benutzererlebnis, Monitoring und Reconciliation müssen dazu passen.

Kafka Partition

Geordneter Teil eines Topics.

Senior-Fokus:
Ordering gilt nur innerhalb einer Partition.

Offset

Position einer Nachricht innerhalb einer Partition.

Senior-Fokus:
Offset-Commit beeinflusst Wiederverarbeitung und Duplikate.

Outbox Pattern

Speicherung von Zustandsänderung und Eventabsicht in einer Datenbanktransaktion.

Senior-Fokus:
Löst Dual-Write-Problem, erzeugt aber Publisher- und Idempotenzanforderungen.

Poison Message

Nachricht, die dauerhaft nicht verarbeitet werden kann.

Senior-Fokus:
Nicht endlos retryen.
Klassifizieren, DLT und Reprocessing definieren.

12. Plattform und OpenShift

ConfigMap

Nicht-sensitive Konfiguration für Pods.

Senior-Fokus:
Änderungen brauchen Rollout- und Validierungsstrategie.

GitOps

Git als deklarative Quelle der Wahrheit für Deployments und Infrastruktur.

Senior-Fokus:
Bietet Audit, Reproduzierbarkeit und Drift-Erkennung.

Liveness Probe

Prüft, ob ein Container neu gestartet werden soll.

Senior-Fokus:
Nicht zu aggressiv konfigurieren,
sonst entstehen Restart-Schleifen.

Operator

Controller, der Betrieb einer Anwendung oder Plattformkomponente automatisiert.

Senior-Fokus:
Operatoren kodieren Day-2-Betriebswissen.

Readiness Probe

Prüft, ob ein Pod Traffic empfangen darf.

Senior-Fokus:
Zentral für Rolling Deployments und störungsfreie Releases.

Resource Limits

Obergrenzen für CPU und Memory eines Containers.

Senior-Fokus:
Beeinflussen JVM, GC, CPU-Throttling und OOM-Verhalten.

Route

OpenShift-Objekt zur externen Exposition eines Services.

Senior-Fokus:
TLS, Timeouts und Security bewusst konfigurieren.

Secret

Kubernetes-/OpenShift-Objekt für sensible Daten.

Senior-Fokus:
Secret Management umfasst Zugriff, Rotation, Audit und sichere Nutzung.

SecurityContextConstraints

OpenShift-Sicherheitsregeln für Pod-Ausführung.

Senior-Fokus:
Image, ServiceAccount, RBAC und SCC müssen zusammenpassen.

Startup Probe

Prüft, ob eine Anwendung initial erfolgreich gestartet ist.

Senior-Fokus:
Wichtig für langsame JVM-Starts, Migrationen und Warmup.

13. Testing

Contract Test

Testet Consumer-Provider-Kompatibilität.

Senior-Fokus:
Ideal gegen Breaking Changes.
Braucht Versionierung und Provider-Verifikation.

E2E Test

End-to-End-Test über einen vollständigen Systempfad.

Senior-Fokus:
Wenige kritische Journeys.
Nicht für jede Kompatibilitätsprüfung.

Fake

Vereinfachte, aber funktionierende Testimplementierung.

Senior-Fokus:
Gut für Logiktests,
aber nicht für echte Infrastruktur-Risiken.

Flaky Test

Nichtdeterministischer Test, der ohne Codeänderung mal besteht und mal fehlschlägt.

Senior-Fokus:
Flakiness ist Qualitätsbug, kein normales CI-Rauschen.

Integrationstest

Testet Zusammenspiel mehrerer Komponenten oder echter Infrastruktur.

Senior-Fokus:
Dort einsetzen, wo Integration das Risiko ist.

Mock

Test Double, das Interaktionen prüfen kann.

Senior-Fokus:
Nicht jede Abhängigkeit mocken.
Mocks können Implementierungskopplung erzeugen.

Mutation Testing

Verändert Produktionscode künstlich, um Testaussagekraft zu prüfen.

Senior-Fokus:
Stärkeres Signal als reine Line Coverage.

Slice Test

Fokussierter Framework-Test, etwa MVC oder JPA.

Senior-Fokus:
Guter Kompromiss aus Realismus und Laufzeit.

Stub

Test Double, das vorbereitete Antworten liefert.

Senior-Fokus:
Gut für kontrollierte Szenarien,
aber nicht für Verhalten echter Systeme.

Testcontainers

Bibliothek zum Starten echter Infrastruktur als Container im Test.

Senior-Fokus:
Dort nutzen, wo Mocks oder In-Memory-Systeme lügen würden.

14. Performance Testing

Load Test

Test unter erwarteter oder definierter Last.

Senior-Fokus:
Lastmodell, Datenmodell und Infrastrukturmodell müssen realistisch sein.

p95

95 Prozent der Requests sind schneller oder gleich diesem Wert.

Senior-Fokus:
Besser als Durchschnitt für Nutzererfahrung.

p99

99 Prozent der Requests sind schneller oder gleich diesem Wert.

Senior-Fokus:
Zeigt Tail-Latency und seltene, oft kritische Ausreißer.

Performance Regression

Verschlechterung von Latenz, Durchsatz oder Ressourcenverbrauch gegenüber einer Baseline.

Senior-Fokus:
Braucht Baselines, Budgets und CI/CD-Gates.

Soak Test

Langzeittest unter Last.

Senior-Fokus:
Findet Memory Leaks, Resource Leaks und langsame Degradation.

Spike Test

Test mit plötzlichem Lastanstieg.

Senior-Fokus:
Prüft Autoscaling, Pools, Queues und Degradationsverhalten.

Stress Test

Test über erwartete Belastungsgrenze hinaus.

Senior-Fokus:
Findet Breaking Points und kontrollierte Überlastreaktion.

Tail-Latency

Latenz der langsameren Requests am Ende der Verteilung.

Senior-Fokus:
Oft entscheidender für echte Nutzererfahrung als Durchschnitt.

15. Leadership und Governance

Architecture Governance

Leitplanken für Architekturentscheidungen, Qualität und Konsistenz über Teams hinweg.

Senior-Fokus:
Gute Governance automatisiert gute Entscheidungen,
statt Teams mit Bürokratie zu blockieren.

Blameless Postmortem

Nachbesprechung eines Incidents ohne Schuldzuweisung.

Senior-Fokus:
Ziel ist systemisches Lernen, nicht individuelle Schuld.

Build vs Buy

Entscheidung, ob eine Lösung selbst gebaut oder eingekauft/genutzt wird.

Senior-Fokus:
Total Cost of Ownership, Betrieb, Security und Lock-in einbeziehen.

Fitness Function

Automatisierte oder messbare Architekturregel.

Beispiele:
- keine verbotenen Modulabhängigkeiten
- p95 unter Budget
- Contract kompatibel
- keine kritischen CVEs

Golden Path

Empfohlener, unterstützter Standardweg für Teams.

Senior-Fokus:
Besser als reine Wiki-Regeln,
wenn durch Templates und Tooling unterstützt.

Technical Debt

Bewusste oder unbewusste technische Entscheidung, die spätere Änderungskosten erhöht.

Senior-Fokus:
Nach Risiko, Änderungsfrequenz und Business-Wirkung priorisieren.

Testing Governance

Standards, Ownership, CI/CD-Gates und Metriken für Testqualität.

Senior-Fokus:
Tests gehören Feature-Teams.
Plattform liefert Enablement und Standards.

Trade-off

Bewusster Zielkonflikt zwischen Alternativen.

Senior-Fokus:
Senior-Antworten erklären nicht nur die Lösung,
sondern auch Kosten und verworfene Alternativen.

16. Abschließende Glossar-Merksätze
Begriffe sind im Senior-Interview nur der Einstieg.
Entscheidend ist, ob du erklären kannst,
warum ein Konzept existiert,
wann es passt,
wann es gefährlich wird
und wie man es produktionsreif einsetzt.
Ein Senior kennt nicht nur Definitionen,
sondern die Kräfte dahinter:
Kopplung, Kohäsion, Konsistenz,
Latenz, Sicherheit, Betrieb,
Teamgrenzen und Änderbarkeit.

Erweiterung integriert: Zusätzliche Begriffe für Senior-Java-, Lead-Developer- und Architekturinterviews sind hier direkt in Anhang H enthalten.

Der Fokus der integrierten Erweiterung liegt auf Begriffen, die besonders relevant sind für:

- Java 21 und moderne Sprachmodellierung
- JVM-Diagnose
- Spring Boot Internals
- Persistence und Datenkonsistenz
- Security
- Messaging
- OpenShift
- Testing
- Architektur und Governance

17. Java 21 und moderne Sprachfeatures

Compact Constructor

Ein kompakter Konstruktor in einem Record erlaubt Validierung und Normalisierung der Record-Komponenten.

Senior-Fokus:
Invarianten gehören direkt an die Grenze des Value Objects.
Records ohne Validierung können ungültige fachliche Zustände erlauben.

Canonical Constructor

Der kanonische Konstruktor eines Records nimmt exakt die Record-Komponenten entgegen.

Senior-Fokus:
Wichtig, wenn Record-Komponenten normalisiert oder validiert werden sollen.

Switch Expression

Eine Switch Expression liefert einen Wert zurück und ist oft kompakter und sicherer als ein klassisches Switch Statement.

Senior-Fokus:
Besonders nützlich bei geschlossenen Status- oder Typmodellen.
Mit sealed Types kann Exhaustiveness geprüft werden.

Exhaustiveness

Exhaustiveness bedeutet, dass alle möglichen Varianten eines Typs oder Werts behandelt werden.

Senior-Fokus:
Hilft, fachliche Zustandsmodelle sicherer zu machen.
Neue Varianten erzeugen Compilerfeedback statt versteckter Laufzeitfehler.

Text Block

Ein Text Block ist eine mehrzeilige String-Syntax.

Senior-Fokus:
Nützlich für SQL, JSON, Testdaten und Templates.
Trotzdem sollten große Templates nicht unkontrolliert im Code wachsen.

Primitive Obsession

Primitive Obsession beschreibt die übermäßige Nutzung primitiver Typen oder Strings für fachliche Konzepte.

Beispiel:
String customerId statt CustomerId.
BigDecimal amount und String currency statt Money.
Senior-Fokus:
Value Objects machen Regeln sichtbar und verhindern ungültige Kombinationen.

Null Object Pattern

Das Null Object Pattern ersetzt null durch ein Objekt mit neutralem Verhalten.

Senior-Fokus:
Kann Null-Prüfungen reduzieren.
Nicht verwenden, wenn Abwesenheit fachlich ausdrücklich sichtbar sein muss.

Temporal Coupling

Temporal Coupling bedeutet, dass Methoden in einer bestimmten Reihenfolge aufgerufen werden müssen, damit ein Objekt korrekt funktioniert.

Senior-Fokus:
Gefährlich, weil falsche Reihenfolge oft erst zur Laufzeit auffällt.
Builder, Konstruktoren und klare Zustandsmodelle helfen.

Invariant

Eine Invariante ist eine Bedingung, die für ein Objekt oder System immer gelten muss.

Senior-Fokus:
Senior-Code schützt Invarianten nahe am Modell,
nicht nur über zufällige UI- oder Controller-Validierung.

Encapsulation

Encapsulation kapselt Zustand und Verhalten hinter klaren Methoden.

Senior-Fokus:
Nicht jedes Feld braucht Getter.
Gute Kapselung schützt Invarianten und reduziert Kopplung.

18. JVM-Diagnose und Laufzeitverhalten

Thread Dump

Ein Thread Dump zeigt den Zustand aller Threads einer JVM.

Senior-Fokus:
Wichtig bei Deadlocks, blockierenden Calls, Thread-Pool-Sättigung und Latenzspitzen.

Heap Dump

Ein Heap Dump zeigt Objekte und Referenzen im Heap zu einem Zeitpunkt.

Senior-Fokus:
Nützlich bei Memory Leaks, unerwarteter Retention und großen Objektgraphen.

Dominator Tree

Ein Dominator Tree zeigt, welche Objekte andere Objekte im Speicher halten.

Senior-Fokus:
Hilft, echte Retention-Ursachen statt nur große Einzelobjekte zu finden.

Retained Size

Retained Size beschreibt, wie viel Speicher freigegeben würde, wenn ein Objekt und alle nur über dieses Objekt erreichbaren Objekte entfernt würden.

Senior-Fokus:
Wichtiger als Shallow Size bei Memory-Leak-Analyse.

Shallow Size

Shallow Size beschreibt den direkten Speicherverbrauch eines Objekts ohne referenzierte Objekte.

Senior-Fokus:
Kann irreführend sein, wenn ein kleines Objekt einen großen Graphen hält.

Native Memory

Native Memory ist Speicher außerhalb des Java-Heaps.

Beispiele:
- Metaspace
- Thread Stacks
- Direct Buffers
- JVM intern
- native Libraries
Senior-Fokus:
Container-OOM kann durch Native Memory entstehen,
auch wenn der Heap nicht voll ist.

Direct Buffer

Ein Direct Buffer nutzt nativen Speicher statt Java-Heap.

Senior-Fokus:
Relevant bei Netty, NIO, Datenbanktreibern und HTTP-Clients.
Leaks zeigen sich nicht immer direkt im Heap.

GC Pause

Eine GC Pause ist eine Zeitspanne, in der Anwendungsthreads ganz oder teilweise für Garbage Collection angehalten werden.

Senior-Fokus:
Auch kurze Pausen können p99 beeinflussen.
Pausen müssen im Kontext von SLOs bewertet werden.

Stop-the-World

Stop-the-World bedeutet, dass Anwendungsthreads während bestimmter JVM-Operationen angehalten werden.

Senior-Fokus:
Nicht jede GC ist komplett Stop-the-World,
aber STW-Phasen sind für Latenz kritisch.

Allocation Hotspot

Ein Allocation Hotspot ist ein Codebereich, der besonders viele Objekte erzeugt.

Senior-Fokus:
Oft wichtiger als reine CPU-Hotspots,
weil Allocation GC-Druck und Tail-Latency erzeugt.

Lock Contention

Lock Contention entsteht, wenn viele Threads um denselben Lock konkurrieren.

Senior-Fokus:
Kann CPU niedrig erscheinen lassen,
während Latenz wegen Warten steigt.

Thread Starvation

Thread Starvation bedeutet, dass Aufgaben nicht ausgeführt werden, weil keine Threads verfügbar sind.

Senior-Fokus:
Typisch bei zu kleinen Pools, blockierenden Calls oder fehlender Trennung von Workloads.

CPU Saturation

CPU Saturation bedeutet, dass CPU-Kapazität ausgeschöpft ist.

Senior-Fokus:
Bei Containern zusätzlich CPU-Limits und Throttling prüfen.
Durchschnitts-CPU kann Tail-Probleme verdecken.

19. Spring Boot Internals und Enterprise-Konfiguration

Conditional Bean

Eine Conditional Bean wird nur erstellt, wenn bestimmte Bedingungen erfüllt sind.

Beispiele:
- Classpath vorhanden
- Property gesetzt
- Bean fehlt
- bestimmtes Profil aktiv
Senior-Fokus:
Kernmechanismus von Auto-Configuration.
Wichtig für Debugging unerwarteter Beans.

Back-off

Back-off bedeutet, dass Spring Boot eine Auto-Configuration nicht aktiviert oder eine Bean nicht erstellt, wenn der Nutzer bereits eine eigene Bean definiert hat.

Senior-Fokus:
Ermöglicht kontrollierte Überschreibung von Defaults.

Bean Scope

Bean Scope definiert die Lebensdauer einer Bean.

Typisch:
- singleton
- prototype
- request
- session
Senior-Fokus:
Singleton ist Default.
Mutable State in Singleton Beans ist gefährlich.

ApplicationRunner

ApplicationRunner führt Logik nach dem Start des Spring ApplicationContext aus.

Senior-Fokus:
Nur für bewusstes Startup-Verhalten verwenden.
Keine langen blockierenden Initialisierungen ohne Probes und Timeout.

CommandLineRunner

CommandLineRunner ist ähnlich wie ApplicationRunner, arbeitet aber direkter mit Kommandozeilenargumenten.

Senior-Fokus:
Geeignet für einfache Startup-Tasks,
aber riskant bei Migrationen oder externen Abhängigkeiten.

Environment

Das Spring Environment enthält Properties, Profile und Property Sources.

Senior-Fokus:
Konfigurationsherkunft muss nachvollziehbar sein,
besonders bei OpenShift, Secrets und mehreren Profilen.

Property Source

Eine Property Source ist eine Quelle für Konfiguration, etwa Datei, Environment Variable oder ConfigMap.

Senior-Fokus:
Konfigurationsreihenfolge kann überraschendes Verhalten erzeugen.

Bean Validation Integration

Spring integriert Bean Validation für Controller-Eingaben, Configuration Properties und Methodenparameter.

Senior-Fokus:
Validation an API-Grenzen ersetzt keine Domain-Invarianten.

RestClient

RestClient ist eine synchrone HTTP-Client-Abstraktion im Spring-Ökosystem.

Senior-Fokus:
Timeouts, Fehlerklassifikation, Observability und Tests mit Mockservern sind entscheidend.

WebClient

WebClient ist ein reaktiver HTTP-Client.

Senior-Fokus:
Nur sinnvoll, wenn reaktives Modell verstanden wird.
Blockieren im reaktiven Flow zerstört Vorteile.

20. REST, API-Design und Integration

Backward Compatibility

Backward Compatibility bedeutet, dass neue Versionen weiterhin mit bestehenden Clients funktionieren.

Senior-Fokus:
Felder hinzufügen ist oft kompatibel.
Felder entfernen, umbenennen oder Semantik ändern ist riskant.

Breaking Change

Ein Breaking Change macht bestehende Consumer inkompatibel.

Beispiele:
- Feld entfernen
- Statuscode ändern
- Pflichtfeld hinzufügen
- Semantik ändern
Senior-Fokus:
Breaking Changes brauchen Versionierung, Migration oder Deprecation.

Deprecation

Deprecation markiert eine API oder Funktion als veraltet, bevor sie entfernt wird.

Senior-Fokus:
Deprecation braucht Kommunikation, Monitoring der Nutzung und klaren Abschaltplan.

Pagination

Pagination teilt große Ergebnismengen in Seiten.

Senior-Fokus:
Offset-Pagination ist einfach,
aber bei großen Datenmengen und instabiler Sortierung problematisch.

Cursor Pagination

Cursor Pagination nutzt einen stabilen Cursor statt Offset.

Senior-Fokus:
Besser für große Datenmengen und Live-Daten,
aber API-Vertrag komplexer.

Rate Limiting

Rate Limiting begrenzt die Anzahl von Requests pro Zeitfenster.

Senior-Fokus:
Schützt Systeme vor Missbrauch, Fehlerkaskaden und unfairer Ressourcennutzung.

Retry-After

Retry-After signalisiert Clients, wann sie eine Anfrage erneut versuchen sollen.

Senior-Fokus:
Besonders relevant bei 429 und temporären Überlastsituationen.

Correlation ID

Eine Correlation ID verbindet Logs, Metriken und Traces über Systemgrenzen hinweg.

Senior-Fokus:
Muss an Downstreams weitergegeben und in Logs sichtbar sein.

Request Timeout

Ein Request Timeout begrenzt, wie lange ein System auf eine Antwort wartet.

Senior-Fokus:
Ohne Timeouts entstehen hängende Ressourcen, Thread-Starvation und Kaskadenfehler.

Retry Budget

Ein Retry Budget begrenzt, wie viele Wiederholungen ein System erzeugen darf.

Senior-Fokus:
Retries ohne Budget können Downstreams in Störungen weiter überlasten.

21. Persistence, Datenmodellierung und Migrationen

Unique Constraint

Ein Unique Constraint verhindert doppelte Werte auf Datenbankebene.

Senior-Fokus:
Wichtig für fachliche Eindeutigkeit, Idempotenz und Race-Condition-Schutz.

Foreign Key

Ein Foreign Key sichert referenzielle Integrität zwischen Tabellen.

Senior-Fokus:
Nicht leichtfertig entfernen.
Aber Service-übergreifende Foreign Keys passen selten zu Microservices.

Index Selectivity

Selectivity beschreibt, wie gut ein Index Daten einschränkt.

Senior-Fokus:
Ein Index auf ein Feld mit wenigen Werten ist oft weniger hilfreich als erwartet.

Covering Index

Ein Covering Index enthält alle Spalten, die ein Query braucht.

Senior-Fokus:
Kann Reads stark beschleunigen,
erhöht aber Speicher- und Schreibkosten.

Query Plan

Ein Query Plan zeigt, wie die Datenbank einen Query ausführt.

Senior-Fokus:
Performanceentscheidungen sollten auf Plänen und Messdaten basieren,
nicht nur auf Vermutungen.

Connection Pool

Ein Connection Pool verwaltet wiederverwendbare Datenbankverbindungen.

Senior-Fokus:
Zu klein erzeugt Wartezeiten.
Zu groß überlastet Datenbank.
Virtual Threads erhöhen DB-Kapazität nicht.

Flyway

Flyway ist ein Tool für versionierte Datenbankmigrationen.

Senior-Fokus:
Migrationen müssen reproduzierbar, reviewbar und rollback-/forward-kompatibel geplant sein.

Liquibase

Liquibase ist ein Tool für strukturierte Datenbankänderungen mit Changesets.

Senior-Fokus:
Gut für kontrollierte Schema-Evolution,
aber Changeset-Disziplin ist entscheidend.

Expand-Contract Migration

Expand-Contract ist ein Migrationsmuster für Zero-Downtime-Deployments.

Ablauf:
1. Neues Schema kompatibel hinzufügen.
2. Anwendung schreibt/liest kompatibel.
3. Alte Struktur nach Migration entfernen.
Senior-Fokus:
Unverzichtbar bei Rolling Deployments und mehreren App-Versionen.

Read Model

Ein Read Model ist eine leseoptimierte Projektion von Daten.

Senior-Fokus:
Gut für Suche, Reporting und API-Listen.
Source of Truth bleibt klar getrennt.

22. Security Vertiefung

CSRF Token

Ein CSRF Token schützt zustandsverändernde Browser-Requests vor Cross-Site Request Forgery.

Senior-Fokus:
Relevant bei Cookie-basierter Authentifizierung.
Bei reinen Bearer-Token APIs anders bewerten.

CORS Preflight

Ein Preflight ist eine Browser-OPTIONS-Anfrage vor bestimmten Cross-Origin-Requests.

Senior-Fokus:
CORS-Fehler sind oft Browser-Policy-Probleme,
nicht Authentifizierungsprobleme.

Token Expiration

Token Expiration begrenzt die Lebensdauer eines Tokens.

Senior-Fokus:
Kurze Lebensdauer reduziert Risiko,
braucht aber Refresh- oder Reauth-Konzept.

Refresh Token

Ein Refresh Token dient dazu, neue Access Tokens zu erhalten.

Senior-Fokus:
Sehr sensibel.
Braucht sichere Speicherung, Rotation und Revocation.

Secret Rotation

Secret Rotation bedeutet, Secrets regelmäßig oder ereignisbasiert zu erneuern.

Senior-Fokus:
Anwendungen müssen Rotation ohne unnötige Downtime unterstützen.

Key Rotation

Key Rotation erneuert kryptografische Schlüssel.

Senior-Fokus:
JWT-Key-Rotation muss mit `kid`, JWKS und Cache-Verhalten abgestimmt sein.

JWKS

JWKS ist ein Format zur Veröffentlichung öffentlicher Schlüssel für Tokenvalidierung.

Senior-Fokus:
Resource Server müssen Key-Rotation und Cache-Failure-Fälle berücksichtigen.

mTLS

Mutual TLS authentifiziert beide Seiten einer TLS-Verbindung.

Senior-Fokus:
Nützlich für Service-to-Service-Security,
aber Zertifikatsmanagement und Rotation sind betrieblich anspruchsvoll.

Security Regression Test

Ein Security Regression Test stellt sicher, dass ein behobener Sicherheitsfehler nicht wiederkehrt.

Senior-Fokus:
Jeder kritische Security-Fix sollte einen Test gegen die Fehlerklasse erzeugen.

Sensitive Data Exposure

Sensitive Data Exposure bedeutet unbeabsichtigte Offenlegung sensibler Daten.

Beispiele:
- Tokens in Logs
- Secrets in Exceptions
- personenbezogene Daten in Fehlerantworten
- Actuator-Details öffentlich

23. Kafka, Messaging und Event-Architektur Vertiefung

Event Contract

Ein Event Contract beschreibt Struktur und Semantik eines Events.

Senior-Fokus:
Events sind langfristige Integrationsverträge,
nicht interne Implementierungsdetails.

Schema Evolution

Schema Evolution beschreibt die kompatible Weiterentwicklung von Event- oder API-Schemas.

Senior-Fokus:
Neue optionale Felder sind meist einfacher als Umbenennen oder Entfernen.

Event Envelope

Ein Event Envelope kapselt Metadaten um Payload.

Typische Felder:
- eventId
- eventType
- occurredAt
- aggregateId
- correlationId
- schemaVersion

Reconciliation

Reconciliation gleicht Systeme nachträglich ab, wenn asynchrone Prozesse oder externe Provider unsicher sind.

Senior-Fokus:
Wichtig bei Payments, Messaging, Reporting und Eventual Consistency.

Rebalancing

Rebalancing verteilt Kafka-Partitionen innerhalb einer Consumer Group neu.

Senior-Fokus:
Kann Verarbeitung unterbrechen und Duplikate begünstigen.
Consumer müssen robust sein.

Commit Offset

Offset Commit speichert, bis wohin ein Consumer verarbeitet hat.

Senior-Fokus:
Zu frühes Committen kann Datenverlust erzeugen.
Zu spätes Committen kann Duplikate erzeugen.

Exactly-Once Semantics

Exactly-Once Semantics beschreibt Garantien gegen doppelte Verarbeitung innerhalb bestimmter Grenzen.

Senior-Fokus:
Nicht als magische Ende-zu-Ende-Garantie missverstehen.
Externe Side Effects brauchen weiterhin Idempotenz.

Compacted Topic

Ein compacted Topic bewahrt pro Key typischerweise den letzten Wert auf.

Senior-Fokus:
Gut für Zustandsverteilung und Konfiguration,
nicht für vollständige Ereignishistorie.

Tombstone Event

Ein Tombstone Event ist eine Nachricht mit Key und leerem Wert, häufig zur Löschung in compacted Topics.

Senior-Fokus:
Consumer müssen Löschsemantik bewusst behandeln.

Message Ordering

Message Ordering beschreibt die Reihenfolge der Verarbeitung von Nachrichten.

Senior-Fokus:
In Kafka nur innerhalb einer Partition garantiert.
Fachlicher Key ist entscheidend.

24. OpenShift, Kubernetes und Betrieb Vertiefung

Horizontal Pod Autoscaler

Der Horizontal Pod Autoscaler skaliert Pods basierend auf Metriken.

Senior-Fokus:
Autoscaling braucht sinnvolle Metriken, Ressourcenrequests und Startverhalten.

Vertical Pod Autoscaler

Der Vertical Pod Autoscaler empfiehlt oder setzt Ressourcenanpassungen für Pods.

Senior-Fokus:
Für JVM-Services mit Heap-Planung vorsichtig einsetzen.

Pod Disruption Budget

Ein Pod Disruption Budget begrenzt, wie viele Pods während freiwilliger Störungen gleichzeitig unavailable sein dürfen.

Senior-Fokus:
Wichtig für Hochverfügbarkeit während Wartung, Upgrades und Node-Drains.

Rolling Update

Ein Rolling Update ersetzt Pods schrittweise durch neue Versionen.

Senior-Fokus:
Braucht Readiness, kompatible Migrationen und kontrolliertes Rollback.

Blue-Green Deployment

Blue-Green Deployment betreibt zwei Umgebungen und schaltet Traffic um.

Senior-Fokus:
Schneller Rollback möglich,
aber Datenbankkompatibilität bleibt kritisch.

Canary Deployment

Canary Deployment rollt eine neue Version zunächst für kleinen Traffic-Anteil aus.

Senior-Fokus:
Braucht Metriken, automatische Bewertung und klare Abbruchkriterien.

ImageStream

Ein OpenShift-Objekt zur Verwaltung und Referenzierung von Container Images.

Senior-Fokus:
Erleichtert Build-/Deployment-Flows,
muss aber sauber versioniert werden.

BuildConfig

Eine OpenShift-Konfiguration für Builds.

Senior-Fokus:
Historisch wichtig in OpenShift.
In modernen Plattformen oft durch Pipelines/GitOps ergänzt.

ServiceAccount

Eine Identität für Pods und Automatisierungen im Cluster.

Senior-Fokus:
ServiceAccounts brauchen minimale RBAC-Rechte.

NetworkPolicy

NetworkPolicies beschränken Netzwerkverkehr zwischen Pods.

Senior-Fokus:
Wichtig für Zero-Trust-nahe Cluster-Segmentierung.

25. Observability und Incident Response

Metric

Eine Metric ist ein numerischer Messwert über Zeit.

Beispiele:
- HTTP request duration
- JVM memory used
- GC pause
- DB pool wait time

Log

Ein Log ist ein diskretes Ereignis oder eine Meldung aus einer Anwendung.

Senior-Fokus:
Logs brauchen Kontext, Correlation ID und dürfen keine Secrets enthalten.

Trace

Ein Trace zeigt den Weg eines Requests durch mehrere Services.

Senior-Fokus:
Besonders wertvoll bei Microservices, Fan-out und p99-Analyse.

Span

Ein Span ist ein einzelner Abschnitt innerhalb eines Traces.

Senior-Fokus:
Downstream-Calls, DB-Queries und Kafka-Flows sollten sinnvoll instrumentiert sein.

SLI

Service Level Indicator: konkrete Messgröße für Servicequalität.

Beispiel:
99 Prozent der Checkout-Requests unter 500 ms.

SLO

Service Level Objective: Zielwert für einen SLI.

Senior-Fokus:
SLOs verbinden Technik mit Nutzererwartung und Alerting.

Error Budget

Error Budget beschreibt, wie viel Unzuverlässigkeit innerhalb eines SLO toleriert wird.

Senior-Fokus:
Hilft, Feature-Speed und Stabilitätsarbeit zu balancieren.

Alert Fatigue

Alert Fatigue entsteht, wenn zu viele oder schlechte Alerts die Aufmerksamkeit erschöpfen.

Senior-Fokus:
Alerts müssen handlungsrelevant sein.
Nicht jede Metrik braucht einen Alarm.

Runbook

Ein Runbook beschreibt konkrete Schritte zur Diagnose oder Behebung eines Betriebsproblems.

Senior-Fokus:
Gute Runbooks reduzieren MTTR und machen Wissen teamfähig.

MTTR

Mean Time To Recovery beschreibt die durchschnittliche Zeit bis zur Wiederherstellung.

Senior-Fokus:
Wichtiger Betriebsindikator,
aber Durchschnittswerte sollten kritisch betrachtet werden.

26. Testing-Erweiterung

Given-When-Then

Struktur zur Beschreibung von Tests.

Given:
Ausgangslage

When:
Aktion

Then:
Erwartetes Verhalten
Senior-Fokus:
Erhöht Lesbarkeit und macht Verhalten sichtbar.

Assertion

Eine Assertion prüft eine Erwartung im Test.

Senior-Fokus:
Viele Zeilen Coverage ohne starke Assertions sind wertlos.

Fixture

Eine Fixture ist vorbereiteter Testzustand.

Senior-Fokus:
Globale Fixtures erzeugen oft versteckte Kopplung.
Builder sind häufig klarer.

Test Data Builder

Ein Test Data Builder erzeugt Testobjekte mit sinnvollen Defaults und gezielten Abweichungen.

Senior-Fokus:
Macht relevante Testdaten sichtbar und reduziert Setup-Rauschen.

Characterization Test

Ein Characterization Test dokumentiert aktuelles Verhalten von Legacy Code.

Senior-Fokus:
Sicherheitsnetz vor Refactoring,
auch wenn das aktuelle Verhalten nicht ideal ist.

Golden Master Test

Ein Golden Master Test vergleicht aktuelle Ausgabe mit einer bekannten Referenzausgabe.

Senior-Fokus:
Nützlich bei Legacy-Systemen und komplexen Transformationen,
aber anfällig für übergroße Snapshots.

Test Pyramid

Die Test Pyramid beschreibt viele schnelle Unit Tests, weniger Integrationstests und wenige E2E-Tests.

Senior-Fokus:
Als Denkmodell nützlich,
aber moderne Systeme brauchen zusätzlich Contract-, Security- und Performance-Tests.

Test Diamond

Der Test Diamond betont stärker Integration- und Contract-Tests, wenn Systeme viele Integrationsrisiken haben.

Senior-Fokus:
Nicht dogmatisch.
Testportfolio nach Risiko, Architektur und Feedbackzeit wählen.

Snapshot Test

Ein Snapshot Test vergleicht Ausgabe mit gespeicherter Referenz.

Senior-Fokus:
Gut für strukturierte Outputs,
aber schlecht, wenn Snapshots blind akzeptiert werden.

Test Smell

Ein Test Smell ist ein Hinweis auf schwache Testqualität.

Beispiele:
- Thread.sleep
- übermäßiges Mocking
- riesiges Setup
- unklare Assertions
- Tests abhängig von Reihenfolge

27. Architektur- und Governance-Erweiterung

Conway’s Law

Conway’s Law beschreibt, dass Systemarchitekturen oft die Kommunikationsstrukturen der Organisation widerspiegeln.

Senior-Fokus:
Servicegrenzen und Teamgrenzen sollten bewusst zusammen betrachtet werden.

Team Topologies

Team Topologies beschreibt Teamtypen und Interaktionsmodi für bessere Softwarelieferung.

Senior-Fokus:
Architektur ist auch Organisationsdesign.

Platform Engineering

Platform Engineering baut interne Plattformen, die Teams durch Self-Service und Golden Paths befähigen.

Senior-Fokus:
Ziel ist weniger Reibung für Produktteams,
nicht zentrale Kontrolle über alles.

Cognitive Load

Cognitive Load beschreibt die mentale Belastung, die ein Team zur Arbeit an einem System bewältigen muss.

Senior-Fokus:
Zu viel Tooling, Kopplung oder Komplexität reduziert Lieferfähigkeit.

Strangler Fig Pattern

Muster zur schrittweisen Ablösung eines Legacy-Systems.

Senior-Fokus:
Reduziert Big-Bang-Rewrite-Risiko durch inkrementelle Migration.

Modularisierung

Modularisierung trennt ein System in kohärente, klar abgegrenzte Teile.

Senior-Fokus:
Gute Module haben hohe Kohäsion und kontrollierte Abhängigkeiten.

Cohesion

Cohesion beschreibt, wie stark Verantwortlichkeiten innerhalb eines Moduls zusammengehören.

Senior-Fokus:
Hohe Kohäsion erleichtert Verständnis und Änderung.

Architecture Drift

Architecture Drift beschreibt die schleichende Abweichung von beabsichtigter Architektur.

Senior-Fokus:
Fitness Functions, Reviews und modulare Tests helfen gegen Drift.

Decision Log

Ein Decision Log sammelt wichtige Entscheidungen und deren Gründe.

Senior-Fokus:
Hilft neuen Teammitgliedern und verhindert Wiederholung alter Diskussionen.

Evolutionary Architecture

Evolutionary Architecture gestaltet Architektur so, dass sie kontrolliert weiterentwickelt werden kann.

Senior-Fokus:
Braucht Feedbackmechanismen wie Tests, Metriken und Fitness Functions.

28. Abschluss der Glossar-Erweiterung

Mit dieser Erweiterung deckt Anhang H nun zusätzlich Begriffe ab, die in fortgeschrittenen Senior-Interviews besonders häufig als Differenzierungsmerkmal dienen:

Java 21 Modellierung
JVM-Diagnose
Spring Boot Internals
API-Kompatibilität
Datenbankmigrationen
Security-Vertiefung
Kafka-Eventarchitektur
OpenShift-Betrieb
Observability
Testing-Governance
Architecture-Governance
Leadership-nahe Architekturbegriffe

Finaler Merksatz

Ein Glossar ist nicht zum Auswendiglernen da.

Es ist eine Landkarte:
Wer die Begriffe versteht,
kann technische Diskussionen präziser führen,
Risiken schneller benennen
und Entscheidungen seniorfähiger begründen.

⌂ Cockpit