Top Java Interview Questions for 6+ Years Experience (2026)

๐Ÿ‘๏ธ 7 Views
|
๐Ÿ“… Aug 07, 2026
|
โฑ๏ธ 19 min read
Top Java Interview Questions for 6+ Years Experience (2026)

Java interviews at the senior level go far beyond syntax and basic OOP. With 6+ years of experience, interviewers expect you to discuss JVM internals, concurrency, memory management, design patterns, microservices, and real production scenarios you have actually solved. This guide covers the most commonly asked questions at the senior level โ€” with detailed answers that reflect the depth interviewers are looking for.

๐Ÿ“‹ Jump to a Question

  1. How does the JVM work internally?
  2. What is Garbage Collection and how does G1GC work?
  3. Explain Java Memory Model โ€” Stack vs Heap
  4. What is the difference between HashMap, LinkedHashMap, and TreeMap?
  5. How does ConcurrentHashMap work internally?
  6. What is the difference between synchronized, volatile, and atomic variables?
  7. Explain Java 8 features โ€” Streams, Lambdas, Optional
  8. What are design patterns you have used in production?
  9. What is the difference between abstract class and interface in Java 8+?
  10. How does Spring Boot work internally?
  11. What is dependency injection and how does Spring handle it?
  12. Explain microservices โ€” how do you handle communication and failures?
  13. What is the difference between checked and unchecked exceptions?
  14. How do you handle thread safety in a high-concurrency application?
  15. What is Java Virtual Threads (Project Loom)?

1. How Does the JVM Work Internally?

The Java Virtual Machine is what makes Java platform-independent. When you compile Java source code, the compiler produces bytecode (.class files) โ€” not machine code. The JVM then interprets or compiles that bytecode for the specific machine it is running on.

The JVM has three main components:

  • Class Loader Subsystem โ€” loads .class files into memory. Has three loaders: Bootstrap (core Java libraries), Extension (ext directory), and Application (your classpath). Follows the parent delegation model โ€” always asks the parent loader first before loading a class itself.
  • Runtime Data Areas โ€” the memory regions: Heap (objects), Stack (method frames), Method Area (class metadata), PC Register (current instruction pointer), and Native Method Stack.
  • Execution Engine โ€” executes bytecode. Contains the Interpreter (executes bytecode line by line), JIT Compiler (compiles hot code paths to native machine code for performance), and Garbage Collector.
// JVM execution flow:
Java Source (.java)
     โ†“ javac compiler
Bytecode (.class)
     โ†“ Class Loader
Method Area (class metadata stored)
     โ†“ Execution Engine
Interpreter โ†’ executes bytecode
JIT Compiler โ†’ detects hot methods, compiles to native code
GC โ†’ manages heap memory automatically

Senior tip: Mention JIT compilation specifically โ€” the JVM does not just interpret bytecode, it profiles your running code and compiles the most-executed paths (hot spots) to native machine code. This is why Java performance improves over time in long-running applications.

2. What Is Garbage Collection and How Does G1GC Work?

Garbage Collection automatically reclaims memory from objects that are no longer reachable. Java uses a generational GC model based on the observation that most objects die young.

Heap regions:

  • Young Generation โ€” where new objects are allocated. Contains Eden space and two Survivor spaces (S0, S1).
  • Old Generation (Tenured) โ€” objects that survived multiple GC cycles get promoted here.
  • Metaspace (Java 8+) โ€” class metadata, replaced PermGen which had fixed size issues.
// GC types and when they occur:
Minor GC  โ†’ cleans Young Generation (frequent, fast, stop-the-world briefly)
Major GC  โ†’ cleans Old Generation (infrequent, slow)
Full GC   โ†’ cleans entire heap (very slow, avoid in production)

// G1GC (Garbage First) โ€” default since Java 9
// Divides heap into equal-sized regions instead of fixed generations
// Collects regions with most garbage first (hence "Garbage First")
// Designed for low pause times on large heaps (4GB+)
// Target pause time configurable: -XX:MaxGCPauseMillis=200

Key JVM flags to know:

-Xms512m              // initial heap size
-Xmx4g               // maximum heap size
-XX:+UseG1GC         // use G1 collector (default Java 9+)
-XX:MaxGCPauseMillis=200  // target max pause time
-XX:+PrintGCDetails  // log GC details (for debugging)

