Skip to content

Hard Java Interview Questions

44 hard questions with answers. Design, trade-off and deep-dive questions asked for senior roles and at top product companies.

Topics in the hard set

Spring Boot and Microservices: 10 JDBC, Hibernate and JPA: 9 Multithreading and Concurrency: 8 Java 8+ Features and Streams: 5 Java Basics and JVM: 4 OOP in Java: 3 Collections Framework: 3 Strings and Exception Handling: 2
Solved — hardStart practicing
3 QUESTIONS FREESign up to unlock the full question bank and track your progress.Unlock every question and answer, then continue practising company-wise and package-wise.
3 questions free Unlock the rest in seconds
01

Compare the G1, ZGC and Parallel garbage collectors.

Save to ▾
Show answer
Parallel GC focuses on throughput and uses multiple threads but stops the application during collection. G1, the default since Java 9, splits the heap into regions and targets a pause time goal, which suits most server apps. ZGC is a low latency collector with pauses typically under a millisecond even on very large heaps, and is generational from Java 21. Choose based on latency versus throughput needs.
02

Your service throws OutOfMemoryError: Java heap space in production. How do you investigate?

Save to ▾
Show answer
First capture evidence: enable -XX:+HeapDumpOnOutOfMemoryError and check GC logs to see if heap grows steadily, which signals a leak, or spikes, which signals a large request. Analyse the heap dump in Eclipse MAT or VisualVM and look at the dominator tree for big retainers like static maps or caches. Fix the leak or bound the cache, then increase -Xmx only if the real workload needs it.
03

Users report the app is slow for the first few minutes after every deployment. Why might this happen?

Save to ▾
Show answer
This is usually JVM warm-up. Code runs in the interpreter until the JIT compiles hot paths, and caches, connection pools and class loading are also cold. Fixes include warm-up traffic before adding the instance to the load balancer, pre-initialising pools, class data sharing (AppCDS) to reduce start time, or newer options like CRaC or GraalVM native image when fast start is critical.
04

GC pauses of two seconds are causing request timeouts. How would you reduce them?

Save to ▾
Show answer
Turn on GC logging with -Xlog:gc* and confirm which collections cause the pauses. Reduce allocation rate by fixing heavy object creation in hot paths. Size the heap correctly and set -Xms equal to -Xmx to avoid resizing. Tune G1 with MaxGCPauseMillis or move to ZGC for sub-millisecond pauses on large heaps. Retest under load before rolling out.
05

What are sealed classes and how do they help?

Save to ▾
Show answer
Sealed classes and interfaces, final in Java 17, restrict which classes can extend them using the permits clause, for example sealed interface Shape permits Circle, Square. Each subclass must be final, sealed or non-sealed. This models a closed set of types, and with pattern matching for switch in Java 21 the compiler can check that every case is handled without a default branch.
06

What is the Liskov Substitution Principle? Give an example of breaking it.

Save to ▾
Show answer
LSP says any subclass object must be usable wherever the parent is expected without breaking behaviour. A classic violation is Square extends Rectangle: setting width also changes height, so code that sets width 5 and height 4 and expects area 20 fails. Another is a subclass throwing UnsupportedOperationException for a parent method. The fix is a better hierarchy or composition.
07

A deep inheritance hierarchy of six levels is hard to change. How would you refactor it?

Save to ▾
Show answer
I would identify which behaviours actually vary and pull them into small interfaces, then replace inheritance with composition where a class holds strategy objects. Shared helper code can move to separate components. I would refactor step by step behind existing tests, starting from the leaves, so behaviour stays the same. The result is flatter classes that are easier to test and extend.
08

A file handle leak causes Too many open files errors after a day. What is the likely cause?

Save to ▾
Show answer
Streams, readers or connections are opened but not closed on all paths, especially when an exception is thrown before close(). Check with lsof or jcmd which files are open. Fix by wrapping every resource in try-with-resources so it always closes, and make sure Files.lines() and Files.list() streams are closed too, since they hold file handles.
09

