Lambdas, functional interfaces, method references, Stream API semantics and pitfalls, collectors, Optional, Date/Time API, default methods, CompletableFuture and the records, pattern matching and sealed types added in Java 14-21.
Theory
Q1
What is a lambda expression and what problem does it solve?
basic
A lambda is a concise implementation of a functional interface: (params) -> body. It removes the boilerplate of anonymous inner classes and lets behaviour be passed as an argument.
The target type is inferred from context (assignment, argument, return).
Comparator<String> c = (a, b) -> a.length() - b.length();
Lambdas enable the Stream API, CompletableFuture callbacks and Optional transformations.
⚠ Follow-up traps
Is a lambda just syntactic sugar for an anonymous class? No. It is compiled to an invokedynamic call site and a private synthetic method; no .class file per lambda is generated.
Can a lambda exist without a target type? No. var f = () -> 1; does not compile.
#lambda#basics
Q2
What is a functional interface?
basic
An interface with exactly one abstract method (SAM). It may also have default, static and private methods, and may redeclare Object methods such as equals.
@FunctionalInterface is optional; it makes the compiler reject a second abstract method.
Comparator is functional even though it declares equals, because that is an Object method.
⚠ Follow-up traps
Is @FunctionalInterface required to use lambdas? No, it is only a compile-time guard.
Can a functional interface extend another interface? Yes, as long as the total abstract method count stays one.
#functional-interface#basics
Q3
Name the core functional interfaces in java.util.function.
basic
Four families: Function<T,R> (T to R), Predicate<T> (T to boolean), Consumer<T> (T to void), Supplier<T> (nothing to T).
BiFunction, BiPredicate, BiConsumer take two arguments.
UnaryOperator<T> and BinaryOperator<T> are specialisations where input and output types match.
Difference between Function and UnaryOperator?UnaryOperator<T> is Function<T,T>, same semantics, clearer intent.
Why do primitive variants exist? Generics cannot hold primitives, so Function<Integer,Integer> boxes on every call.
#functional-interface#java.util.function
Q4
How do you compose functions and predicates?
intermediate
Function has andThen and compose; Predicate has and, or, negate and static not (Java 11).
Function<Integer, Integer> plus1 = x -> x + 1;Function<Integer, Integer> times2 = x -> x * 2;plus1.andThen(times2).apply(3); // (3+1)*2 = 8plus1.compose(times2).apply(3); // (3*2)+1 = 7
Predicate.not(String::isBlank) replaces s -> !s.isBlank().
and/or short-circuit like &&/||.
⚠ Follow-up traps
Which runs first in f.compose(g)?g, then f.
Does Predicate.negate() mutate the predicate? No, it returns a new one.
#composition#function#predicate
Q5
What are the four kinds of method references?
basic
Static (Integer::parseInt), bound instance (System.out::println), unbound instance (String::length, receiver becomes first argument) and constructor (ArrayList::new).
A method reference is only valid if the method's signature fits the target functional interface.
For String::compareToIgnoreCase as Comparator<String>, the first argument is the receiver.
⚠ Follow-up traps
When is this::foo evaluated? The receiver is evaluated once when the reference is created; a bound reference to a null receiver throws NPE immediately.
Can overloads make a method reference ambiguous? Yes, e.g. String::valueOf with some target types; use a lambda with explicit types.
#method-reference
Q6
What does "effectively final" mean and why does it exist?
basic
A local variable captured by a lambda or inner class must be final or never reassigned after initialization. Captured locals are copied into the lambda, so mutation would create two diverging values.
Instance and static fields are not subject to this rule because the lambda captures this or the class, not a copy.
Workarounds: single-element array, AtomicInteger, or restructure with reduce/collectors.
⚠ Follow-up traps
Can the lambda mutate an object that a captured final reference points to? Yes, the reference is final, not the object.
Is using AtomicInteger counters in parallel streams safe? The counter is thread-safe, but order-dependent logic is still wrong.
#effectively-final#closure
Q7
How does `this` behave inside a lambda versus an anonymous class?
intermediate
In a lambda, this refers to the enclosing instance because a lambda introduces no new scope. In an anonymous class, this refers to the anonymous instance.
Lambda parameters cannot shadow local variables of the enclosing method (compile error); anonymous class members can.
A lambda has no identity of its own: toString and hashCode are not meaningful.
⚠ Follow-up traps
How do you reach the outer instance from an anonymous class?Outer.this.
Can a lambda refer to itself recursively? Not through a local; use a field, this-held reference or a helper class.
#lambda#this#scope
Q8
How are lambdas implemented at the bytecode level?
advanced
javac desugars the body into a private static (or instance, if it captures this) method and emits an invokedynamic instruction. On first execution the bootstrap method LambdaMetafactory.metafactory spins a hidden class implementing the interface and links the call site.
Non-capturing lambdas are cached as a singleton; capturing ones allocate an instance per evaluation.
Not generating classes at compile time keeps jar size small and lets the JVM change the strategy later.
⚠ Follow-up traps
Does every evaluation of a lambda create an object? Only if it captures state; non-capturing lambdas are reused.
Why can lambda stack traces look odd? Frames like lambda$main$0 and the hidden class appear in traces.
#invokedynamic#lambda#internals
Q9
What are default methods and why were they added?
basic
A default method has a body inside an interface, letting library authors evolve interfaces without breaking implementers. It enabled Collection.stream(), List.sort and Map.getOrDefault.
Implementers inherit the default but may override it.
Static interface methods (Java 8) and private interface methods (Java 9) support helper logic.
⚠ Follow-up traps
Can a default method override equals or hashCode? No, defaults cannot override Object methods.
Do interfaces now have state? No, only constants; no instance fields.
#default-methods#interfaces
Q10
How does Java resolve the diamond problem with default methods?
intermediate
Rules in order: (1) a class method (including inherited from a superclass) wins over any interface default; (2) the most specific interface wins; (3) otherwise it is a compile error and the class must override, optionally calling A.super.hello().
interface A { default String hi() { return "A"; } }interface B { default String hi() { return "B"; } }class C implements A, B { public String hi() { return A.super.hi() + B.super.hi(); }}
⚠ Follow-up traps
If a superclass has a concrete hi() and an interface has a default hi()? The class method wins, no conflict.
If one interface declares hi() abstract and the other default? Still a conflict; the class must implement it.
#default-methods#diamond#resolution
Q11
Explain the Stream API pipeline model.
basic
A pipeline is a source, zero or more intermediate operations, and one terminal operation. Intermediate operations return a new stream lazily; nothing runs until the terminal operation is invoked.
Elements flow one at a time through the whole chain (vertical processing), not stage by stage, except at stateful barriers like sorted.
⚠ Follow-up traps
Does a stream store data? No. It is a view over a source; it does not modify the source.
Can a stream be reused? No; a second terminal call throws IllegalStateException.
#stream#pipeline
Q12
What does lazy evaluation mean for streams and why does it matter?
intermediate
Intermediate operations only record a stage; the terminal operation pulls elements. That allows fusion of stages and early termination.
Stream.of("a","bb","ccc") .filter(s -> { System.out.println("f " + s); return s.length() > 1; }) .map(s -> { System.out.println("m " + s); return s.toUpperCase(); }) .findFirst();// f a, f bb, m bb (ccc never touched)
⚠ Follow-up traps
Does peek with no terminal op print anything? No, nothing executes.
Is output order filter-all then map-all? No, element by element.
#stream#lazy
Q13
Which operations are short-circuiting?
intermediate
Intermediate: limit, takeWhile (Java 9). Terminal: findFirst, findAny, anyMatch, allMatch, noneMatch. They can finish without consuming the whole source, which is what makes infinite streams usable.
Stream.iterate(1, x -> x + 1).filter(x -> x % 7 == 0).limit(3) terminates.
anyMatch stops on first true; allMatch on first false.
⚠ Follow-up traps
Does allMatch on an empty stream return true? Yes (vacuous truth); anyMatch returns false.
Does sorted().limit(1) stay lazy over an infinite source? No, sorted must see everything and hangs.
#stream#short-circuit
Q14
What are stateless versus stateful intermediate operations?
intermediate
Stateless (map, filter, flatMap, peek) process each element independently. Stateful (sorted, distinct, limit, skip) need to remember earlier elements or see all of them.
sorted is a full barrier and buffers everything.
distinct keeps a hash set of seen elements: memory grows with cardinality.
Stateful ops are expensive or order-sensitive in parallel pipelines.
⚠ Follow-up traps
Is limit stateless? No, it counts elements, which makes it costly on ordered parallel streams.
Can a lambda passed to map be stateful? It can compile, but it violates the contract and breaks in parallel.
#stream#stateful
Q15
What is the difference between map and flatMap?
basic
map transforms each element one-to-one. flatMap maps each element to a stream and flattens the results into one stream.
Use it for one-to-many, for flattening Optional<Stream> style structures and for words -> chars.
Optional.flatMap avoids Optional<Optional<T>>.
⚠ Follow-up traps
What if the mapper returns null? NPE for streams; return Stream.empty() instead.
Is the inner stream closed? Yes, flatMap closes each mapped stream after its contents are placed.
#flatmap#map
Q16
Explain reduce and its three forms.
intermediate
reduce(BinaryOperator) returns Optional<T>; reduce(identity, op) returns T; reduce(identity, accumulator, combiner) allows a different result type and supports parallel merging.
The operator must be associative and the identity must be neutral, otherwise parallel results differ.
Prefer collect for mutable results; reduce with a StringBuilder or List is a bug.
⚠ Follow-up traps
Why is reduce(0, (a, b) -> a - b) wrong? Subtraction is not associative; parallel results change.
When is the combiner called? Only in parallel execution; in sequential mode it is ignored.
#reduce
Q17
What is the difference between collect and reduce?
intermediate
reduce is immutable reduction (each step produces a new value). collect is mutable reduction into a container via supplier, accumulator and combiner, so it avoids repeated copying.
reduce(new ArrayList<>(), (l, e) -> { l.add(e); return l; }, ...) shares one list across threads and is incorrect in parallel.
Collectors.toList() creates one list per thread and merges them.
⚠ Follow-up traps
Why is string concat via reduce("", String::concat) slow? Each step copies, making O(n^2); use Collectors.joining.
Is collect(Collectors.toList()) guaranteed to be an ArrayList? No, only unspecified mutable List.
#collect#reduce#mutable-reduction
Q18
How do toList(), Collectors.toList() and Collectors.toUnmodifiableList() differ?
intermediate
Stream.toList() (Java 16) returns an unmodifiable list that permits nulls. Collectors.toList() returns a mutable list, type unspecified. Collectors.toUnmodifiableList() (Java 10) returns an unmodifiable list that rejects nulls with NPE.
List.copyOf and List.of also reject nulls.
Modifying the result of Stream.toList() throws UnsupportedOperationException.
⚠ Follow-up traps
Can you drop Collectors.toList() for toList() everywhere? Not if callers mutate the result.
Does Stream.toList() accept null elements? Yes, unlike toUnmodifiableList.
#collectors#immutability#java16
Q19
How does Collectors.groupingBy work?
intermediate
It classifies elements by a key function into a Map<K, List<T>>; a downstream collector reduces each group, and a map factory chooses the map type.
Common downstreams: counting, summingInt, mapping, averagingDouble, maxBy.
Key function must not return null (NPE: "element cannot be mapped to a null key").
Default map is HashMap; no ordering guarantee.
⚠ Follow-up traps
Is the group's list order preserved? Yes for ordered streams, encounter order.
Does groupingBy include empty groups? No, only keys that appeared.
#collectors#groupingby
Q20
What is partitioningBy and how does it differ from groupingBy?
basic
partitioningBy(predicate) always returns a Map<Boolean, List<T>> with both true and false keys present, even if one list is empty. groupingBy only creates keys that occur.
Accepts a downstream collector, e.g. partitioningBy(p, counting()).
Good for pass/fail splits; the map is a special compact implementation.
⚠ Follow-up traps
Can you partition into three buckets? No; use groupingBy with an enum or classifier.
Does get(true) risk NPE? No, both keys always exist.
#collectors#partitioningby
Q21
What happens when Collectors.toMap hits a duplicate key?
intermediate
The two-argument toMap throws IllegalStateException: Duplicate key K (attempted merging values A and B). Supply a merge function as third argument to resolve, and a map supplier as fourth to control type.
Map<String, Integer> byEmail = users.stream().collect( Collectors.toMap(User::email, User::age, (a, b) -> a, LinkedHashMap::new));
toMap also throws NPE if a value is null (it uses HashMap.merge).
toConcurrentMap is the parallel-friendly variant.
⚠ Follow-up traps
Does toMap produce an ordered map? No, a HashMap by default.
Does the message include the key? Yes, key and both values.
#collectors#tomap#duplicates
Q22
What is Collectors.teeing?
advanced
teeing (Java 12) feeds every element into two downstream collectors and combines their results with a merger function, in a single pass.
record Stats(long count, double avg) {}Stats s = nums.stream().collect(Collectors.teeing( Collectors.counting(), Collectors.averagingInt(i -> i), Stats::new));
Removes the need to traverse a source twice or to hold intermediate maps.
⚠ Follow-up traps
Can it take more than two collectors? No; nest teeing calls.
Is it parallel-safe? Yes if both downstreams are.
#collectors#teeing#java12
Q23
What do Collectors.mapping, filtering and flatMapping add?
advanced
They adapt downstream collectors. mapping transforms before collecting; filtering and flatMapping (Java 9) do the same for filter/flatMap inside each group.
groupingBy(k, filtering(p, toList())) keeps empty groups; pre-filtering with .filter(p) drops the group key entirely.
summarizingInt/Long/Double returns count, sum, min, max, average at once.
⚠ Follow-up traps
Difference between stream .filter before groupingBy and filtering inside? The latter keeps keys whose groups become empty.
Is Collectors.collectingAndThen the same? It post-processes the final result, e.g. wrap in Collections::unmodifiableList.
#collectors#downstream#java9
Q24
How are Collectors.joining and sorted/Comparator chains used?
basic
joining(delim, prefix, suffix) concatenates CharSequence elements using a StringBuilder. Sorting uses Comparator.comparing(...).thenComparing(...) and reversed().
Comparator.nullsFirst/nullsLast wrap comparators for null-safe sorting.
⚠ Follow-up traps
Does joining accept non-strings? No; map to String first.
Is comparing(X::a).reversed().thenComparing(X::b) reversing both? No, only the first comparator is reversed.
#collectors#joining#comparator
Q25
How do you use sorted and what is its stability?
intermediate
sorted() on an ordered stream is stable; it uses the same TimSort as List.sort. It buffers all elements, so it is a barrier and costs O(n log n).
Elements must be Comparable or a comparator must be supplied, otherwise ClassCastException at runtime.
On unordered streams stability is not promised.
⚠ Follow-up traps
Can sorted be followed by limit efficiently? The JVM uses a bounded top-k optimisation for sorted().limit(n) in some cases; do not rely on it for infinite sources.
Does sorted change the source list? No.
#stream#sorted#stability
Q26
Explain Optional and its intended use.
basic
Optional<T> is a container for a value that may be absent, designed as a return type to make "no result" explicit and to prevent NPE through composable operations.
Create with of (non-null), ofNullable, empty.
Query with map, flatMap, filter, orElse, orElseGet, orElseThrow, ifPresent, ifPresentOrElse (9), or (9), stream (9), isEmpty (11).
⚠ Follow-up traps
Does Optional.of(null) work? No, NPE; use ofNullable.
Is Optional serializable? No; it is not Serializable by design.
#optional#basics
Q27
What is the difference between orElse and orElseGet?
intermediate
orElse(value) evaluates its argument eagerly, always. orElseGet(supplier) only invokes the supplier when the Optional is empty.
repo.findCached(id).orElse(repo.loadFromDb(id)); // DB hit every timerepo.findCached(id).orElseGet(() -> repo.loadFromDb(id)); // only on miss
orElseThrow(Supplier) is similar: lazy exception creation.
⚠ Follow-up traps
Is orElse(null) fine? It compiles, but it defeats the point; consider returning the Optional.
When is orElse preferable? For cheap constants and literals.
#optional#orelse#orelseget
Q28
List common Optional anti-patterns.
intermediate
Misuse includes: Optional as a field, method parameter or collection element; isPresent() followed by get(); returning null from a method that returns Optional; Optional.get() without a check; wrapping in Optional only to unwrap immediately.
Use Optional<T> as a return type only.
Return empty collections, not Optional<List<T>>.
Do not use Optional in JPA entities or DTOs for JSON; serialization and frameworks handle it poorly.
Optional<Integer> is boxed; prefer OptionalInt in hot code.
⚠ Follow-up traps
Is opt.isPresent() ? opt.get() : x wrong? It works but orElse/map express it better.
Why not Optional parameters? Callers must wrap values and null Optional is still possible; use overloads.
#optional#best-practices#antipatterns
Q29
What do Optional.map and flatMap do differently?
intermediate
map applies a function and wraps the result with ofNullable, so a null result becomes empty. flatMap takes a function that already returns an Optional and avoids nesting.
Optional<String> city = user.flatMap(User::address).map(Address::city);// User::address returns Optional<Address>
⚠ Follow-up traps
What if the mapper to map returns null? Result is empty, no exception.
Does flatMap accept a function returning null? No, NPE.
#optional#map#flatmap
Q30
What is the Java 8 Date/Time API and why replace Date and Calendar?
basic
java.time (JSR-310) provides immutable, thread-safe types: LocalDate, LocalTime, LocalDateTime, Instant, ZonedDateTime, OffsetDateTime, Duration, Period. It replaces Date (mutable, misnamed, 1900-based years) and Calendar (0-based months, not thread-safe).
DateTimeFormatter is thread-safe; SimpleDateFormat is not.
Arithmetic returns new instances: date.plusDays(1).
⚠ Follow-up traps
Is LocalDateTime an instant in time? No, it has no zone or offset.
Is Date deprecated? Not the class, but most methods are; convert with Date.from(instant).
#datetime#java.time
Q31
Compare Instant, LocalDateTime, ZonedDateTime and OffsetDateTime.
intermediate
Instant is a point on the UTC timeline (nanoseconds since epoch). LocalDateTime is a date and time without zone. OffsetDateTime adds a fixed UTC offset. ZonedDateTime adds a region rule (Europe/Paris) so daylight saving is handled.
Store/exchange timestamps as Instant or UTC OffsetDateTime; convert to zoned time for display.
For DB columns of type timestamp with time zone, Hibernate 6 and JDBC 4.2 support OffsetDateTime/Instant.
⚠ Follow-up traps
Which should a "created at" audit field use?Instant (or UTC offset), never LocalDateTime without agreed zone.
Does an offset capture DST rules? No, only a fixed offset.
#datetime#zones
Q32
Duration versus Period?
basic
Duration is time-based (seconds and nanos), used with Instant/LocalTime. Period is date-based (years, months, days), used with LocalDate.
ChronoUnit.DAYS.between(a, b) gives total units.
Adding Period.ofDays(1) to a ZonedDateTime keeps local time across DST; Duration.ofHours(24) adds exactly 24h.
⚠ Follow-up traps
Does Duration.ofDays(1) equal one calendar day? It equals 24 hours exactly; a calendar day can be 23 or 25 hours across DST.
Can you add Period to an Instant? No, UnsupportedTemporalTypeException.
#datetime#duration#period
Q33
How do you parse and format dates safely?
intermediate
Use DateTimeFormatter constants; they are immutable and thread-safe. Parsing is strict about the target type: a pattern without a time part cannot parse into LocalDateTime.
DateTimeFormatter f = DateTimeFormatter.ofPattern("dd-MM-uuuu");LocalDate d = LocalDate.parse("15-08-2024", f);
Prefer uuuu over yyyy (year-of-era) when using ResolverStyle.STRICT.
ISO formats have built-ins: DateTimeFormatter.ISO_LOCAL_DATE.
⚠ Follow-up traps
What does LocalDate.parse("2024-02-30") do? Throws DateTimeParseException.
MM vs mm? Month versus minute.
#datetime#formatter#parsing
Q34
What is CompletableFuture and how does it differ from Future?
intermediate
Future only supports blocking get(). CompletableFuture is a composable promise: chain stages with thenApply, thenCompose, thenCombine, handle errors with exceptionally/handle, and complete manually with complete.
supplyAsync/runAsync run on ForkJoinPool.commonPool() by default; pass an Executor for IO-bound work.
allOf/anyOf coordinate several futures.
⚠ Follow-up traps
thenApply or thenCompose for a function returning a future?thenCompose, else you get CompletableFuture<CompletableFuture<T>>.
Does supplyAsync use a dedicated pool? No, the shared common pool unless given an executor.
#completablefuture#async
Q35
How does exception handling work in CompletableFuture?
advanced
An exception completes the stage exceptionally and skips downstream thenApply stages. exceptionally(fn) recovers; handle((v, ex) -> ...) sees both; whenComplete observes without changing the result.
join() throws CompletionException wrapping the cause; get() throws ExecutionException.
Inside exceptionally the throwable of dependent stages is often wrapped in CompletionException; unwrap with getCause().
orTimeout and completeOnTimeout (Java 9) add timeouts.
⚠ Follow-up traps
Are exceptions in runAsync logged automatically? No, they are swallowed unless something observes the future.
Does whenComplete swallow exceptions? No, it propagates the original.
#completablefuture#exceptions
Q36
What is the difference between thenApply, thenCompose and thenCombine?
intermediate
thenApply maps the value (like map). thenCompose flattens a function returning another future (like flatMap). thenCombine joins two independent futures with a bi-function once both finish.
thenApplyAsync variants run the stage on another executor thread rather than the completing thread.
Non-async callbacks may run on the thread that completes the future, or the caller if already complete.
⚠ Follow-up traps
Are thenCombine futures run in parallel? They are independent; they run concurrently if each was started with async.
Which thread runs a plain thenApply? The completing thread, or the caller if already done.
#completablefuture#composition
Q37
What is a closure and how do lambdas implement it?
intermediate
A closure is a function bundled with the variables it captured from its enclosing scope. In Java, captured locals are copied as values into the lambda instance, which is why they must be effectively final.
Function<Integer, Integer> adder(int n) { return x -> x + n; }adder(5).apply(3); // 8
Captured this makes a lambda hold the enclosing object, which can leak memory if the lambda is stored long-term.
⚠ Follow-up traps
Does a long-lived lambda keep the outer object alive? Yes, if it uses instance members.
Can closures capture loop variables? Enhanced-for variables, yes (new per iteration); a classic for counter, no.
#closure#lambda#capture
Q38
What are Java records?
basic
A record (final in Java 16) is a transparent immutable data carrier: record Point(int x, int y) {} generates private final fields, canonical constructor, accessors x(), y(), equals, hashCode and toString.
Compact constructor validates or normalizes: record Range(int lo, int hi) { Range { if (lo > hi) throw new IllegalArgumentException(); } }.
Records cannot extend classes, cannot declare instance fields beyond components, but can implement interfaces and have static members.
⚠ Follow-up traps
Are records deeply immutable? No, a List component can still be mutated; copy with List.copyOf in the constructor.
Can a record be a JPA entity? No, entities need a no-arg constructor and mutability; use records for DTOs and projections.
#records#java16
Q39
Explain pattern matching for instanceof and switch.
intermediate
instanceof patterns (Java 16) bind a typed variable: if (o instanceof String s && s.length() > 3). Switch patterns (Java 21) match on type and guard with when, and the switch is an expression that must be exhaustive.
String describe(Object o) { return switch (o) { case null -> "null"; case Integer i when i > 10 -> "big " + i; case Integer i -> "int " + i; case String s -> "str " + s; default -> "other"; };}
Case order matters; a dominated pattern is a compile error.
⚠ Follow-up traps
Does switch (o) accept null? Not by default (NPE) unless case null is present in Java 21.
Scope of the pattern variable? Only where the compiler can prove the match succeeded.
#pattern-matching#java21
Q40
What are record patterns?
advanced
Record patterns (final in Java 21) deconstruct a record in a pattern, optionally nested.
sealed interface Shape permits Circle, Rect {}record Circle(double r) implements Shape {}record Rect(double w, double h) implements Shape {}double area(Shape s) { return switch (s) { case Circle(double r) -> Math.PI * r * r; case Rect(double w, double h) -> w * h; };}
⚠ Follow-up traps
Is default needed above? No, the sealed hierarchy makes the switch exhaustive.
Are record patterns allowed in enhanced for loops? That preview feature was removed in Java 21.
#records#pattern-matching#java21
Q41
What are sealed classes and interfaces?
intermediate
sealed (final in Java 17) restricts which classes can extend or implement a type via permits. Each permitted subtype must be final, sealed or non-sealed.
Permitted subtypes must be in the same module (or same package in the unnamed module); permits can be omitted when all are in the same file.
They give a closed set of subtypes, enabling exhaustive switches without default.
⚠ Follow-up traps
Can a record be a permitted subtype? Yes, records are implicitly final.
Do sealed types prevent reflection-based subclassing? The JVM enforces PermittedSubclasses at class-load time.
#sealed#java17
Q42
What other language additions between Java 9 and 21 matter for interviews?
basic
Key additions: var (10), List.of/Map.of (9), switch expressions (14), text blocks (15), records (16), sealed classes (17), pattern switch and record patterns (21), Stream.toList (16), virtual threads (21), sequenced collections (21).
var is local-only type inference, still statically typed.
Text blocks use """ and strip incidental indentation.
⚠ Follow-up traps
Is var dynamic typing? No, the type is inferred at compile time.
Can var be used for lambdas? No, there is no target type.
#java-versions#language-features
Q43
How do switch expressions differ from statements?
intermediate
A switch expression yields a value, uses arrow cases with no fall-through, and must be exhaustive. Use yield inside a block.
int days = switch (month) { case FEB -> 28; case APR, JUN, SEP, NOV -> 30; default -> { log(month); yield 31; }};
⚠ Follow-up traps
Is fall-through possible with arrows? No.
Exhaustive over an enum without default? Yes, but a missing constant added later causes a runtime MatchException/IncompatibleClassChangeError unless recompiled.
#switch#java14
Q44
What are parallel streams and when are they appropriate?
intermediate
parallelStream() splits the source with a Spliterator and runs stages on ForkJoinPool.commonPool() using fork/join, then combines results. It helps for CPU-bound, stateless, associative work on large, cheaply splittable sources.
Rule of thumb: N x Q > 10,000 (elements times per-element cost).
⚠ Follow-up traps
Is parallel always faster? No, splitting, merging and thread handoff can dominate for small or cheap work.
How many threads? Common pool parallelism = cores - 1, plus the calling thread.
#parallel-stream#forkjoin
Q45
What are the pitfalls of parallel streams?
advanced
Shared common pool contention, non-thread-safe accumulation, order-dependent logic, blocking I/O inside tasks and unpredictable ordering.
Blocking calls in a parallel stream starve the shared pool used by every other parallel stream and CompletableFuture default.
Mutating shared state (forEach(list::add)) causes lost updates; use collect.
forEach is unordered; forEachOrdered preserves order but loses parallel benefit.
Isolation trick: submit the terminal operation to a custom ForkJoinPool; it relies on implementation behaviour.
⚠ Follow-up traps
Does findFirst work in parallel? Yes but it forces ordering and can be slow; findAny is cheaper.
Does parallel() on a stream with a later sequential() call use both? The last call wins for the whole pipeline.
#parallel-stream#pitfalls#concurrency
Q46
What is a Spliterator and how do characteristics affect streams?
advanced
A Spliterator traverses (tryAdvance, forEachRemaining) and splits (trySplit) a source. Its characteristics (ORDERED, SIZED, SORTED, DISTINCT, IMMUTABLE, CONCURRENT, SUBSIZED, NONNULL) let the framework skip work.
ArrayList is SIZED | SUBSIZED | ORDERED, so splitting is balanced and count() can be O(1).
HashSet is not ordered; sorted() on a TreeSet source can skip sorting (SORTED).
Stream.count() may skip the pipeline entirely if size is known and no filtering stage exists (Java 9+), which means peek may not run.
⚠ Follow-up traps
Does list.stream().map(...).count() run the map in Java 9+? No, the size is known so it can skip execution.
Why is LinkedList poor in parallel? It splits by walking, giving uneven halves.
Specialised streams avoid boxing and offer numeric terminals: sum, average, min, max, summaryStatistics. Convert with mapToInt, mapToObj, boxed.
int total = orders.stream().mapToInt(Order::qty).sum();IntStream.rangeClosed(1, 5).sum(); // 15
average() returns OptionalDouble; sum() of an empty stream is 0.
IntStream.sum() overflows silently; use LongStream or mapToLong for large totals.
⚠ Follow-up traps
Is Stream<Integer>.sum() available? No; use mapToInt(Integer::intValue).sum() or reduce.
range vs rangeClosed? The end is exclusive versus inclusive.
#primitive-streams#boxing
Q48
How do iterate, generate, takeWhile and dropWhile work?
intermediate
Stream.iterate(seed, f) and Stream.generate(supplier) produce infinite streams; iterate(seed, hasNext, next) (Java 9) is bounded like a for loop. takeWhile/dropWhile slice by predicate on ordered streams.
Stream.iterate(1, i -> i < 100, i -> i * 2).toList(); // [1,2,4,...,64]Stream.of(1, 2, 5, 1).takeWhile(i -> i < 3).toList(); // [1, 2]
⚠ Follow-up traps
takeWhile versus filter?takeWhile stops at the first failure; filter continues scanning.
Is generate safe in parallel? It is unordered and the supplier must be thread-safe.
#stream#infinite#java9
Q49
What does peek do and when should it be used?
basic
peek(Consumer) is an intermediate operation intended for debugging: it observes elements as they flow. It must not be relied on for side effects in production because it may be skipped (e.g. count() optimisation) or run out of order in parallel.
⚠ Follow-up traps
Can peek mutate elements? It can mutate mutable objects, but that is a code smell.
Does peek run if a short-circuit op stops early? Only for elements actually pulled.
#stream#peek#debugging
Q50
What are the rules for non-interference and side effects in streams?
advanced
Behavioural parameters must be non-interfering (do not modify the source during the pipeline) and, for most operations, stateless. Modifying an ArrayList source during traversal throws ConcurrentModificationException, usually at the terminal operation.
Late-binding: a stream sourced from a collection reads the collection at terminal-op time, so modifications before the terminal op are visible.
Side effects in map/filter hurt parallel correctness and readability.
⚠ Follow-up traps
Can you modify the source in forEach of a concurrent collection? Weakly consistent iterators tolerate it, but results are not deterministic.
Is list.stream() evaluated when called? The source is bound at the terminal operation, not at stream() for most collections.
#stream#non-interference#side-effects
Q51
What is the difference between findFirst and findAny, and what does ordering mean in streams?
intermediate
Streams from ordered sources (List, arrays) have an encounter order. findFirst returns the first in that order. findAny may return any element, which is cheaper in parallel.
unordered() removes the constraint and can speed up distinct, limit and collect in parallel.
HashSet streams have no meaningful encounter order.
⚠ Follow-up traps
Will findAny on a sequential stream return the first? Usually, but it is not guaranteed.
Does map preserve order? Yes, ordering is preserved through stateless ops.
#stream#ordering#findfirst
Q52
Which functional-style design practices improve backend code?
intermediate
Prefer pure functions and immutable data, push side effects to the edges, keep lambdas short (extract a named method or method reference), and avoid streams where a plain loop is clearer.
Use Function/Supplier for strategy-like variation instead of class hierarchies.
Name complex predicates (Predicate<Order> isOverdue = ...).
Avoid checked exceptions in lambdas by wrapping in a small adapter, not by sneaky throws.
⚠ Follow-up traps
Are streams always better than loops? No; loops are clearer for indexed, early-exit or multi-accumulator logic.
Can a lambda throw a checked exception? Only if the functional interface's method declares it.
#functional#best-practices
Q53
How do you handle checked exceptions inside lambdas?
intermediate
Standard functional interfaces declare no checked exceptions. Options: wrap in a try/catch in the lambda and rethrow as unchecked, define a custom functional interface that throws, or use a wrapper helper.
Is wrapping in RuntimeException always fine? It loses typed handling; consider a domain-specific unchecked exception.
What happens to the stream when one element throws? The pipeline aborts and no partial result is returned.
#lambda#exceptions#checked
Scenarios
Q54
What happens when a stream is consumed twice?
basic
The second terminal call throws IllegalStateException: stream has already been operated upon or closed.
Stream<String> s = Stream.of("a", "b");s.count();s.count(); // IllegalStateException
Fix: create a new stream from the source, or use a Supplier<Stream<T>>.
⚠ Follow-up traps
Does calling an intermediate op twice on the same stream reference fail? Yes, e.g. s.filter(..) then s.map(..).
Can you collect to a list and stream again? Yes, that is the usual workaround.
#stream#reuse
Q55
What is printed by this lazy pipeline?
intermediate
Nothing, because there is no terminal operation.
Stream.of(1, 2, 3).peek(System.out::println).map(i -> i * 2);
Adding .toList() prints 1 2 3, one per line, in order.
⚠ Follow-up traps
What with .count() instead on a List.stream() source? In Java 9+ nothing prints for sized sources without filter, because count is computed from the size.
What with .filter before count? It prints, because the size is no longer known.
#stream#lazy#peek
Q56
What is the output of interleaved filter and map?
intermediate
Elements flow one at a time through the whole chain.
Stream.of("x", "yy", "zzz") .filter(s -> { System.out.print("F" + s + " "); return s.length() > 1; }) .map(s -> { System.out.print("M" + s + " "); return s; }) .collect(Collectors.toList());// Fx Fyy Myy Fzzz Mzzz
⚠ Follow-up traps
Would sorted() in the middle change the interleaving? Yes, everything before sorted completes first.
Same output for a parallel stream? No, the order of prints is nondeterministic.
#stream#ordering
Q57
toMap throws on a duplicate; how do you build a lookup by id safely?
intermediate
Two users share an email, so toMap(User::email, Function.identity()) throws IllegalStateException. Decide the policy and encode it in the merge function.
Map<String, User> latest = users.stream().collect(Collectors.toMap( User::email, u -> u, BinaryOperator.maxBy(Comparator.comparing(User::updatedAt))));
Or group into Map<String, List<User>> if duplicates are legitimate.
Silently keeping (a, b) -> a hides data problems; log or reject if duplicates are unexpected.
⚠ Follow-up traps
What if a value is null?toMap throws NPE; use groupingBy or filter nulls.
Which map type results?HashMap; pass a 4th argument for LinkedHashMap/TreeMap.
#collectors#tomap#backend
Q58
Aggregate order totals per customer.
basic
Use groupingBy with summingDouble, or with BigDecimal use reducing.
Both keys exist even when the list is empty, so get never returns null.
Use partitioningBy(p, counting()) for counts only.
⚠ Follow-up traps
Is the returned map modifiable? Do not rely on it; treat it as read-only.
Two filters instead? That traverses twice and evaluates the predicate twice.
#partitioningby#validation
Q63
Compute count and average in one pass.
advanced
Use teeing or summarizingDouble.
var r = prices.stream().collect(Collectors.teeing( Collectors.summingDouble(Double::doubleValue), Collectors.counting(), (sum, n) -> n == 0 ? 0.0 : sum / n));
DoubleSummaryStatistics gives min, max, sum, count and average without teeing.
Guard against divide-by-zero on an empty stream.
⚠ Follow-up traps
What does average() return for empty input?OptionalDouble.empty().
Why not call stream() twice? A one-shot source (e.g. Files.lines) cannot be replayed.
#teeing#statistics
Q64
A parallel stream adds to an ArrayList. What happens?
intermediate
Behaviour is undefined: lost elements, ArrayIndexOutOfBoundsException, or null slots.
List<Integer> out = new ArrayList<>();IntStream.range(0, 100_000).parallel().forEach(out::add); // unsafe
Fix: .boxed().collect(Collectors.toList()), which uses per-thread containers.
Collections.synchronizedList would be correct but contended.
⚠ Follow-up traps
Would Vector fix it? It is thread-safe but ordering is still arbitrary.
Is a sequential stream fine with forEach(out::add)? Works, but collect is clearer.
#parallel-stream#thread-safety
Q65
A parallel stream calls a slow HTTP service per element. Why is the whole app slow?
advanced
Blocking threads of the shared ForkJoinPool.commonPool() (cores - 1 threads) starve every other parallel stream and default CompletableFuture task in the JVM.
Use a dedicated executor with CompletableFuture.supplyAsync(task, ioExecutor), or virtual threads (Java 21) for blocking I/O.
Bound concurrency, apply timeouts and rate-limit downstream.
Streams are for CPU-bound data processing, not I/O fan-out.
⚠ Follow-up traps
Can you set the pool size for a parallel stream? Via -Djava.util.concurrent.ForkJoinPool.common.parallelism, or by running inside a custom ForkJoinPool.
Does parallel() guarantee more threads than cores? No.
#parallel-stream#commonpool#io
Q66
Result of reduce with a non-associative operation in parallel?
advanced
IntStream.rangeClosed(1, 4).parallel().reduce(0, (a, b) -> a - b);
Sequentially this is ((((0-1)-2)-3)-4) = -10. In parallel the pipeline splits and the combiner subtracts partial results, so the answer can differ (for example -2 or others) from run to run.
Operators must be associative; identity must satisfy op(identity, x) == x.
reduce(1, (a, b) -> a + b) has a non-neutral identity and returns different results when split.
⚠ Follow-up traps
Is Integer::sum safe? Yes, associative and commutative.
Is string concat associative? Yes but not commutative; order must be preserved, which reduce does for ordered streams.
#reduce#parallel-stream#associativity
Q67
What does this Optional code print?
intermediate
Optional<String> o = Optional.of("a");String r = o.orElse(compute());
compute() is executed even though o is present, so any side effect or cost happens. With orElseGet(this::compute) it is skipped.
⚠ Follow-up traps
Does orElseThrow(() -> new X()) construct the exception when present? No, the supplier is lazy.
Better still: design domain types to avoid null in the first place.
Allocation of Optional objects is negligible outside hot loops.
⚠ Follow-up traps
What does map do if Order::customer returns null? Chain becomes empty and falls to orElse.
Should Order.customer() itself return Optional? Only if absence is a legitimate state.
#optional#refactoring
Q69
A repository method returns Optional<List<Order>>. What is wrong?
basic
Collections already express "none" with an empty collection, so double wrapping forces callers to handle two empty states. Return List<Order> and never return null.
Spring Data findAll... returns empty collections, not null.
Reserve Optional for single-value lookups like findById.
⚠ Follow-up traps
Optional vs OptionalInt? The primitive variant avoids boxing.
What about Optional<Map<..>>? Same issue as lists.
#optional#antipatterns#collections
Q70
Optional.get() throws in production. What are the safer options?
basic
get() on empty throws NoSuchElementException. Prefer orElseThrow() (no-arg, Java 10) which signals intent, or a domain exception.
User u = repo.findById(id) .orElseThrow(() -> new UserNotFoundException(id));
Use ifPresentOrElse when both branches have side effects.
Do not guard get with isPresent as a habit; map/orElse are clearer.
⚠ Follow-up traps
Does orElseThrow() differ from get()? Same behaviour, but the name signals that the throw is intentional.
Does orElseThrow(Supplier) allow checked exceptions? Yes, <X extends Throwable>.
#optional#get#nosuchelement
Q71
A field uses Optional in a JPA entity. What happens?
intermediate
JPA/Hibernate does not map Optional<T> fields: you get a mapping error or the field is treated as a basic type with no converter. The usual practice is a nullable field with an Optional getter.
Result: sorted set of all distinct tags. A nulltags() list would throw NPE, so return an empty list or wrap with Optional.ofNullable(...).stream().flatMap(List::stream).
⚠ Follow-up traps
map(o -> o.tags().stream()) instead? You get Stream<Stream<String>>, not flattened.
Is flatMap lazy for infinite inner streams? Before Java 10, inner streams were fully consumed even with limit; fixed in Java 10.
HashMap itself allows null keys, but groupingBy rejects them explicitly.
partitioningBy has no such issue because keys are booleans.
⚠ Follow-up traps
Can toMap have null keys? Keys may be null in HashMap.merge? The value must be non-null; null values throw NPE.
How to avoid the sentinel? Filter nulls into a separate bucket via partitioningBy first.
#groupingby#null
Q76
Why does this lambda not compile, and how do you fix it?
basic
int total = 0;list.forEach(x -> total += x); // error: local variables referenced from a lambda must be final or effectively final
Fix with a terminal operation: int total = list.stream().mapToInt(Integer::intValue).sum();. Using AtomicInteger compiles but is an anti-pattern for what is a reduction.
⚠ Follow-up traps
Would int[] t = {0}; list.forEach(x -> t[0] += x); compile? Yes, but it is unsafe in parallel and unidiomatic.
Would it work if total was a field? Yes, fields are not captured by copy.
#effectively-final#compile-error
Q77
What does this loop-capture code print?
intermediate
List<Runnable> rs = new ArrayList<>();for (int i = 0; i < 3; i++) { int copy = i; rs.add(() -> System.out.print(copy + " "));}rs.forEach(Runnable::run); // 0 1 2
Using i directly does not compile because i is mutated. With an enhanced for (int v : arr), each iteration has a fresh variable so capture works.
⚠ Follow-up traps
Does JavaScript-style "all print 3" happen in Java? No, captures are by value.
Is copy effectively final? Yes, assigned once per iteration.
#closure#loops#capture
Q78
Two interfaces provide the same default method. What happens?
intermediate
class C implements A, B where both have default hi() fails with "types A and B are incompatible; class C inherits unrelated defaults for hi()". Override in C and delegate with A.super.hi().
If B extends A and overrides hi(), B wins; no error.
If C extends a class that defines hi(), the class method wins.
⚠ Follow-up traps
If A is an abstract class instead of interface? A class method (abstract or concrete) wins; an abstract one forces C to implement.
Can C call A.super.hi() if it does not directly implement A? No.
#default-methods#diamond#compile-error
Q79
A default method was added to a widely implemented interface. What can still break?
advanced
Source and binary compatibility is mostly kept, but conflicts arise if an implementer's superclass or another interface already declares a same-signature method with a different return type, or incompatible final/visibility semantics (default methods are public).
Class methods that are not public cannot implement an interface method (compile error).
Libraries such as Collection.removeIf (Java 8) clashed with some custom collections that already had removeIf.
⚠ Follow-up traps
Can a default method call abstract methods of the interface? Yes, template-method style.
Is it safe to add an abstract method instead? No, it breaks all implementers.
#default-methods#compatibility
Q80
Debug a stream pipeline that returns wrong results.
intermediate
Split the pipeline into named intermediate variables or use peek temporarily to see element flow. Remember laziness: a debugger breakpoint in a lambda is hit per element, interleaved.
IntelliJ's "Trace Current Stream Chain" visualises each stage.
Extract complex lambdas into named methods so stack traces are readable.
Remove peek before shipping.
⚠ Follow-up traps
Why does a breakpoint hit inside map only after the terminal op? Laziness.
Why are stack traces so deep? Pipeline stages and ReferencePipeline frames sit between your lambda and the caller.
#debugging#peek#streams
Q81
Why does a stream over a modified list throw?
intermediate
list.stream().forEach(x -> { if (x < 0) list.remove(x); });
Throws ConcurrentModificationException (for ArrayList) because the source is structurally modified during traversal, violating non-interference. Use list.removeIf(x -> x < 0) or collect into a new list.
⚠ Follow-up traps
When is the exception raised? Often at the end of forEachRemaining, not at the removal itself.
Does a stream over CopyOnWriteArrayList throw? No, it iterates a snapshot.
#stream#concurrentmodificationexception
Q82
What does `list.stream().map(this::expensive).count()` do in Java 17?
advanced
Because the list is SIZED and map preserves size, count() returns the size directly (Java 9+) and expensive is never called. In Java 8 it was always executed.
Do not rely on side effects inside map/peek to be executed.
Adding a filter or flatMap removes the size info and the pipeline runs.
⚠ Follow-up traps
How do you force execution? Use a forEach/collect or a non-sized stage.
Does this apply to limit?limit also affects size info; behaviour depends on the stages.
#count#spliterator#optimisation
Q83
Compare a for loop and a stream for a hot path.
advanced
For tight loops over small data a plain loop is usually faster: no lambda allocation, no pipeline objects, better JIT inlining. For moderate data the difference is negligible and clarity matters more.
Boxed streams (Stream<Integer>) are much slower than IntStream or arrays.
Streams add ~tens of nanoseconds of setup; irrelevant outside very hot paths.
Measure with JMH, not System.nanoTime in main; JIT warm-up skews results.
Parallel only after measuring.
⚠ Follow-up traps
Are streams O(n) slower? Same complexity; constant factors differ.
Do capturing lambdas allocate in a loop? Yes, one instance per evaluation unless the JIT's escape analysis removes it.
#performance#streams#loops
Q84
Why is boxing a hidden cost in stream code?
intermediate
Stream<Integer> stores Integer objects; sum via reduce(0, Integer::sum) unboxes and reboxes. Use mapToInt(...).sum() to stay primitive.
Integer cache covers only -128..127; others allocate.
Collectors.summingInt and averagingInt avoid reboxing of the accumulator via arrays internally.
⚠ Follow-up traps
Does IntStream.boxed() cost? Yes, allocation per element.
Can the JIT remove boxing? Sometimes via escape analysis, not reliably across stream stages.
#performance#boxing#primitive-streams
Q85
What is wrong with using a stream for a simple existence check?
basic
list.stream().filter(p).count() > 0 scans the whole list. anyMatch(p) stops at the first hit.
For membership use Set.contains (O(1)) instead of a stream over a list.
noneMatch(p) is the negative form.
⚠ Follow-up traps
filter(p).findFirst().isPresent() vs anyMatch? Equivalent behaviour; anyMatch is clearer.
allMatch on an empty list?true.
#anymatch#performance
Q86
Convert a List<Order> to Map<id, Order> while preserving insertion order.
basic
Map<Long, Order> byId = orders.stream().collect(Collectors.toMap( Order::id, Function.identity(), (a, b) -> { throw new IllegalStateException("dup id " + a.id()); }, LinkedHashMap::new));
A custom merge function that throws gives a clearer message and preserves order via the map supplier. The default toMap would yield a HashMap.
⚠ Follow-up traps
Can Function.identity() be replaced by o -> o? Equivalent.
Is Collectors.toUnmodifiableMap an option? Yes, immutable and rejects nulls.
#tomap#linkedhashmap
Q87
Parallel stream combined with a stateful lambda, what goes wrong?
advanced
AtomicInteger seq = new AtomicInteger();list.parallelStream().map(x -> x + "-" + seq.incrementAndGet()).toList();
Counter values are assigned in execution order, not encounter order, so the output list order is preserved but the numbers are scrambled. The result changes run to run.
Use IntStream.range with indices, or assign ids after collecting.
Stream.toList() keeps encounter order for ordered sources even in parallel.
⚠ Follow-up traps
Is the result list order preserved? Yes, collection into a list respects encounter order.
Is AtomicInteger here a memory-safety issue? No, a determinism issue.
#parallel-stream#stateful#nondeterminism
Q88
Process a very large file with streams.
intermediate
Files.lines(path) is lazy and constant-memory, but it holds an open file handle; close it with try-with-resources.
Using thenApply for the second call would give a nested future. Always pass an executor for blocking clients.
⚠ Follow-up traps
What happens if userClient.get throws? Downstream stages are skipped; join() throws CompletionException.
Does thenCompose run the inner supplier on io? Only because it is explicitly passed to supplyAsync.
#completablefuture#composition#backend
Q90
Run three independent calls in parallel and wait for all.
intermediate
var a = CompletableFuture.supplyAsync(this::loadProfile, io);var b = CompletableFuture.supplyAsync(this::loadOrders, io);var c = CompletableFuture.supplyAsync(this::loadPrefs, io);CompletableFuture.allOf(a, b, c).join();Page page = new Page(a.join(), b.join(), c.join());
allOf returns CompletableFuture<Void>; read results from each future afterwards. If any fails, join() on allOf throws CompletionException, but the other tasks keep running (no cancellation propagation).
⚠ Follow-up traps
Does allOf fail fast? No, it completes when all complete.
How to add a timeout?.orTimeout(2, TimeUnit.SECONDS) (Java 9).
#completablefuture#allof
Q91
An exception is thrown in a supplyAsync; the caller sees nothing. Why?
advanced
supplyAsync(...) returns a future; if nobody calls join/get or attaches exceptionally/handle, the failure is captured silently inside the future. With runAsync fire-and-forget this goes unnoticed.
get() wraps in ExecutionException, join() in CompletionException.
⚠ Follow-up traps
Does exceptionally receive the raw exception? Often a CompletionException wrapping it, if from a dependent stage.
Does whenComplete recover? No, it observes only.
#completablefuture#exceptions#swallowed
Q92
LocalDateTime stored in DB gives wrong times across regions. Why?
intermediate
LocalDateTime has no zone, so the stored value is interpreted in whatever zone the server, driver or reader uses. Services in different zones disagree on the instant.
Store Instant (UTC) and convert to a ZoneId only at presentation.
Use ZonedDateTime when the local wall clock and rule matter (e.g. a recurring 09:00 meeting in Europe/Berlin).
Set hibernate.jdbc.time_zone=UTC for consistent behaviour.
⚠ Follow-up traps
Is LocalDate for a birthday fine? Yes, it has no time component.
Is JVM default timezone reliable? No, avoid ZoneId.systemDefault() in business logic.
#datetime#timezone#bugs
Q93
Add one day across the daylight-saving change. What is the result?
advanced
ZonedDateTime t = ZonedDateTime.of(2024, 3, 9, 12, 0, 0, 0, ZoneId.of("America/New_York"));t.plusDays(1); // 2024-03-10T12:00-04:00 (local time kept)t.plus(Duration.ofHours(24)); // 2024-03-10T13:00-04:00
plusDays works on the local timeline; Duration works on the instant timeline. The DST gap on 10 March skips one hour, so 24 elapsed hours land at 13:00.
⚠ Follow-up traps
What does an invalid local time in the gap resolve to? It is shifted forward by the gap length.
Ambiguous time in the overlap? The earlier offset is chosen by default.
#datetime#dst#duration
Q94
Compute the days between two dates, and age from a birthdate.
basic
long days = ChronoUnit.DAYS.between(LocalDate.of(2024, 1, 1), LocalDate.of(2024, 3, 1)); // 60int age = Period.between(birthDate, LocalDate.now(clock)).getYears();
2024 is a leap year, hence 31 + 29 = 60 days. Period.getDays() returns only the day component, not the total.
⚠ Follow-up traps
Why inject a Clock?LocalDate.now(clock) makes time-dependent logic testable.
Does Period.between give total days? No, it gives years/months/days parts.
#datetime#period#chronounit
Q95
SimpleDateFormat in a shared static field causes random failures. Why?
intermediate
SimpleDateFormat keeps an internal Calendar, so concurrent parse/format calls corrupt each other and give wrong dates or NumberFormatException. Replace with the immutable DateTimeFormatter, which is safe to share as a constant.
⚠ Follow-up traps
Would ThreadLocal<SimpleDateFormat> work? Yes, but DateTimeFormatter is simpler.
Does DateTimeFormatter produce Date? No, java.time types; convert if legacy APIs need Date.
#datetime#thread-safety#legacy
Q96
What happens with an unsorted-parallel `findFirst` vs `findAny`?
intermediate
findFirst on an ordered parallel stream must return the first element in encounter order, so later splits may be discarded after the first split finishes; it is deterministic but slower. findAny may return any matching element, differing between runs.
For search-where-any-is-fine (e.g. any matching record), use findAny.
Calling .unordered() lets other ops be cheaper too.
⚠ Follow-up traps
Same output on a single-element match? Yes, both return it.
Is findAny faster sequentially? No difference.
#parallel-stream#findany#ordering
Q97
Convert a record to JSON-ready DTO with validation.
intermediate
public record CreateOrder(String sku, int qty, List<String> tags) { public CreateOrder { Objects.requireNonNull(sku); if (qty <= 0) throw new IllegalArgumentException("qty must be > 0"); tags = List.copyOf(tags); }}
The compact constructor assigns fields at the end; reassigning the parameter tags is how you normalise.
Jackson 2.12+ and Spring Boot 3 deserialize records natively.
List.copyOf makes a defensive immutable copy and rejects null elements.
⚠ Follow-up traps
Does the record toString hide fields? No, include any sensitive fields in toString automatically; override to mask.
Generated equals compares components with equals (primitives by value). Arrays as components would compare by reference, so record R(int[] a) gives false for equal contents.
⚠ Follow-up traps
Can you override equals in a record? Yes, but keep it consistent with hashCode.
Is hashCode stable across runs? The algorithm is unspecified; do not persist it.
#records#equals
Q99
Replace an instanceof chain with a pattern switch.
intermediate
sealed interface Event permits Created, Cancelled, Shipped {}record Created(String id) implements Event {}record Cancelled(String id, String reason) implements Event {}record Shipped(String id, String carrier) implements Event {}String log(Event e) { return switch (e) { case Created c -> "created " + c.id(); case Cancelled(var id, var why) -> "cancelled " + id + ": " + why; case Shipped s -> "shipped via " + s.carrier(); };}
No default, so adding a new permitted subtype causes a compile error at every switch, which is the intent of sealed hierarchies.
⚠ Follow-up traps
What if you add default? The compiler stops flagging new subtypes; avoid it for sealed types.
Is the switch null-safe? No; a null throws NPE unless case null is added.
#pattern-matching#switch#refactoring
Q100
In a pattern switch, why does this fail to compile?
advanced
switch (o) { case Number n -> ...; case Integer i -> ...; // error: this case label is dominated by a preceding case label default -> ...;}
A more general pattern before a specific one makes the specific one unreachable. Order specific first, general last; guarded patterns (when) go before their unguarded counterparts.
⚠ Follow-up traps
Does a guard like case Integer i when i > 0 dominate case Integer i? No, the guarded one does not dominate; the reverse order would be an error.
Can default appear before other cases? In Java 21 it must not dominate patterns; place it last.
#pattern-matching#dominance#compile-error
Q101
Model API results with sealed types instead of exceptions or nulls.
Callers cannot forget the failure branch. Use for expected business outcomes (validation, not found); keep exceptions for unexpected faults.
⚠ Follow-up traps
Does it replace Optional? Optional is for absence; a result type carries a reason.
Is it idiomatic in Spring controllers? Fine inside the service layer; map to HTTP status at the edge.
#sealed#design#result-type
Q102
Mixing `sorted` with `limit` on a big stream, what is the cost?
intermediate
sorted().limit(k) buffers all n elements and sorts them in O(n log n), although the JDK has a top-k optimisation in some paths. For top-k over huge data, keep a bounded PriorityQueue of size k or push ORDER BY ... LIMIT into SQL.
PriorityQueue<Order> heap = new PriorityQueue<>(Comparator.comparing(Order::amount));for (Order o : orders) { heap.add(o); if (heap.size() > 3) heap.poll(); }
⚠ Follow-up traps
limit().sorted() vs sorted().limit()? Different semantics: the first sorts only the first k elements.
Memory for distinct() on a huge stream? Proportional to unique elements.
#performance#sorted#limit
Q103
Why can `Collectors.toList()` be risky when the caller later mutates the result?
intermediate
The returned list type is unspecified; today it is ArrayList, so mutation works, but this is not guaranteed. If your API promises immutability, return Stream.toList() or List.copyOf, and document it.
What happens on add to Stream.toList()?UnsupportedOperationException.
Do unmodifiable wrappers copy?Collections.unmodifiableList is a view; List.copyOf is a copy.
#collectors#immutability#apis
Q104
Return a stream from a repository method. What can go wrong?
advanced
Spring Data JPA can return Stream<T> from query methods, but the stream keeps a cursor open and must be consumed inside a transaction and closed. Using it after the transaction ends gives errors, and forgetting to close leaks connections.
@Transactional(readOnly = true)public long countActive() { try (Stream<User> s = repo.streamAllActive()) { return s.filter(User::verified).count(); }}
⚠ Follow-up traps
Is it truly lazy fetching? Depends on JDBC fetch size; MySQL needs useCursorFetch or Integer.MIN_VALUE.
Return the stream from a controller? No; materialise first.
#stream#resources#spring-data
Q105
What is printed by `Stream.of(1,2,3,4).takeWhile(...)` vs `filter(...)`?