3. Explain Java Memory Model โ€” Stack vs Heap

// Stack
// - Each thread has its own stack
// - Stores: method frames, local variables, references (not objects)
// - LIFO โ€” last in first out
// - Fixed size per thread (StackOverflowError if exceeded)
// - Very fast allocation and deallocation

// Heap
// - Shared across all threads
// - Stores: all objects, instance variables
// - Managed by Garbage Collector
// - Slower than stack (allocation + GC overhead)
// - OutOfMemoryError if full

public class MemoryExample {
    // Instance variable โ€” stored in HEAP with the object
    private int instanceVar = 10;

    public void method() {
        // Local variable โ€” stored in STACK
        int localVar = 20;

        // 'obj' reference is on STACK
        // The actual String object is on HEAP
        String obj = new String("hello");
    }
}

// Static variables โ€” stored in Method Area (Metaspace)
static int staticVar = 100;

4. What Is the Difference Between HashMap, LinkedHashMap, and TreeMap?

HashMap LinkedHashMap TreeMap
Order No order Insertion order Sorted (natural or comparator)
get/put time O(1) avg O(1) avg O(log n)
Null keys 1 null key allowed 1 null key allowed Not allowed
Thread safe No No No
Backed by Hash table Hash table + LinkedList Red-Black Tree
Use when Fast lookup, order irrelevant Need insertion order Need sorted keys
// HashMap internal working (Java 8+)
// Array of Node<K,V> (buckets)
// hashCode() determines bucket index
// equals() resolves collisions within bucket
// Java 8+: when bucket has >8 entries, linked list converts to Red-Black Tree
// Load factor default 0.75 โ€” resizes when 75% full

// hashCode() contract:
// If a.equals(b) โ†’ a.hashCode() == b.hashCode() (MUST)
// If a.hashCode() == b.hashCode() โ†’ a.equals(b) might be false (collision)

5. How Does ConcurrentHashMap Work Internally?

ConcurrentHashMap allows multiple threads to read and write simultaneously without locking the entire map โ€” unlike Hashtable or Collections.synchronizedMap() which lock the whole structure.

// Java 8+ ConcurrentHashMap
// Uses segment-level locking (striped locking)
// Only the specific bucket (segment) is locked during write
// Reads are completely lock-free using volatile
// Default concurrency level: 16 โ€” 16 threads can write simultaneously

ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();

// Thread-safe operations
map.put("key", 1);                           // locks only that bucket
map.get("key");                              // lock-free read
map.putIfAbsent("key", 2);                   // atomic check-then-act
map.computeIfAbsent("key", k -> k.length()); // atomic compute

// โŒ Not atomic โ€” two separate operations
if (!map.containsKey("key")) {
    map.put("key", 1); // race condition between these two lines
}

// โœ… Atomic
map.putIfAbsent("key", 1);

6. What Is the Difference Between synchronized, volatile, and Atomic Variables?

// synchronized โ€” mutual exclusion + visibility
// Only one thread can execute the block at a time
// Guarantees both atomicity AND visibility
public synchronized void increment() {
    count++; // only one thread at a time
}

// Or synchronized block (preferred โ€” locks only what you need)
public void increment() {
    synchronized(this) {
        count++;
    }
}

// volatile โ€” visibility only, NOT atomicity
// Guarantees that reads/writes go directly to main memory
// Prevents caching in CPU registers or local thread caches
private volatile boolean running = true;

// This is safe with volatile:
public void stop() { running = false; }   // write visible to all threads
public void run()  { while (running) { } } // always reads latest value

// This is NOT safe with volatile (not atomic):
private volatile int count = 0;
count++; // read-modify-write โ€” not atomic even with volatile

// Atomic โ€” atomicity WITHOUT locking (uses CPU compare-and-swap)
private AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet(); // atomic, lock-free, thread-safe

// When to use which:
// volatile  โ†’ single writer, multiple readers (flags, stop signals)
// atomic    โ†’ multiple writers, simple operations (counters)
// synchronized โ†’ complex multi-step operations that must be atomic together

7. Explain Java 8 Features โ€” Streams, Lambdas, Optional

// Lambda โ€” anonymous function
// Before Java 8
Runnable r = new Runnable() {
    public void run() { System.out.println("running"); }
};

// Java 8 lambda
Runnable r = () -> System.out.println("running");

