Toolmingo
Guides27 min read

50 Java Interview Questions (With Answers)

Top Java interview questions with clear answers and code examples — covering OOP, collections, concurrency, Java 8+ features, JVM internals, and Spring Boot basics.

Java interviews test object-oriented design, the standard library, memory model, concurrency, and modern Java features. This guide covers the 50 most common questions — with concise answers and runnable code examples.

Quick reference

Topic Most asked questions
OOP Inheritance, polymorphism, encapsulation, abstraction
Collections List vs Set vs Map, HashMap internals, generics
Java 8+ Streams, lambdas, Optional, default methods
Concurrency synchronized, volatile, locks, thread pool
JVM Heap vs stack, garbage collection, class loading
Exceptions checked vs unchecked, try-with-resources
Design patterns Singleton, Factory, Builder, Observer

OOP fundamentals

1. What are the four pillars of OOP?

Pillar Definition Java example
Encapsulation Bundle data + behaviour; hide internals via private getter/setter, private fields
Inheritance Subclass inherits state and behaviour from superclass class Dog extends Animal
Polymorphism Same interface, different runtime behaviour method overriding, Animal a = new Dog()
Abstraction Expose what, hide how abstract class, interface
// Polymorphism in action
abstract class Shape {
    abstract double area();
}

class Circle extends Shape {
    private final double r;
    Circle(double r) { this.r = r; }

    @Override
    public double area() { return Math.PI * r * r; }
}

class Rectangle extends Shape {
    private final double w, h;
    Rectangle(double w, double h) { this.w = w; this.h = h; }

    @Override
    public double area() { return w * h; }
}

// Shape reference → runtime dispatch
Shape s = new Circle(5);
System.out.println(s.area()); // 78.54

2. What is the difference between abstract class and interface?

Feature abstract class interface
Multiple inheritance No (single extends) Yes (implements multiple)
Constructor Yes No
Fields Any access modifier public static final only
Method body Allowed (any) default/static since Java 8
Use when Shared state + partial impl Contract / capability
// Abstract class — shared state
abstract class Vehicle {
    protected int speed;
    abstract void accelerate();
    void brake() { speed = 0; }   // concrete method
}

// Interface — capability
interface Electric {
    int batteryLevel();
    default void charge() { System.out.println("Charging..."); }
}

class Tesla extends Vehicle implements Electric {
    @Override public void accelerate() { speed += 20; }
    @Override public int batteryLevel() { return 80; }
}

3. What is method overloading vs method overriding?

Overloading — same name, different parameter list, resolved at compile time.
Overriding — same signature in subclass, resolved at runtime (dynamic dispatch).

class Calculator {
    // Overloading — compile-time polymorphism
    int add(int a, int b)         { return a + b; }
    double add(double a, double b){ return a + b; }
    int add(int a, int b, int c)  { return a + b + c; }
}

class Animal {
    String speak() { return "..."; }
}

class Dog extends Animal {
    @Override
    String speak() { return "Woof"; }   // runtime polymorphism
}

Rules for overriding:

  • Same name, same parameters, same (or covariant) return type.
  • Cannot reduce visibility (public method cannot be private in subclass).
  • Cannot throw new checked exceptions broader than the parent's.

4. What is the difference between == and .equals()?

  • == compares references (identity) for objects; compares values for primitives.
  • .equals() compares logical equality (content), if properly overridden.
String a = new String("hello");
String b = new String("hello");

System.out.println(a == b);       // false — different objects
System.out.println(a.equals(b));  // true  — same content

// String pool interning
String c = "hello";
String d = "hello";
System.out.println(c == d);       // true — same pool reference

Always use .equals() for object comparison. For null-safety: Objects.equals(a, b).


5. What is final, finally, and finalize?

Keyword Where used Meaning
final variable, method, class Variable: constant; method: cannot override; class: cannot extend
finally try-catch block Always executes (cleanup code)
finalize method (deprecated) Called by GC before object collection — avoid using it
final int MAX = 100;           // constant

try {
    riskyOperation();
} catch (IOException e) {
    handle(e);
} finally {
    closeResource();           // always runs
}

finalize() is deprecated since Java 9 — use try-with-resources or Cleaner instead.


6. What is the difference between static and instance members?

static members belong to the class; instance members belong to each object.

class Counter {
    private static int total = 0;   // shared across all instances
    private int id;                  // unique per instance

    Counter() {
        total++;
        this.id = total;
    }

    static int getTotal() { return total; }  // no 'this' available
    int getId()           { return id; }
}

