Chaturmind
LearnDSASystem DesignInterview PrepDevOpsEngineering GrowthBlog
Start learning
Chaturmind

Structured learning paths for engineers who want to go deep. Written by practitioners.

Learn

  • Java
  • DSA
  • System Design
  • Spring Boot
  • AI / ML
  • DevOps
  • Engineering Growth
  • Java Interview Prep

Company

  • Blog
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Chaturmind. All rights reserved.

Built for engineers who want to go deep.


← Java Interview Prep: 8+ Years (Senior & Lead)

Expert Core Java

  • Tricky Java Output, Operators & OOP Edge Cases — Interview Questions
  • Tricky Exceptions, Memory & Keyword Questions — Interview Questions
  • Classic Java Language Questions, Senior-Grade Answers — Interview Questions
  • Classic Collections, Threads & JDK APIs, Senior-Grade Answers — Interview Questions
  • Reflection, Dynamic Proxies, final & Modern OOP Design — Interview Questions

JVM Internals & Performance

  • Class Loading, Bytecode & Object Layout — Interview Questions
  • JIT Compilation & Runtime Optimisations — Interview Questions
  • Garbage Collectors Deep Dive — Interview Questions
  • JVM Tuning, GC Logs & Memory Footprint — Interview Questions
  • Memory Leaks, OutOfMemoryErrors & Profiling Tools — Interview Questions
  • Modules, Agents & Advanced JVM APIs — Interview Questions

Collections & Concurrency at Scale

  • Collections Internals & Complexity — Interview Questions
  • Iterators, Comparators & Ordering Contracts — Interview Questions
  • Concurrent Collections, Queues & Lock-Free Structures — Interview Questions
  • Threads, Executors & ForkJoin Internals — Interview Questions
  • Locks, Atomics, CAS & Synchronizers — Interview Questions
  • Java Memory Model, volatile, Fences & ThreadLocal — Interview Questions
  • Deadlock, Livelock, Starvation & Concurrent Design — Interview Questions
  • CompletableFuture, Parallel Streams & Non-Blocking I/O — Interview Questions

Modern Java (8 to 21+)

  • Lambdas & Functional Interfaces Internals — Interview Questions
  • Streams & Collectors Deep Dive — Interview Questions
  • Optional & Interface Default/Static Methods — Interview Questions
  • Java 9–25 Features & Virtual Threads — Interview Questions

Design Patterns, SOLID & Clean Code

  • Design Pattern Trade-offs & Combinations — Interview Questions
  • SOLID, Clean Code & Anti-Patterns — Interview Questions

Spring & Spring Boot Internals

  • IoC, Dependency Injection & Bean Lifecycle Internals — Interview Questions
  • Spring AOP, Proxies & @Async Internals — Interview Questions
  • Spring Configuration, Auto-Configuration & Custom Starters — Interview Questions
  • Spring MVC & REST Internals, Exception Frameworks — Interview Questions
  • Spring Security Advanced Internals — Interview Questions
  • Spring WebFlux, Reactor & R2DBC — Interview Questions
  • Spring Cloud, Observability & Distributed Tracing — Interview Questions
  • Spring Boot 3, Native Images & Production Scenarios — Interview Questions

JPA, Hibernate & Databases at Scale

  • Spring Data JPA — Queries, Projections, Custom Repositories & Locking — Interview Questions
  • JPA Entity Mapping, Associations & Cascades — Interview Questions
  • JPQL vs Native Queries in Depth — Interview Questions
  • Hibernate Caching — First-Level, Second-Level & Query Cache — Interview Questions
  • Lazy vs Eager Loading, LazyInitializationException & N+1 — Interview Questions
  • JPA Transactions, Propagation, Isolation & Dirty Checking — Interview Questions
  • SQL vs NoSQL, Indexing & Query Tuning — Interview Questions
  • Database Scaling, Replication, Pooling & Consistency Models — Interview Questions
  • Redis, Search, Time-Series, CDC & Transactional Data Modelling — Interview Questions

Testing Strategy & API Design

  • Spring Boot Test Slices, Context & Test Strategy — Interview Questions
  • Testing Web, Persistence, Security, Async & Messaging in Spring Boot — Interview Questions
  • JUnit 5 & Mockito, Advanced — Interview Questions