Should a service layer throw checked or unchecked exceptions? Defend your choice.

Save to ▾
Show answer
I prefer unchecked domain exceptions in the service layer. Checked exceptions force every caller to catch or declare them, leak low level details through layers and do not work well with lambdas. Unchecked exceptions with clear names, handled centrally by a controller advice, keep code clean. Spring follows the same idea by translating SQLException into its unchecked DataAccessException family.
10

Why must you be careful when initial capacity of a HashMap is too small?

Save to ▾
Show answer
If a HashMap starts small and grows large, it resizes many times; each resize allocates a new array and rehashes every entry, which costs CPU and creates garbage. When the expected size is known, use HashMap.newHashMap(n) in Java 19 or later, or set capacity to n divided by 0.75. Too large a capacity wastes memory, so size it to real data.
11

Memory usage is high because millions of Integer objects are stored in an ArrayList. What would you do?

Save to ▾
Show answer
Each Integer is a separate object with header overhead and a reference, so a List of Integer uses several times more memory than the raw data. If size is known, use an int[] array. Otherwise use primitive collections from libraries such as Eclipse Collections or fastutil, which store ints directly. This also improves cache performance and reduces GC pressure.
12

Find the top K most frequent words in a list.

Save to ▾
Show answer
Count with a map, then keep a min-heap of size K. Map<String, Integer> cnt = new HashMap<>(); for (String w : words) cnt.merge(w, 1, Integer::sum); PriorityQueue<String> pq = new PriorityQueue<>( Comparator.comparingInt(cnt::get)); for (String w : cnt.keySet()) { pq.offer(w); if (pq.size() > k) pq.poll(); } Time O(n log k). Reverse the heap contents for highest first.
13

Explain the happens-before relationship in the Java Memory Model.

Save to ▾
Show answer
Happens-before is the rule that guarantees one action's effects are visible to another. Examples: unlocking a monitor happens-before a later lock of the same monitor, a volatile write happens-before later reads of it, Thread.start() happens-before actions in the started thread, and actions in a thread happen-before another thread's successful join() on it. Without such an edge, threads may see stale or reordered values.
14

What is CompletableFuture and how is it better than Future?

Save to ▾
Show answer
Future only lets you block with get() to obtain a result. CompletableFuture supports non-blocking composition: thenApply to transform, thenCompose to chain async calls, thenCombine or allOf to join several, and exceptionally or handle for errors. It runs on the common ForkJoinPool by default, but pass your own executor for blocking I/O to avoid starving the pool.
15

Your application hangs and CPU is near zero. How do you find out why?

Save to ▾
Show answer
Low CPU with a hang suggests threads are blocked, often a deadlock or waiting on a slow resource. Take several thread dumps with jcmd pid Thread.print. The JVM reports detected deadlocks at the end of the dump. Otherwise look for many threads BLOCKED on the same lock or WAITING on a connection pool or socket read, then fix lock order, add timeouts or increase the pool.
16

After moving to virtual threads, throughput did not improve and some requests stalled. What could cause it?

Save to ▾
Show answer
Common causes are pinning, where a virtual thread blocks inside a synchronized block or native call and holds its carrier thread; this was largely fixed in Java 24 for synchronized, but native calls still pin. Another cause is a fixed downstream bottleneck such as a small JDBC pool, so more threads just wait. Detect pinning with JFR events, use ReentrantLock in older versions and size pools to real capacity.
17

A thread pool fills up and tasks get rejected during traffic spikes. How do you handle it?

Save to ▾
Show answer
First check why tasks are slow, since slow tasks fill pools. Then choose a policy: a bounded queue with CallerRunsPolicy applies backpressure by running the task in the submitting thread; otherwise return a fast 503 so clients retry. Tune pool and queue sizes from load tests. Never use an unbounded queue, because it hides the problem until memory runs out.
18

Two scheduled jobs on different servers process the same records twice. How do you prevent this?