Counter c1 = new Counter(); // total = 1, id = 1
Counter c2 = new Counter(); // total = 2, id = 2
System.out.println(Counter.getTotal()); // 2

Collections & Generics

7. What is the Java Collections hierarchy?

Iterable
└── Collection
    ├── List (ordered, duplicates allowed)
    │   ├── ArrayList
    │   └── LinkedList
    ├── Set (no duplicates)
    │   ├── HashSet (unordered)
    │   ├── LinkedHashSet (insertion order)
    │   └── TreeSet (sorted)
    └── Queue
        ├── PriorityQueue
        └── ArrayDeque

Map (key-value, not Collection)
├── HashMap (unordered)
├── LinkedHashMap (insertion order)
├── TreeMap (sorted by key)
└── ConcurrentHashMap (thread-safe)

8. How does HashMap work internally?

HashMap uses an array of buckets (default 16). Each entry's bucket index is hash(key) & (n - 1).

  1. put(k, v) — compute hash(k), find bucket, add to linked list (or tree if ≥ 8 entries in bucket, Java 8+).
  2. get(k) — compute hash(k), walk bucket's list/tree to find matching equals(k).
  3. Rehashing — when load factor (default 0.75) is exceeded, capacity doubles and all entries are rehashed.
Map<String, Integer> map = new HashMap<>();
map.put("alice", 1);
map.put("bob", 2);

// Iteration order is NOT guaranteed
for (Map.Entry<String, Integer> e : map.entrySet()) {
    System.out.println(e.getKey() + " = " + e.getValue());
}

Why override both hashCode and equals?
HashMap first compares hash codes (fast) then calls equals (slower). If you override only equals, equal objects may land in different buckets — lookup fails.


9. What is the difference between ArrayList and LinkedList?

Operation ArrayList LinkedList
get(i) O(1) O(n)
add at end O(1) amortised O(1)
add/remove at middle O(n) O(n) to find + O(1) to splice
Memory Less (contiguous array) More (node objects + pointers)
Cache locality Excellent Poor

Default choice: ArrayList. Use LinkedList only when you do many insertions/deletions at both ends (deque use case) — and even then, ArrayDeque is usually faster.


10. What are Generics and why are they used?

Generics provide compile-time type safety and eliminate casts.

// Without generics — runtime ClassCastException risk
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0); // cast required

// With generics — checked at compile time
List<String> typed = new ArrayList<>();
typed.add("hello");
String s2 = typed.get(0);        // no cast

// Generic method
public <T extends Comparable<T>> T max(T a, T b) {
    return a.compareTo(b) >= 0 ? a : b;
}

// Bounded wildcards
void printAll(List<? extends Number> nums) { // producer — covariance
    for (Number n : nums) System.out.println(n);
}
void addInts(List<? super Integer> list) {   // consumer — contravariance
    list.add(42);
}

Type erasure: Generics are a compile-time feature. At runtime List<String> and List<Integer> are both just List — generic type info is erased.


11. What is the difference between Iterator and ListIterator?

Feature Iterator ListIterator
Direction Forward only Forward and backward
Collections All Collection List only
Add elements No Yes (add())
Replace elements No Yes (set())
Current index No Yes (nextIndex())

Java 8+ Features

12. What are lambda expressions?

Lambdas are anonymous functions that implement a functional interface (exactly one abstract method).

// Before Java 8
Comparator<String> comp = new Comparator<String>() {
    @Override
    public int compare(String a, String b) { return a.compareTo(b); }
};

// Java 8 lambda
Comparator<String> comp = (a, b) -> a.compareTo(b);

// Method reference (even shorter)
Comparator<String> comp = String::compareTo;

// Usage
List<String> names = Arrays.asList("Charlie", "Alice", "Bob");
names.sort((a, b) -> a.compareTo(b));
// or
names.sort(String::compareTo);

13. What is the Stream API? How is it different from a for loop?

Streams provide a declarative, pipeline-based way to process collections. They don't store data — they process it lazily.

List<String> names = List.of("Alice", "Bob", "Charlie", "Anna");

// Imperative
List<String> result = new ArrayList<>();
for (String name : names) {
    if (name.startsWith("A")) result.add(name.toUpperCase());
}
result.sort(Comparator.naturalOrder());

// Declarative (Stream)
List<String> result = names.stream()
    .filter(n -> n.startsWith("A"))
    .map(String::toUpperCase)
    .sorted()
    .collect(Collectors.toList());

// Parallel stream (use only for CPU-bound, large datasets)
long count = names.parallelStream()
    .filter(n -> n.length() > 3)
    .count();