// Streams โ€” declarative data processing pipeline
List<String> names = Arrays.asList("Randhir", "Priya", "Amit", "Neha");

List<String> result = names.stream()
    .filter(n -> n.length() > 4)       // intermediate โ€” lazy
    .map(String::toUpperCase)           // intermediate โ€” lazy
    .sorted()                           // intermediate โ€” lazy
    .collect(Collectors.toList());      // terminal โ€” triggers execution
// ["PRIYA", "RANDHIR"]

// Parallel stream โ€” uses ForkJoinPool
long count = names.parallelStream()
    .filter(n -> n.startsWith("R"))
    .count();
// Use carefully โ€” not always faster due to overhead

// Optional โ€” avoid NullPointerException
Optional<String> name = Optional.ofNullable(getName());

// Bad pattern โ€” defeats the purpose
if (name.isPresent()) { System.out.println(name.get()); }

// Good patterns
name.ifPresent(System.out::println);
String result = name.orElse("Unknown");
String result = name.orElseGet(() -> computeDefault());
String result = name.orElseThrow(() -> new RuntimeException("No name"));

8. What Design Patterns Have You Used in Production?

Do not just list pattern names โ€” explain when and why you used them. Here are the most common ones to discuss at senior level:

// Singleton โ€” one instance, globally accessible
// Used for: database connection pools, config managers, loggers
public class DatabasePool {
    private static volatile DatabasePool instance;

    private DatabasePool() {}

    public static DatabasePool getInstance() {
        if (instance == null) {                    // first check (no lock)
            synchronized (DatabasePool.class) {
                if (instance == null) {             // second check (with lock)
                    instance = new DatabasePool();  // double-checked locking
                }
            }
        }
        return instance;
    }
}

// Builder โ€” construct complex objects step by step
// Used for: building requests, configuration objects
User user = new User.Builder()
    .name("Randhir")
    .email("randhir@example.com")
    .role("admin")
    .build();

// Observer โ€” notify multiple dependents of state changes
// Used for: event systems, real-time notifications
// In Spring: ApplicationEventPublisher / @EventListener

// Strategy โ€” swap algorithms at runtime
// Used for: payment processing, sorting, pricing rules
PaymentProcessor processor = new PaymentProcessor(new UPIStrategy());
processor.process(order);
// Later: processor.setStrategy(new CreditCardStrategy());

// Factory โ€” create objects without specifying exact class
// Used for: creating different notification types, DB connections
NotificationFactory.create(NotificationType.EMAIL).send(message);

9. What Is the Difference Between Abstract Class and Interface in Java 8+?

// Before Java 8 โ€” clear distinction
// Interface: only abstract methods, constants
// Abstract class: can have concrete methods, state, constructors

// Java 8+ blurred the line โ€” interfaces can now have:
// - default methods (concrete method with implementation)
// - static methods

// Java 9+ interfaces can also have:
// - private methods (shared logic between default methods)

interface Vehicle {
    // Abstract โ€” must be implemented
    void start();

    // Default โ€” optional to override
    default void stop() {
        System.out.println("Stopping vehicle");
    }

    // Static โ€” called on interface, not instance
    static Vehicle create(String type) {
        return type.equals("car") ? new Car() : new Bike();
    }
}

// Key remaining differences:
// 1. Abstract class can have constructors โ€” interface cannot
// 2. Abstract class can have instance fields โ€” interface only constants
// 3. A class can implement MULTIPLE interfaces โ€” only extend ONE abstract class
// 4. Abstract class can have private/protected members โ€” interface cannot (before Java 9)

// When to use which:
// Interface  โ†’ define a contract, enable multiple inheritance of behaviour
// Abstract   โ†’ share code between related classes, define partial implementation

10. How Does Spring Boot Work Internally?

This goes beyond "it auto-configures things." Here is the actual mechanism a senior developer should understand:

@SpringBootApplication
// This annotation is a combination of three annotations:
// @Configuration     โ€” marks class as a source of bean definitions
// @EnableAutoConfiguration โ€” enables Spring Boot's auto-configuration
// @ComponentScan     โ€” scans current package for components

// Auto-configuration mechanism:
// 1. Spring Boot scans META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
//    in all jars on the classpath
// 2. Each listed class is conditionally loaded based on @Conditional annotations
// 3. For example โ€” DataSourceAutoConfiguration loads only if:
//    - A DataSource class is on the classpath (@ConditionalOnClass)
//    - No DataSource bean is already defined (@ConditionalOnMissingBean)