Save to ▾
Show answer
In-JVM locks do not work across servers. Use a distributed lock such as ShedLock with a database table or Redis, so only one instance runs the job at a time. Make the processing idempotent too, for example by marking records with a status and updating with WHERE status = 'NEW' so only one worker claims each row. Database SELECT FOR UPDATE SKIP LOCKED also works well.
19

How would you write a simple thread safe rate limiter in Java?

Save to ▾
Show answer
A token bucket with synchronized refill is simple and correct. class RateLimiter { private final long capacity, refillPerSec; private double tokens; private long last = System.nanoTime(); RateLimiter(long cap, long rps) { capacity = cap; refillPerSec = rps; tokens = cap; } synchronized boolean tryAcquire() { long now = System.nanoTime(); tokens = Math.min(capacity, tokens + (now - last) / 1e9 * refillPerSec); last = now; if (tokens >= 1) { tokens--; return true; } return false; } } Across servers, keep the bucket in Redis.
20

Tell me about a time you solved a concurrency bug.

Save to ▾
Show answer
Situation: our wallet service sometimes showed wrong balances under load. Task: find the race and fix it without slowing the API. Action: I reproduced it with a JMeter test, found a read-then-update pattern in two concurrent requests, and replaced it with an atomic SQL update plus optimistic locking with a version column. Result: zero balance mismatches across festival traffic and a reusable load test for future changes.
21

When should you avoid parallel streams?

Save to ▾
Show answer
Parallel streams use the common ForkJoinPool and help only for large, CPU-bound, stateless work on sources that split well, like arrays and ArrayList. Avoid them for small data, I/O or blocking calls, LinkedList or iterate() sources, ordered operations like limit and findFirst, and shared mutable state. In a web server they compete for the same common pool, so measure before using them.
22

Explain pattern matching for switch and record patterns in Java 21.

Save to ▾
Show answer
Pattern matching for switch lets a case match on type and bind a variable, such as case Circle c -> Math.PI * c.r() * c.r(). Record patterns deconstruct a record directly: case Point(int x, int y). Guards add conditions with when. With sealed types the compiler checks the switch is exhaustive, so adding a new subtype causes a compile error where it is not handled.
23

What is the difference between reduce() and collect()?

Save to ▾
Show answer
reduce() combines elements into a single immutable value using an identity and an associative function, such as summing numbers. collect() is a mutable reduction that accumulates into a container like a List, Map or StringBuilder, which is far more efficient for building collections. Using reduce to build a list creates many intermediate copies, so use collect for containers.
24

You need to process a 5 GB file using streams without running out of memory. How?

Save to ▾
Show answer
Use Files.lines(path), which reads lazily line by line, inside try-with-resources. Apply filter and map, then aggregate with a collector that keeps small results such as counts or sums instead of collecting all lines into a list. If the output is large, write it incrementally to a BufferedWriter. Avoid sorted() and distinct() on huge data because they buffer everything.
25

A stream over a list of orders is slow in a hot path. How do you analyse it?

Save to ▾
Show answer
Profile first with JFR or async-profiler to confirm the stream is the hotspot. Check for repeated work like calling sorted() inside a loop, boxing overhead with Stream of Integer instead of IntStream, or re-streaming the same list many times. Cache computed maps, use primitive streams and, if still slow, a plain loop can be clearer and faster for very hot code.
26

What is the first level and second level cache in Hibernate?

Save to ▾
Show answer
The first level cache is the persistence context itself; it is per session or EntityManager, always on, and returns the same object for repeated finds by id in one transaction. The second level cache is shared across sessions and optional, backed by providers like Ehcache or Infinispan through JCache. Use it for read-mostly reference data, and be careful about stale data in clustered setups.
27

What are transaction isolation levels and which problems do they prevent?

Save to ▾
Show answer
Read Uncommitted allows dirty reads. Read Committed prevents dirty reads and is the default in PostgreSQL and SQL Server. Repeatable Read also prevents non-repeatable reads and is the MySQL InnoDB default. Serializable prevents phantom reads too but reduces concurrency. Choose the lowest level that keeps data correct, and use explicit locking for specific hot rows.
28