Lazy evaluation: Intermediate operations (filter, map, sorted) build the pipeline. Terminal operations (collect, count, forEach) trigger execution.


14. What is Optional and when should you use it?

Optional<T> is a container that may or may not hold a value — it's a signal to callers that null is a possible outcome.

// Bad — null return is easy to miss
String findName(int id) { return null; }

// Good — explicit about nullability
Optional<String> findName(int id) {
    if (id == 1) return Optional.of("Alice");
    return Optional.empty();
}

// Usage
findName(1)
    .map(String::toUpperCase)
    .ifPresent(System.out::println);  // "ALICE"

// Safe access with default
String name = findName(99).orElse("Unknown");
String name2 = findName(99).orElseGet(() -> computeDefault());
String name3 = findName(99).orElseThrow(() -> new NotFoundException(99));

Don't use Optional as a field or method parameter — it's designed as a return type.


15. What are default and static methods in interfaces?

Java 8 added these to allow interface evolution without breaking existing implementations.

interface Sorter {
    void sort(List<?> list);

    // Default method — subclasses may override
    default void sortAndPrint(List<?> list) {
        sort(list);
        System.out.println(list);
    }

    // Static utility method
    static Sorter natural() {
        return list -> Collections.sort((List<Comparable>) list);
    }
}

class QuickSorter implements Sorter {
    @Override
    public void sort(List<?> list) { /* custom impl */ }
    // sortAndPrint is inherited
}

If two interfaces provide conflicting defaults, the implementing class must override the method.


16. What are the key Stream terminal operations?

List<Integer> nums = List.of(1, 2, 3, 4, 5);

// collect
List<Integer> evens = nums.stream().filter(n -> n % 2 == 0)
                           .collect(Collectors.toList());

// reduce
int sum = nums.stream().reduce(0, Integer::sum);

// findFirst / findAny
Optional<Integer> first = nums.stream().filter(n -> n > 3).findFirst();

// anyMatch / allMatch / noneMatch
boolean anyEven = nums.stream().anyMatch(n -> n % 2 == 0);

// count / min / max
long count = nums.stream().filter(n -> n > 2).count();
Optional<Integer> max = nums.stream().max(Integer::compare);

// groupingBy
Map<Boolean, List<Integer>> partitioned =
    nums.stream().collect(Collectors.partitioningBy(n -> n % 2 == 0));

// joining
String joined = Stream.of("a","b","c")
    .collect(Collectors.joining(", ", "[", "]")); // [a, b, c]

Concurrency

17. What is the difference between Thread and Runnable?

// Extends Thread — limits inheritance
class MyThread extends Thread {
    @Override
    public void run() { System.out.println("Running"); }
}
new MyThread().start();

// Implements Runnable — preferred (composition over inheritance)
Runnable task = () -> System.out.println("Running");
new Thread(task).start();

// ExecutorService — best practice for production
ExecutorService executor = Executors.newFixedThreadPool(4);
executor.submit(() -> System.out.println("Task"));
executor.shutdown();

Always prefer ExecutorService over raw Thread — it provides pooling, lifecycle management, and Future results.


18. What is synchronized? What problems does it solve?

synchronized ensures mutual exclusion — only one thread can execute the block/method at a time. It also provides visibility (happens-before relationship).

class SafeCounter {
    private int count = 0;

    // Synchronized method — locks on 'this'
    public synchronized void increment() {
        count++;
    }

    // Synchronized block — narrower lock scope (more efficient)
    public void decrement() {
        synchronized (this) {
            count--;
        }
    }

    public synchronized int get() { return count; }
}

Problems without synchronisation:

  • Race condition — two threads read-modify-write simultaneously.
  • Visibility issue — one thread's write not seen by another (CPU cache).

19. What is volatile? When to use it?

volatile guarantees visibility (write is immediately visible to all threads) but not atomicity.

class Stopper {
    private volatile boolean stop = false;  // without volatile, loop may never stop

    void stop() { stop = true; }

    void run() {
        while (!stop) {
            doWork();
        }
    }
}

Use volatile for flags that are written by one thread and read by others. Do not use it for compound operations like count++ — use AtomicInteger or synchronized instead.


20. What is AtomicInteger and the java.util.concurrent.atomic package?

Atomic classes use compare-and-swap (CAS) hardware instructions — lock-free, thread-safe operations.

import java.util.concurrent.atomic.*;

AtomicInteger counter = new AtomicInteger(0);

