Thread lifecycle, synchronization, the Java Memory Model, locks, atomics, executors, CompletableFuture, concurrent collections, virtual threads and classic race-condition scenarios.
Core language and runtime fundamentals that interviewers use to check whether a candidate understands what the JVM actually does. Baseline is Java 17/21 with Java 8 differences called out.
Theory
Q1
What is the difference between JDK, JRE and JVM?
basic
The JVM executes bytecode, the JRE = JVM + core libraries, and the JDK = JRE + developer tools (javac, jar, jlink, jshell).
Since Java 11 there is no separate JRE download; use the JDK, or build a trimmed runtime with jlink.
⚠ Follow-up traps
Is the JVM platform independent? No. Bytecode is portable; each OS needs its own JVM implementation.
Is Java purely interpreted? No. HotSpot interprets first, then JIT-compiles hot methods (C1/C2).
#jvm#jdk
Q2
Is Java pass-by-value or pass-by-reference?
basic
Always pass-by-value. For objects, the value copied is the reference, so a method can mutate the object but cannot make the caller's variable point elsewhere.
static void change(StringBuilder sb, String s) { sb.append("!"); // visible to caller sb = new StringBuilder("new"); // not visible s = s + "?"; // not visible, Strings are immutable}
⚠ Follow-up traps
What does the caller see after change(sb, s) with sb="a", s="b"?sb is "a!", s is "b".
#methods#references
Q3
What are the main components of the JVM architecture?
basic
Class loader subsystem, runtime data areas, execution engine, and native interface.
Class loader subsystem: loads, links and initializes classes.
Runtime data areas: heap, per-thread stacks, program counter registers, native method stacks, metaspace (method area).
Which areas are shared between threads? Heap and metaspace. Stacks and PC registers are per-thread.
Is the JVM specification the same as HotSpot? No. The spec defines behaviour; HotSpot, OpenJ9 and GraalVM are implementations.
#jvm#architecture
Q4
What are the phases of class loading?
intermediate
Loading, linking (verification, preparation, resolution) and initialization.
Loading: read the .class bytes and create a Class object.
Verification: check bytecode structure and type safety.
Preparation: allocate static fields and set them to default values (0, null, false).
Resolution: replace symbolic references with direct ones (may be lazy).
Initialization: run static initializers and assign static field initializer values, in textual order.
⚠ Follow-up traps
What value does a static field have during preparation? The type default, not the declared initializer; the initializer runs in the initialization phase.
When is a class initialized? On first new, static method call, non-constant static field access, or Class.forName with initialization. Compile-time constants (static final int X = 5) do not trigger it.
#class-loading#jvm
Q5
Explain the class loader hierarchy and the delegation model.
intermediate
Loaders delegate to their parent first; a child loads a class only if every ancestor failed. This prevents user code from replacing core classes like java.lang.String.
Bootstrap loader: core java.base classes, written in native code (getClassLoader() returns null).
Platform loader (extension loader before Java 9): other JDK modules.
Who breaks parent-first delegation? Servlet containers (webapp loaders look locally first), OSGi, and some plugin frameworks.
Can two loaders load the same class name? Yes, and the results are different runtime types; casting between them throws ClassCastException.
#class-loading#classloader
Q6
What are the JVM runtime memory areas?
basic
Heap (objects), thread stacks (frames with locals and operand stack), metaspace (class metadata), PC registers, native stacks, plus off-heap memory such as direct buffers and the code cache.
Heap and metaspace are shared; stacks are per-thread.
Total process memory = heap + metaspace + thread stacks + code cache + direct memory + GC overhead.
⚠ Follow-up traps
Does -Xmx cap the whole process? No, only the heap. Native memory is additional.
Where do local variables live? Primitives and references live in the stack frame; the objects they reference live on the heap.
#memory#jvm
Q7
How is the heap organized in generational collectors?
intermediate
Young generation (Eden + two survivor spaces) and old generation. New objects go to Eden; survivors of minor GCs are copied between survivor spaces and promoted to old after reaching a tenuring threshold.
Based on the weak generational hypothesis: most objects die young.
G1 uses equal-sized regions that play the eden/survivor/old roles dynamically.
⚠ Follow-up traps
Where do huge objects go? Large arrays can be allocated directly in old gen (G1: humongous regions).
Is there still a permanent generation? No, removed in Java 8.
#heap#gc
Q8
What does the thread stack contain, and what is a stack frame?
basic
Each method call pushes a frame holding local variables, an operand stack and a reference to the constant pool. The frame is popped on return or exception unwinding.
Size is set with -Xss (default typically 1 MB on 64-bit Linux).
Too-deep recursion causes StackOverflowError.
⚠ Follow-up traps
Does a larger -Xss always help? It only delays overflow and multiplies memory cost by thread count.
Are objects ever allocated on the stack? Not by language semantics; escape analysis may scalar-replace them, which is an optimization.
#stack#memory
Q9
What is Metaspace and how does it differ from PermGen?
intermediate
Metaspace stores class metadata in native memory and replaced PermGen in Java 8. It grows dynamically instead of having a fixed size inside the Java heap.
Limit it with -XX:MaxMetaspaceSize; unlimited by default.
Metadata is freed when the defining class loader becomes unreachable.
Interned strings and static field values moved to the heap (Java 7/8).
⚠ Follow-up traps
What causes OutOfMemoryError: Metaspace? Class loader leaks, excessive dynamic class generation (proxies, bytecode libraries), or a too-low max.
Is Metaspace part of the heap? No, it is native memory.
#metaspace#memory
Q10
List the primitive types, their sizes and default values.
basic
byte 8-bit, short 16, int 32, long 64, float 32, double 64, char 16 (unsigned), boolean (JVM-dependent size, one bit of information).
Fields default to 0, 0.0, '\u0000', false; references default to null.
Local variables have no default and must be definitely assigned before use.
⚠ Follow-up traps
Is char signed? No, it is the only unsigned integral type, range 0..65535.
Does int size vary by platform? No, sizes are fixed by the language spec.
#data-types#primitives
Q11
How do autoboxing and unboxing work?
basic
The compiler inserts Integer.valueOf(int) for boxing and intValue() for unboxing. No magic at runtime, just generated calls.
Integer, Long, Short, Byte, Character (0..127) cache small values; Boolean has two constants.
Float and Double have no cache.
⚠ Follow-up traps
What if the wrapper is null when unboxed?NullPointerException.
Why is boxing in tight loops slow? Each boxed value may allocate an object and adds GC pressure; prefer primitive streams or arrays.
#autoboxing#wrapper-classes
Q12
What is the difference between widening and narrowing conversions?
basic
Widening (int to long) is implicit and safe in range; narrowing (long to int) needs an explicit cast and can lose data via truncation of high bits.
int to float and long to double are widening but can lose precision.
Compound assignment (+=) includes an implicit narrowing cast.
⚠ Follow-up traps
Does byte b = 10; compile without a cast? Yes, constant expressions that fit the target type are allowed.
What is (byte) 200?-56, the low 8 bits reinterpreted as signed.
#casting#data-types
Q13
Why should you not use `float`/`double` for money?
basic
Binary floating point cannot represent most decimal fractions exactly (0.1), so errors accumulate. Use BigDecimal (constructed from a String) or integer minor units.
IEEE 754: 1 sign bit, exponent, mantissa.
BigDecimal needs an explicit RoundingMode for non-terminating divisions.
⚠ Follow-up traps
What does new BigDecimal(0.1) hold? The exact binary value of the double, 0.1000000000000000055...; use BigDecimal.valueOf(0.1) or a string.
Does BigDecimal.equals ignore scale? No. 2.0 and 2.00 are not equal; use compareTo.
#floating-point#bigdecimal
Q14
How does `char` relate to Unicode and integer arithmetic?
intermediate
char is a 16-bit UTF-16 code unit, and arithmetic on it promotes to int. Characters outside the BMP take two chars (a surrogate pair).
Use String.codePoints() or codePointAt for real characters.
'a' + 1 is the int 98; (char) ('a' + 1) is 'b'.
⚠ Follow-up traps
What does "😀".length() return?2, since it is two UTF-16 units.
Does c++ on a char compile? Yes, compound/increment operators include the narrowing cast.
#char#unicode
Q15
Why are Strings immutable and what is the string pool?
basic
String is final with a private final value array, which makes it thread-safe, cacheable by hash and safe as a map key. Literals are interned into a pool on the heap so identical literals share one instance.
Immutability enables the pool, hash caching and security (class names, paths).
Since Java 9 strings use compact Latin-1/UTF-16 byte arrays.
⚠ Follow-up traps
Is the string pool in PermGen? It was before Java 7; since Java 7 it is on the heap.
Does new String("a") create one object? It creates a new heap object; the literal "a" is in the pool separately.
#string#string-pool
Q16
Compare `String`, `StringBuilder` and `StringBuffer`.
basic
String is immutable; StringBuilder is a mutable, unsynchronized builder; StringBuffer is the synchronized legacy version.
Use StringBuilder for loops that append; + in a loop creates a builder per iteration.
Java 9+ compiles + using invokedynamic (StringConcatFactory).
⚠ Follow-up traps
Is StringBuffer useful for thread-safe building? Rarely; each call is synchronized but compound operations are not atomic.
Is + always slower than StringBuilder? No, a single expression is optimized equally; only loops hurt.
#string#stringbuilder
Q17
What is the difference between `==` and `equals`?
basic
== compares primitives by value and references by identity; equals compares logical content if overridden. The default Object.equals is identity.
⚠ Follow-up traps
Why compare strings with equals? Strings built at runtime are different objects even with equal content.
Is == between Integer and int identity? No, the Integer is unboxed and values compared.
#equality#object
Q18
What is short-circuit evaluation?
basic
&& and || skip evaluating the right operand when the left already decides the result. & and | on booleans always evaluate both sides.
if (s != null && s.length() > 3) { /* safe */ }
⚠ Follow-up traps
What does s != null & s.length() > 3 do for null s? Throws NullPointerException, since both operands evaluate.
Does ?: evaluate both branches? No, only the chosen one.
#operators#logical
Q19
How do pre- and post-increment differ?
basic
++i increments then yields the new value; i++ yields the old value then increments. As standalone statements they are equivalent.
⚠ Follow-up traps
Is i++ atomic? No, it is read-modify-write; even for volatile fields.
What does i = i++ leave in i? The original value, because the old value is assigned back after the increment.
#operators#increment
Q20
What happens on integer overflow in Java?
intermediate
It wraps silently in two's complement; no exception. Integer.MAX_VALUE + 1 equals Integer.MIN_VALUE.
Use Math.addExact, multiplyExact, toIntExact to throw ArithmeticException.
Floating point overflows to Infinity instead.
⚠ Follow-up traps
What is Math.abs(Integer.MIN_VALUE)?Integer.MIN_VALUE (still negative), since +2^31 does not fit.
What about integer division by zero?ArithmeticException; for doubles it gives Infinity or NaN.
#overflow#arithmetic
Q21
Explain `<<`, `>>` and `>>>`.
intermediate
<< shifts left filling zeros, >> is an arithmetic shift that preserves the sign bit, and >>> is a logical shift that fills with zeros.
The shift distance is masked: int uses the low 5 bits, long the low 6 bits.
x >> 1 equals floor division by 2; -7 >> 1 is -4, while -7 / 2 is -3.
⚠ Follow-up traps
What is 1 << 32 for an int?1, because the distance is 32 & 31 = 0.
What is -1 >>> 1 for an int?2147483647.
#bitwise#operators
Q22
How does the ternary operator determine its result type?
advanced
If both operands are numeric, binary numeric promotion applies; if one is a boxed type and the other a primitive, the result is unboxed. So true ? 1 : 2.0 yields 1.0.
Mixed Integer/int with a null operand can cause an unboxing NPE.
With two references the result is the lub (least upper bound) type.
⚠ Follow-up traps
What does Object o = true ? 1 : "x"; give? An Integer 1; there is no numeric promotion with a String.
What does flag ? (Integer) null : 0 do? NPE: the 0 makes it an int expression, so the null unboxes.
#operators#ternary#promotion
Q23
How does the classic `switch` statement behave, including fall-through?
basic
Execution jumps to the matching case and continues through subsequent cases until a break or the end. default can appear anywhere.
Supported selector types: byte, short, char, int, their wrappers, enum, String (Java 7), and patterns (Java 21).
⚠ Follow-up traps
Can a case label be a variable? Only a compile-time constant (final with constant initializer).
What if the selector is a null String? NPE in a classic switch.
#switch#control-flow
Q24
What are switch expressions and what do they improve?
intermediate
Since Java 14 switch can be an expression that produces a value, uses -> arms without fall-through, and must be exhaustive.
int days = switch (month) { case FEB -> 28; case APR, JUN, SEP, NOV -> 30; default -> { int d = 31; yield d; }};
Over an enum or sealed type exhaustiveness is checked without default.
Java 21 adds pattern matching and case null.
⚠ Follow-up traps
Can you mix -> and : labels? No, a single switch uses one form.
What happens to a missing enum constant at runtime? For switches without default over enums the compiler inserts a MatchException (Java 21) / IncompatibleClassChangeError path.
#switch#java14
Q25
How does the enhanced for loop work, and what are its limits?
basic
For arrays it compiles to an index loop; for Iterable it uses iterator(). You cannot see the index or remove elements safely.
⚠ Follow-up traps
Why ConcurrentModificationException when removing in for-each? The list's modCount changes and the iterator detects it; use Iterator.remove() or removeIf.
Does reassigning the loop variable change the collection? No, it is a local copy of the element reference.
#loops#iterator
Q26
How do labeled `break` and `continue` work?
intermediate
A label names an enclosing statement; break label exits it and continue label jumps to the next iteration of that labeled loop. It is structured, not a goto.
outer:for (int i = 0; i < 3; i++) for (int j = 0; j < 3; j++) { if (j == 1) continue outer; if (i == 2) break outer; }
⚠ Follow-up traps
Can you label any statement? Yes, including blocks, but continue targets only loops.
Is goto usable? It is a reserved but unused keyword.
#control-flow#labels
Q27
What does the `static` keyword mean for fields, methods, blocks and nested classes?
basic
static members belong to the class, not to instances; they exist once per class loader and are initialized when the class is initialized.
Static methods cannot access instance members directly and have no this.
A static nested class has no implicit reference to an outer instance, unlike an inner class.
⚠ Follow-up traps
Can static methods be overridden? No, they are hidden; resolution uses the declared type.
Is a static field unique per JVM? Per class loader, not per JVM.
#static#oop
Q28
What are static and instance initializer blocks, and when do they run?
intermediate
A static {} block runs once during class initialization; an instance {} block runs on every construction, before the constructor body (after super()).
Both run in textual order together with field initializers.
Instance initializers are rarely useful; constructors or field initializers are clearer.
⚠ Follow-up traps
Can a static initializer throw a checked exception? No, it must not complete abruptly with one; unchecked ones become ExceptionInInitializerError.
Can an instance initializer run without a constructor call? No; the compiler copies it into every constructor that calls super.
#initialization#static
Q29
What does `final` mean on variables, methods and classes?
basic
A final variable can be assigned once, a final method cannot be overridden, and a final class cannot be extended.
A blank final field must be assigned in every constructor or initializer.
Final fields get special safe-publication guarantees in the Java Memory Model.
⚠ Follow-up traps
Is a final parameter allowed? Yes, it just prevents reassignment inside the method.
Does final make a method faster? Not meaningfully; the JIT devirtualizes by class hierarchy analysis anyway.
#final#oop
Q30
Does `final` make an object immutable?
basic
No. final fixes the reference, not the object's state; final List<String> l can still have elements added.
True immutability: final class, private final fields, no setters, defensive copies in and out.
List.of, Map.of and records (shallowly) support this style.
⚠ Follow-up traps
Does Collections.unmodifiableList copy? No, it is a read-only view; changes to the backing list show through.
Is String deeply immutable? Its state cannot change, but reflection and Unsafe can bypass that; the language contract holds.
#final#immutability
Q31
What does the `transient` keyword do?
basic
transient excludes a field from default Java serialization; on deserialization it gets the type default (null, 0, false).
Used for derived data, caches, and secrets such as passwords.
Does not apply to static fields, which are never serialized anyway.
JSON libraries such as Jackson ignore transient unless configured; JPA treats it as non-persistent.
⚠ Follow-up traps
Are field initializers rerun on deserialization? No, constructors of Serializable classes are not called; restore state in readObject or readResolve.
Does the first non-serializable superclass get its constructor called? Yes, its no-arg constructor.
#transient#serialization
Q32
What does `volatile` guarantee?
intermediate
Visibility and ordering: a write to a volatile variable happens-before every subsequent read of it, and reads/writes are not reordered across it. It does not provide atomicity for compound actions.
Good for flags and safe publication of immutable objects.
long and double volatile reads/writes are atomic (non-volatile may tear on 32-bit JVMs per spec).
⚠ Follow-up traps
Is volatile int count; count++ thread-safe? No, use AtomicInteger or a lock.
Does volatile on an array make elements volatile? No, only the reference.
#volatile#jmm
Q33
What does "effectively final" mean and why do lambdas need it?
intermediate
A local variable is effectively final if it is never reassigned after initialization. Lambdas and inner classes capture the value, so captured locals must not change.
⚠ Follow-up traps
How do you mutate a captured counter? Use a field, an AtomicInteger, or a one-element array; the reference stays effectively final.
Do captured fields have the same limit? No, lambdas capture this and can mutate fields freely.
#lambda#final
Q34
What is the object initialization order?
intermediate
Class init: superclass static initializers, then subclass static, once. For each instance: superclass instance initializers and constructor body, then subclass instance initializers and constructor body.
Within one class, static fields/blocks and instance fields/blocks run in textual order.
Fields not yet initialized hold defaults, which is why calling an overridable method from a constructor is dangerous.
⚠ Follow-up traps
What does a subclass method see in a field when called from the super constructor? The default value (0/null), as the subclass field initializers have not run.
Are static blocks rerun for every new? No, only on first class initialization.
#initialization#constructors
Q35
How do constructors, `this(...)` and `super(...)` chain?
basic
Every constructor starts with this(...) or super(...), explicit or implicit (super()). The call must be the first statement (Java 22 preview relaxes this for code that does not reference this).
If a class declares any constructor, the compiler does not add the default one.
If the superclass lacks a no-arg constructor, the subclass must call another explicitly.
⚠ Follow-up traps
Can a constructor call both this() and super()? No, only one; this() delegates and eventually reaches super.
Can constructors be inherited or final? Neither; constructors are not members.
#constructors#oop
Q36
How does `var` (local variable type inference) work?
basic
var lets the compiler infer the type of a local variable from its initializer (Java 10). The type is still static and fixed; var is not dynamic typing.
Not allowed: fields, method parameters/returns, no initializer, null initializer, array initializer {1,2}.
var is a reserved type name, not a keyword; var var = 1; compiles.
⚠ Follow-up traps
What is the type of var list = new ArrayList<>();?ArrayList<Object>.
Does var hurt readability? It can when the initializer does not reveal the type; use it when the right side is obvious.
#var#java10
Q37
What are records and what does the compiler generate?
intermediate
A record is a transparent, shallowly immutable data carrier (Java 16). The compiler generates private final fields, a canonical constructor, accessors (name(), not getName()), equals, hashCode and toString.
record Point(int x, int y) { Point { // compact constructor if (x < 0) throw new IllegalArgumentException(); }}
Records are implicitly final, cannot extend classes, but can implement interfaces and have static members.
No instance fields beyond the components.
⚠ Follow-up traps
Is a record deeply immutable? No, a List component can still be mutated; copy defensively in the compact constructor.
Can records be JPA entities? No (final, no no-arg constructor); they work well for DTOs and projections.
#records#java16
Q38
What are text blocks?
basic
Multi-line string literals delimited by """ (Java 15). Incidental indentation is stripped based on the least-indented line (including the closing delimiter), and trailing spaces are removed.
String json = """ {"name": "A"} """;
Line endings are normalized to \n.
\ at line end suppresses the newline; \s keeps a trailing space.
⚠ Follow-up traps
Does the closing """ position matter? Yes: placing it on its own line adds a trailing newline and affects indentation stripping.
Is a text block a different type? No, it is a regular String (and is interned like any literal).
#text-blocks#java15
Q39
How does an object become eligible for garbage collection?
basic
When it is no longer reachable from any GC root (thread stacks, static fields, JNI references, active class loaders). The GC traces reachability; it does not count references.
Cyclic references among unreachable objects are collected.
System.gc() is only a hint.
⚠ Follow-up traps
Does setting a local variable to null help? Rarely; the JIT already treats a variable as dead after its last use.
Can an eligible object be resurrected? Via finalize in old code; finalization is deprecated for removal (Java 18).
#gc#reachability
Q40
What are the common garbage collectors and their defaults?
intermediate
Serial (single thread), Parallel (throughput), G1 (balanced, default since Java 9 on server-class machines), ZGC and Shenandoah (low pause, concurrent compaction).
Select with -XX:+UseSerialGC, -XX:+UseParallelGC, -XX:+UseG1GC, -XX:+UseZGC.
Generational ZGC arrived in Java 21 (-XX:+ZGenerational, default in 23).
Java 8 default is Parallel GC.
⚠ Follow-up traps
Which collector for tiny containers? Serial; the JVM picks it when fewer than 2 CPUs or under ~1792 MB are available.
Is lower pause always better? No, concurrent collectors cost throughput and CPU.
#gc#g1#zgc
Q41
What is a stop-the-world pause and what are GC roots?
intermediate
A stop-the-world pause halts all application threads at a safepoint so the GC can work with a consistent heap. GC roots are the starting points for tracing: local variables on thread stacks, static fields, JNI handles, and live class loaders.
Concurrent collectors shrink pauses but still need short pauses for root scanning.
Time-to-safepoint can be long when a thread is in a counted loop.
⚠ Follow-up traps
Does a minor GC stop the world? Yes, in all generational collectors young collections pause the application.
Are pauses proportional to heap size? For ZGC and Shenandoah no; for Parallel GC full collections yes.
#gc#safepoint
Q42
What are strong, soft, weak and phantom references?
advanced
Strong references block collection. Soft references are cleared under memory pressure (caches), weak ones at the next GC when only weakly reachable (WeakHashMap), phantom ones are enqueued after finalization and used for cleanup via Cleaner.
⚠ Follow-up traps
Is WeakHashMap safe if the value references the key? No, the key stays strongly reachable via the value and the entry never clears.
Why prefer Cleaner over finalize? Finalizers are unpredictable, slow and deprecated for removal.
#references#gc
Q43
How does the JIT compiler work in HotSpot?
intermediate
The JVM starts by interpreting bytecode, profiles invocation and loop counts, and compiles hot methods to native code. Tiered compilation uses C1 (fast, light optimization) first and C2 (aggressive, profile-guided) for the hottest code.
Compiled code lives in the code cache.
Deoptimization falls back to the interpreter when an assumption (e.g., a monomorphic call site) breaks.
-XX:+PrintCompilation shows compile events.
⚠ Follow-up traps
Why is the first run of a benchmark slower? Warm-up: code is still interpreted or C1-compiled.
Can the JIT make code faster than C++? In some cases, thanks to runtime profile-based speculation.
#jit#performance
Q44
Which optimizations does the JIT perform?
advanced
Method inlining, escape analysis (scalar replacement, lock elision), loop unrolling, dead code elimination, range-check elimination and devirtualization.
Inlining is limited by bytecode size (MaxInlineSize, FreqInlineSize).
Escape analysis can avoid heap allocation for objects that do not leave a method.
⚠ Follow-up traps
Does escape analysis put objects on the stack? Not literally; it replaces the object by its fields in registers.
Does dead-code elimination break naive microbenchmarks? Yes; unused results get removed, so use JMH Blackhole.
#jit#escape-analysis
Q45
What are the key JVM memory flags?
basic
-Xms initial heap, -Xmx max heap, -Xss thread stack size, -XX:MaxMetaspaceSize metaspace cap, -XX:MaxDirectMemorySize direct buffers.
Setting -Xms equal to -Xmx avoids resize pauses.
In containers use -XX:MaxRAMPercentage (default 25%).
⚠ Follow-up traps
Why does the process use more RSS than -Xmx? Metaspace, stacks, code cache, direct buffers and GC structures.
Is the JVM container aware? Yes since Java 10 (backported to 8u191).
#jvm-flags#memory
Q46
How does method overloading resolution work versus overriding?
intermediate
Overloads are chosen at compile time using the static argument types; overrides are dispatched at runtime using the object's actual class.
Overload phases: exact/widening, then boxing, then varargs.
Overriding requires the same signature, a compatible return type (covariant) and no weaker access.
⚠ Follow-up traps
Are fields polymorphic? No, field access uses the declared type.
Can an override throw broader checked exceptions? No, only the same, narrower or unchecked ones.
#overloading#overriding#polymorphism
Q47
What are the access modifiers in Java?
basic
private (class), package-private (default, same package), protected (package plus subclasses), public (everywhere). With modules (Java 9) a package must also be exportsed.
⚠ Follow-up traps
Can a subclass in another package access a protected member via a parent-typed reference? No, only via its own type or subtype.
Can top-level classes be private? No, only public or package-private.
#access-modifiers#oop
Q48
How do varargs work?
basic
void f(String... a) is compiled to String[] a; the compiler packs the arguments into an array. It must be the last parameter, and only one is allowed.
⚠ Follow-up traps
What does f() pass? An empty array, not null.
Which overload wins between f(int) and f(int...)? The fixed-arity one; varargs is the last phase.
#varargs#methods
Q49
What do interface default, static and private methods add?
intermediate
Default methods (Java 8) let interfaces evolve without breaking implementors; static methods hold utilities; private methods (Java 9) share code between defaults. Interfaces still cannot hold instance state.
If two interfaces supply the same default, the class must override and may pick one via A.super.m().
A class method always wins over an interface default.
⚠ Follow-up traps
Is an interface with defaults an abstract class? No: no constructors, no instance fields, multiple inheritance of behaviour only.
Can a default method override Object.equals? No, it is a compile error.
#interfaces#java8
Q50
What is the `equals`/`hashCode` contract?
intermediate
Equal objects must have equal hash codes; equals must be reflexive, symmetric, transitive, consistent and false for null. Unequal objects may share a hash code.
Override both together; hash collections break otherwise.
Do not use mutable fields in hashCode for objects stored in hash sets/maps.
⚠ Follow-up traps
What if only equals is overridden?HashMap lookups likely miss, because the identity hashes differ.
What does Objects.hash(a, b) cost? It allocates a varargs array; hand-write for hot paths.
#equals#hashcode
Q51
What are pattern matching for `instanceof` and sealed types?
intermediate
if (o instanceof String s) tests and binds in one step (Java 16). sealed classes/interfaces (Java 17) restrict permitted subtypes, which enables exhaustive switch without default.
sealed interface Shape permits Circle, Square {}record Circle(double r) implements Shape {}record Square(double a) implements Shape {}double area(Shape s) { return switch (s) { case Circle c -> Math.PI * c.r() * c.r(); case Square q -> q.a() * q.a(); };}
(The switch pattern form is final in Java 21.)
⚠ Follow-up traps
What is the scope of the binding s? Only where the match is definitely true, e.g. if (!(o instanceof String s)) return; makes s available after.
Does null instanceof String s match? No, instanceof is false for null.
#pattern-matching#sealed#java17
Q52
What are the methods of `java.lang.Object` and which ones are commonly overridden?
basic
equals, hashCode, toString, getClass, clone, finalize (deprecated), wait, notify, notifyAll. Typically equals, hashCode and toString are overridden.
getClass, wait, notify and notifyAll are final.
wait/notify require holding the object's monitor.
⚠ Follow-up traps
Why is clone considered flawed? It is protected, shallow, needs Cloneable (a marker without the method) and bypasses constructors; prefer copy constructors.
What does default toString print?ClassName@hexHashCode.
#object#methods
Scenarios
Scenarios
Q53
1000 threads each run `counter++` 1000 times on a plain `int`. What is the result?
basic
Usually less than 1,000,000 and different on each run. counter++ is read, add, write; concurrent threads overwrite each other's updates. Fix with AtomicInteger/LongAdder or synchronized.
⚠ Follow-up traps
Does making counter volatile fix it? No, visibility is fixed but the increment is still not atomic.
#race-condition#counter
Q54
A worker loops on `while (!stop) { ... }` with a plain boolean `stop`. Main sets `stop = true`. What happens?
basic
The worker may never exit: with no happens-before edge the JIT can hoist the read of stop out of the loop. Declare stopvolatile (or use interruption).
⚠ Follow-up traps
Why might it work with a System.out.println in the loop?println synchronizes, creating happens-before edges by accident.
#volatile#visibility
Q55
Two threads call `transfer(a, b)` and `transfer(b, a)` with nested `synchronized`. What goes wrong and how do you fix it?
intermediate
Thread 1 holds a waits for b; thread 2 holds b waits for a: deadlock. Always acquire in a consistent global order, e.g. by account id.
Account first = a.id < b.id ? a : b, second = first == a ? b : a;synchronized (first) { synchronized (second) { move(a, b, amt); } }
⚠ Follow-up traps
What if ids tie? Use a tie-breaker lock or System.identityHashCode plus a global tie lock.
#deadlock#lock-ordering
Q56
How do you detect a deadlock in a running JVM?
intermediate
Take a thread dump with jstack <pid> (or jcmd <pid> Thread.print, or kill -3). The dump ends with Found one Java-level deadlock: and lists the cycle. Programmatically use ThreadMXBean.findDeadlockedThreads().
⚠ Follow-up traps
Does it detect deadlocks involving ReentrantLock?findDeadlockedThreads does (ownable synchronizers); findMonitorDeadlockedThreads only monitors.
Does it detect livelock? No.
#deadlock#thread-dump
Q57
How would you read a thread dump to find why a service is hanging?
intermediate
Take 3 dumps 5-10 s apart and compare. Look for:
Many threads BLOCKED on the same monitor (waiting to lock <0x...>) and who owns it.
Threads in WAITING on a pool/queue or connection pool (HikariPool.getConnection).
A thread RUNNABLE in the same frame across dumps (spinning/infinite loop).
Pool threads all stuck in one remote call.
⚠ Follow-up traps
Are RUNNABLE threads always using CPU? No; they may be in native socket reads.
How to map a hot OS thread to Java?top -H, convert TID to hex, match nid=0x....
#thread-dump#debugging
Q58
CPU is at 100%. How do you find the culprit thread?
advanced
Run top -H -p <pid> to find the hottest thread id, convert to hex, then jstack <pid> | grep -A20 nid=0x<hex>. Check for tight loops, regex backtracking, a spinning CAS loop, or GC threads (name GC Thread). Confirm with a profiler (async-profiler/JFR).
⚠ Follow-up traps
What if the hot thread is a GC thread? The problem is heap pressure, not application code.
#debugging#cpu#thread-dump
Q59
A fixed pool of 10 threads has tasks that submit subtasks to the same pool and wait for them. What happens?
intermediate
Thread-starvation deadlock: all 10 workers can be occupied by parents blocking on child futures, while the children sit in the queue forever. Use separate pools, async composition, or ForkJoinPool.
⚠ Follow-up traps
Would a cached pool avoid it? Yes, but unbounded thread growth is the cost.
#thread-pool#deadlock
Q60
With `core=2, max=4, queue=ArrayBlockingQueue(2)`, 7 tasks of long duration are submitted at once. What happens?
intermediate
Tasks 1-2 start core threads, 3-4 go to the queue, 5-6 create extra threads (max 4), task 7 is rejected (AbortPolicy throws RejectedExecutionException).
⚠ Follow-up traps
What if the queue were unbounded? Only 2 threads ever run; 5 tasks queue.
What if it were a SynchronousQueue? Tasks 1-4 run, 5-7 rejected.
#threadpoolexecutor#rejection
Q61
Under overload your service must slow producers rather than lose requests. Which rejection policy?
intermediate
CallerRunsPolicy: the submitting thread executes the task itself, so it cannot submit more until done, throttling intake naturally. Pair with a bounded queue. For HTTP, prefer returning 429/503 via AbortPolicy if the caller thread is a request thread you cannot afford to block.
⚠ Follow-up traps
Downside? Task order is lost and the caller thread may do slow work (e.g. an event-loop thread).
#back-pressure#rejection
Q62
A scheduled job stops running after one failure. Why?
basic
ScheduledExecutorService suppresses subsequent executions if any run throws, and the exception is stored in the Future unseen. Wrap the task body in try/catch and log.
⚠ Follow-up traps
What does Spring @Scheduled do? It catches and logs errors, continuing the schedule.
#scheduler#exceptions
Q63
`CompletableFuture.supplyAsync(() -> blockingHttpCall())` is used heavily and the whole app slows down. Why?
intermediate
Blocking tasks occupy the small common pool (cores - 1 threads), starving parallel streams and other async stages. Supply a dedicated bounded executor, or use virtual threads for blocking calls.
⚠ Follow-up traps
Does wrapping in ManagedBlocker help? It lets the pool compensate with extra threads, but a dedicated executor is simpler.
#completablefuture#forkjoinpool
Q64
What is printed? `CompletableFuture.supplyAsync(() -> { throw new RuntimeException("x"); }).thenApply(v -> "ok").exceptionally(ex -> ex.getClass().getSimpleName() + ":" + ex.getMessage()).join()`
advanced
CompletionException:java.lang.RuntimeException: x. The failure passes through thenApply and arrives at exceptionally wrapped in CompletionException because it came from an upstream stage. If exceptionally were attached directly to the failing stage, you would see the raw RuntimeException ("x").
⚠ Follow-up traps
How to unwrap? Check ex.getCause() when ex instanceof CompletionException.
#completablefuture#exceptions
Q65
You need results of 3 independent remote calls with a 2 s overall timeout. How?
intermediate
Start all three, then combine and apply orTimeout (Java 9+).
var a = CompletableFuture.supplyAsync(this::callA, pool);var b = CompletableFuture.supplyAsync(this::callB, pool);var c = CompletableFuture.supplyAsync(this::callC, pool);CompletableFuture.allOf(a, b, c).orTimeout(2, TimeUnit.SECONDS).join();
completeOnTimeout(default, ...) supplies a fallback instead of failing.
⚠ Follow-up traps
Does the timeout cancel the underlying calls? No; the tasks keep running.
Does allOf give results? It returns Void; read from each future.
#completablefuture#timeout
Q66
Two threads run `if (!map.containsKey(k)) map.put(k, compute(k))` on a `ConcurrentHashMap`. What is wrong?
intermediate
Check-then-act race: both can see absence and both compute/put, the last write wins, and compute runs twice. Use computeIfAbsent(k, this::compute), which runs the function at most once per absent key.
⚠ Follow-up traps
Does computeIfAbsent with a slow function hurt? It holds the bin lock, blocking other writers to that bin.
#concurrenthashmap#race-condition
Q67
What happens when a `computeIfAbsent` mapping function updates the same `ConcurrentHashMap` (e.g. recursive Fibonacci memoization)?
advanced
In Java 8 it can livelock/hang (infinite loop on a reserved bin); since Java 9 it throws IllegalStateException: Recursive update when detected. HashMap throws ConcurrentModificationException (Java 9+). Compute outside the lambda or use a loop with get then putIfAbsent.
⚠ Follow-up traps
Is detection guaranteed? No, it is best-effort.
#concurrenthashmap#computeifabsent
Q68
A `HashMap` shared by threads gets corrupted. What symptoms can occur?
basic
Lost updates, wrong size, null for present keys, and in Java 7 an infinite loop during resize (circular bucket list). Java 8 removed the cycle risk, but the map is still unsafe. Use ConcurrentHashMap.
⚠ Follow-up traps
Is Hashtable the fix? It is thread-safe per call but single-lock and obsolete; compound ops still race.
#hashmap#thread-safety
Q69
You iterate a `Collections.synchronizedList` while another thread adds. What happens?
intermediate
ConcurrentModificationException (best-effort) because iteration is not atomic. Wrap iteration in synchronized (list) { ... }, or use CopyOnWriteArrayList (snapshot iteration).
⚠ Follow-up traps
Does the for-each over the list use a lock? No.
#synchronized-collections#cme
Q70
A listener list with frequent adds (1000/s) and iteration on each event uses `CopyOnWriteArrayList`. Issue?
intermediate
Each add copies the array: O(n) allocation and GC churn, and writes block each other. Choose ConcurrentLinkedQueue, a ConcurrentHashMap.newKeySet(), or batch the changes.
⚠ Follow-up traps
Would iterators see new listeners mid-iteration? No, only the snapshot.
#copyonwritearraylist#performance
Q71
A consumer thread calls `queue.take()` and the app cannot shut down. Why and how to fix?
intermediate
The consumer is parked forever on an empty queue (non-daemon thread). Interrupt it (shutdownNow() or thread.interrupt()) and treat InterruptedException as the exit signal, or enqueue poison pills.
⚠ Follow-up traps
What if you swallow InterruptedException in a loop? The thread never stops; restore the flag or exit.
#blockingqueue#shutdown
Q72
A bounded queue is full and producers call `put()`. What are the options to avoid blocking forever?
basic
Use offer(e, timeout, unit) to wait a limited time and handle failure (retry, drop, spill to disk, return an error), or offer(e) for non-blocking. Choose based on whether data loss or latency is worse.
⚠ Follow-up traps
What does add() do on a full queue? Throws IllegalStateException.
#blockingqueue#timeout
Q73
You need to start 5 services in parallel and block `main` until all are ready. Which tool?
basic
CountDownLatch(5); each service calls countDown() in finally, main calls await() (ideally with timeout).
⚠ Follow-up traps
What if one service fails? Without finally, await() never returns; use a timeout and check status.
#countdownlatch
Q74
Worker threads in a simulation need to sync at the end of each round. Which primitive?
intermediate
CyclicBarrier(parties, mergeAction): all wait at await(), the barrier action merges round results, then the barrier resets for the next round. If one worker dies, others get BrokenBarrierException, so handle that.
⚠ Follow-up traps
What if you use CountDownLatch? You would need a new latch per round.
#cyclicbarrier
Q75
You must limit calls to a rate-limited API to 5 concurrent. How?
basic
Semaphore(5); acquire() before the call and release() in finally. This limits concurrency, not rate per second; for rate use a token bucket (e.g. Guava RateLimiter).
⚠ Follow-up traps
Concurrency limit vs rate limit? They differ: 5 concurrent slow calls can be well under the rate cap.
#semaphore
Q76
A `ThreadLocal<SimpleDateFormat>` is used in a Tomcat app and memory grows after redeploys. Why?
advanced
Pool threads outlive the webapp; their ThreadLocalMap values reference classes loaded by the old classloader, pinning it and all its static data (Metaspace growth). Call remove() in finally, avoid thread locals for such objects (use immutable DateTimeFormatter), and clean up in a context-destroy hook.
⚠ Follow-up traps
Does Tomcat warn? Yes, it logs "created a ThreadLocal ... but failed to remove it" at stop.
#threadlocal#classloader-leak
Q77
A request filter stores user context in a `ThreadLocal`, then async work runs on another pool. What is lost?
intermediate
The new pool thread has no (or stale) value from the previous request. Capture the context and pass it explicitly or decorate the executor (Spring TaskDecorator, MDC copy, Micrometer context-propagation). Always clear in finally so pooled threads do not leak a previous user's data.
⚠ Follow-up traps
Does InheritableThreadLocal fix it? Not with pools; copying happens only at thread creation.
#threadlocal#context-propagation
Q78
Check-then-act on a lazily initialized field: `if (cache == null) cache = load();`. What can go wrong and how do you fix?
basic
Two threads can both load; another can see a partially built object when unsafely published. Fix with a holder class, synchronized accessor, volatile DCL, or Supplier memoization with AtomicReference.
⚠ Follow-up traps
Is a duplicate load harmful? Only if loading has side effects or cost; the unsafe publication is the real hazard.
#lazy-init#race-condition
Q79
What is the output for a `StampedLock` optimistic read when a writer interleaves?
advanced
validate(stamp) returns false if any write lock was acquired since tryOptimisticRead(); values read in between may be inconsistent (e.g. new x, old y) and must be discarded. The reader falls back to readLock() and rereads. Never act on optimistic values before validating.
⚠ Follow-up traps
Can you call methods on the read values before validation? Only with care; they may violate invariants (e.g. divide by zero).
#stampedlock#optimistic
Q80
A read-heavy cache uses `ReentrantReadWriteLock`; a reader tries to upgrade to write while holding read. What happens?
intermediate
It deadlocks: the write lock waits for all readers (including itself) to release. Release the read lock, acquire the write lock, and recheck the condition. Downgrading (write then read then release write) is permitted.
⚠ Follow-up traps
Why recheck after reacquiring? Another writer may have changed state in the gap.
#readwritelock#deadlock
Q81
`lock.lock()` is called but `unlock()` is skipped on an exception path. Consequence?
basic
The lock stays held forever, so other threads block indefinitely (unlike synchronized, which auto-releases). Always use try/finally. Place lock() before try, so a failed acquire does not trigger unlock().
⚠ Follow-up traps
What if lock() is inside try? A failed lock() leads to unlock() by a non-owner, throwing IllegalMonitorStateException and masking the original error.
#reentrantlock#bug
Q82
Two threads each try `tryLock()` on two locks in opposite order and release on failure, then retry immediately. Outcome?
advanced
Possible livelock: both keep acquiring one lock, failing the second, releasing and retrying in lockstep. Add randomized back-off, a timeout, or a consistent lock order.
⚠ Follow-up traps
Is it better than deadlock? It avoids blocking but can burn CPU without progress.
#livelock#trylock
Q83
A starvation scenario: a high-volume task type floods a shared pool and a critical task never runs. How do you fix it?
intermediate
Isolate with separate pools (bulkheads) per workload, or use a PriorityBlockingQueue with bounded size, and reserve capacity for critical tasks. Add queue-length and wait-time metrics.
⚠ Follow-up traps
Does thread priority fix it? Rarely; OS-dependent and often ignored.
#starvation#bulkhead
Q84
What does this print? `synchronized` method `a()` calls `synchronized` method `b()` on the same object.
basic
It works without deadlock: monitors are reentrant, and the owning thread's hold count increments. The monitor is released only when the hold count returns to zero.
⚠ Follow-up traps
Is a Semaphore(1) equivalent? No, it is not reentrant and would self-deadlock.
#synchronized#reentrancy
Q85
Static `synchronized` method and instance `synchronized` method run on two threads. Do they block each other?
basic
No. The static method locks Foo.class, the instance method locks this; they are different monitors, so they can run concurrently and do not protect shared state with each other.
⚠ Follow-up traps
How to protect a static field from instance methods?synchronized (Foo.class) or a private static lock.
#synchronized#class-lock
Q86
`synchronized (this)` vs a private lock object: which is safer?
intermediate
A private final lock object. this is publicly lockable, so outside code (or a subclass) can interfere or cause deadlocks. Never lock on a String literal, boxed Integer, or a reassigned field either.
⚠ Follow-up traps
What happens locking on Integer from valueOf(1) in two unrelated classes? They share the cached instance and block each other unexpectedly.
#synchronized#encapsulation
Q87
A `final` field object is published through a data race. What can the reader see?
advanced
If the class is correctly constructed (no this escape), the reader sees the final fields correctly initialized. Non-final fields might be seen as defaults. If the object is a mutable one with non-final state, publish it safely: volatile field, synchronized, concurrent collection, or static initializer.
⚠ Follow-up traps
Does a final Map field guarantee contents visible after later puts? No, only up to the end of construction.
#safe-publication#final
Q88
Walk through what happens: `volatile int a; int b;` Thread 1: `b = 1; a = 2;` Thread 2: `if (a == 2) print(b)`. What can print?
advanced
If thread 2 sees a == 2, it prints 1. The volatile write happens-before the read, and b = 1 precedes it in program order, so by transitivity b = 1 is visible (piggybacking). If a is not volatile, it may print 0.
⚠ Follow-up traps
What if the writes were reversed (a = 2; b = 1;)?b could print 0; no ordering for the later write.
#happens-before#volatile
Q89
Why does `i++` on `AtomicInteger` via `set(get()+1)` fail while `incrementAndGet()` works?
basic
get then set is two operations, so updates interleave and are lost. incrementAndGet is one atomic CAS loop. For custom updates use updateAndGet/accumulateAndGet with a side-effect-free function (it may be retried).
⚠ Follow-up traps
Why must the function be side-effect free? Because a failed CAS reruns it.
#atomics#race-condition
Q90
Implement a lock-free stack with `AtomicReference`. What hazards exist?
advanced
Push/pop via CAS on the head.
class Stack<T> { private record Node<T>(T v, Node<T> next) {} private final AtomicReference<Node<T>> head = new AtomicReference<>(); void push(T v) { Node<T> h, n; do { h = head.get(); n = new Node<>(v, h); } while (!head.compareAndSet(h, n)); } T pop() { Node<T> h; do { h = head.get(); if (h == null) return null; } while (!head.compareAndSet(h, h.next())); return h.v(); }}
In Java, ABA is mostly neutralized by garbage collection since nodes are freshly allocated and not reused, but reusing nodes (pooling) reintroduces it.
⚠ Follow-up traps
Is it wait-free? No, lock-free: some thread progresses, but one can retry indefinitely.
#cas#lock-free
Q91
A `LongAdder` counter is read for a billing total while updates continue. Is that OK?
intermediate
No. sum() is not an atomic snapshot, and you cannot get exact point-in-time totals. For exact figures, use AtomicLong or a lock, or accept approximate values for metrics only (sumThenReset is also racy).
⚠ Follow-up traps
When is it ideal? Hot metrics counters where reads are rare.
#longadder#consistency
Q92
`wait()` is called without holding the lock. What happens?
basic
IllegalMonitorStateException. wait/notify must be invoked inside synchronized on the same object. Same result when synchronizing on a different object than the one you call wait on.
⚠ Follow-up traps
Does Thread.sleep require a lock? No.
#wait-notify#exceptions
Q93
A `notify()` is sent before the other thread calls `wait()`. What happens?
intermediate
Lost notification: the signal is not remembered and the waiter blocks forever. The fix is to guard with a state variable checked in a while loop under the same lock, so the waiter sees the condition already true and never waits.
⚠ Follow-up traps
notify vs notifyAll with multiple conditions on one monitor? Use notifyAll (or separate Conditions) so the right waiter is woken.
#wait-notify#lost-wakeup
Q94
Implement a bounded buffer with `wait/notify`. What bug is common?
intermediate
Both producers and consumers wait on the same monitor, so notify() can wake the wrong kind (a producer waking a producer) and everyone sleeps. Use notifyAll(), or ReentrantLock with notFull/notEmpty conditions and signal().
Why prefer BlockingQueue? It is tested and avoids this entire bug class.
#producer-consumer#wait-notify
Q95
Design a thread pool for a service making blocking DB calls (8 cores, DB pool of 20). How many threads?
intermediate
More threads than 20 gain nothing, since they wait for connections. Size the worker pool at about 20 (or slightly above with a short connection timeout) and bound the queue. CPU count (8) is irrelevant for IO waits, but too many threads add contention and memory. Verify with load tests and queue-wait metrics.
⚠ Follow-up traps
What if requests also call a 3rd party API? Separate pool for it with its own limit.
#pool-sizing#jdbc
Q96
Compute-bound batch on a 16-core box: 16 threads vs 64 threads?
basic
About 16 (or 17). More threads add context switches and cache thrash with no extra throughput for CPU-bound work. For parallel stream or fork/join, the common pool defaults to cores - 1 workers plus the caller.
⚠ Follow-up traps
What about hyperthreading? Benchmarks decide; logical cores are not equal to physical.
#pool-sizing#cpu-bound
Q97
`executor.submit(task)` throws `RejectedExecutionException` after shutdown. How do you avoid losing work during graceful shutdown?
intermediate
Stop accepting input first, then shutdown(), awaitTermination(timeout), and only on timeout shutdownNow(), followed by another await.
What does shutdownNow return? The list of tasks that never started.
#shutdown#executorservice
Q98
A task blocked on `future.get()` hangs forever. How do you prevent it?
basic
Use get(timeout, unit) and on TimeoutException call future.cancel(true). Ensure the task responds to interruption. Add timeouts to the underlying I/O too.
⚠ Follow-up traps
Does cancel(true) guarantee stopping? No, the task can ignore interruption.
#future#timeout
Q99
Compare 10,000 concurrent blocking HTTP calls using a fixed pool of 200 platform threads vs virtual threads.
intermediate
With 200 platform threads only 200 calls are in flight, the rest queue, so total time is about 50 x latency. With virtual threads, all 10,000 blocking calls proceed concurrently with a few carrier threads, giving much higher throughput for I/O-bound work. Remaining limits: downstream capacity, connection pools, memory; use a Semaphore to cap concurrency to protected resources.
⚠ Follow-up traps
Do virtual threads remove the need for limits? No, they remove the thread bottleneck, not the downstream one.
#virtual-threads#throughput
Q100
After migrating to virtual threads, throughput stalls. Possible causes?
advanced
Common causes on Java 21: pinning from blocking inside synchronized (JDBC drivers, legacy libs), blocking native calls, a fixed-size pool of connections or a semaphore throttling everything, and ThreadLocal heavy caching (millions of copies). Diagnose with JFR event jdk.VirtualThreadPinned and -Djdk.tracePinnedThreads=full. Fixes: use ReentrantLock, upgrade the library, or Java 24+.
⚠ Follow-up traps
Is CPU-bound work faster? No; carriers are limited to core count.
#virtual-threads#pinning
Q101
A `StructuredTaskScope.ShutdownOnFailure` forks two subtasks and one fails. What happens to the other?
advanced
The scope shuts down: the sibling is interrupted/cancelled, join() returns, and throwIfFailed() rethrows the first failure wrapped in ExecutionException. Accessing get() on an unfinished or failed subtask throws IllegalStateException. The try-with-resources guarantees no subtask outlives the block. Preview API in Java 21, so it requires --enable-preview.
⚠ Follow-up traps
Which variant returns the first successful result?ShutdownOnSuccess.
#structured-concurrency#cancellation
Q102
A parallel stream `list.parallelStream().forEach(results::add)` with an `ArrayList` results. What happens?
basic
Data race on the unsynchronized ArrayList: lost elements, ArrayIndexOutOfBoundsException, or nulls. Use collect(Collectors.toList()) (or toList()), which merges per-thread containers safely.
⚠ Follow-up traps
Does Collections.synchronizedList fix it? Correct but contended; collectors are better.
#parallel-streams#race-condition
Q103
A fork/join task calls blocking I/O inside `compute()`. Consequence?
intermediate
Workers block instead of stealing, the pool (sized by cores) starves, and throughput drops, especially in the shared common pool, affecting unrelated stream and CompletableFuture users. Use a dedicated pool, ForkJoinPool.managedBlock, or non-fork/join executors for I/O.
⚠ Follow-up traps
Choosing the threshold? Too small causes overhead; typically a few thousand elementary ops.
#forkjoin#blocking
Q104
A heavy-contention log appender with `synchronized` becomes the bottleneck. What are your options?
advanced
Shrink the critical section (format outside the lock), use asynchronous appenders (queue plus one consumer, e.g. Log4j2 with Disruptor / Logback AsyncAppender), stripe locks, or use lock-free structures. Decide the policy on overflow (block, drop, or discard low levels). Confirm with a profiler and lock-contention metrics before and after.
⚠ Follow-up traps
Risk of async logging? Log loss on crash and memory growth if the queue is unbounded.