Java Interview Questions 2026: Complete Guide with Code Examples
Top 50 Java interview questions for 2026 covering OOP, Collections, Stream API, multithreading, JVM internals, and Spring with code examples and answers.
Why Java Dominates Backend Interviews
Java remains the #1 language for backend engineering interviews at FAANG companies. Its strong typing, mature ecosystem, and performance characteristics make it ideal for large-scale systems. Java's garbage collection, strong memory management, and extensive standard library make it perfect for building reliable, high-performance applications. Companies like Amazon, Google, and Netflix rely heavily on Java for their backend services because of its stability and scalability.
Core Java Questions
Q1: HashMap vs Hashtable
| Feature | HashMap | Hashtable |
|---|---|---|
| Thread Safety | Not synchronized | Synchronized |
| Null Keys/Values | Allows one null key | No nulls allowed |
| Performance | Faster | Slower due to synchronization |
| Legacy | Modern (Java 1.2) | Legacy (Java 1.0) |
Best Answer: Use ConcurrentHashMap for thread-safe scenarios. HashMap is preferred for single-threaded contexts. Hashtable is considered legacy and should rarely be used in modern Java applications.
Q2: fail-fast vs fail-safe Iterators
// fail-fast: throws ConcurrentModificationException
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
for (String s : list) {
list.remove(s); // Exception!
}
// fail-safe: works on clone
List<String> list = new CopyOnWriteArrayList<>(Arrays.asList("a", "b", "c"));
for (String s : list) {
list.remove(s); // No exception
}
Key Insight: fail-fast iterators use a modification count (modCount) to detect concurrent changes. fail-safe iterators work on a copy of the collection, so they never throw exceptions but may not reflect recent changes.
Q3: String Pool and Immutable Strings
Java maintains a String pool for memory optimization. Strings are immutable because:
- Security: Class loading, network connections, file paths
- Hashing: Hash code cached, consistent across HashMap
- Thread Safety: No synchronization needed
- Immutability: Enables string interning
String a = "hello";
String b = "hello";
String c = new String("hello");
System.out.println(a == b); // true (same pool reference)
System.out.println(a == c); // false (different objects)
System.out.println(a.equals(c)); // true (same content)
Interview Tip: Always use .equals() for string comparison, never ==. The == operator checks reference equality, not content equality.
Collections Framework Deep Dive
Q4: ArrayList vs LinkedList
| Operation | ArrayList | LinkedList |
|---|---|---|
| get(index) | O(1) | O(n) |
| add(end) | O(1) amortized | O(1) |
| add(index) | O(n) | O(n) |
| remove(index) | O(n) | O(n) |
| Memory | Compact | Extra pointers |
Real-world Example: Use ArrayList when you need random access (like reading by index). Use LinkedList when you're frequently adding/removing from the beginning or end, like implementing a queue or stack.
Q5: ConcurrentHashMap Internals
Java 8+ uses CAS + synchronized blocks on individual nodes:
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("key", 1); // Thread-safe
map.compute("key", (k, v) -> v + 1); // Atomic operation
// Real-world example: Thread-safe word counter
public class WordCounter {
private final ConcurrentHashMap<String, AtomicInteger> wordCounts = new ConcurrentHashMap<>();
public void addWord(String word) {
wordCounts.computeIfAbsent(word, k -> new AtomicInteger(0)).incrementAndGet();
}
public int getCount(String word) {
return wordCounts.getOrDefault(word, new AtomicInteger(0)).get();
}
}
Q6: TreeMap vs HashMap vs LinkedHashMap
| Feature | HashMap | TreeMap | LinkedHashMap |
|---|---|---|---|
| Ordering | No order | Sorted by key | Insertion order |
| Time Complexity | O(1) | O(log n) | O(1) |
| Null Keys | One null | No nulls | One null |
| Use Case | General purpose | Sorted data | Maintaining insertion order |
Best Practice: Use LinkedHashMap when you need to maintain insertion order, like building an LRU cache. Use TreeMap when you need sorted keys, like in a phone directory application.
Q7: Queue and Deque Implementations
// Queue implementations
Queue<String> queue = new LinkedList<>(); // Unbounded queue
Queue<String> boundedQueue = new ArrayDeque<>(10); // Bounded queue
Queue<String> priorityQueue = new PriorityQueue<>(); // Priority-based queue
// Deque implementations
Deque<String> deque = new ArrayDeque<>(); // Better than Stack
deque.push("first"); // Add to front
deque.push("second"); // Add to front
deque.pop(); // Remove from front
// Real-world example: Task scheduler
public class TaskScheduler {
private final Deque<Runnable> taskQueue = new ArrayDeque<>();
public void addTask(Runnable task) {
taskQueue.offerLast(task);
}
public Runnable getNextTask() {
return taskQueue.pollFirst();
}
public Runnable getHighestPriorityTask() {
return taskQueue.pollFirst();
}
}
Q8: Queue Implementations Comparison
| Queue Type | Characteristics | Use Case |
|---|---|---|
| LinkedList | Unbounded, FIFO | General purpose |
| ArrayDeque | Resizable array, faster than LinkedList | Stack/Queue implementation |
| PriorityQueue | Heap-based, min-ordered | Priority scheduling |
| BlockingQueue | Thread-safe, blocking operations | Producer-consumer patterns |
Multithreading Questions
Q9: synchronized vs ReentrantLock
// synchronized: implicit lock
synchronized void method() { }
// ReentrantLock: explicit lock with features
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
// critical section
} finally {
lock.unlock();
}
When to use ReentrantLock:
- TryLock with timeout
- Fair ordering
- Multiple condition variables
- Interruptible lock acquisition
Real-world Example: Database connection pool management:
public class ConnectionPool {
private final ReentrantLock lock = new ReentrantLock();
private final Condition notEmpty = lock.newCondition();
private final Condition notFull = lock.newCondition();
private final Queue<Connection> connections = new LinkedList<>();
private final int maxSize;
public Connection getConnection(long timeout, TimeUnit unit) throws InterruptedException {
lock.lock();
try {
long deadline = System.nanoTime() + unit.toNanos(timeout);
while (connections.isEmpty()) {
if (!notEmpty.await(timeout, unit)) {
throw new TimeoutException("No connection available");
}
}
return connections.poll();
} finally {
lock.unlock();
}
}
public void releaseConnection(Connection conn) {
lock.lock();
try {
connections.offer(conn);
notEmpty.signal();
} finally {
lock.unlock();
}
}
}
Q10: volatile vs synchronized
| Feature | volatile | synchronized |
|---|---|---|
| Atomicity | No | Yes |
| Visibility | Yes | Yes |
| Mutual Exclusion | No | Yes |
| Blocking | No | Yes |
| Performance | Faster | Slower |
Key Insight: volatile only guarantees visibility, not atomicity. For example, volatile int count++; is not atomic because it's actually three operations: read, increment, write.
Q11: Thread Pool Configuration
ExecutorService executor = new ThreadPoolExecutor(
4, // core pool size
8, // max pool size
60L, TimeUnit.SECONDS, // keep alive
new LinkedBlockingQueue<>(100) // work queue
);
Formula: Number of CPU cores + 1 for CPU-bound tasks. For I/O-bound: number of CPU cores * 2.
Real-world Example: Web server thread pool configuration:
// For CPU-intensive tasks (like data processing)
int cpuCores = Runtime.getRuntime().availableProcessors();
ExecutorService cpuPool = Executors.newFixedThreadPool(cpuCores + 1);
// For I/O-bound tasks (like database queries)
ExecutorService ioPool = Executors.newFixedThreadPool(cpuCores * 2);
// For mixed workloads
ThreadPoolExecutor mixedPool = new ThreadPoolExecutor(
cpuCores, // core threads
cpuCores * 2, // max threads
60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000),
new ThreadPoolExecutor.CallerRunsPolicy() // rejection policy
);
Q12: Deadlock Prevention
// Deadlock example - DON'T DO THIS
public void transferMoney(Account from, Account to, int amount) {
synchronized (from) {
synchronized (to) {
// Transfer logic
}
}
}
// Solution: Always acquire locks in consistent order
public void transferMoneySafe(Account from, Account to, int amount) {
Account first = from.getId() < to.getId() ? from : to;
Account second = from.getId() < to.getId() ? to : from;
synchronized (first) {
synchronized (second) {
// Transfer logic
}
}
}
JVM Internals
Q13: Memory Model
+-------------------+
| Method Area | (Class metadata, static variables)
+-------------------+
| Heap | (Objects, arrays)
| +-------------+ |
| | Young Gen | | (Eden + Survivor spaces)
| +-------------+ |
| | Old Gen | | (Long-lived objects)
+-------------------+
| Stack (per thread) | (Local variables, method calls)
+-------------------+
Q14: Garbage Collection Algorithms
| Algorithm | Best For | Trade-off |
|---|---|---|
| Serial | Small apps | Stop-the-world |
| Parallel | Throughput | Stop-the-world |
| G1 | Balanced | Predictable pauses |
| ZGC | Ultra-low latency | More memory |
Real-world Example: JVM tuning for different scenarios:
# Low latency application
java -XX:+UseZGC -XX:MaxGCPauseMillis=10 -Xmx4g MyApp
# High throughput application
java -XX:+UseParallelGC -XX:MaxGCPauseMillis=500 -Xmx8g MyApp
# Balanced configuration
java -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -Xmx4g MyApp
Q15: Class Loading and Memory Leaks
// Memory leak example - static collection growing forever
public class DataCache {
private static final Map<String, Object> cache = new HashMap<>();
public static void addData(String key, Object value) {
cache.put(key, value); // Never cleared!
}
}
// Solution: Use WeakHashMap or bounded cache
public class DataCache {
private static final Map<String, Object> cache =
Collections.synchronizedMap(new WeakHashMap<>());
public static void addData(String key, Object value) {
cache.put(key, value);
}
}
Spring Boot Questions
Q16: @Autowired vs Constructor Injection
// Field injection (avoid)
@Autowired
private UserService userService;
// Constructor injection (preferred)
@Service
public class OrderService {
private final UserService userService;
public OrderService(UserService userService) {
this.userService = userService;
}
}
Why constructor injection: Immutability, testability, explicit dependencies.
Real-world Example: Service with multiple dependencies:
@Service
public class PaymentService {
private final PaymentRepository paymentRepository;
private final UserService userService;
private final NotificationService notificationService;
// Constructor injection with all dependencies
public PaymentService(PaymentRepository paymentRepository,
UserService userService,
NotificationService notificationService) {
this.paymentRepository = paymentRepository;
this.userService = userService;
this.notificationService = notificationService;
}
// Easy to test - just mock the dependencies
// No need for reflection or Spring context
}
Q17: @Transactional Propagation
| Propagation | Description |
|---|---|
| REQUIRED | Join existing or create new |
| REQUIRES_NEW | Always create new |
| NESTED | Nested transaction with savepoints |
| SUPPORTS | Join if exists, non-tx otherwise |
| NOT_SUPPORTED | Suspend current transaction |
Real-world Example: Order processing with nested transactions:
@Service
public class OrderService {
@Transactional
public void processOrder(Order order) {
// Main transaction
orderRepository.save(order);
// Nested transaction - can be rolled back independently
processPayment(order.getPayment());
// If payment fails, order is still saved
sendConfirmation(order);
}
@Transactional(propagation = Propagation.NESTED)
public void processPayment(Payment payment) {
paymentRepository.save(payment);
// If this fails, only payment is rolled back
}
}
Q18: REST API Best Practices
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
User user = userService.findById(id);
if (user == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(user);
}
@PostMapping
public ResponseEntity<User> createUser(@Valid @RequestBody UserDto userDto) {
User user = userService.create(userDto);
URI location = URI.create("/api/users/" + user.getId());
return ResponseEntity.created(location).body(user);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidation(
MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach(error -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
return ResponseEntity.badRequest().body(errors);
}
}
Java 8+ Features
Q19: Stream API
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
// Filter and collect
List<String> filtered = names.stream()
.filter(name -> name.length() > 3)
.collect(Collectors.toList());
// Map and reduce
int totalLength = names.stream()
.map(String::length)
.reduce(0, Integer::sum);
// Grouping
Map<Integer, List<String>> grouped = names.stream()
.collect(Collectors.groupingBy(String::length));
// Real-world example: Processing employee data
public class EmployeeAnalyzer {
public Map<String, Double> calculateAverageSalaryByDepartment(List<Employee> employees) {
return employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.averagingDouble(Employee::getSalary)
));
}
public List<Employee> findTopEarners(List<Employee> employees, int count) {
return employees.stream()
.sorted(Comparator.comparingDouble(Employee::getSalary).reversed())
.limit(count)
.collect(Collectors.toList());
}
}
Q20: Lambda Expressions
// Traditional approach
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("Hello");
}
};
// Lambda approach
Runnable lambdaRunnable = () -> System.out.println("Hello");
// Method reference
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.forEach(System.out::println);
// Functional interfaces
@FunctionalInterface
interface Calculator {
int calculate(int a, int b);
}
Calculator add = (a, b) -> a + b;
Calculator multiply = (a, b) -> a * b;
Q21: Optional Class
// Avoiding NullPointerException
public String getUserName(Long userId) {
return Optional.ofNullable(userRepository.findById(userId))
.map(User::getName)
.orElse("Unknown User");
}
// With default value
public String getUserNameWithDefault(Long userId) {
return Optional.ofNullable(userRepository.findById(userId))
.map(User::getName)
.orElseGet(() -> "User " + userId);
}
// Throwing exception
public User getUserOrThrow(Long userId) {
return Optional.ofNullable(userRepository.findById(userId))
.orElseThrow(() -> new UserNotFoundException("User not found: " + userId));
}
// Real-world example: Safe navigation
public class OrderService {
public String getOrderCustomerEmail(Long orderId) {
return Optional.ofNullable(orderRepository.findById(orderId))
.map(Order::getCustomer)
.map(Customer::getEmail)
.orElse(null);
}
}
Common Java Anti-Patterns
Q22: String Concatenation in Loops
// BAD: O(n^2) time complexity
String result = "";
for (String s : list) {
result += s; // Creates new String object each time
}
// GOOD: O(n) time complexity
StringBuilder sb = new StringBuilder();
for (String s : list) {
sb.append(s);
}
String result = sb.toString();
Q23: Catching Generic Exceptions
// BAD: Too broad
try {
// code
} catch (Exception e) {
// Handles everything
}
// GOOD: Specific exceptions
try {
// code
} catch (FileNotFoundException e) {
// Handle file not found
} catch (IOException e) {
// Handle other IO errors
} catch (SQLException e) {
// Handle database errors
}
Q24: Mutable Static State
// BAD: Thread unsafe
public class UserCache {
private static Map<String, User> cache = new HashMap<>();
public static User getUser(String id) {
return cache.get(id); // Not thread safe
}
}
// GOOD: Thread safe
public class UserCache {
private static final ConcurrentHashMap<String, User> cache = new ConcurrentHashMap<>();
public static User getUser(String id) {
return cache.get(id); // Thread safe
}
}
Practice Problems with Solutions
Problem 1: Implement LRU Cache
public class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int capacity;
public LRUCache(int capacity) {
super(capacity, 0.75f, true); // Access order
this.capacity = capacity;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > capacity;
}
}
// Usage
LRUCache<String, Integer> cache = new LRUCache<>(3);
cache.put("a", 1);
cache.put("b", 2);
cache.put("c", 3);
cache.get("a"); // Access "a", making it most recently used
cache.put("d", 4); // Evicts "b" (least recently used)
Problem 2: Producer-Consumer Pattern
public class ProducerConsumer {
private final BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(10);
public void produce() throws InterruptedException {
int value = 0;
while (true) {
queue.put(value++);
System.out.println("Produced: " + value);
Thread.sleep(100);
}
}
public void consume() throws InterruptedException {
while (true) {
int value = queue.take();
System.out.println("Consumed: " + value);
Thread.sleep(200);
}
}
public static void main(String[] args) {
ProducerConsumer pc = new ProducerConsumer();
Thread producer = Thread.startVirtualThread(() -> {
try {
pc.produce();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
Thread consumer = Thread.startVirtualThread(() -> {
try {
pc.consume();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
}
Problem 3: Thread-safe Singleton
// Option 1: Enum (preferred)
public enum Singleton {
INSTANCE;
public void doSomething() {
// Business logic
}
}
// Option 2: Double-checked locking
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
Problem 4: Custom Stream Operations
public class StreamUtils {
public static <T> Collector<T, ?, Optional<T>> toSingleton() {
return Collector.of(
() -> new AtomicReference<>(),
(ref, t) -> ref.set(t),
(ref1, ref2) -> {
ref1.set(ref2.get());
return ref1;
},
ref -> Optional.ofNullable(ref.get()),
Collector.Characteristics.UNORDERED
);
}
// Usage
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> sum = numbers.stream()
.collect(Collectors.summingInt(Integer::intValue));
}
Problem 5: Custom Predicate Composition
public class PredicateUtils {
public static <T> Predicate<T> and(Predicate<T>... predicates) {
return Arrays.stream(predicates)
.reduce(Predicate::and)
.orElse(t -> true);
}
public static <T> Predicate<T> or(Predicate<T>... predicates) {
return Arrays.stream(predicates)
.reduce(Predicate::or)
.orElse(t -> false);
}
public static <T> Predicate<T> not(Predicate<T> predicate) {
return predicate.negate();
}
// Usage
Predicate<String> isNotNull = Objects::nonNull;
Predicate<String> isNotEmpty = s -> !s.isEmpty();
Predicate<String> startsWithA = s -> s.startsWith("A");
Predicate<String> combined = and(isNotNull, isNotEmpty, startsWithA);
List<String> names = Arrays.asList("Alice", "Bob", null, "", "Charlie");
List<String> result = names.stream()
.filter(combined)
.collect(Collectors.toList());
// Result: ["Alice"]
}
Resources
- Java Mastery Roadmap - Complete Java learning path
- Java Topics - Deep dive into Java concepts
- Practice Problems - Coding practice
- SQL Interview Questions - Database preparation
- System Design Guide - Architecture rounds
- Senior Java Developer - Advanced Java path
Related Articles
SDE Interview Roadmap 2026: Complete Guide to SWE Interviews
Master your SDE interview in 2026 with our complete roadmap. Cover DSA, System Design, Behavioral questions, and company-specific prep.
Company SpecificAmazon SDE Interview Questions 2026: Complete Guide with Answers
Ace your Amazon SDE interview with 50+ real interview questions covering coding, system design, and leadership principles.
System DesignSystem Design Interview Guide 2026: Complete Preparation Framework
Master system design interviews with our complete guide. Learn the framework, common topics, real-world case studies, and practice questions with model answers.
Continue Your Prep
Apply what you learned with our structured roadmaps and practice problems.