Java Interview Questions for 5 Years of Experience

Java Interview Questions for 5 Years of Experience

October 26th, 2026
20
15: 00 Minutes

Java interviews change after the five-year mark. Interviewers stop asking what a class is. They start asking how you design, debug and tune real Java applications.

In my experience, strong candidates at this level do three things well. They explain concepts in simple words. They connect each concept to a production problem. They also know what is new in the language, including Java 25 and Java 27.

This guide covers the Java interview questions for 5 years experience that matter most in 2027. It includes core Java, advanced Java, scenario-based questions and coding problems. Each answer is short, so you can read it, understand it and explain it in your own words.

Let’s get started!

Also Explore: Inheritance in Java

Core Java Interview Questions for 5 Years of Experience

These questions test how well you know the Java language and the modern features that teams use every day.

1. What are the main differences between HashMap, LinkedHashMap and TreeMap in Java?

All three classes store key-value pairs. They differ in ordering, speed and use cases.

FeatureHashMapLinkedHashMapTreeMap
OrderingNo guaranteed orderMaintains insertion order by defaultSorts keys in ascending order by default
PerformanceUsually O(1) for insertion, lookup and deletionUsually O(1) for insertion, lookup and deletionO(log n) for insertion, lookup and deletion
Internal structureHash tableHash table with a linked list connecting entriesRed-black tree
Null keysAllows one null keyAllows one null keyGenerally does not allow null keys with natural ordering
Null valuesAllows multiple null valuesAllows multiple null valuesAllows multiple null values
Sorting supportNo built-in key sortingCan maintain insertion or access orderSupports sorted keys and range-based operations
Best use caseFast data retrieval when order does not matterMaintaining the order in which entries are addedStoring data in sorted order
ExampleHashMap<Integer, String>LinkedHashMap<Integer, String>TreeMap<Integer, String>

I use HashMap by default. I switch to LinkedHashMap when output order matters. TreeMap when I need sorted keys or range methods such as subMap() and floorKey().

Since Java 21, LinkedHashMap and TreeMap also implement the SequencedMap interface, so they offer methods like firstEntry() and lastEntry().

2. How do equals() and hashCode() work together and what problems can occur if a class overrides only one of them?

The equals() method decides whether two objects are logically equal. The hashCode() method decides which bucket a hash-based collection uses. The contract is simple: if two objects are equal, they must return the same hash code.

Problems appear when a class overrides only one method.

  • If you override equals() but not hashCode(), two equal objects can land in different buckets. A HashMap can then fail to find a key that it already stores and a HashSet can hold duplicates.
  • If you override hashCode() but not equals(), the default equals() still compares object identity. Lookups fail even though the hash codes match.

Use the same fields in both methods. Avoid mutable fields in keys, because a changed hash code makes the entry unreachable. Records and Objects.hash() reduce this risk.

3. What are Java records and how do they differ from traditional classes?

A record is a compact class for immutable data. Java 16 made records a standard feature. You write one line and the compiler generates the private final fields, the constructor, the accessors, equals(), hashCode() and toString().

public class Main {
static class Employee {
private String name;
private int age;
public Employee(String name, int age) {
if (age < 18) {
throw new IllegalArgumentException(
"Age must be 18 or above"
);
}
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Employee[name=" + name + ", age=" + age + "]";
}
}
public static void main(String[] args) {
Employee emp = new Employee("Saachi", 25);
System.out.println(emp);
}
}

Example of Java Program

Test Your Java Code Here: Online Java Compiler

The block inside the record is a compact constructor. It validates data before the fields are assigned.

Records differ from traditional classes in these ways:

  • A record is implicitly final and cannot extend another class. It can implement interfaces.
  • A record cannot declare extra instance fields.
  • Its immutability is shallow. A List component can still change, so copy it with List.copyOf() in the constructor.

Use records for DTOs, API responses and map keys. Avoid them for JPA entities, because entities need mutable state and a no-argument constructor.

4. How do lambda expressions and functional interfaces work in Java?

A lambda expression is a short block of code that implements the single abstract method of a functional interface. The compiler infers the target type from the context. A lambda can read a local variable only when that variable is final or effectively final.

You can create your own functional interface with @FunctionalInterface. The annotation makes the compiler check that the interface has exactly one abstract method.

@FunctionalInterface
interface Validator<T> {
boolean validate(T value);
}
public class Main {
public static void main(String[] args) {
Validator<String> validator =
value -> value != null && !value.isEmpty();
System.out.println(validator.validate("Saachi"));
System.out.println(validator.validate(""));
}
}

Java lambda expressions and functional interfaces in Java.

Validator<String> notBlank = s -> s != null && !s.isBlank();

System.out.println(notBlank.validate("Java"));