// Spring Boot startup sequence:
// 1. SpringApplication.run() called
// 2. Create ApplicationContext
// 3. Load bean definitions from @Configuration classes
// 4. Run auto-configuration โ€” conditionally add missing beans
// 5. Refresh context โ€” instantiate all beans
// 6. Run ApplicationRunner / CommandLineRunner beans
// 7. Application ready

// Embedded server:
// Spring Boot includes Tomcat (or Jetty/Undertow) as a dependency
// DispatcherServlet registered as a servlet in the embedded container
// No need to deploy WAR to external server

11. What Is Dependency Injection and How Does Spring Handle It?

// Dependency Injection โ€” provide dependencies from outside
// instead of creating them inside the class

// โŒ Without DI โ€” tightly coupled
public class OrderService {
    private PaymentService paymentService = new PaymentService(); // hard dependency
}

// โœ… With DI โ€” loosely coupled
public class OrderService {
    private final PaymentService paymentService;

    // Constructor injection โ€” preferred in Spring
    public OrderService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }
}

// Spring IoC Container creates and manages all beans
// Three types of injection:

// 1. Constructor injection (recommended โ€” immutable, testable)
@Service
public class OrderService {
    private final PaymentService paymentService;

    @Autowired // optional in modern Spring when only one constructor
    public OrderService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }
}

// 2. Setter injection (optional dependencies)
@Autowired
public void setPaymentService(PaymentService paymentService) {
    this.paymentService = paymentService;
}

// 3. Field injection (convenient but not recommended โ€” hard to test)
@Autowired
private PaymentService paymentService;

// Bean scopes:
// @Scope("singleton")  โ€” one instance per ApplicationContext (default)
// @Scope("prototype")  โ€” new instance every time it is requested
// @Scope("request")    โ€” one per HTTP request (web apps)
// @Scope("session")    โ€” one per HTTP session

12. Explain Microservices โ€” How Do You Handle Communication and Failures?

// Synchronous communication โ€” REST or gRPC
// Service A calls Service B directly and waits for response
// Problem: if B is slow or down, A is affected

// Asynchronous communication โ€” Message queues (Kafka, RabbitMQ)
// Service A publishes event, Service B consumes when ready
// Services are decoupled โ€” B being down does not affect A immediately

// Handling failures โ€” Circuit Breaker pattern (Resilience4j)
@CircuitBreaker(name = "paymentService", fallbackMethod = "paymentFallback")
public PaymentResponse processPayment(Order order) {
    return paymentServiceClient.pay(order);
}

public PaymentResponse paymentFallback(Order order, Exception ex) {
    // Return cached response or queue for retry
    return PaymentResponse.queued(order.getId());
}

// Circuit breaker states:
// CLOSED   โ†’ normal operation, requests pass through
// OPEN     โ†’ too many failures, requests fail immediately (no calls to service)
// HALF_OPEN โ†’ test if service recovered, allow limited requests through

// Service discovery โ€” Eureka / Consul
// Services register themselves on startup
// Client looks up service address dynamically
// No hardcoded URLs โ€” services can scale and move

// API Gateway โ€” single entry point
// Handles: authentication, rate limiting, routing, load balancing
// Examples: Spring Cloud Gateway, Kong, AWS API Gateway

13. What Is the Difference Between Checked and Unchecked Exceptions?

// Checked โ€” compiler forces you to handle or declare them
// Subclass of Exception (but not RuntimeException)
// Represent recoverable external issues: file not found, network failure, DB error
public void readFile(String path) throws IOException {
    FileReader reader = new FileReader(path); // throws FileNotFoundException (checked)
}

// Unchecked โ€” compiler does not force handling
// Subclass of RuntimeException
// Represent programming bugs: null access, array out of bounds
String s = null;
s.length(); // NullPointerException (unchecked) โ€” should be fixed, not caught

// Exception hierarchy:
// Throwable
// โ”œโ”€โ”€ Error (JVM errors โ€” don't catch: OutOfMemoryError, StackOverflowError)
// โ””โ”€โ”€ Exception
//     โ”œโ”€โ”€ RuntimeException (unchecked)
//     โ”‚   โ”œโ”€โ”€ NullPointerException
//     โ”‚   โ”œโ”€โ”€ IllegalArgumentException
//     โ”‚   โ””โ”€โ”€ ArrayIndexOutOfBoundsException
//     โ””โ”€โ”€ IOException (checked)
//         โ”œโ”€โ”€ FileNotFoundException
//         โ””โ”€โ”€ SQLException