// Thread-safe increment (no synchronized needed)
counter.incrementAndGet();      // ++i
counter.getAndIncrement();      // i++
counter.addAndGet(5);           // += 5

// CAS — only update if current value matches expected
boolean swapped = counter.compareAndSet(10, 0); // set to 0 if current == 10

AtomicBoolean flag = new AtomicBoolean(false);
AtomicReference<String> ref = new AtomicReference<>("initial");

21. What is ReentrantLock? How does it differ from synchronized?

Feature synchronized ReentrantLock
Trylock (non-blocking) No tryLock()
Interruptible lock No lockInterruptibly()
Timed lock No tryLock(time, unit)
Fairness No new ReentrantLock(true)
Multiple conditions No newCondition()
Manual unlock needed No Yes — always in finally
import java.util.concurrent.locks.*;

ReentrantLock lock = new ReentrantLock();

void transfer(Account from, Account to, int amount) {
    lock.lock();
    try {
        from.debit(amount);
        to.credit(amount);
    } finally {
        lock.unlock();   // ALWAYS in finally
    }
}

// Try without blocking
if (lock.tryLock(100, TimeUnit.MILLISECONDS)) {
    try { /* work */ }
    finally { lock.unlock(); }
}

22. What is a deadlock? How do you prevent it?

A deadlock occurs when two or more threads wait for each other's locks indefinitely.

// Deadlock scenario
Thread T1 acquires lock A → waits for lock B
Thread T2 acquires lock B → waits for lock A
// Both wait forever

Prevention strategies:

  1. Lock ordering — always acquire locks in the same global order.
  2. Timeout — use tryLock(timeout) instead of blocking lock().
  3. Lock-free data structures — use ConcurrentHashMap, AtomicInteger.
  4. Reduce lock scope — hold locks for the shortest time possible.
  5. Single lock — merge data guarded by two locks into one structure.

23. What is the difference between Callable and Runnable?

Runnable Callable<V>
Return value void V
Checked exceptions Cannot throw Can throw
Future result Future<?> Future<V>
Callable<Integer> task = () -> {
    Thread.sleep(100);
    return 42;  // returns a value
};

ExecutorService exec = Executors.newSingleThreadExecutor();
Future<Integer> future = exec.submit(task);

// Block until result available
int result = future.get();           // 42
int result2 = future.get(1, SECONDS); // with timeout
exec.shutdown();

24. What are CountDownLatch and CyclicBarrier?

CountDownLatch — one or more threads wait until a count reaches zero. One-time use.

CountDownLatch latch = new CountDownLatch(3);

// 3 worker threads each call latch.countDown() when done
Runnable worker = () -> { doWork(); latch.countDown(); };
for (int i = 0; i < 3; i++) executor.submit(worker);

latch.await();  // main thread waits here until count == 0
System.out.println("All workers done");

CyclicBarrier — all threads wait at a barrier point before proceeding. Reusable.

CyclicBarrier barrier = new CyclicBarrier(3, () -> System.out.println("Phase done"));

Runnable phase = () -> {
    doPhaseWork();
    barrier.await();  // wait until all 3 threads reach here
    doNextPhase();
};

JVM & Memory

25. What is the difference between heap and stack memory?

Stack Heap
Stores Primitives, references, method frames Objects, arrays, class instances
Lifetime Until method returns Until GC collects
Size Small (few MB) Large (configured by -Xmx)
Thread Per-thread Shared across threads
Error StackOverflowError OutOfMemoryError
void method() {
    int x = 5;             // x is on the stack
    String s = new String("hi"); // reference s on stack; object on heap
}
// When method() returns: x, s are popped; object may be GCed

26. What is garbage collection in Java?

GC automatically reclaims heap memory occupied by unreachable objects.

Generational GC (default HotSpot):

Young Generation (Eden + S0 + S1)  →  Old Generation  →  Metaspace
Minor GC (fast, frequent)              Major/Full GC (slower)
  • Most objects die young (short-lived allocations).
  • Objects that survive several GC cycles are promoted to Old Generation.
  • Stop-the-world pauses are minimised by collectors like G1GC (default since Java 9) and ZGC/Shenandoah (low-latency).

JVM flags:

-Xms512m -Xmx2g          # initial and max heap
-XX:+UseG1GC             # G1 collector
-XX:+UseZGC              # low-latency ZGC (Java 15+)
-verbose:gc              # GC logging

27. What is the difference between String, StringBuilder, and StringBuffer?