An interface can still contain default and static methods. Java also ships ready-made interfaces like Function, Predicate, Supplier and Consumer. Use them before you create a new one.

5. What is the difference between intermediate and terminal operations in the Stream API?

Intermediate operations such as filter(), map() and sorted() build a stream pipeline without executing it immediately. Execution begins when a terminal operation such as collect(), count() or forEach() is invoked.

A terminal operation consumes the stream, making it unavailable for further use. However, consuming a stream does not generally mean that its underlying resources have been closed. For resource-backed streams, such as streams created with Files.lines(), use try-with-resources to ensure proper cleanup.

Short-circuiting operations such as findFirst() and anyMatch() can stop processing once their result is determined.

This behavior is called lazy evaluation. The pipeline sends one element at a time through all the stages. It does not finish one stage for the whole data set before it starts the next stage.

Short-circuit operations stop work early. Examples are findFirst(), anyMatch() and limit(). If a stream finds a match at the third element, it does not read the rest.

import java.util.List;
import java.util.Optional;
public class Main {
public static void main(String[] args) {
List<String> names = List.of(
"Lana", "Saachi", "Ria", "Angel"
);
Optional<String> first = names.stream()
.filter(n -> n.startsWith("A"))
.findFirst();
System.out.println(first.orElse("No name found"));
}
}

Java Stream API intermediate and terminal operations.

A stream cannot be reused after a terminal operation. Also, avoid parallel streams for small or blocking tasks.

6. How do Optional, map(), flatMap() and orElseGet() help manage missing values?

Optional is a container that may or may not hold a value. It makes a missing value visible in the method signature.

  • map() transforms the value when it is present and returns an Optional.
  • flatMap() does the same when the mapping function already returns an Optional. It avoids Optional<Optional<T>>.
  • orElse() always evaluates its argument, even when a value exists.
  • orElseGet() calls a Supplier only when the value is missing. Use it when the default is expensive to build.
import java.util.Optional;
public class Main {
static class Address {
private String city;
Address(String city) {
this.city = city;
}
public String getCity() {
return city;
}
}
static class User {
private Address address;
User(Address address) {
this.address = address;
}
public Optional<Address> getAddress() {
return Optional.ofNullable(address);
}
}
static Optional<User> findUser(int id) {
if (id == 1) {
return Optional.of(
new User(new Address("Jaipur"))
);
}
return Optional.empty();
}
public static void main(String[] args) {
int id = 1;
String city = findUser(id)
.flatMap(User::getAddress)
.map(Address::getCity)
.orElseGet(() -> "Unknown");
System.out.println(city);
}
}

Java Optional, map(), flatMap(), and orElseGet() for handling missing values.

Use Optional as a return type. Do not use it for fields or method parameters.

7. What are sealed classes and interfaces in Java?

A sealed class or interface lists exactly which classes can extend or implement it. Java 17 made this feature standard.

public sealed interface Shape permits Circle, Square {}

public record Circle(double radius) implements Shape {}

public record Square(double side) implements Shape {}

Each permitted subclass must be final, sealed, or non-sealed. This rule keeps the hierarchy under your control.

Sealed types improve type safety because the compiler knows every possible subtype. A switch expression over a sealed type does not need a default branch. The compiler reports an error when you forget a case.

public class Main {
interface Shape {
}
static class Circle implements Shape {
double radius;
Circle(double radius) {
this.radius = radius;
}
}
static class Square implements Shape {
double side;
Square(double side) {
this.side = side;
}
}
public static void main(String[] args) {
Shape shape = new Circle(5);
double area;
if (shape instanceof Circle) {
Circle c = (Circle) shape;
area = Math.PI * c.radius * c.radius;
} else if (shape instanceof Square) {
Square s = (Square) shape;
area = s.side * s.side;
} else {
throw new IllegalArgumentException("Unknown shape");
}
System.out.println("Area: " + area);
}
}

Java sealed classes and interfaces controlling permitted subclasses.

Pattern matching for switch is a standard feature since Java 21.

8. What are the key features introduced in Java 25?

Java 25 is an LTS release from September 2025. Many teams plan to move to it. These features matter most:

  • Module import declarations (JEP 511). One line, such as import module java.base;, imports every package that the module exports. It removes long import lists.
  • Compact source files and instance main methods (JEP 512). A small program can start with void main() and no class declaration. This helps with scripts, demos and quick tests.
  • Flexible constructor bodies (JEP 513). A constructor can run statements before super() or this(). These statements can validate arguments, but they cannot use the object that is under construction.
  • Scoped values (JEP 506). This feature shares immutable data safely with child threads. It works well with virtual threads.
  • Compact object headers (JEP 519). HotSpot can use smaller object headers as a product option.