// Senior best practice:
// Create custom exceptions for domain-specific errors
public class InsufficientFundsException extends RuntimeException {
    private final double amount;
    public InsufficientFundsException(double amount) {
        super("Insufficient funds. Short by: " + amount);
        this.amount = amount;
    }
}

14. How Do You Handle Thread Safety in a High-Concurrency Application?

// Level 1 โ€” Immutability (best โ€” no synchronization needed)
// Immutable objects are inherently thread-safe
public final class Money {
    private final double amount;
    private final String currency;

    public Money(double amount, String currency) {
        this.amount   = amount;
        this.currency = currency;
    }
    // No setters โ€” cannot be modified after creation
}

// Level 2 โ€” Thread-local state
// Each thread has its own copy โ€” no sharing, no synchronization
ThreadLocal<DateFormat> dateFormat =
    ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));

// Level 3 โ€” Atomic variables (lock-free for simple operations)
AtomicLong requestCount = new AtomicLong(0);
requestCount.incrementAndGet();

// Level 4 โ€” Concurrent collections
ConcurrentHashMap<String, Session> sessions = new ConcurrentHashMap<>();
CopyOnWriteArrayList<Listener> listeners = new CopyOnWriteArrayList<>();
// CopyOnWriteArrayList: creates new copy on every write โ€” great for read-heavy

// Level 5 โ€” Explicit locks (when synchronized is too coarse)
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

public String getData() {
    lock.readLock().lock();    // multiple threads can read simultaneously
    try {
        return data;
    } finally {
        lock.readLock().unlock();
    }
}

public void setData(String value) {
    lock.writeLock().lock();   // only one thread can write
    try {
        data = value;
    } finally {
        lock.writeLock().unlock();
    }
}

15. What Are Java Virtual Threads (Project Loom)?

Virtual threads were introduced as a preview in Java 19 and became stable in Java 21. They are a lightweight alternative to platform (OS) threads that dramatically simplifies concurrent programming.

// Traditional platform thread โ€” expensive
// Each thread = OS thread = ~1MB stack memory
// A server handling 10,000 concurrent requests needs 10,000 OS threads
// OS has hard limits โ€” typically 10,000-100,000 threads max

// Virtual thread โ€” lightweight, managed by JVM
// Millions of virtual threads can run on a handful of OS threads
// JVM schedules virtual threads onto OS threads automatically
// When a virtual thread blocks (I/O wait), the OS thread is freed for other virtual threads

// Creating virtual threads (Java 21)
Thread vt = Thread.ofVirtual().start(() -> {
    // This runs on a virtual thread
    System.out.println("Running in: " + Thread.currentThread());
});

// With ExecutorService
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
    for (int i = 0; i < 100_000; i++) {
        executor.submit(() -> {
            // 100,000 virtual threads โ€” no problem
            Thread.sleep(Duration.ofSeconds(1));
            return "done";
        });
    }
}

// In Spring Boot 3.2+ โ€” enable virtual threads
// application.properties:
// spring.threads.virtual.enabled=true
// All request handling threads become virtual threads automatically

// When virtual threads help:
// โœ… I/O-bound workloads โ€” web servers, REST clients, database calls
// โŒ CPU-bound workloads โ€” no benefit, use parallel streams or ForkJoinPool

How to Answer These Questions in an Interview

At senior level, the interviewer is not just checking if you know the answer โ€” they are checking if you have used it. For every answer, try to attach a real example:

  • "We used ConcurrentHashMap in our session store because..."
  • "We switched from synchronized to ReentrantReadWriteLock because our read-to-write ratio was 10:1 and it improved throughput by..."
  • "We had a memory leak caused by a ThreadLocal not being cleared after request processing โ€” here is how we diagnosed and fixed it..."

Real stories from production experience carry more weight than any perfectly memorized definition. Prepare 2-3 specific technical problems you have actually solved and tie them to these concepts.

Related Interview Guides

Subscribe to Our Newsletter

Join Our Developer Community!

Unsubscribe Anytime | No Spam