String StringBuilder StringBuffer
Mutability Immutable Mutable Mutable
Thread-safe Yes (immutable) No Yes (synchronised)
Performance Slowest for concat Fastest Slower than SB
Use when Fixed strings Single-thread concat Multi-thread (rare)
// String concatenation in loop — creates N+1 String objects
String result = "";
for (int i = 0; i < 1000; i++) result += i;  // slow

// StringBuilder — single mutable buffer
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) sb.append(i); // fast
String result = sb.toString();

String pool: String literals are interned. "hello" == "hello" is true because both reference the same pool entry.


28. What is ClassLoader?

ClassLoader loads .class files into the JVM at runtime.

Delegation model:

  1. Bootstrap ClassLoader — loads java.lang, java.util etc. from rt.jar / modules.
  2. Platform ClassLoader (ext) — loads extension libraries.
  3. Application ClassLoader — loads your app's classpath.
// Retrieve class loader
ClassLoader cl = MyClass.class.getClassLoader();
System.out.println(cl); // sun.misc.Launcher$AppClassLoader

// Custom class loader (for plugins, sandboxing)
class PluginLoader extends URLClassLoader {
    PluginLoader(URL[] urls) { super(urls, null); } // null parent = isolated
}

Exception Handling

29. What is the difference between checked and unchecked exceptions?

Checked Unchecked
Extends Exception RuntimeException
Must handle Yes (compile error) No
Common examples IOException, SQLException NullPointerException, IllegalArgumentException
Signals External, recoverable failures Programming bugs
// Checked — caller must handle or declare
public String readFile(String path) throws IOException {
    return Files.readString(Path.of(path));
}

// Unchecked — programming error, let it propagate
public int divide(int a, int b) {
    if (b == 0) throw new IllegalArgumentException("b must not be zero");
    return a / b;
}

30. What is try-with-resources?

Introduced in Java 7, it automatically closes AutoCloseable resources.

// Before Java 7 — verbose, easy to forget close()
Connection conn = null;
try {
    conn = getConnection();
    // use conn
} finally {
    if (conn != null) conn.close();
}

// Java 7+ try-with-resources
try (Connection conn = getConnection();
     PreparedStatement ps = conn.prepareStatement(sql)) {
    ResultSet rs = ps.executeQuery();
    // process rs
} // conn and ps closed automatically, even on exception

Multiple resources are closed in reverse order. The resource's close() exception is suppressed if an exception was already thrown from the try block (accessible via Throwable.getSuppressed()).


31. What is exception chaining?

Wrapping a low-level exception in a higher-level one while preserving the cause.

// Service layer wraps repository exception
class UserService {
    User findUser(int id) {
        try {
            return userRepo.findById(id);
        } catch (SQLException e) {
            throw new ServiceException("Failed to load user " + id, e); // chain
        }
    }
}

// Log full stack trace including cause
catch (ServiceException e) {
    logger.error("Error", e);          // prints wrapped cause too
    e.getCause();                       // the original SQLException
}

Design Patterns

32. How do you implement the Singleton pattern thread-safely?

// Lazy initialisation with double-checked locking (Java 5+)
public class DatabasePool {
    private static volatile DatabasePool instance;

    private DatabasePool() {}

    public static DatabasePool getInstance() {
        if (instance == null) {
            synchronized (DatabasePool.class) {
                if (instance == null) {             // double-check
                    instance = new DatabasePool();  // volatile ensures visibility
                }
            }
        }
        return instance;
    }
}

// Even simpler — Initialization-on-demand holder (preferred)
public class Config {
    private Config() {}

    private static class Holder {
        static final Config INSTANCE = new Config(); // class loaded lazily
    }

    public static Config getInstance() { return Holder.INSTANCE; }
}

// Enum Singleton — serialisation-safe, reflection-safe
public enum Logger {
    INSTANCE;
    public void log(String msg) { System.out.println(msg); }
}
Logger.INSTANCE.log("Hello");

33. What is the Builder pattern?

Builder separates construction of a complex object from its representation.

public class HttpRequest {
    private final String url;
    private final String method;
    private final Map<String, String> headers;
    private final String body;
    private final int timeoutMs;

    private HttpRequest(Builder b) {
        this.url       = Objects.requireNonNull(b.url);
        this.method    = b.method;
        this.headers   = Map.copyOf(b.headers);
        this.body      = b.body;
        this.timeoutMs = b.timeoutMs;
    }

    public static class Builder {
        private final String url;
        private String method = "GET";
        private Map<String, String> headers = new HashMap<>();
        private String body;
        private int timeoutMs = 5000;