public class Main {
public static void main(String[] args) {
System.out.println("Hello, Java 25");
}
}

Java 25 key features and new language enhancements.

9. What are the key features introduced in Java 27 and what is the status of primitive types in patterns?

Java 27 arrived September 15, 2026. It is a non-LTS release with nine JEPs. Five are still in preview or incubator status.

Primitive types in patterns, instanceof and switch (JEP 532) is in its fifth preview. It removes the old limit that pattern matching works only with reference types. A primitive type pattern checks whether a value can convert to the target type without losing information.

public class Main {
public static void main(String[] args) {
int code = 120;
if (code >= Byte.MIN_VALUE && code <= Byte.MAX_VALUE) {
byte b = (byte) code;
System.out.println("Safe as a byte: " + b);
} else {
System.out.println("Value is outside the byte range");
}
}
}

Java 27 features and primitive types in patterns.

The condition is true only because 120 fits in a byte. In this preview, switch also accepts selectors of type long, float, double and boolean. Because the feature is a preview, you must compile and run with --enable-preview. The rules can still change before the final release.

Other notable Java 27 changes:

  • Compact object headers are on by default (JEP 534).
  • G1 is the default garbage collector in all environments (JEP 523).
  • Post-quantum hybrid key exchange is available for TLS 1.3 (JEP 527).
  • JFR in-process data redaction is available (JEP 536).
  • Structured concurrency, lazy constants, PEM encodings and the Vector API remain in preview or incubator status.

10. What is the difference between checked and unchecked exceptions and how should you design custom exceptions?

A checked exception extends Exception and the compiler forces you to handle it or declare it. An unchecked exception extends RuntimeException and the compiler does not force anything.

Use a checked exception when the caller can recover, such as a missing file the user can replace. Use an unchecked exception for programming errors and business rule violations. Spring also reverses transactions on unchecked exceptions by default.

Follow these rules for custom exceptions:

  • Give each exception a clear name, like InsufficientBalanceException.
  • Keep the original cause. Pass it to the constructor so the stack trace stays complete.
  • Never swallow an exception with an empty catch block.
  • Do not log and rethrow the same exception. This creates duplicate log lines.
  • Use try-with-resources to close resources.
  • Handle errors in one place, such as a @RestControllerAdvice class in a Spring Boot application.

Also Explore: Top Spring Boot Interview Questions and Answers

Advanced Java Interview Questions for Experienced Developers

These questions test your knowledge of concurrency, the JVM and the newest platform features.

1. What is the difference between platform threads and virtual threads?

A platform thread is a thin wrapper around an operating system thread. Each one is costly, so an application can run only a few thousand of them. A virtual thread is a lightweight thread managed by JVM. Java 21 made virtual threads a standard feature (JEP 444).

When a virtual thread blocks on I/O, the JVM unmounts it from its carrier thread. The carrier thread then runs another virtual thread. This design lets one application run hundreds of thousands of blocking tasks.

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
public static void main(String[] args) {
ExecutorService executor = Executors.newSingleThreadExecutor();
try {
executor.submit(() -> callPaymentService());
} finally {
executor.shutdown();
}
}
static void callPaymentService() {
System.out.println("Payment service called");
}
}

We can follow these rules:

  • Use virtual threads for I/O-bound work such as HTTP calls and database queries. They do not speed up CPU-bound work.
  • Do not pool virtual threads. Create one for each task.
  • Limit access to scarce resources with a Semaphore. Virtual threads do not limit database connections for you.
  • Since Java 24, synchronized blocks no longer pin the carrier thread (JEP 491). Native calls can still cause pinning.

2. What is structured concurrency and how does it simplify concurrent programming?

Structured concurrency treats a group of related tasks as one unit of work. The parent task opens a scope, forks subtasks and waits for them in the same block. The scope ends only after every subtask finishes or is cancelled.

try (var scope = StructuredTaskScope.open()) {
var user = scope.fork(() -> loadUser(id));
var orders = scope.fork(() -> loadOrders(id));
scope.join();
return new Profile(user.get(), orders.get());
}

This model solves four problems:

  • Cancellation is automatic. The scope can stop sibling tasks when one task fails.
  • Exceptions flow back to the parent task.
  • Threads cannot leak outside the block.
  • Thread dumps show the parent-child relationship, which helps debugging.

Structured concurrency is still a preview feature in Java 27 (JEP 533, seventh preview). Run it with enable-preview. The API has changed between previews, so read the release notes before you adopt it.

3. How does the Java Memory Model define happens-before? How do volatile, synchronized and atomic classes help?

The Java Memory Model (JMM) defines when one thread must see the writes of another thread. The happens-before relationship is the core rule. If action A happens-before action B, then B sees the result of A.