Chaturmind
← Java Interview Prep: 8+ Years (Senior & Lead)

Expert Core Java

  • Tricky Java Output, Operators & OOP Edge Cases — Interview Questions
  • Tricky Exceptions, Memory & Keyword Questions — Interview Questions
  • Classic Java Language Questions, Senior-Grade Answers — Interview Questions
  • Classic Collections, Threads & JDK APIs, Senior-Grade Answers — Interview Questions
  • Reflection, Dynamic Proxies, final & Modern OOP Design — Interview Questions

JVM Internals & Performance

  • Class Loading, Bytecode & Object Layout — Interview Questions
  • JIT Compilation & Runtime Optimisations — Interview Questions
  • Garbage Collectors Deep Dive — Interview Questions
  • JVM Tuning, GC Logs & Memory Footprint — Interview Questions
  • Memory Leaks, OutOfMemoryErrors & Profiling Tools — Interview Questions
  • Modules, Agents & Advanced JVM APIs — Interview Questions

Collections & Concurrency at Scale

  • Collections Internals & Complexity — Interview Questions
  • Iterators, Comparators & Ordering Contracts — Interview Questions
  • Concurrent Collections, Queues & Lock-Free Structures — Interview Questions
  • Threads, Executors & ForkJoin Internals — Interview Questions
  • Locks, Atomics, CAS & Synchronizers — Interview Questions
  • Java Memory Model, volatile, Fences & ThreadLocal — Interview Questions
  • Deadlock, Livelock, Starvation & Concurrent Design — Interview Questions
  • CompletableFuture, Parallel Streams & Non-Blocking I/O — Interview Questions

Modern Java (8 to 21+)

  • Lambdas & Functional Interfaces Internals — Interview Questions
  • Streams & Collectors Deep Dive — Interview Questions
  • Optional & Interface Default/Static Methods — Interview Questions
  • Java 9–25 Features & Virtual Threads — Interview Questions

Design Patterns, SOLID & Clean Code

  • Design Pattern Trade-offs & Combinations — Interview Questions
  • SOLID, Clean Code & Anti-Patterns — Interview Questions

Spring & Spring Boot Internals

  • IoC, Dependency Injection & Bean Lifecycle Internals — Interview Questions
  • Spring AOP, Proxies & @Async Internals — Interview Questions
  • Spring Configuration, Auto-Configuration & Custom Starters — Interview Questions
  • Spring MVC & REST Internals, Exception Frameworks — Interview Questions
  • Spring Security Advanced Internals — Interview Questions
  • Spring WebFlux, Reactor & R2DBC — Interview Questions
  • Spring Cloud, Observability & Distributed Tracing — Interview Questions
  • Spring Boot 3, Native Images & Production Scenarios — Interview Questions

JPA, Hibernate & Databases at Scale

  • Spring Data JPA — Queries, Projections, Custom Repositories & Locking — Interview Questions
  • JPA Entity Mapping, Associations & Cascades — Interview Questions
  • JPQL vs Native Queries in Depth — Interview Questions
  • Hibernate Caching — First-Level, Second-Level & Query Cache — Interview Questions
  • Lazy vs Eager Loading, LazyInitializationException & N+1 — Interview Questions
  • JPA Transactions, Propagation, Isolation & Dirty Checking — Interview Questions
  • SQL vs NoSQL, Indexing & Query Tuning — Interview Questions
  • Database Scaling, Replication, Pooling & Consistency Models — Interview Questions
  • Redis, Search, Time-Series, CDC & Transactional Data Modelling — Interview Questions

Testing Strategy & API Design

  • Spring Boot Test Slices, Context & Test Strategy — Interview Questions
  • Testing Web, Persistence, Security, Async & Messaging in Spring Boot — Interview Questions
  • JUnit 5 & Mockito, Advanced — Interview Questions
HomeLearnJava Interview PrepJava Interview Prep: 8+ Years (Senior & Lead)Expert Core Java
✓ FreeAdvanced· 15 min read

Tricky Exceptions, Memory & Keyword Questions — Interview Questions