        public Builder(String url)            { this.url = url; }
        public Builder method(String m)       { this.method = m; return this; }
        public Builder header(String k, String v){ headers.put(k, v); return this; }
        public Builder body(String b)         { this.body = b; return this; }
        public Builder timeout(int ms)        { this.timeoutMs = ms; return this; }
        public HttpRequest build()            { return new HttpRequest(this); }
    }
}

// Usage
HttpRequest req = new HttpRequest.Builder("https://api.example.com/users")
    .method("POST")
    .header("Authorization", "Bearer token123")
    .body("{\"name\":\"Alice\"}")
    .timeout(3000)
    .build();

34. What is the Factory Method pattern?

Factory Method defines an interface for creating an object but lets subclasses decide which class to instantiate.

interface Notification {
    void send(String message);
}

class EmailNotification implements Notification {
    @Override public void send(String msg) { System.out.println("Email: " + msg); }
}

class SmsNotification implements Notification {
    @Override public void send(String msg) { System.out.println("SMS: " + msg); }
}

// Factory
class NotificationFactory {
    public static Notification create(String type) {
        return switch (type) {
            case "email" -> new EmailNotification();
            case "sms"   -> new SmsNotification();
            default      -> throw new IllegalArgumentException("Unknown: " + type);
        };
    }
}

Notification n = NotificationFactory.create("email");
n.send("Hello!");

String & Data

35. How does Java handle String immutability?

Strings are immutable because:

  1. Security — class names, file paths, network connections use strings.
  2. Thread safety — immutable objects are safe to share without synchronisation.
  3. String pool — the JVM can intern literals safely.
  4. Hash caching — hashCode() is computed once and cached.
String s = "hello";
s.toUpperCase();    // returns a new String — s is unchanged
System.out.println(s); // "hello"

String upper = s.toUpperCase(); // must reassign

36. What are the key differences between Comparable and Comparator?

Comparable<T> Comparator<T>
Method compareTo(T o) compare(T o1, T o2)
Defined in The class itself Separate class / lambda
Sorting order Natural (single) Custom (multiple)
Modifying class Required Not required
class Student implements Comparable<Student> {
    String name;
    double gpa;

    @Override
    public int compareTo(Student o) {
        return Double.compare(this.gpa, o.gpa); // natural order by GPA
    }
}

// Custom comparator — without changing Student
Comparator<Student> byName = Comparator.comparing(s -> s.name);
Comparator<Student> byGpaDesc = Comparator.comparingDouble(Student::gpa).reversed();

students.sort(byName.thenComparing(byGpaDesc));

Java Records, Sealed Classes & Modern Features

37. What are Java Records (Java 16+)?

Records are immutable data carriers that auto-generate constructor, getters, equals, hashCode, toString.

// Before — verbose DTO
public final class Point {
    private final int x, y;
    public Point(int x, int y) { this.x = x; this.y = y; }
    public int x() { return x; }
    public int y() { return y; }
    @Override public boolean equals(Object o) { ... }
    @Override public int hashCode() { ... }
    @Override public String toString() { return "Point[x=" + x + ", y=" + y + "]"; }
}

// Record — one line
record Point(int x, int y) {}

Point p = new Point(3, 4);
System.out.println(p.x());  // 3
System.out.println(p);       // Point[x=3, y=4]

Records can have compact constructors for validation:

record Range(int min, int max) {
    Range {  // compact constructor — parameters already assigned
        if (min > max) throw new IllegalArgumentException("min > max");
    }
}

38. What are Sealed Classes (Java 17+)?

Sealed classes restrict which classes can extend them — useful for exhaustive pattern matching.

public sealed interface Shape
    permits Circle, Rectangle, Triangle {}

record Circle(double radius)          implements Shape {}
record Rectangle(double w, double h)  implements Shape {}
record Triangle(double base, double h)implements Shape {}

// Pattern matching with exhaustive switch (Java 21+)
double area(Shape s) {
    return switch (s) {
        case Circle(var r)         -> Math.PI * r * r;
        case Rectangle(var w, var h) -> w * h;
        case Triangle(var b, var h)  -> 0.5 * b * h;
    };
}

39. What is pattern matching for instanceof (Java 16+)?

// Before Java 16
if (obj instanceof String) {
    String s = (String) obj;
    System.out.println(s.length());
}

// Java 16+ — pattern variable
if (obj instanceof String s) {
    System.out.println(s.length()); // s is in scope here
}

// Java 21 — switch patterns
Object value = getPayload();
String result = switch (value) {
    case Integer i -> "int: " + i;
    case String s  -> "str: " + s.toUpperCase();
    case null      -> "null";
    default        -> "other";
};