These are the key happens-before rules:

  • Each action in a thread happens-before the next action in the same thread.
  • An unlock of a monitor happens-before every later lock of the same monitor.
  • A write to a volatile field happens-before every later read of that field.
  • Thread.start() happens-before the first action in the new thread and the last action in a thread happens-before a successful join() on that thread.

The tools differ in what they guarantee:

  • volatile guarantees visibility and ordering. It does not make count++ atomic.
  • synchronized guarantees both mutual exclusion and visibility.
  • Atomic classes like AtomicInteger use compare-and-swap (CAS) for lock-free updates. LongAdder performs better under heavy contention.

A volatile boolean works well as a stop flag. An AtomicInteger works well as a counter. Use a lock when several variables must change together.

4. How do G1 GC and ZGC differ and how do you choose the right garbage collector?

G1 is a region-based collector that balances throughput and pause time. You set a pause goal and G1 tries to meet it. Java 27 makes G1 the default collector in all environments (JEP 523). In the past, the JVM could choose Serial GC on small machines.

ZGC is a concurrent collector that targets very short pauses, often under one millisecond, even on very large heaps. Generational ZGC is the only ZGC mode in recent releases. ZGC can use more CPU and memory than G1, so it may lower raw throughput.

How to choose:

  • Start with G1 for most web services and batch jobs.
  • Move to ZGC when pause times hurt your latency targets or when the heap is very large.
  • Test with real traffic. Read the GC logs (-Xlog:gc*) and compare p99 latency, throughput and memory use.

Do not switch collectors based on a generic benchmark. Measure your own workload.

5. What are compact object headers and how can they reduce memory overhead?

Every Java object has a header that stores metadata. On 64-bit HotSpot, the header traditionally takes 96 bits (12 bytes). Compact object headers reduce it to 64 bits (8 bytes).

The savings add up when an application creates many small objects. Smaller heap means less GC work and better cache use. The feature was experimental in Java 24, became a product feature in Java 25 (JEP 519) and is on by default in Java 27 (JEP 534). You can turn it off with -XX:-UseCompactObjectHeaders.

Consider these factors when you evaluate it:

  • Workloads with many small objects gain the most. Workloads with a few large objects gain little.
  • Measure heap use, GC time and throughput before and after the change.
  • Check that your libraries do not depend on the old header layout.
  • Remember that the new layout limits the number of loaded classes. Most applications never reach that limit.

6. What is the Java Vector API and what is its status in Java 27?

The Vector API lets you write SIMD (single instruction, multiple data) code in Java. A vector operation processes many values in one CPU instruction. The JIT compiler turns the API calls into vector instructions on supported CPUs. When the CPU cannot support them, the code still runs with a scalar fallback.

The API helps with numeric loops, image processing, analytics and AI inference work, such as dot products and similarity scores over embeddings.

Java 27 still ships the Vector API as an incubator feature (JEP 537, twelfth incubation). It lives in the jdk.incubator.vector module, so you must add --add-modules jdk.incubator.vector. The API will move to preview only after Project Valhalla provides value classes. Treat it with care, because it can still change.

7. What are lazy constants in Java 27 and how do they differ from final fields?

A lazy constant is a holder for a value that Java computes once, on first use. After that, the value never changes.

A traditional final field must be set when the object or class is created. This forces eager initialization, so the application pays the startup cost even if it never uses the value. A lazy constant delays that cost. It also guarantees thread-safe, one-time initialization, so you do not need a custom double-checked locking pattern.

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
public static void main(String[] args) {
ExecutorService executor = Executors.newSingleThreadExecutor();
try {
executor.submit(() -> callPaymentService());
} finally {
executor.shutdown();
}
}
static void callPaymentService() {
System.out.println("Payment service called");
}
}

Java 27 lazy constants compared with final fields.

After initialization, the JVM can treat the value as a constant and apply optimizations similar to those for final fields.

Lazy constants are a preview feature in Java 27 (JEP 531, third preview). The feature started as stable values in Java 25 and received a new name in Java 26. Expect more API changes.

8. What is post-quantum hybrid key exchange for TLS 1.3 in Java 27?

Large quantum computers could break today's key exchange algorithms, like ECDH. Attackers can already record encrypted traffic and try to decrypt it later. This threat is called harvest now, decrypt later.

A hybrid key exchange runs a classical algorithm and a quantum-resistant algorithm together. The TLS handshake combines both results into one shared secret. An attacker must break both algorithms to read the traffic. The classical part still protects you if someone finds a flaw in the new algorithm.

JEP 527 adds this support to TLS 1.3 in Java 27. It builds on the ML-KEM key encapsulation mechanism that Java 24 introduced (JEP 496). A typical hybrid group pairs X25519 with ML-KEM-768. The trade-off is larger handshake messages, so test the effect on latency and network devices. Both the client and the server must support the hybrid group.

9. What is the difference between Spring AI and LangChain4j?

Both frameworks help Java applications use large language models (LLMs). They differ in design and fit.

BasisSpring AILangChain4j
DefinitionA framework for integrating AI models into Spring applications.A Java library for building applications powered by large language models (LLMs).
Main focusIntegrating AI capabilities into the Spring ecosystem.Providing flexible tools and APIs for building AI applications in Java.
Spring Boot supportDeep integration with Spring Boot and its auto-configuration features.Supports Spring Boot through dedicated integrations and starters.
Framework supportPrimarily designed for Spring-based applications.Works with Spring Boot, Quarkus, Helidon, Micronaut and plain Java.
API styleUses ChatClient for interacting with AI models.Uses APIs like ChatLanguageModel and AiServices.
LLM integrationSupports multiple model providers through a consistent API.Supports multiple model providers through a unified API and integration modules.
RAG supportProvides advisors and vector stores to implement Retrieval-Augmented Generation (RAG).Provides content retrievers and retrieval augmentors to implement RAG.
Tool callingAllows AI models to call Java methods registered as tools or functions.Allows AI models to call Java methods using features like @Tool annotation.
Chat memoryProvides chat-memory components for retaining conversation context.Provides chat-memory components and implementations for managing conversation context.
ConfigurationCommonly uses Spring beans, dependency injection and application properties.Uses builders, interfaces, annotations and optional dependency-injection integrations.
Best suited forApplications already built with Spring Boot.Applications that require flexibility across Java frameworks or use LangChain4j's AI Services API.
Example use caseAdding an AI assistant to the existing Spring Boot app.Building a chatbot or RAG application using Java with Spring Boot, Quarkus, or another supported framework.

Choose Spring AI when your team already uses Spring Boot. Choose LangChain4j when you need framework independence.

10. What is the Model Context Protocol (MCP) and how can Java applications use it?

The Model Context Protocol (MCP) is an open standard that connects AI applications to external tools and data. It uses a client-server design. The MCP server exposes tools, resources and prompts. The MCP client lives inside the AI application, discovers those capabilities and lets the model call them. Messages use JSON-RPC over stdio or Streamable HTTP.

A Java application can play either role:

  • As a client, the application connects to an existing MCP server, such as a database tool or a ticketing system.
  • As a server, the application exposes its own services, such as an order lookup, so any MCP-aware AI model can use them.

The official MCP Java SDK supports both roles. Spring AI adds Boot starters and annotations that keep the setup short.

Enterprise systems need extra care. Secure the server with OAuth, give each tool the least privilege it needs, log every call and require human approval for actions that change data.

Read Also: Top Design Patterns Interview Questions

Scenario-Based Java Interview Questions for 5 Years of Experience

Scenario questions test how you solve real problems. Interviewers want to hear your process, not only your final answer. A good answer follows this order: find the cause, fix it and stop it from returning.

1. Java application throws OutOfMemoryError after running for several hours. How would you identify and fix it?

First, I would check the application logs and identify when the OutOfMemoryError occurs. Then, I would take a heap dump and analyze it using tools like Eclipse MAT or VisualVM.

I would check which objects are consuming the most memory and whether any objects are being retained unnecessarily. I would also check for memory leaks, such as static collections, unclosed resources, or objects that are not being released.

After finding the root cause, I would fix the code and release unnecessary references. I would also check resource handling and review the JVM heap settings.

Finally, I would monitor the application and run load tests to make sure the issue does not happen again.

2. Two threads update the same customer's account balance simultaneously. How would you prevent race conditions?

First, I would identify the shared data that both threads are updating. The problem occurs because both threads can read and update the balance at the same time.

For a simple in-memory operation, I could use synchronization or a ReentrantLock to allow only one thread to update the balance at a time.

If the balance is stored in a database, I would use a database transaction with proper locking, such as optimistic locking with a version column or pessimistic locking with SELECT FOR UPDATE.

I would also make sure that the balance update is performed as one atomic operation. This would prevent lost updates and help maintain data consistency.

3. Your Java REST API takes five seconds to respond during peak traffic. How would you optimize it?

First, I would measure the API response time and check the application logs, metrics and traces to find the bottleneck.

I would check whether the delay comes from database queries, external API calls, application logic, or thread pool exhaustion.

If database queries are slow, I would analyze the execution plan, add suitable indexes and optimize the queries. If the API makes repeated calls for the same data, I would consider caching. I would also check connection pool settings and avoid unnecessary processing.

Finally, I would run load tests and compare the response time before and after the changes. This would help me confirm that the optimization has improved performance.

4. Multiple threads read and update a shared HashMap. How would you make it thread-safe?