Senior-level edge cases in exception handling (return in try and finally, finally swallowing exceptions, uncaught exceptions, try without catch, constructor failures, suppressed exceptions, precise rethrow, wrapping), memory and GC (reachability vs scope, compile-time string constants, leaks vs retention, System.gc, finalize and Cleaner, escape analysis, static references, heap vs non-heap, Metaspace OOM) and keywords (blank finals, volatile vs synchronized, static/instance initialiser order, this in static context, strictfp, package-private).

Published September 25, 2026


How to use this lesson

These questions reward precise knowledge of the language specification and the JVM. Several have answers that changed recently:

  • finalize is deprecated for removal.
  • strictfp is redundant since Java 17.

Current answers show you keep up with the platform.

Q1. What happens when both try and finally contain return statements?

Short answer: The finally block's return wins.

  1. The try block's return value is computed and saved.
  2. Then finally runs, and its return replaces that value.

If the try block threw an exception, a return in finally discards the exception entirely.

static int f() {
    try { return 1; }
    finally { return 2; }     // f() returns 2
}
static int g() {
    try { throw new IllegalStateException("lost!"); }
    finally { return 0; }     // g() returns 0; the exception silently disappears
}

Learn it in depth → Exception Handling

Q2. Can a finally block prevent an exception from propagating?

Short answer: Yes, and that's a bug magnet. If finally:

  • returns, breaks or continues, the pending exception is discarded;
  • throws its own exception, that exception replaces the original, which is lost.

Keep finally blocks for cleanup only. Better still, use try-with-resources, which preserves the original exception, and attaches close failures as suppressed exceptions.

Q3. What happens if an exception is thrown, never caught, and there's no finally?

Short answer: It propagates up the call stack, unwinding each frame, until a handler is found. If none is found, the thread dies:

  • The thread's UncaughtExceptionHandler is invoked (or the thread group's, or the default one). The default prints the stack trace to System.err.
  • If it was the main thread and no other non-daemon threads are running, the JVM exits, and the java launcher returns a non-zero exit code (1).
  • In an executor, an exception from a task submitted with submit() is captured in the Future. Nothing is printed unless someone calls get(), which is a common source of silently failing background jobs. execute() lets it reach the handler.

Key points to cover:

  • In production, set Thread.setDefaultUncaughtExceptionHandler to log errors, and always inspect futures, or use CompletableFuture.exceptionally/whenComplete.

Q4. Can you have try without catch or finally?

Short answer: No. A plain try must be followed by at least one catch or a finally. It's a compile error otherwise. The exception is try-with-resources: try (var in = Files.newInputStream(p)) { ... } is legal on its own, because the resource closing acts as an implicit finally.

Q5. What happens if a constructor throws an exception?

Short answer: The object is never returned. The new expression completes abruptly, so the reference is never assigned, and the partially built instance becomes unreachable, and eligible for GC. But watch out for these consequences:

  • Resources opened earlier in the constructor leak, unless you close them in a catch before rethrowing. Prefer static factory methods that acquire resources after validation.
  • If this escaped (registered as a listener, or passed to another thread) before the throw, others can see a half-initialised object.
  • A finalizer attack: a malicious subclass's finalize() can resurrect a partially constructed object. Mitigate by making security-sensitive classes final, validating before calling super(...) (possible since Java 25's flexible constructor bodies), or checking in a static factory.
  • Static initialiser failures are worse: ExceptionInInitializerError the first time, then NoClassDefFoundError on every later use of the class.

Q6. What does try { return 1; } finally { return 2; } return?

Short answer: 2. The finally block always runs after the try block's return value is evaluated, and its own return overrides it. Compilers and linters warn about this pattern (-Xlint:finally, Sonar rule S1143), because it also swallows exceptions.

Q7. What are suppressed exceptions?

Short answer: When try-with-resources closes a resource after the body has already thrown, and close() also throws, the close exception is not allowed to hide the original. It's attached to the primary exception with Throwable.addSuppressed(), and available through getSuppressed(). Stack traces print these as Suppressed: ....

try (var conn = dataSource.getConnection()) {
    throw new SQLException("query failed");          // primary
}   // if conn.close() also fails, its exception is added to the primary's suppressed list
catch (SQLException e) {
    for (Throwable s : e.getSuppressed()) log.warn("close also failed", s);
}