40. What are Virtual Threads (Java 21)?

Virtual threads (Project Loom) are lightweight JVM-managed threads — you can create millions of them without exhausting OS threads.

// Traditional thread pool — OS threads are scarce
ExecutorService platform = Executors.newFixedThreadPool(200);

// Virtual thread per task — scales to millions
ExecutorService virtual = Executors.newVirtualThreadPerTaskExecutor();

try (var exec = Executors.newVirtualThreadPerTaskExecutor()) {
    for (int i = 0; i < 100_000; i++) {
        exec.submit(() -> {
            Thread.sleep(Duration.ofMillis(100)); // blocking is fine
            return "done";
        });
    }
}

Virtual threads are not faster for CPU-bound tasks — they shine for I/O-bound work (HTTP, database, file I/O) where threads spend most time waiting.


Spring Boot Basics

41. What is dependency injection? How does Spring implement it?

DI is a technique where an object's dependencies are provided externally rather than created internally (Inversion of Control).

// Without DI — tight coupling
class OrderService {
    private PaymentGateway gateway = new StripeGateway(); // hardcoded
}

// With Spring DI — loose coupling
@Service
class OrderService {
    private final PaymentGateway gateway;

    @Autowired  // Spring injects the implementation
    OrderService(PaymentGateway gateway) {
        this.gateway = gateway;
    }
}

@Component
class StripeGateway implements PaymentGateway { ... }

Spring's IoC container wires beans together. Constructor injection (shown above) is preferred over field injection (@Autowired on field) — it makes dependencies explicit and testable.


42. What are the main Spring annotations?

Annotation Purpose
@SpringBootApplication Entry point (@Configuration + @EnableAutoConfiguration + @ComponentScan)
@RestController @Controller + @ResponseBody
@GetMapping, @PostMapping HTTP method shortcuts
@Service Business layer bean
@Repository Data layer bean (wraps persistence exceptions)
@Component Generic bean
@Autowired Inject dependency
@Value("${prop}") Inject property
@Transactional Wrap method in transaction
@Entity, @Table JPA entity mapping
@Bean Register a method's return value as a bean

43. What is @Transactional? What are its pitfalls?

@Service
class TransferService {
    @Transactional  // commits on success, rolls back on RuntimeException
    public void transfer(Account from, Account to, BigDecimal amount) {
        from.debit(amount);
        to.credit(amount);
        // if exception thrown here, BOTH operations roll back
    }
}

Common pitfalls:

  1. Self-invocationthis.transfer() bypasses the proxy, so @Transactional has no effect. Inject self or restructure.
  2. Checked exceptions — by default only RuntimeException triggers rollback. Use @Transactional(rollbackFor = Exception.class).
  3. @Transactional on private methods — proxies can't intercept private methods.
  4. Lazy loading outside transactionLazyInitializationException when accessing collections after transaction ends.

Miscellaneous & Tricky Questions

44. What is the difference between == for Integer objects?

Integer a = 127;
Integer b = 127;
System.out.println(a == b);  // true — cached (-128 to 127)

Integer c = 128;
Integer d = 128;
System.out.println(c == d);  // false — different objects beyond cache range
System.out.println(c.equals(d)); // true — always use equals()

Java caches Integer values from -128 to 127 (and Boolean, small Long, Short, Byte). Comparing with == outside this range gives unexpected results.


45. What is covariant return type?

An overriding method can return a more specific type than the parent's return type.

class Animal {
    Animal clone() { return new Animal(); }
}

class Dog extends Animal {
    @Override
    Dog clone() { return new Dog(); } // covariant return — Dog is a subtype of Animal
}

Animal a = new Dog();
Dog d = ((Dog) a).clone(); // returns Dog, no cast needed via Dog reference

46. What is the contract between hashCode and equals?

  1. If a.equals(b) is true, then a.hashCode() == b.hashCode() must be true.
  2. If a.hashCode() != b.hashCode(), then a.equals(b) must be false.
  3. If a.hashCode() == b.hashCode(), a.equals(b) may be true or false (collision).

Breaking this contract causes HashMap/HashSet to malfunction — objects become unretrievable.

class Point {
    int x, y;

    @Override
    public boolean equals(Object o) {
        if (!(o instanceof Point p)) return false;
        return x == p.x && y == p.y;
    }

    @Override
    public int hashCode() {
        return Objects.hash(x, y); // consistent with equals
    }
}

47. What are var (local variable type inference, Java 10+)?

var lets the compiler infer the type of a local variable from the right-hand side expression.