A normal HashMap is not designed for concurrent updates by multiple threads.

If multiple threads need to read and update the map, I would use ConcurrentHashMap. It supports concurrent access and provides better performance than synchronizing every operation on the entire map.

If multiple operations must work together as one atomic operation, I would use methods such as compute(), computeIfAbsent(), or putIfAbsent(), depending on the requirement.

I would also check whether the application needs a consistent view of the entire map, because thread-safe individual operations do not automatically make a sequence of operations atomic.

Finally, I would test concurrent access to make sure the data remains correct.

5. Spring Boot application throws database connection timeout errors during peak traffic. How would you resolve this?

First, I would check the database connection pool metrics, application logs and database performance to find out why connections are not available.

I would check whether the connection pool is exhausted, database queries are taking too long, or connections are not being released properly.

If queries are slow, I would optimize them. If connections are being held for too long, I would reduce transaction duration and make sure resources are closed correctly.

I would also review the HikariCP connection pool settings, including maximum pool size and connection timeout. I would increase the pool size only after checking the database capacity and application workload.

Finally, I would perform load testing and monitor the pool to make sure the problem is resolved.

6. Payment API processes a payment, but the client receives a timeout and retries. How would you prevent duplicate payments?

I would implement an idempotency mechanism to make sure the same payment request is not processed more than once.

The client would send a unique idempotency key with each payment request. I would store this key in the database with a unique constraint, along with the payment status and transaction details.

When a request comes in, I would check whether the key already exists. If the payment has already been processed, I would return the existing result instead of charging the customer again.

I would also use database transactions to maintain consistency and handle concurrent requests safely. If an external payment gateway is involved, I would use its idempotency support wherever available.

Finally, I would test timeout scenarios and repeated requests to make sure the customer is not charged twice.

7. Several threads stop making progress because of a deadlock. How would you identify and resolve it?

First, I would take a thread dump using tools like jstack or the JVM monitoring tools. I would analyze the thread states and identify which threads are waiting for locks.

I would check whether two or more threads are holding locks that the other threads need. This would help me identify the deadlock.

To fix it, I would try to acquire locks in the same order across the application. I would also reduce unnecessary nested locking and keep synchronized blocks as small as possible.

Where suitable, I could use ReentrantLock with tryLock() and a timeout to avoid waiting forever.

Finally, I would test the changes under concurrent load and monitor the application to ensure the deadlock does not return.

8. Your application needs to process millions of database records, but loading all records causes OutOfMemoryError. How would you fix it?

I would avoid loading all records into memory at once. Instead, I would process the data in smaller batches.

I could use pagination, JDBC fetch size, or Spring Batch, depending on the requirement. I would fetch a limited number of records, process them, save the results if needed and then move to the next batch.

I would also avoid keeping processed records in memory unnecessarily. For very large datasets, I would consider cursor-based processing or keyset pagination to improve efficiency.

Finally, I would monitor memory usage and processing time and test with a large dataset to make sure the application can handle the workload without running out of memory.

9. An external API becomes slow or unavailable and affects your Java microservice. How would you prevent this?

I would use timeouts to prevent requests from waiting indefinitely for the external API.

I would also implement a circuit breaker using Resilience4j. If the external API keeps failing, the circuit breaker would temporarily stop sending requests and allow the service to recover.

Where appropriate, I would use limited retries with exponential backoff. I would avoid unlimited retries because they could increase the load on an already failing service.

For non-critical operations, I could use asynchronous processing with a message queue. I would also consider a fallback response if the business requirement allows it.

Finally, I would monitor failures, response times and circuit breaker states to make sure the service remains stable when the external API has problems.

10. Your team wants to build an AI assistant that answers questions using internal company documents. How would you implement it using Java and Spring AI?

I would implement this feature using Retrieval-Augmented Generation, also called RAG, with Spring AI.

First, I would collect the internal documents, extract their text, split the text into smaller chunks and generate embeddings. I would store these embeddings in a vector database so that relevant document sections can be retrieved when a user asks a question.

When a user submits a question, the application would search the vector database for relevant content and send that content along with the question to the AI model. The model would generate an answer based on the retrieved information.

I would also implement authentication and role-based access control so users can access only the documents they are allowed to see. I would validate document access before retrieving content and avoid sending sensitive information to unauthorized external services.

To reduce incorrect answers, I would instruct the model to answer only from the provided context, return source references and say when the information is not available. I would also monitor answer quality, protect against prompt injection and avoid logging sensitive data unnecessarily.

Finally, I would test the system with real questions and evaluate its accuracy, security and response time before releasing it to production.

Note: A strong way to end each answer is: “After implementing the solution, I would test it under realistic conditions and monitor the application to make sure the issue is resolved.”

Also Explore: Encapsulation in Java

Java Coding and Problem-Solving Questions

Coding rounds check how clearly you think. Explain your approach first, write clean code and then state the time complexity.

1. How do you find the first non-repeating character in a string using Java?

The first non-repeating character is the character that appears only once and comes first in the string.

For example, in the string "swiss", the character s appears multiple times, but w appears only once and comes before the other non-repeating characters.

A LinkedHashMap can solve this problem because it stores characters in the order they appear.

import java.util.LinkedHashMap;
import java.util.Map;
public class Main {
public static Character firstNonRepeating(String str) {
Map<Character, Integer> frequency = new LinkedHashMap<>();
for (char ch : str.toCharArray()) {
frequency.put(ch, frequency.getOrDefault(ch, 0) + 1);
}
for (Map.Entry<Character, Integer> entry : frequency.entrySet()) {
if (entry.getValue() == 1) {
return entry.getKey();
}
}
return null;
}
public static void main(String[] args) {
System.out.println(firstNonRepeating("swiss"));
}
}

Java program to find the first non-repeating character in a string.

How does the code work?

  1. The first loop reads each character in the string and counts how many times it appears.
  2. The LinkedHashMap stores each character and its count while maintaining the original order.
  3. The second loop checks the character counts from the beginning.
  4. When a character has a count of 1, the method returns that character.
  5. If every character repeats, the method returns null.

For example, the character counts for "swiss" are s = 3, w = 1 and i = 1. Since w appears before i, the answer is w.

Time complexity: O(n), because the string is processed twice.

2. How do you find duplicate elements in an array or a list using Java?

Duplicate elements are values that appear more than once in an array or list. A HashSet can help identify them because it does not allow duplicate values.

The solution uses one set to store the elements already seen and another set to store the duplicate values.

import java.util.*;
public class Main {
public static Set<Integer> findDuplicates(int[] numbers) {
Set<Integer> seen = new HashSet<>();
Set<Integer> duplicates = new LinkedHashSet<>();
for (int number : numbers) {
if (!seen.add(number)) {
duplicates.add(number);
}
}
return duplicates;
}
public static void main(String[] args) {
int[] numbers = {10, 20, 30, 20, 40, 10, 50};
System.out.println(findDuplicates(numbers));
}
}

Java program to find duplicate elements in an array or list.

How does the code work?

  1. The scene set stores every number encountered for the first time.
  2. The add() method returns true when a number is added successfully.
  3. If the number already exists, add() returns false.
  4. The code then adds that number to the duplicates set.
  5. The method returns all duplicate values without repeating any value in the result.

For example, 20 appears twice and 10 appears twice, so both numbers are returned.

Time complexity: O(n) on average, because each number is checked using a set.

3. How do you find the second-highest number in an array without sorting it?

The second-highest number is the second-largest distinct value in the array.

For example, in {10, 40, 20, 50, 30, 50}, the highest number is 50 and the second-highest number is 40. The repeated value 50 does not count as the second-highest number.

The solution uses two variables to track the highest and second-highest values.

public class Main {
public static Integer secondHighest(int[] numbers) {
Integer highest = null;
Integer secondHighest = null;
for (int number : numbers) {
if (highest == null || number > highest) {
secondHighest = highest;
highest = number;
} else if (number < highest &&
(secondHighest == null || number > secondHighest)) {
secondHighest = number;
}
}
return secondHighest;
}
public static void main(String[] args) {
int[] numbers = {10, 40, 20, 50, 30, 50};
System.out.println(secondHighest(numbers));
}
}

Java second-highest number in an array without sorting.

How does the code work?

  1. The highest variable stores the largest number found so far.
  2. The secondHighest variable stores the second-largest distinct number.
  3. When a number is greater than highest, the old highest number becomes the second-highest number.
  4. If a number is smaller than highest but greater than secondHighest, it becomes the new second-highest number.
  5. Duplicate occurrences of the highest number are ignored.

For example, when the code finds 50, it updates the highest value. When it finds 40, that value becomes the second-highest number. Repeating 50 does not change the result.

Time complexity: O(n), because the array is traversed only once.

Why is this approach useful? It avoids sorting the entire array, which would take more time than a single traversal for this problem.

4. How do you check whether two strings are anagrams of each other using Java?

Two strings are anagrams when they contain the same characters with the same frequency, but the characters appear in a different order.

For example, "listen" and "silent" are anagrams because both strings contain the same letters.

A character-frequency array can compare the characters efficiently.

