← All languages
Java interview questions
40 curated Java concept questions with model answers. Practice them out loud in the scored Fundamentals room.
Java interviews lean on the JVM, memory model, and collections because they reveal how well you reason about performance and correctness at scale. Be ready to explain garbage collection, generics type erasure, checked exceptions, and the difference between HashMap and ConcurrentHashMap.
- JVM memory and garbage collection: heap vs stack, the young/old generations, and what a stop-the-world pause means for latency.
- Generics and type erasure: why you cannot create a generic array, and how wildcards (? extends / ? super) constrain APIs.
- Collections and concurrency: HashMap vs Hashtable vs ConcurrentHashMap, and why fail-fast iterators exist.
What interviewers are really testing
Java interviewers care about the JVM more than the syntax. Questions about garbage collection, generics, and the collection hierarchy are really asking whether you can reason about memory, type safety, and concurrency inside a long-running service — the places where a plausible-looking change causes a production incident.
Basic
What's the difference between the JDK, JRE, and JVM?
- The JVM executes bytecode and provides memory management, JIT compilation, and the runtime.
- The JRE is the JVM plus the core class libraries needed to run a Java application.
- The JDK is the JRE plus development tools — javac, jar, jshell, the debugger.
- Java compiles once to bytecode that any JVM can run, which is the 'write once, run anywhere' promise.
- You need the JDK to build and the JRE/JVM to run.
Why is `==` different from `.equals()` for strings?
- `==` compares references for objects; `.equals()` compares logical value.
- String literals are interned in a pool, so `"a" == "a"` happens to be true, but `new String("a") == "a"` is false.
- Always use .equals() (or Objects.equals) for string and object value comparison.
- For primitives, `==` compares values directly.
- Overriding equals also requires overriding hashCode to keep the contract.
What are primitives vs wrapper classes, and what is autoboxing?
- Primitives (int, long, boolean, double, …) store raw values; wrappers (Integer, Long, Boolean, Double) are objects.
- Autoboxing converts a primitive to its wrapper automatically; unboxing does the reverse.
- Wrappers are needed for generics and collections, which cannot hold primitives.
- Integer caching: values from -128 to 127 are cached, so `Integer.valueOf(100) == Integer.valueOf(100)` is true but 1000 is false.
- Unboxing a null wrapper throws NullPointerException — a common bug.
When would you choose ArrayList over LinkedList?
- ArrayList is backed by an array: O(1) random access, amortized O(1) append, O(n) insert/remove in the middle.
- LinkedList is a doubly linked list: O(1) insert/remove given a node, but O(n) access and poor cache locality.
- In practice ArrayList wins for almost all workloads; LinkedList is rarely worth it.
- For key→value use HashMap; for ordered/sorted maps use TreeMap; for thread-safe use ConcurrentHashMap.
- Always program to the interface (List, Map) so the implementation can change.
Why not use HashMap in a multithreaded context?
- HashMap is not synchronized; concurrent writes can lose updates or corrupt internal state.
- Hashtable synchronizes every method but is legacy and serializes all access.
- ConcurrentHashMap allows concurrent reads and lock-striped writes with much better throughput.
- Collections.synchronizedMap wraps a map but still locks the whole map and requires manual sync when iterating.
- Prefer concurrent collections over ad-hoc synchronization.
What's the difference between checked and unchecked exceptions?
- Checked exceptions extend Exception (not RuntimeException) and must be caught or declared — the compiler enforces it.
- Unchecked exceptions extend RuntimeException and are not enforced; they usually signal programming errors (NPE, IndexOutOfBounds).
- Errors extend Error and represent unrecoverable JVM problems — don't catch them.
- Use checked for recoverable conditions callers must handle; unchecked for bugs or unrecoverable states.
- Catch specific types, and use try-with-resources to close AutoCloseable resources.
When do you use an interface vs an abstract class?
- An interface defines a contract; a class can implement many interfaces. It can hold constants, static/private/default methods (Java 8+).
- An abstract class can hold state, constructors, and concrete methods; a class extends only one.
- Use an interface when unrelated types should share behavior; use an abstract class to share implementation and state.
- Interfaces enable polymorphism and are the base for functional interfaces and lambdas.
- Design rule: prefer interfaces; add an abstract class only when shared code/state is needed.
What are the different uses of the final keyword?
- final variable: cannot be reassigned (but the referenced object can still mutate unless immutable).
- final method: cannot be overridden.
- final class: cannot be subclassed (e.g. String).
- final fields must be initialized once, in the declaration or constructor, which makes them safely published.
- Combined with static, final defines constants.
Why is String immutable, and when do you use StringBuilder?
- String is immutable: its value cannot change after construction, which makes it safe to share and cache its hashCode.
- Immutability enables the string pool, thread safety, and use as map keys.
- Every concatenation creates a new String, so repeated `s += x` in a loop is O(n^2).
- StringBuilder is a mutable buffer for building strings in a loop; StringBuffer is the synchronized version.
- The compiler turns simple `+` concatenation into StringBuilder, but not across loop iterations.
What is the difference between static and instance members?
- Instance members belong to an object and are accessed through an instance.
- Static members belong to the class, are shared by all instances, and are loaded once.
- Static methods cannot access instance fields or `this` directly.
- Static initialization blocks and static fields run when the class is first loaded.
- Overuse of static hides dependencies and hurts testability; prefer instances/DI.
What is the string constant pool?
- String literals are interned: identical literals share one instance in the pool.
- new String("a") always creates a distinct object, so == differs from equals.
- String.intern() returns the pooled instance for a value.
- Immutability is what makes interning safe to share across code.
- Compile-time constant concatenation is also interned; runtime concatenation is not.
How do you read input from the console?
- Scanner is convenient for tokens and typed input (nextInt, nextLine) but slower.
- BufferedReader is faster for bulk line reading.
- nextInt does not consume the newline, so a following nextLine can read an empty line.
- Always close resources with try-with-resources or a wrapper that owns the stream.
- System.in need not be closed for short-lived CLI programs.
What is `var` and where can it be used?
- var is local variable type inference (Java 10+): the compiler infers the static type from the initializer.
- It can only be used for local variables with an initializer, not fields, parameters, or return types.
- The type is still static — you cannot reassign a different type.
- It reduces boilerplate but can hide the actual type; use it when the type is obvious from the right side.
- var in a lambda parameter requires explicit types on all parameters.
How do switch expressions differ from switch statements?
- A switch expression returns a value and has no fall-through; a statement executes branches.
- Use arrow labels (case X -> ...) to avoid fall-through and break.
- Switch expressions must be exhaustive: either a default or all enum/sealed cases.
- yield returns a value from a block arm.
- The classic colon form still exists for statements and retains fall-through.
When do you use an array instead of an ArrayList?
- Arrays are fixed-size and can hold primitives; ArrayList is resizable and holds objects only.
- Arrays are faster and lighter, with no boxing for primitive arrays.
- ArrayList gives rich API (add, remove, contains) and integrates with generics.
- Arrays.asList is fixed-size (set allowed, add/remove not) and backed by the array.
- Use arrays for fixed data and performance-critical numeric work; collections otherwise.
What operations does StringBuilder provide?
- append, insert, replace, delete, reverse, and toString are the common methods.
- All mutating methods return the builder for chaining.
- setLength(0) clears it for reuse; it retains capacity.
- StringBuffer is the synchronized sibling for shared use.
- It is mutable, so it does not participate in the string pool.
What are static and instance initializer blocks?
- A static block runs once when the class is initialized, before any instance is created.
- An instance initializer block runs before the constructor body for every instance.
- Instance blocks are copied into each constructor, so they can share setup code.
- Field initializers and instance blocks run in textual order.
- A static block is where you initialize complex static state or register drivers.
Explain short-circuit && and || and the ternary operator.
- && and || short-circuit: they stop evaluating once the result is determined.
- & and | on booleans evaluate both sides (and are also bitwise operators on integers).
- The ternary (cond ? a : b) is an expression that must resolve to a common type.
- The compiler applies binary numeric promotion, so mixing types can widen unexpectedly.
- Prefer explicit parentheses when mixing && and || for readability.
What can a Java enum hold beyond constants?
- An enum can have fields, constructors, and methods, so each constant can carry data.
- Every enum implicitly extends java.lang.Enum and cannot extend another class.
- An enum can implement interfaces.
- values() and valueOf() are generated, along with name() and ordinal().
- Enums are ideal for type-safe constant sets and switch expressions.
What are annotations and where does retention matter?
- Annotations attach metadata to code and are read by the compiler, tools, or runtime reflection.
- @Retention controls visibility: SOURCE (discarded), CLASS (in class file, not runtime), RUNTIME (read via reflection).
- @Target restricts where an annotation may be applied.
- Built-ins include @Override, @Deprecated, @SuppressWarnings and @FunctionalInterface.
- Frameworks (Spring, Jackson) rely heavily on runtime annotations.
Advanced
Explain Java's garbage collection and generations.
- The heap is divided into young (Eden + two Survivor spaces) and old generation.
- New objects are allocated in Eden; a minor GC copies survivors between survivor spaces and promotes long-lived objects to old gen.
- A major/full GC collects the old generation and is more expensive; modern collectors (G1, ZGC, Shenandoah) aim for low pauses.
- Objects are collected when unreachable from GC roots (stack refs, statics, JNI).
- System.gc() is a hint, not a guarantee; avoid relying on it.
What does volatile guarantee, and how does it differ from synchronized?
- volatile guarantees visibility: a write is immediately visible to other threads, and it prevents reordering around the access.
- volatile does not make compound operations atomic — `count++` is still a race.
- synchronized provides both mutual exclusion (atomicity) and visibility via the monitor lock.
- Use volatile for a simple flag/state; use synchronized or java.util.concurrent atomics for read-modify-write.
- AtomicInteger/CAS give lock-free atomicity for counters.
What is type erasure and what are its consequences?
- Generics are erased at compile time; at runtime List<String> and List<Integer> are both just List.
- You cannot do `new T()`, `new T[]`, `instanceof List<String>`, or overload methods that differ only by generic type.
- Generics provide compile-time type safety but no runtime type information.
- Wildcards (extends/super) express covariance/contravariance; the PECS rule applies (Producer Extends, Consumer Super).
- Reifiable types are those preserved at runtime (raw, unbounded wildcard, non-generic).
How do Java Streams work and why does laziness matter?
- A stream is a pipeline: a source, zero or more intermediate operations (map/filter) that are lazy, and one terminal operation (collect/forEach/reduce) that triggers execution.
- Intermediate operations do not run until a terminal operation is invoked.
- Streams do not mutate the source and can be consumed only once.
- Parallel streams use the common ForkJoinPool and can hurt performance or introduce shared-state bugs if misused.
- Prefer streams for readable data transformation, loops for complex control flow.
Why must you override hashCode when you override equals?
- The contract: equal objects must have equal hash codes, or hash-based collections break.
- HashMap/HashSet locate a bucket by hashCode and then compare with equals; a mismatch means lookups miss.
- Use the same fields in both, typically via Objects.hash and a short-circuiting equals.
- Mutable fields used in hashCode make an object unfindable after it is inserted and mutated.
- Records auto-generate consistent equals/hashCode/toString.
How does try-with-resources work?
- Any resource implementing AutoCloseable declared in the try header is closed automatically when the block exits.
- Resources close in reverse order of declaration, even if the body throws.
- Exceptions from the body are primary; a close exception is attached as suppressed rather than replacing it.
- It removes the finally-close boilerplate and the leak when close itself throws.
- Nested resources each get their own try or a single header with multiple declarations.
What is a functional interface and how do lambdas relate to it?
- A functional interface has exactly one abstract method (default/static methods don't count).
- A lambda provides an implementation of that single method without a class declaration.
- Common ones: Function, Consumer, Supplier, Predicate, Runnable, Comparator.
- @FunctionalInterface documents and enforces the contract at compile time.
- Method references (Class::method) are shorthand for simple lambdas.
What is Optional for, and how is it misused?
- Optional models a possibly-absent return value explicitly, instead of returning null.
- Use map/flatMap/filter/orElse/orElseGet/orElseThrow to chain safely.
- Misuse: using Optional as a field type or method parameter, or calling get() without isPresent — that reintroduces NPE risk.
- orElse evaluates its argument eagerly; orElseGet is lazy and takes a supplier.
- Optional is for return types, not for serialization or hot-path allocation.
What is reflection and what are its costs?
- Reflection inspects and manipulates classes, methods, and fields at runtime (Class.forName, getDeclaredMethods, invoke).
- It powers frameworks like Spring and Jackson that wire code without compile-time knowledge.
- Costs: slower (until JIT warms up), bypasses compile-time type checking, breaks encapsulation, and complicates native-image/JPMS.
- Use MethodHandles/VarHandles or compile-time codegen for hot paths.
- setAccessible(true) may be blocked by the module system.
What is the Java Memory Model and why does it matter?
- It defines when one thread's writes become visible to another, allowing CPUs/caches to reorder for performance.
- Without synchronization, a thread may never see another thread's update (visibility) — e.g. a non-volatile stop flag.
- Happens-before relationships (synchronized, volatile, thread start/join, concurrent collections) establish ordering.
- Safe publication means an object is fully constructed and visible to other threads (final fields help).
- Race conditions and stale reads are correctness bugs the model makes precise.
What are records and when should you use them?
- A record is a concise immutable data carrier: the compiler generates a canonical constructor, accessors, equals, hashCode, and toString.
- Components are final fields; there are no setters.
- You can add methods and extra constructors, but not instance fields.
- Records can implement interfaces and are great as DTOs, keys, and value objects.
- They are not beans (no getX naming) and cannot extend a class.
What are sealed classes and interfaces?
- sealed restricts which types may extend or implement a type via permits.
- Subtypes must be final, sealed, or non-sealed.
- It enables exhaustive switch without a default when combined with records.
- It models closed hierarchies (e.g. a fixed set of shapes or results) at compile time.
- It complements records for algebraic-data-type-style modeling.
How does CompletableFuture compose async work?
- It represents a future result with a rich set of combinators (thenApply, thenCompose, thenCombine, exceptionally).
- thenApply maps, thenCompose flattens nested futures, thenCombine joins two independent futures.
- allOf/anyOf coordinate multiple futures.
- Async variants (thenApplyAsync) run on a supplied or the common ForkJoinPool.
- Always handle failures with exceptionally/handle/whenComplete or they are silently lost.
What is ThreadLocal for and what are its pitfalls?
- Each thread gets its own independent copy of the value.
- Useful for per-request context, non-thread-safe formatters, or transaction state.
- In thread pools the value persists across tasks, so you must remove() it to prevent leaks and cross-request bleed.
- InheritableThreadLocal propagates to child threads on creation; on pools use TransmittableThreadLocal-style libraries.
- Virtual threads make ThreadLocal-scale designs expensive; prefer explicit context passing.
How do CAS and atomic classes work?
- Compare-and-swap atomically updates a value only if it still equals an expected value.
- AtomicInteger/AtomicReference wrap CAS in a lock-free API (incrementAndGet, compareAndSet).
- LongAdder is better than AtomicLong under heavy contention.
- CAS loops can spin under contention (livelock-ish cost) and suffer the ABA problem.
- They are the building blocks of concurrent collections and locks.
How does class loading work?
- Loading, linking (verify, prepare, resolve), and initialization are the phases.
- The parent-delegation model asks the parent loader first, which protects core classes.
- Class loaders (bootstrap, platform, application, custom) define namespaces.
- A ClassNotFoundException/NoClassDefFoundError points at a loader/path problem.
- Static initializers run at first active use, not at load time.
What does the JIT compiler do?
- It compiles hot bytecode to native code at runtime, tiered from interpreted to C1 then C2 optimized.
- It uses profiling (call counts, type feedback) to inline and devirtualize.
- Warmup means peak performance comes after many iterations — a benchmark must account for it.
- On-stack replacement replaces a running method with its compiled version.
- Deoptimization falls back to interpreted code when assumptions break.
What is off-heap memory in Java?
- ByteBuffer.allocateDirect allocates memory outside the heap, so it is not managed by the GC.
- Off-heap is useful for large buffers, low-latency caches, and avoiding GC pauses.
- You must free it yourself (via Cleaner/Unsafe) or it leaks until the process ends.
- Access via Unsafe is fast but not bounds-safe; VarHandle/MemorySegment (panama) are the modern safe APIs.
- Direct buffers can still be reclaimed by the GC's Cleaner when unreachable, but not deterministically.
How do you make an immutable class?
- Make the class final, all fields private final, and set them only in the constructor.
- Defensively copy mutable inputs and outputs (dates, collections).
- Return unmodifiable views or copies from getters.
- Do not leak `this` from the constructor, which would publish a partially built object.
- Immutability gives thread safety and safe use as map keys for free.
How does pattern matching work in modern Java?
- instanceof patterns bind a variable when the test passes: if (o instanceof Point p).
- Switch patterns match types, with guards (when) and record deconstruction patterns.
- Exhaustiveness is required for sealed types in switch expressions.
- It removes the cast and the boilerplate of type-dispatch chains.
- Null handling in switch is explicit (case null).
Common mistakes
- Saying HashMap is thread-safe, or that ConcurrentHashMap simply locks the entire map for every operation.
- Explaining type erasure but still expecting something like instanceof List<String> to compile.
- Treating String as a primitive, or asserting that == compares text rather than references.
- Confusing checked and unchecked exceptions, or swallowing InterruptedException inside a catch block.
How to rehearse these answers
- Walk an object from Eden to the survivor spaces to the old generation, and describe when a stop-the-world pause happens.
- Write a generic method with ? extends and ? super bounds and explain why each wildcard is required.
- Compare synchronized, ReentrantLock, and AtomicInteger for the same counter under contention.
- Explain the equals/hashCode contract using a class you actually store in a HashMap key.
New to technical interviews? Read how to rehearse coding and system design.
Other languages
JavaScript interview questions · Python interview questions · C++ interview questions · C# interview questions · Go interview questions