Key points to cover:

  • You can use addSuppressed yourself when aggregating failures, for example cleaning up several resources.

Q8. Can you rethrow a checked exception without declaring it?

Short answer: In two cases:

  1. Precise rethrow (Java 7+): catch (Exception e) { log(e); throw e; } compiles without declaring throws Exception, as long as e is effectively final. The compiler knows the try block can only throw the checked types it actually throws (plus unchecked exceptions), and checks against those.
  2. "Sneaky throw": a generic trick exploits type erasure to throw any checked exception undeclared (Lombok's @SneakyThrows). It's legal bytecode, but it defeats checked-exception contracts. Callers can't catch it by type without a compile error. Avoid it in library APIs.
void process() throws IOException {            // only IOException needs declaring
    try { readFile(); }                         // throws IOException
    catch (Exception e) { audit(e); throw e; }  // precise rethrow: compiles
}

Q9. What's the difference between rethrowing and wrapping an exception?

Short answer:

  • Rethrowing propagates the same exception object: its type and stack trace are unchanged. Use it when this layer can't add anything but wants to log or clean up.
  • Wrapping throws a new exception that carries the original as its cause (new OrderProcessingException("order " + id, e)). Use it to:
    • translate between abstraction layers (no SQLException leaking out of a repository API);
    • add context (IDs, operation);
    • convert checked to unchecked exceptions.

Common trap: wrapping without passing the cause (new MyException(e.getMessage())) loses the original stack trace. Always pass e as the cause. And don't log and rethrow at every layer, or the same error appears ten times in the logs.

Q10. Can an object be garbage collected while it's still in scope?

Short answer: Yes. GC is based on reachability, not on lexical scope. Once the JIT determines that a local variable is no longer used later in the method, the object it references can be collected, even though the variable is still "in scope". This can surprise code that relies on finalizers or cleaners running "after" a method is done. Reference.reachabilityFence(obj) (Java 9+) keeps an object reachable up to a point.

Q11. What does String s1 = "abc"; String s2 = "ab" + "c"; System.out.println(s1 == s2); print?

Short answer: true. "ab" + "c" is a compile-time constant expression, so the compiler folds it into the literal "abc", which is interned in the same pool entry as s1.

Key points to cover:

  • If either part is a non-final variable (String a = "ab"; String s3 = a + "c";), concatenation happens at runtime, and creates a new object, so s1 == s3 is false.
  • With final String a = "ab"; it's constant again, so true.

Q12. What's the difference between a memory leak and object retention?

Short answer:

  • Java has no "forgotten free()" leaks. A Java memory leak means objects that are no longer needed but still reachable, so the GC can't reclaim them, and memory grows without bound over time. For example:
    • an ever-growing static Map cache;
    • listeners that are never removed;
    • ThreadLocals in pooled threads;
    • classloader leaks on redeploy.
  • Object retention is the broader idea of how long objects are kept alive. Some retention is intentional and bounded (a sized cache, a session store). It becomes a problem only when the retained size is larger or lives longer than needed.

In heap analysis, you look at the retained size and GC root paths (Eclipse MAT's dominator tree) to tell a bounded cache from a leak.

Learn it in depth → Memory Leaks in Java

Q13. Does calling System.gc() guarantee garbage collection?

Short answer: No. It's a request that the JVM may ignore:

  • -XX:+DisableExplicitGC turns it into a no-op.
  • With G1, -XX:+ExplicitGCInvokesConcurrent makes it start a concurrent cycle.

By default, in HotSpot, it usually triggers a full, stop-the-world GC. That's why calling it in production code (or libraries doing so, like old RMI DGC calls) causes latency spikes. For tests or diagnostics, use jcmd <pid> GC.run, or heap-dump tools.

Q14. What does finalize() do, and when is it called?

Short answer: Object.finalize() was meant as a cleanup hook, called by a finalizer thread at some unspecified time after an object becomes unreachable, possibly never. It has serious problems:

  • unpredictable timing;
  • performance cost (finalizable objects need at least two GC cycles);
  • object resurrection;
  • security issues;
  • exceptions that are silently ignored.

It has been deprecated since Java 9, and deprecated for removal since Java 18 (JEP 421). Use try-with-resources/AutoCloseable for deterministic cleanup, and java.lang.ref.Cleaner as a safety net for native resources.

public final class NativeBuffer implements AutoCloseable {
    private static final Cleaner CLEANER = Cleaner.create();
    private final Cleaner.Cleanable cleanable;
    public NativeBuffer(long size) {
        long address = allocate(size);
        this.cleanable = CLEANER.register(this, () -> free(address));   // the action must not capture 'this'
    }
    @Override public void close() { cleanable.clean(); }                // deterministic path
}

Q15. What is escape analysis, and how does it affect memory?

Short answer: Escape analysis is a C2 JIT analysis that checks whether an object escapes the method or thread that created it (returned, stored in a field, or passed to unknown code). If it doesn't escape, HotSpot can:

  • Scalar-replace it: keep its fields in registers or on the stack, and skip the heap allocation entirely. That means less GC pressure.
  • Elide locks on it (synchronized on a non-escaping object is removed).

It works best on small, short-lived objects: iterators, builders, Optionals, boxed values in tight loops. Inlining matters, because escape analysis only sees what's inlined. You can diagnose it with -XX:+PrintEscapeAnalysis (debug builds), or indirectly with allocation profiling (JFR).

Q16. Can an object referenced from a static field be garbage collected?

Short answer: Not while the reference exists. Static fields belong to the class, and a loaded class is reachable through its ClassLoader. So the object becomes collectable only if:

  1. the field is reassigned or set to null; or
  2. the class is unloaded, which happens only when its ClassLoader becomes unreachable (typical in application servers and plugin systems, and never for the system class loader).

That's why "static collections that only grow" are the most common leak.

Q17. What's the difference between heap and non-heap memory?

Short answer:

  • Heap: all objects and arrays. It's managed by the GC, and sized with -Xms/-Xmx.
  • Non-heap (native) memory used by the JVM:
    • Metaspace (class metadata) and the compressed class space;
    • the code cache (JIT-compiled code);
    • thread stacks (-Xss per thread);
    • direct ByteBuffers and memory-mapped files;
    • GC data structures;
    • symbol tables and interned-string metadata;
    • native libraries' malloc calls.

Key points to cover:

  • A container can be OOM-killed even with a healthy heap, because total process memory = heap + non-heap. Use Native Memory Tracking (-XX:NativeMemoryTracking=summary, then jcmd <pid> VM.native_memory) to see the breakdown, and size containers for it.

Q18. What is OutOfMemoryError: Metaspace?

Short answer: The JVM couldn't allocate space for class metadata, because Metaspace hit -XX:MaxMetaspaceSize (unbounded by default, so it's limited only by native memory) or the compressed class space. Typical causes:

  • ClassLoader leaks: hot redeploys in application servers, where old web-app classloaders are pinned by threads, ThreadLocals, JDBC drivers or static caches.
  • Unbounded generation of classes at runtime: dynamic proxies, bytecode generation (CGLIB, ByteBuddy), Groovy scripts, lambdas in generated code, or JSP recompilation.
  • A limit set too low for a large application.

How to diagnose:

  • jcmd <pid> VM.metaspace and VM.classloader_stats;
  • a heap dump, looking at classloader instances and their GC roots;
  • -Xlog:class+load for load and unload patterns.

Q19. Can a final variable be initialised in a constructor?

Short answer: Yes. A blank final instance field (declared final, but not initialised at declaration) must be assigned exactly once on every constructor path, or in an instance initialiser. Otherwise it's a compile error. A static final blank field must be assigned in a static initialiser.

public final class Money {
    private final long minorUnits;
    private final Currency currency;
    public Money(long minorUnits, Currency currency) {
        this.minorUnits = minorUnits;                                // assigned once
        this.currency = Objects.requireNonNull(currency);
    }
}

Key points to cover:

  • Final fields also get a JMM guarantee: once the constructor finishes (without leaking this), every thread sees their correctly initialised values, without synchronisation. That's the foundation of safe immutable objects.

Q20. What's the difference between volatile and synchronized?

Short answer:

volatilesynchronized
Visibility✅ reads see the latest writes✅ (on entering and exiting the monitor)
Ordering✅ a happens-before edge on each write → read✅
AtomicityOnly single reads and writes (including long/double)✅ the whole block is atomic
Mutual exclusion❌✅ one thread at a time per monitor
BlockingNever blocksCan block, and deadlock
Typical useFlags, publishing immutable objects, double-checked lockingCompound actions: check-then-act, read-modify-write, invariants over several fields

volatile int count; count++ is not thread-safe. Use AtomicInteger or LongAdder, or a lock.

Learn it in depth → volatile and the Memory Model

Q21. Can a class have both static and instance (non-static) initialiser blocks? In what order do they run?

Short answer: Yes.

  • Static blocks and static field initialisers run once, in textual order, when the class is initialised (first active use).
  • Instance blocks and instance field initialisers run on every new: after the superclass constructor (super(...)) returns, and before the rest of the constructor body, in textual order.

Across a hierarchy, new Child() runs:

  1. Parent static init, then Child static init (first use only);
  2. Parent instance init, then the Parent constructor body;
  3. Child instance init, then the Child constructor body.

Common trap: calling an overridable method from a superclass constructor. The subclass override runs before the subclass's fields are initialised, so it sees null or 0.

Q22. Can this be used in a static method? Why not?

Short answer: No, it's a compile error. A static method belongs to the class, and runs without any instance, so there's no this (and no super). To use instance state, pass an instance as a parameter, or make the method an instance method.

Q23. What does the strictfp keyword do?

Short answer: Historically, strictfp forced floating-point calculations in a class or method to use strict IEEE 754 semantics, so results were identical on every platform. Without it, x87-era JVMs could use extended precision for intermediate results. It applied to classes, interfaces and methods, but not to variables. Since Java 17 (JEP 306), all floating-point arithmetic is strict, so the keyword is redundant. The compiler warns that it's unnecessary.

Common trap: describing strictfp as something you still need for consistent results. You don't, on Java 17 and later.

Q24. What's the default access modifier? What does "package-private" mean?

Short answer: With no modifier, a class, field, method or constructor is package-private. It's accessible only from code in the same package. Subclasses in other packages can't see it, unlike protected. Visibility, from widest: public > protected (package + subclasses) > package-private > private.

Key points to cover:

  • Interface members are implicitly public (fields are public static final), unless they're declared private (Java 9+).
  • Package-private is great for encapsulating implementation classes inside a module or package. Combined with JPMS module exports, it keeps APIs small.
  • Test classes in the same package (in the test source set) can access package-private members.

Q25. What's the effect of return in a finally block?

Short answer:

  1. It overrides any return value from try or catch.
  2. It silently discards any exception thrown in try or catch, including Errors like OutOfMemoryError.

Callers never learn that the operation failed. Never return from finally. Use it only for cleanup that can't throw, or use try-with-resources.

Follow-up questions this topic invites — and their answers

Q: What's printed by try { System.exit(0); } finally { System.out.println("finally"); }? A: Nothing. System.exit halts the JVM (after shutdown hooks), so finally doesn't run. It also won't run if the thread is killed, or the process crashes.

Q: What is a multi-catch, and what restriction does it have? A: catch (IOException | SQLException e) handles several unrelated types in one block. The types can't be subclasses of each other, and e is implicitly final.

Q: How do you find which object is holding memory in a leak? A: Capture a heap dump (jcmd <pid> GC.heap_dump, or -XX:+HeapDumpOnOutOfMemoryError), open it in Eclipse MAT or VisualVM, check the dominator tree and leak suspects, then follow the path to GC roots (excluding weak references) to find the static field, thread or cache keeping it alive.

Q: Why are objects with finalizers slower to allocate and collect? A: The JVM must register each one in a finalizer queue at allocation. On collection, it must first run finalize() on the finalizer thread, and only reclaim the memory in a later GC cycle. That throttles throughput, and can cause OOMs if finalization falls behind.

Previous

Tricky Java Output, Operators & OOP Edge Cases — Interview Questions

Next

Classic Java Language Questions, Senior-Grade Answers — Interview Questions

AI Tutor

Lesson: Tricky Exceptions, Memory & Keyword Questions — Interview Questions

Quick actions

AI responses can be inaccurate. Verify critical information.