public class Main {
public static boolean areAnagrams(String str1, String str2) {
if (str1 == null || str2 == null) {
return false;
}
str1 = str1.toLowerCase();
str2 = str2.toLowerCase();
if (str1.length() != str2.length()) {
return false;
}
int[] frequency = new int[Character.MAX_VALUE + 1];
for (int i = 0; i < str1.length(); i++) {
frequency[str1.charAt(i)]++;
frequency[str2.charAt(i)]--;
}
for (int count : frequency) {
if (count != 0) {
return false;
}
}
return true;
}
public static void main(String[] args) {
System.out.println(areAnagrams("listen", "silent"));
}
}

Java program to check whether two strings are anagrams.

How does the code work?

  1. The method checks whether either string is null.
  2. Both strings are converted to lowercase so that uppercase and lowercase letters are treated equally for ordinary English text.
  3. If their lengths differ, they cannot be anagrams.
  4. The code increases the count for each character in the first string and decreases the count for the corresponding character in the second string.
  5. If every count becomes zero, both strings contain the same characters with the same frequencies.

For example, the letter l appears once in each string. Its count increases by one and decreases by one, resulting in zero. The same happens for every other character.

Time complexity: O(n) for the fixed-size character array approach.

Important: This implementation does not ignore spaces or punctuation. For applications that support multiple languages, a Unicode-aware solution may also be necessary.

5. How do you find the missing number in an array containing integers from 1 to N?

If an array contains numbers from 1 to N and one number is missing, the missing number can be found by subtracting the actual sum from the expected sum.

The formula for the sum of numbers from 1 to N is:

Formula for expected sum of integers from 1 to N to find a missing number in an array.

For example, if the array is {1, 2, 4, 5, 6}, the missing number is 3.

public class Main {
public static long findMissingNumber(int[] numbers, int n) {
long expectedSum = (long) n * (n + 1) / 2;
long actualSum = 0;
for (int number : numbers) {
actualSum += number;
}
return expectedSum - actualSum;
}
public static void main(String[] args) {
int[] numbers = {1, 2, 4, 5, 6};
System.out.println(findMissingNumber(numbers, 6));
}
}

Java code to find the missing number in an array, with output showing 3.

How does the code work?

  1. The value of n is 6, so the expected numbers are 1, 2, 3, 4, 5, 6.
  2. The formula calculates their expected sum: 6 × 7 / 2 = 21.
  3. The loop adds the numbers present in the array: 1 + 2 + 4 + 5 + 6 = 18.
  4. The code subtracts the actual sum from the expected sum: 21 - 18 = 3.
  5. Therefore, the missing number is 3.

Time complexity: O(n), because the array is traversed once.

Why use long? It helps prevent integer overflow when calculating sums for larger number ranges.

Important: This solution assumes that all numbers are distinct, exactly one number is missing and every number belongs to the range from 1 to N.

Also Explore: What is Java Used For?

How to Prepare for a Java Interview With 5 Years of Experience

Reading questions is not enough. Interviewers at this level expect clear reasoning, real examples and honest trade-offs. Use this plan two weeks before your interview.

  1. Review your own projects

Many interviews start with your resume. Be ready to explain the architecture, your role, one hard bug and one performance fix. Use numbers when you have them, such as response time before and after a change.

  1. Revise the core topics

Cover collections, concurrency, JVM memory, garbage collection, exceptions and the features from Java 8 to Java 25.

  1. Learn what is new

Read the release notes for Java 25 and Java 27 on their official website. Know which features are final and which are still in preview or incubator status.

  1. Practice coding without help

Solve problems in a plain editor and explain each step aloud. Practice strings, collections, simple algorithms.

  1. Refresh your framework stack

Revise Spring Boot, JPA and Hibernate, REST design, JUnit, Mockito and common microservice patterns.

  1. Practice trade-offs

Prepare to answer why you chose one design over another. A good answer names the benefit, the cost and the situation.

  1. Run a mock interview

Ask a friend to question you, or record yourself. You will notice weak answers quickly.

When you answer, follow a simple pattern. Give a short definition, show a small example and finish with a real use case or a risk.

Final Verdict

A Java interview for 5 years experience tests depth, not memory. Interviewers want to see that you can build, debug and improve real systems. They also want to see you keep learning.

Start with the core topics, especially collections, concurrency and the JVM. Then add the modern features from Java 25 and Java 27 and say clearly which ones are still in preview. Practice the scenario questions until you can explain each in two minutes. Finish with coding practice, so your solutions feel natural.

Prepare with this approach and you will answer with confidence instead of guesswork.

About the Author
Vaibhav Joshi | Igmguru
About the Author

Vaibhav Joshi has written systems-level and application code in Java, C, and C++ over a career spanning enterprise software and performance-focused projects. Debugging memory issues and bottlenecks under real production load shapes his careful, detail-oriented writing. He tests language and compiler updates directly, helping programmers write efficient, maintainable code.

Drop Us a Query
Fields marked * are mandatory
×

Your Shopping Cart


Your shopping cart is empty.