// Before
Map<String, List<Integer>> map = new HashMap<String, List<Integer>>();

// With var
var map = new HashMap<String, List<Integer>>();

// Useful in for-each
for (var entry : map.entrySet()) {
    System.out.println(entry.getKey() + " → " + entry.getValue());
}

// NOT allowed: return types, fields, method params, null literal
// var x = null;          // compile error — type cannot be inferred
// private var name = ""; // compile error — not local variable

48. What are CompletableFuture key methods?

// Async chain
CompletableFuture.supplyAsync(() -> fetchUser(id))       // runs on ForkJoinPool
    .thenApply(user -> fetchOrders(user.id))             // transform
    .thenAccept(orders -> sendEmail(orders))             // consume
    .exceptionally(ex -> { log(ex); return null; });     // error handler

// Combine two futures
CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> cf2 = CompletableFuture.supplyAsync(() -> "World");

cf1.thenCombine(cf2, (a, b) -> a + " " + b)
   .thenAccept(System.out::println);  // "Hello World"

// Wait for all
CompletableFuture.allOf(cf1, cf2).join();

// Wait for any
CompletableFuture.anyOf(cf1, cf2).thenAccept(System.out::println);

49. What is the difference between Iterator.remove() and Collection.remove()?

List<String> list = new ArrayList<>(List.of("a", "b", "c"));

// WRONG — ConcurrentModificationException
for (String s : list) {
    if (s.equals("b")) list.remove(s); // modifying while iterating
}

// Correct — Iterator.remove()
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    if (it.next().equals("b")) it.remove(); // safe
}

// Or use removeIf (Java 8+)
list.removeIf(s -> s.equals("b"));

50. What is String.intern()? When would you use it?

intern() returns the canonical representation of a String from the JVM's String pool.

String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2);         // false — heap objects

String s3 = s1.intern();
String s4 = s2.intern();
System.out.println(s3 == s4);         // true — same pool reference

When to use: Processing millions of repeated strings (e.g., parsing log files with many repeated field names) can save memory via interning. Modern JVMs handle many common cases automatically (literals, class names). Profile before using — interning has overhead and can stress the old generation.


Common mistakes

Mistake Problem Fix
== on Strings / Integer Reference comparison, not value Use .equals() or Objects.equals()
Catching Exception everywhere Hides bugs Catch specific exception types
e.printStackTrace() in production No structured logging Use a logger (SLF4J + Logback)
Ignoring InterruptedException Swallows cancellation signal Restore interrupt: Thread.currentThread().interrupt()
Accessing lazy JPA collection outside transaction LazyInitializationException Use @Transactional or fetch eagerly
Mutable static fields Hidden global state, thread safety issues Use final fields; inject via DI
Not closing streams / connections Resource leak Use try-with-resources
null return instead of Optional Unexpected NPEs Return Optional<T> from methods

FAQ

Q: What Java version should I target for interviews?
Most questions target Java 8-11 (streams, lambdas, Optional) but knowing Java 17+ features (records, sealed classes, pattern matching) stands out. Java 21 (virtual threads, sequenced collections) is increasingly relevant.

Q: Is Java pass-by-value or pass-by-reference?
Always pass-by-value. For objects, the value passed is the reference (memory address), not the object itself. You can mutate the object through the reference, but you can't make the caller's variable point to a different object.

void change(StringBuilder sb) {
    sb.append(" world");    // mutates the object — visible to caller
    sb = new StringBuilder("replaced"); // rebinds local var only — NOT visible
}

Q: What is the difference between throw and throws?
throw is a statement that throws an exception. throws is a method declaration that declares what checked exceptions a method may throw.

void risky() throws IOException {   // declares
    throw new IOException("failed"); // throws
}

Q: Can you override a static method?
No. static methods belong to the class, not an instance — they are hidden, not overridden. Calling via a superclass reference invokes the superclass's version regardless of the actual runtime type.

Q: What happens if main() throws an exception?
The JVM prints the stack trace to stderr and exits with a non-zero exit code. The thread's uncaught exception handler is invoked first (Thread.getDefaultUncaughtExceptionHandler()).

Q: What is the difference between String.format() and text blocks (Java 15+)?

// String.format
String sql = String.format("SELECT * FROM %s WHERE id = %d", table, id);

// Text block (Java 15+) — preserves indentation, no escape needed
String json = """
        {
            "name": "Alice",
            "age": 30
        }
        """;

Text blocks handle multiline strings cleanly. Use String.format() or formatted() for dynamic substitution within text blocks.

Related tools

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools