Master these 31 carefully curated interview questions to ace your next Java interview.
Object-oriented, platform-independent (JVM), automatic GC, strong typing, multithreaded, rich standard library.
Compiles to bytecode, runs on JVM. OOP: encapsulation, inheritance, polymorphism, abstraction. Modern Java (17+): records, sealed classes, pattern matching, text blocks, virtual threads.
JVM executes bytecode; JRE = JVM + libraries for running Java; JDK = JRE + development tools (compiler, debugger).
JVM: abstract machine executing bytecode, platform-specific. JRE: JVM + core libraries (java.lang, java.util). JDK: JRE + javac compiler, debugger, docs. Since Java 11, JRE not distributed separately.
Abstract classes have state and constructors; interfaces define contracts with default methods, support multiple inheritance.
Abstract: instance variables, constructors, mixed methods, single inheritance. Interfaces (Java 8+): default methods, static methods, constants, multiple inheritance. Use abstract for sharing state; interface for contracts across unrelated classes.
Framework with List (ArrayList, LinkedList), Set (HashSet, TreeSet), Map (HashMap, TreeMap), Queue (PriorityQueue).
List: ordered, duplicates OK. ArrayList (O(1) get), LinkedList (O(1) add/remove). Set: no duplicates. HashSet (O(1)), TreeSet (sorted). Map: key-value. HashMap (O(1)), TreeMap (sorted), ConcurrentHashMap (thread-safe). Choose based on access patterns.
Comprehensive Java framework: dependency injection, Spring MVC, Spring Boot (auto-config), Spring Data, Spring Security.
Core: IoC container, DI. Spring Boot: opinionated defaults, embedded server. Spring Data: database abstraction. Spring Security: auth. Spring Cloud: microservices. @Autowired for DI, @RestController for APIs. De facto standard for Java web apps.
Concurrent execution via Thread class, Runnable, ExecutorService. Synchronization, locks, and CompletableFuture for async.
Creating: extend Thread or implement Runnable. ExecutorService manages pools. synchronized keyword, Lock interface, volatile. Java 21+: virtual threads (Project Loom) for million-scale concurrency. Issues: deadlocks, race conditions.
Generational collection (young/old gen) with collectors: G1 (default), ZGC (ultra-low latency), Shenandoah.
Young gen: Eden + Survivor (fast minor GC). Old gen: Tenured (slower major GC). G1: balanced. ZGC: <1ms pauses. Tuning: -Xms/-Xmx heap size. Monitor: JVisualVM, GC logs. Leaks: unclosed resources, static collections, listener registration.
Records are immutable data carriers with auto-generated methods. Sealed classes restrict which classes can extend them.
record Point(int x, int y) auto-generates constructor, getters, equals, hashCode, toString. sealed class Shape permits Circle, Square restricts subclasses. Together they create algebraic data types for pattern matching.
Use heap dumps with JVisualVM or Eclipse MAT, identify retained objects, fix unclosed resources and growing collections.
Steps: (1) Monitor heap with JVisualVM. (2) Take heap dump: jmap -dump. (3) Analyze with Eclipse MAT. (4) Check: static collections, unclosed streams (try-with-resources), event listeners, ThreadLocal. Use -XX:+HeapDumpOnOutOfMemoryError.
Use ConcurrentHashMap with doubly-linked list, or LinkedHashMap with removeEldestEntry, or Caffeine library.
Simple: synchronized LinkedHashMap with removeEldestEntry. Production: Caffeine library (near-optimal hit rate). Features: TTL, size-based eviction, compute-if-absent. Google Guava Cache inspired Caffeine.
JVM executes bytecode; JRE includes JVM + libraries for running Java; JDK includes JRE + development tools for building Java.
JVM (Java Virtual Machine): interprets/compiles bytecode, manages memory (GC), provides platform independence. JRE (Java Runtime Environment): JVM + core libraries (java.lang, java.util, java.io) — needed to run Java programs. JDK (Java Development Kit): JRE + compiler (javac), debugger, jar tool, javadoc — needed to develop Java programs. JVM implementations: HotSpot (Oracle), OpenJ9 (IBM), GraalVM (polyglot). JIT compilation: JVM compiles hot bytecode to native code at runtime for performance.
Java has four access modifiers: public (everywhere), protected (package + subclass), default/package-private (package), private (class only).
public: accessible from any class. protected: accessible within same package + subclasses in other packages. default (no keyword): accessible only within same package. private: accessible only within the declaring class. Encapsulation: use private fields + public getters/setters. Best practice: use most restrictive modifier possible. Classes can be public or default. Inner classes can use all four. Interfaces: methods are implicitly public. Records (Java 16+): components generate public accessor methods. sealed classes (Java 17): restrict which classes can extend.
Abstract classes can have state and constructors with partial implementation; interfaces define contracts with default methods but no state.
Abstract class: can have instance fields, constructors, concrete methods, any access modifier. Single inheritance only. Interface (Java 8+): constants only, abstract methods, default methods (with implementation), static methods, private methods (Java 9+). Multiple interfaces allowed. When to use: abstract class for shared state and is-a relationship, interface for capability contracts (can-do). Java 8+ default methods blur the line. Interfaces cannot have constructors or mutable state. sealed interfaces (Java 17) restrict implementations.
Java GC automatically frees unreachable objects using generational collection with young, old, and metaspace regions.
Generational GC: Young Gen (Eden + Survivor spaces — minor GC, fast), Old Gen (long-lived objects — major GC, slower). GC algorithms: G1 (default Java 9+ — region-based, predictable pauses), ZGC (ultra-low latency <10ms pauses), Shenandoah (concurrent), Serial (single-thread), Parallel (throughput). Key concepts: stop-the-world pauses, GC roots (threads, static fields), finalize() deprecated — use Cleaner/PhantomReference. Tuning: -Xmx (max heap), -XX:+UseG1GC, -XX:MaxGCPauseMillis. GC logging: -Xlog:gc for analysis. Avoid: finalizers, large objects in young gen.
Spring is a comprehensive Java framework providing dependency injection, AOP, MVC web framework, and enterprise integration.
Core concepts: IoC (Inversion of Control) — Spring manages object lifecycle. DI (Dependency Injection) — @Autowired injects dependencies. AOP (Aspect-Oriented Programming) — @Aspect for cross-cutting concerns (logging, security). Spring Boot: convention over configuration, embedded server, auto-configuration. Spring MVC: @Controller, @RestController, @RequestMapping. Spring Data: repository pattern for database access. Spring Security: authentication/authorization. Spring Cloud: microservices (config server, service discovery, circuit breaker). Bean scopes: singleton (default), prototype, request, session.
Streams provide functional-style operations for processing collections with lazy evaluation, method chaining, and parallel execution.
Creation: collection.stream(), Stream.of(), Arrays.stream(), IntStream.range(). Intermediate (lazy): filter(), map(), flatMap(), sorted(), distinct(), peek(), limit(), skip(). Terminal (trigger execution): collect(), forEach(), reduce(), count(), anyMatch(), findFirst(), toArray(). Collectors: toList(), toMap(), groupingBy(), joining(), partitioningBy(). Parallel: collection.parallelStream() — fork/join pool. Rules: don't modify source, each stream used once, avoid side effects. Optional for handling null results. Use for declarative data processing.
Records (16+) are immutable data carriers with auto-generated methods; sealed classes (17+) restrict which classes can extend them.
Records: record Point(int x, int y) {} — auto-generates constructor, getters (x(), y()), equals(), hashCode(), toString(). Immutable by design. Compact constructors for validation. Can implement interfaces. Cannot extend classes. Sealed classes: sealed class Shape permits Circle, Square {} — exhaustive hierarchy. Subclasses must be final, sealed, or non-sealed. Enables pattern matching with exhaustive switch. Combined with pattern matching (Java 21): switch(shape) { case Circle c -> c.radius(); }. Used together for algebraic data types.
Virtual threads (Java 21) are lightweight threads managed by JVM, enabling millions of concurrent threads for I/O-bound tasks.
Traditional threads: OS-managed, ~1MB stack each, limited to thousands. Virtual threads: JVM-managed, mounted on carrier (platform) threads, ~1KB initial stack, can create millions. Creation: Thread.ofVirtual().start(task) or Executors.newVirtualThreadPerTaskExecutor(). Benefits: write blocking code that scales like async — no callback hell. Best for: I/O-bound work (HTTP calls, database queries). Not for: CPU-bound tasks (use platform threads). Don't pool virtual threads. synchronized blocks can pin virtual threads — prefer ReentrantLock.
Use synchronized blocks, ConcurrentHashMap, atomic classes, ReentrantLock, or immutable objects to ensure thread safety.
Options: (1) synchronized keyword: simple mutual exclusion. (2) ReentrantLock: more flexible (tryLock, fairness, conditions). (3) ConcurrentHashMap: thread-safe map with segment locking. (4) AtomicInteger/AtomicReference: lock-free CAS operations. (5) volatile: visibility guarantee for single variable reads/writes. (6) Immutable objects: no synchronization needed. (7) CopyOnWriteArrayList: safe for read-heavy scenarios. (8) ThreadLocal: per-thread copies. Best practices: minimize lock scope, avoid nested locks (deadlock risk), prefer concurrent collections over synchronized wrappers, use java.util.concurrent utilities.
Use Spring Boot with @RestController, proper HTTP methods, status codes, validation, pagination, and error handling.
Setup: Spring Boot + Spring Web. Controllers: @RestController + @RequestMapping. Methods: @GetMapping (read), @PostMapping (create), @PutMapping (update), @DeleteMapping (delete). Validation: @Valid + @NotNull, @Size, @Email on DTOs. Exception handling: @ControllerAdvice + @ExceptionHandler for global error handling. Pagination: Pageable parameter + Page<T> response. HATEOAS: spring-hateoas for self-describing APIs. Documentation: springdoc-openapi for Swagger UI. Security: Spring Security + JWT. Testing: @WebMvcTest + MockMvc. Response: proper HTTP status codes (201 Created, 404 Not Found, 422 Unprocessable).
Singleton ensures one instance exists. Thread-safe: enum singleton, Bill Pugh (inner class), or double-checked locking.
Problem: ensure exactly one instance globally. Implementations: (1) Enum singleton (recommended): enum Singleton { INSTANCE } — thread-safe, serialization-safe. (2) Bill Pugh (lazy inner class): static inner class loaded on first access — thread-safe via class loading. (3) Double-checked locking: volatile field + synchronized block in getInstance(). (4) Eager: static final instance — simple but not lazy. Avoid: synchronized getInstance() — performance bottleneck. Spring @Component is singleton scoped by default. Use sparingly — makes testing harder (use DI instead).
The Java Memory Model defines how threads interact through memory, with happens-before guarantees ensuring visibility of changes.
JMM defines: when a write by one thread is visible to a read by another. Without happens-before: compiler/CPU may reorder operations, cache values in registers. Happens-before rules: (1) Program order within thread. (2) synchronized: unlock happens-before subsequent lock. (3) volatile: write happens-before subsequent read. (4) Thread.start() happens-before first action in started thread. (5) Thread.join(): all actions happen-before join returns. (6) final fields: visible after constructor completes. volatile prevent reordering + ensure visibility. synchronized provides both mutual exclusion + visibility.
DI inverts object creation — dependencies are provided externally rather than created internally, improving testability and loose coupling.
Types: constructor injection (recommended — immutable, testable), setter injection (optional dependencies), field injection (@Autowired on field — hard to test). Spring: @Component marks beans, @Autowired injects. @Configuration + @Bean for manual configuration. @Qualifier for disambiguation. Profiles: @Profile('dev') for environment-specific beans. Scopes: singleton (default), prototype (new instance each time), request/session (web). Advantages: testability (mock dependencies), loose coupling, configurable. Constructor injection is preferred: immutable, all dependencies required, no null fields.
Use specific exceptions, try-with-resources for cleanup, custom exception hierarchy, and avoid catching generic Exception.
Best practices: (1) Catch specific exceptions, not Exception/Throwable. (2) try-with-resources for AutoCloseable (streams, connections). (3) Custom exception hierarchy: AppException extends RuntimeException, with subclasses for specific errors. (4) Don't use exceptions for flow control. (5) Include context: throw new UserNotFoundException('user ' + id). (6) Log and rethrow (don't swallow). (7) Checked vs unchecked: checked for recoverable conditions, unchecked (RuntimeException) for programming errors. (8) @ControllerAdvice in Spring for global API error handling with consistent error responses.
SOLID: Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, Dependency Inversion — for maintainable OOP code.
S: class has one reason to change (UserService handles users, not emails). O: open for extension, closed for modification (use interfaces, strategy pattern). L: subtypes replaceable for base types without breaking (Square extends Rectangle violates if setWidth affects height). I: small focused interfaces (Readable, Writable vs ReadWrite). D: depend on abstractions not concretes (inject PaymentProcessor interface, not StripeProcessor). Benefits: testable, maintainable, extensible code. Java frameworks like Spring heavily rely on SOLID. Most important: S and D for everyday clean code. Avoid over-engineering small projects.
HashMap is not thread-safe and allows null keys; ConcurrentHashMap is thread-safe with segment-level locking and no null keys.
HashMap: single-threaded, O(1) get/put, allows one null key and null values, fail-fast iterator (ConcurrentModificationException). ConcurrentHashMap: thread-safe, lock striping (segments locked independently), no null keys/values, weakly consistent iterator. Java 8+: ConcurrentHashMap uses CAS + synchronized on bucket head (not segments). Alternatives: Hashtable (legacy, single lock — avoid), Collections.synchronizedMap (wrapper, single lock). Choose: HashMap for single-threaded, ConcurrentHashMap for concurrent access, TreeMap for sorted order.
Annotations add metadata to code elements. Custom: @interface with @Retention, @Target annotations, processed at compile or runtime.
Built-in: @Override, @Deprecated, @SuppressWarnings, @FunctionalInterface. Meta-annotations: @Retention (SOURCE, CLASS, RUNTIME), @Target (METHOD, FIELD, TYPE), @Inherited, @Repeatable. Custom: @interface MyAnnotation { String value(); int count() default 0; }. Processing: compile-time (annotation processors — Lombok, MapStruct), runtime (reflection — Spring, JPA). Spring uses extensively: @Component, @Autowired, @RequestMapping, @Transactional. JPA: @Entity, @Column, @ManyToOne. Custom validation: @Constraint + ConstraintValidator. Annotation processing API generates code at compile time.
Observer pattern defines a one-to-many dependency where subjects notify observers of state changes, enabling loose coupling.
Components: Subject (Observable — maintains observer list, notifies on change), Observer (receives updates). Java implementations: (1) Custom: interface Observer<T> { void update(T event); }, Subject maintains List<Observer>. (2) PropertyChangeListener for JavaBeans. (3) Flow API (Java 9+): Publisher, Subscriber, Subscription (reactive streams). (4) Spring Events: @EventListener + ApplicationEventPublisher. Use cases: GUI event handling, real-time notifications, event-driven architecture. Advantages: loose coupling, extensible. Drawbacks: event ordering, memory leaks (deregister observers).
Profile with VisualVM, optimize data structures, use object pooling, tune GC, avoid memory leaks, and reduce object creation.
Steps: (1) Profile: VisualVM, JFR (Java Flight Recorder), jmap heap dumps. (2) Reduce allocations: StringBuilder for concatenation, primitive arrays over boxed. (3) Right-size collections: ArrayList initial capacity, HashMap load factor. (4) Object pooling: connection pools, thread pools. (5) Weak/Soft references for caches. (6) GC tuning: choose right GC (G1, ZGC), set heap size (-Xmx). (7) Memory leaks: unclosed resources, static collections growing, listener not deregistered. (8) Off-heap: DirectByteBuffer for large data. (9) Compact strings (Java 9+): Latin1 uses 1 byte/char vs 2.
Java serialization converts objects to byte streams via Serializable interface, but modern alternatives like JSON/protobuf are preferred.
Built-in: implements Serializable, ObjectOutputStream/ObjectInputStream. serialVersionUID for version control. transient keyword excludes fields. Custom: writeObject/readObject methods. Externalizable interface for full control. Problems: security vulnerabilities (deserialization attacks), performance overhead, versioning fragility. Modern alternatives: JSON (Jackson, Gson), Protocol Buffers (compact binary), Avro, MessagePack. Java 17: serialization filters (ObjectInputFilter) for security. Records support serialization automatically. Prefer structured formats (JSON) over Java serialization for APIs and storage.
Ready to master Java?
Start learning with our comprehensive course and practice these questions.