Java-Backend-Interviews drehen sich um die Kernsemantik der Sprache, Collections, Nebenläufigkeit, Spring und dein Verständnis der JVM. Die folgenden Fragen werden am häufigsten gestellt, jeweils mit einer Musterantwort. Middle: tieferes Verständnis, Optimierung und reale Arbeitssituationen.
1
Wie funktioniert HashMap intern, und was hat sich in Java 8 geändert?
Antwort
Einträge liegen in einem Array von Buckets, deren Index sich aus einer Funktion des Key-Hashes ergibt; Kollisionen bilden im selben Bucket eine verkettete Liste. Java 8 brachte die Treeification: Sobald ein Bucket mehr als acht Einträge enthält, wird er zu einem Rot-Schwarz-Baum, sodass sich der Worst Case beim Lookup von O(n) auf O(log n) verbessert. Außerdem wird der Hash über h ^ (h >>> 16) verteilt, damit die höherwertigen Bits den niedrigwertigen Index beeinflussen.
2
Was garantiert volatile, und was nicht?
Antwort
Es garantiert Sichtbarkeit und Ordering: Ein Schreibvorgang wird sofort an andere Threads publiziert, und Lesezugriffe werden nicht in einem Register gecacht. Es garantiert keine Atomarität, weshalb ein volatile-Zähler trotzdem Inkremente verlieren kann, denn ++ ist Lesen-Ändern-Schreiben, und zwei Threads können denselben Wert lesen. volatile eignet sich für ein Flag, das viele Threads lesen, AtomicInteger oder synchronized dagegen, wenn die Operation Lesen und Schreiben kombiniert.
3
Erkläre @Transactional-Propagation. Wann ist REQUIRES_NEW wichtig?
Antwort
Propagation entscheidet, was passiert, wenn eine transaktionale Methode aus einer bestehenden Transaktion heraus aufgerufen wird. REQUIRED, der Default, tritt der vorhandenen Transaktion bei — ein Rollback an beliebiger Stelle rollt also alles zurück. REQUIRES_NEW pausiert die äußere Transaktion und startet eine eigene, was man für ein Audit-Log oder eine Benachrichtigung braucht, die einen Fehler des Aufrufers überleben muss. Der klassische Fehler ist zu erwarten, dass REQUIRED einen Fehler isoliert, obwohl es das nicht kann.
4
Du siehst das N+1-Problem in einem Hibernate-basierten Endpoint. Wie findest und behebst du es?
Antwort
Man findet es, indem man SQL-Logging aktiviert und die Queries pro Request zählt — das typische Muster ist ein einzelnes select gefolgt von Hunderten fast identischer. Es tritt auf, wenn man Parents lädt und anschließend bei jedem eine lazy Collection anfasst. Die Lösung ist JOIN FETCH in JPQL, ein Entity Graph, @BatchSize für gruppenweises Nachladen oder eine Projektion, die nur die von der Response benötigten Felder selektiert. Auf EAGER umzustellen ist keine Lösung, sondern verschiebt nur das Problem.
5
Was unterscheidet Stream von einer for-Schleife, und wann ist eine Schleife besser?
Antwort
Ein Stream beschreibt eine Pipeline lazy ausgeführter Operationen, die von einem terminalen Aufruf angestoßen wird, was Filtern und Mappen lesbar hält. Eine Schleife ist im Hot Path besser, wo die Abstraktion messbar Zeit kostet, bei Logik mit Seiteneffekten und dort, wo ein break mitten in der Schleife ausdrucksstärker wäre als ein takeWhile. Die eigentliche Falle sind parallele Streams — sie helfen nur bei großer CPU-lastiger Arbeit und schaden sonst meist eher.
6
Ein Service ist langsam, aber die CPU-Auslastung ist niedrig. Wo schaust du zuerst nach?
Antwort
Niedrige CPU-Last bei schlechtem Durchsatz deutet eher auf Warten als auf Rechnen hin: blockierendes I/O, ein erschöpfter Connection Pool, Lock-Contention oder ein externer Aufruf ohne Timeout. Man nimmt einen Thread Dump und schaut, worauf die Threads tatsächlich geparkt sind. Am häufigsten ist ein erschöpfter Connection Pool die Ursache, und meist lässt sich das auf eine Transaktion zurückführen, die über einen Netzwerkaufruf hinweg offen gehalten wird.
7
Was unterscheidet Spring-Boot-Auto-Configuration von einer normalen @Configuration-Klasse?
Antwort
Auto-Configuration ist bedingte Konfiguration, die ein Starter mitbringt: Klassen mit @ConditionalOnClass oder @ConditionalOnMissingBean, die nur greifen, wenn eine Abhängigkeit im Classpath liegt und man die Bean nicht selbst definiert hat. Genau der letzte Teil macht sie zugleich magisch und vorhersagbar: Die eigene Bean gewinnt immer, und so überschreibt man jeden Default.
8
Was unterscheiden `wait()`, `sleep()` und `join()`?
Antwort
sleep pausiert den aktuellen Thread und behält dabei jedes gehaltene Lock — womit man sich einen Deadlock einhandeln kann, der harmlos aussieht. wait gibt den Monitor frei und parkt den Thread bis notify, und genau deshalb darf man es nur innerhalb eines synchronized-Blocks aufrufen. join wartet, bis ein anderer Thread fertig ist. wait und notify gehören zu Object statt zu Thread, weil das Lock zum Objekt gehört.
9
Was macht `transient`, und wo beißt einen die Serialisierung?
Antwort
transient schließt ein Feld von der Serialisierung aus, sodass es bei der Deserialisierung mit dem Default-Wert seines Typs zurückkommt. Man nutzt es für alles Abgeleitete, alles sehr Große und alles Geheime — ein Passwort oder eine laufende Verbindung haben in einem Byte-Stream nichts verloren. Die größere Falle ist, dass Serialisierung die Form der Klasse in einem Format einfriert: Ändert man die Felder, lassen sich alte Daten nicht mehr lesen, sofern man serialVersionUID und Kompatibilität nicht vorab geplant hat — deshalb setzen die meisten Teams stattdessen auf JSON oder Protobuf.
🦎
Antworten lesen reicht nicht
Im echten Interview sprichst du unter Druck. Cam stellt genau diese Fragen, bewertet jede Antwort und zeigt dir genau, was du verbessern musst.
Middle Java Backend-Interview üben →Kostenlos · 3 Interviews im Monat