What is optimistic versus pessimistic locking in JPA?

Save to ▾
Show answer
Optimistic locking adds a @Version column; on update Hibernate checks the version and throws OptimisticLockException if another transaction changed the row, so you retry. It suits low contention. Pessimistic locking uses SELECT FOR UPDATE through LockModeType.PESSIMISTIC_WRITE, blocking others until commit; it suits high contention on the same rows, like seat booking, but can reduce throughput.
29

Why is connection pooling important and how do you size a pool?

Save to ▾
Show answer
Opening a database connection is slow, so a pool like HikariCP keeps ready connections for reuse. A bigger pool is not always better, because the database has limited CPU and disk. A common starting point is around cores times two plus effective spindles per database, then tune with load tests. Watch pool wait time and active connections metrics, and set connection and query timeouts.
30

A batch job inserting 1 lakh rows through JPA takes 30 minutes. How do you speed it up?

Save to ▾
Show answer
Enable JDBC batching with hibernate.jdbc.batch_size around 50 and order_inserts, and avoid IDENTITY id generation, which disables insert batching; use a SEQUENCE with a pooled optimizer. Flush and clear the EntityManager every batch so the persistence context does not grow. For very large loads use JdbcTemplate batchUpdate or the database bulk loader. This often cuts time to a minute or two.
31

Two users booking the last seat both succeed. How do you prevent double booking?

Save to ▾
Show answer
This is a race between read and write. Use a database guarantee: a unique constraint on seat and show, an atomic conditional update like UPDATE seats SET status = 'BOOKED' WHERE id = ? AND status = 'FREE' and check the affected row count, or pessimistic locking with SELECT FOR UPDATE. Optimistic locking with a version column also works, retrying or failing the loser.
32

Production shows Connection is not available, request timed out after 30000ms from HikariCP. What do you check?

Save to ▾
Show answer
The pool is exhausted. Check for connection leaks, where code holds connections without closing, by setting leakDetectionThreshold. Look for long transactions doing remote calls inside @Transactional, slow queries holding connections and a pool size too small for the thread count. Fix leaks, move external calls outside transactions, optimise slow queries and then size the pool from load tests.
33

Add optimistic locking to an entity and handle the conflict.

Save to ▾
Show answer
Add a version field; Hibernate checks it on update. @Entity class Account { @Id Long id; long balance; @Version Long version; } @Retryable(retryFor = ObjectOptimisticLockingFailureException.class, maxAttempts = 3) @Transactional public void debit(Long id, long amt) { Account a = repo.findById(id).orElseThrow(); a.balance -= amt; } The update includes WHERE version = ?, so a stale write fails and is retried.
34

Write a JPQL query that returns a DTO projection instead of entities.

Save to ▾
Show answer
Projections load only needed columns and avoid dirty checking. public record OrderSummary(Long id, String customer, long total) {} @Query(""" select new com.shop.OrderSummary(o.id, c.name, o.total) from Order o join o.customer c where o.createdAt >= :from """) List<OrderSummary> summaries(@Param("from") LocalDate from); Interface-based projections in Spring Data are another option.
35

How does @Transactional work and what are its common pitfalls?

Save to ▾
Show answer
@Transactional is applied through a proxy that opens a transaction before the method and commits or rolls back after. Pitfalls: calling a transactional method from the same class bypasses the proxy, so no transaction starts; by default rollback happens only for unchecked exceptions; it works on public methods; and long transactions with remote calls hold connections. Use propagation and readOnly where suitable.
36

What are the key patterns used in microservices built with Spring Boot?

Save to ▾
Show answer
Common patterns are API gateway (Spring Cloud Gateway) for routing and auth, service discovery or Kubernetes services, externalised config, circuit breaker and retry with Resilience4j, database per service, event-driven communication with Kafka, saga for distributed transactions, and observability with Micrometer, tracing and centralised logs. Each service owns its data and deploys independently.
37

How does JWT based authentication work in a Spring Boot API?

Save to ▾
Show answer
After login, the auth server issues a signed JWT with claims like user id, roles and expiry. The client sends it in the Authorization: Bearer header. The API, configured as an OAuth2 resource server in Spring Security, verifies the signature with the public key, checks expiry and builds the security context, so no session is needed. Keep tokens short-lived and use refresh tokens for renewal.
38

A downstream payment service is slow and your API threads pile up. How do you protect your service?

Save to ▾
Show answer
Set strict connect and read timeouts on the HTTP client. Add a Resilience4j circuit breaker so calls fail fast once the error rate crosses a threshold, a bulkhead to limit concurrent calls and retry with backoff only for safe idempotent requests. Return a clear fallback such as payment pending. Monitor breaker state through Actuator metrics and alerts.
39

How would you split a monolith into microservices?

Save to ▾
Show answer
Do not split everything at once. Identify bounded contexts with the business, such as orders, payments and catalogue, and pick a module with clear boundaries and high change rate first. Use the strangler fig pattern: route its traffic through a gateway to the new service, give it its own database and sync data through events. Add observability and CI/CD before scaling out further.
40

An order must update inventory and payment in different services. How do you keep data consistent?

Save to ▾
Show answer
Distributed two-phase commit is fragile, so use the saga pattern. Each service performs a local transaction and publishes an event; failures trigger compensating actions, such as releasing stock when payment fails. Use the transactional outbox pattern so the database change and event publish never diverge, and make consumers idempotent because Kafka may deliver a message more than once.
41

Your Spring Boot service takes 40 seconds to start in Kubernetes and pods get killed. How do you fix it?

Save to ▾
Show answer
First set a startupProbe so Kubernetes waits for startup before liveness checks begin. Then reduce start time: remove unused starters, enable lazy initialisation where safe, avoid heavy work in @PostConstruct, give enough CPU at start and consider AppCDS. For very fast startup, a GraalVM native image built with Spring AOT can start in under a second, with some trade-offs.
42

Configure a Resilience4j circuit breaker on a client call in Spring Boot.

Save to ▾
Show answer
Annotate the method and provide a fallback. @CircuitBreaker(name = "inventory", fallbackMethod = "stockUnknown") public Stock check(String sku) { return restClient.get().uri("/stock/{sku}", sku) .retrieve().body(Stock.class); } private Stock stockUnknown(String sku, Throwable t) { return Stock.unknown(sku); } # application.yml resilience4j.circuitbreaker.instances.inventory: failure-rate-threshold: 50 wait-duration-in-open-state: 10s
43

Write a Kafka producer and consumer in Spring Boot.

Save to ▾
Show answer
Spring Kafka hides most boilerplate. @Service class OrderEvents { private final KafkaTemplate<String, OrderPlaced> kafka; OrderEvents(KafkaTemplate<String, OrderPlaced> k) { kafka = k; } void publish(OrderPlaced e) { kafka.send("orders", e.orderId(), e); } } @Component class OrderListener { @KafkaListener(topics = "orders", groupId = "inventory") void on(OrderPlaced e) { inventory.reserve(e); } } Using orderId as key keeps events for one order in the same partition and order.
44

Tell me about a microservice you designed end to end.

Save to ▾
Show answer
Situation: our monolith's notification code slowed every release. Task: extract it into its own service. Action: I designed a Spring Boot service consuming Kafka events, with its own database for templates and delivery logs, retries with backoff, idempotent consumers and Actuator metrics in Grafana. Result: it handles 20 lakh messages a day, deploys independently and notification incidents dropped sharply.

Frequently asked questions

Who should practise hard Java questions?

Candidates targeting senior roles or top product companies, and anyone who finds the medium set easy.

How should I practise these?

Answer each question aloud before opening the answer, mark it as solved only when your answer matches the key idea, and save misses to Last-Minute Revision.

Other difficulty levels