Skip to main content
DevHivesBlog

Search the blog

Find a post by title, tag or topic

Java interview questions for 3 years experience: 15 answers that pass

At three years, interviewers test depth, not syntax. Here are the 15 Java questions you'll face on JVM, GC, threads, Spring and microservices, with model answers.

Arnab Biswas12 min read
WhatsApp
A question bubble and an answer bubble, showing the interview Q&A format.

At three years of experience, nobody asks you to write a for loop. They ask why your service paused for two seconds in production, and most candidates go quiet. Say you can build a REST API in an afternoon, but that question makes you freeze. You know the code works. You just cannot explain what happens underneath it.

A 3-year Java interviewer tests depth, not syntax. Expect 15 questions across six areas: JVM memory, equality and immutability, garbage collection, concurrency, Spring internals and microservices. Each answer needs a mechanism and a trade-off, not a definition. This post adds a red-flag answer and runnable code to each topic, and you can check the topic mix against the InterviewBit Java question bank. Memorised textbook lines are the main reason candidates fail this round.

If you are still building the basics, start with my list of Java developer skills for freshers and come back here after.

JVM memory questions: bytecode, the heap with the stack, and the JIT compiler

Interviewers start with the JVM because it shows whether you know what runs your code, not just how to write it, and the InterviewBit Java question bank opens with the same topics. The three questions below come up in almost every round at this level.

The 15 questions fall into five areas, and each one tests depth.
The 15 questions fall into six areas, and each one tests depth.

How does Java achieve platform independence?

Model answer: The compiler turns source code into bytecode, not machine code. Bytecode runs on any machine that has a JRE, so the same .class file works on Windows and on Linux without recompiling.

Likely follow-up: "Is the JVM itself platform independent?" No. Each operating system needs its own JVM build. The bytecode is portable; the machine that reads it is not.

What lives on the heap and what lives on the stack?

Model answer: Each thread gets its own stack. It holds method frames, local primitives and references to objects. The objects themselves live on the heap, which all threads share.

This explains the two errors. A StackOverflowError usually means recursion with no exit, so frames pile up until the stack is full. An OutOfMemoryError means the heap is full of objects that are still reachable, often from a cache or a static list that only grows.

Likely follow-up: "How would you find that leak?" Take a heap dump and look for the object type with the most retained memory.

What does the JIT compiler do?

Model answer: The JIT compiler is part of the JVM. It watches which bytecode runs often (the "hot" code) and compiles it to native machine code, so later calls skip interpretation.

Likely follow-up: "Why are the first requests after a deploy slower?" Because the JIT has not compiled the hot paths yet. Teams often send warm-up traffic before a new instance takes real load.

Watch: Infosys 3 Years Java Experience | Java | Spring Boot | Microservices | Selected — CloudTech (YouTube)

Equality and immutability: equals paired with hashCode, and the final keyword

These two questions look basic, but a weak answer here tells the interviewer you have hit real bugs without understanding them. Both are listed in the InterviewBit set, and both lead into longer discussions.

What is the difference between == and equals(), and why does hashCode matter?

Model answer: == compares references, so it asks "is this the same object?" equals() compares contents. The contract says that if two objects are equal, they must return the same hashCode().

A HashMap uses the hash code to pick a bucket first. It only calls equals() inside that bucket. Override equals() alone and two "equal" keys land in different buckets:

class UserKey {
    private final String email;
    UserKey(String email) { this.email = email; }
    @Override
    public boolean equals(Object o) {
        if (!(o instanceof UserKey)) return false;
        return email.equals(((UserKey) o).email);
    }
    // hashCode() is NOT overridden
}
Map<UserKey, String> roles = new HashMap<>();
roles.put(new UserKey("a@x.com"), "admin");
roles.get(new UserKey("a@x.com")); // usually returns null

Red flag answer: "I always generate both in the IDE." That is good practice, but it does not explain the bucket lookup. The interviewer wants the reason.

What does final do, and how do you build an immutable class?

Model answer: On a variable, final stops reassignment. On a method, it stops overriding. On a class, it stops anyone extending it. To build an immutable class, mark the class final, make every field private final, skip setters, and copy any mutable input (like a List) in the constructor and getters.

String is the classic example, which is why the String vs StringBuilder vs StringBuffer question often follows. If final gets mixed up with static in your head, read static vs non-static in Java first.

Red flag answer: "A final variable makes the object immutable." It does not. A final List can still have items added. Only the reference is fixed.

Garbage collection: G1 vs CMS vs Parallel compared

One caution first: CMS was removed in JDK 14, so the table below compares G1 with Parallel and ZGC, and keeps CMS only as a reminder. G1 is the default collector on Java 17 and 21. The collector you name matters less than whether you can explain compaction and pause-time trade-offs in plain words. Oracle's G1 tuning guide gives you that comparison, though it describes Java 8. Compaction means moving live objects together so free memory sits in one block instead of scattered gaps. Interviewers also ask about ZGC and Shenandoah, which aim for even shorter pauses. Know G1 first.

CollectorCompactionPause-time behaviourHeap layout
G1CompactsMeets a pause target with high probability, not as a guaranteeEqually-sized regions
CMS (removed in JDK 14)Does not compactShort pauses, but fragmentation can force long onesContiguous young and old generations
ZGCCompacts while the application runsAims for very short pauses, even on large heapsHeap split into regions
ParallelWhole-heap compaction onlyCompacting the whole heap causes long stop-the-world pausesContiguous young and old generations

How does G1 differ from CMS and Parallel?

Model answer: G1 is a server-style collector built for multiprocessor machines with large heaps. It splits the heap into equal regions, so it can clean the regions with the most garbage first. Unlike CMS, it compacts, which avoids fragmentation. Unlike Parallel, it does not have to compact the whole heap at once. Oracle calls G1 the long-term replacement for CMS.

Red flag answer: "G1 is newer, so it is faster." Speed is the wrong word. G1 trades some throughput for shorter, more predictable pauses.

How do you tune a JVM for pause time?

Model answer: Per the Oracle G1 guide, MaxGCPauseMillis defaults to 200 ms and InitiatingHeapOccupancyPercent defaults to 45. The second flag is the heap occupancy at which G1 starts a concurrent marking cycle. G1 is not real-time, so the pause goal is a target it usually hits. The same guide warns against fixing the young generation size with -Xmn, because that overrides the pause goal. This is also how you answer the two-second pause question. Turn on GC logging with -Xlog:gc and look for a line like this (an illustrative example, your numbers will differ): [12.345s][info][gc] GC(87) Pause Young (Normal) (G1 Evacuation Pause) 2048M->1900M(4096M) 2013.456ms. Read the last number first: 2013 ms is the pause, ten times the 200 ms goal. The heap figures show almost nothing was freed, since 2048M only fell to 1900M, so a large set of objects is still reachable. Your first action is to take a heap dump and look for a cache or static list that keeps growing. Your second is to check that -Xmn is not fixed, and only then adjust the pause goal.

Red flag answer: Setting a very small pause goal and calling it a guarantee. A smaller goal makes G1 collect more often, which can lower throughput.

Concurrency questions: synchronization with deadlock, and why thread pools matter

Concurrency questions separate people who have read about threads from people who have debugged one, and the Oracle concurrency tutorial covers every topic in this section. Its main headings are synchronization, deadlock and liveness.

What is the difference between synchronized and volatile?

Model answer: synchronized gives two guarantees: only one thread runs the block at a time, and changes are visible to the next thread that takes the lock. volatile gives visibility only. A volatile int count still breaks under count++, because read, add and write are three separate steps.

Red flag answer: "volatile makes a variable thread-safe." It makes reads fresh. It does not make updates atomic. Use AtomicInteger for counters.

How does a deadlock happen, and how do you prevent it?

Model answer: Thread A holds lock X and waits for lock Y. Thread B holds Y and waits for X. Neither can move. You spot it with a thread dump (jstack prints "Found one Java-level deadlock"). You prevent it by always taking locks in the same order, or by using tryLock with a timeout so a thread backs off.

A trimmed thread dump shows the problem like this:

Found one Java-level deadlock:
"Thread-A": waiting to lock monitor 0x01 (object lockY),
  which is held by "Thread-B"
"Thread-B": waiting to lock monitor 0x02 (object lockX),
  which is held by "Thread-A"

Mention that deadlock is one liveness problem among others. Starvation (a thread never gets the lock) and livelock (threads keep reacting to each other and never finish) are the two the tutorial also names.

Red flag answer: "Just add more synchronized." More locks usually create more chances for deadlock.

Why use an ExecutorService instead of new Thread()?

Model answer: Since version 5.0, Java has shipped high-level APIs in java.util.concurrent, including executors and thread pools, as the Oracle tutorial explains. A pool reuses threads instead of creating one per task, caps how many run at once, and returns a Future you can wait on with a timeout.

ExecutorService pool = Executors.newFixedThreadPool(poolSize);
try {
    Future<Report> future = pool.submit(() -> buildReport(userId));
    Report report = future.get(timeoutSeconds, TimeUnit.SECONDS);
} finally {
    pool.shutdown();
}

Red flag answer: Forgetting shutdown(). The pool's threads keep the JVM alive, and the interviewer will notice.

Spring questions: bean lifecycle, AOP and @Transactional

Spring questions test whether you know what the framework does behind your annotations, and the Spring AOP reference explains the proxy model that ties all three together.

What is the Spring bean lifecycle?

Model answer: Walk through it in order.

  1. Instantiate

    The container calls the constructor and creates the object.

  2. Inject dependencies

    Spring fills fields, setters or constructor arguments with other beans.

  3. Run init callbacks

    @PostConstruct, then afterPropertiesSet(), then any custom init method.

  4. Apply BeanPostProcessors

    These run before and after the init callbacks, and one of them can wrap the bean in a proxy.

  5. Ready for use

    The bean (or its proxy) is handed to anyone who asks for it.

  6. Destroy

    On shutdown, @PreDestroy runs for singleton beans.

What is AOP, and where does Spring use it?

Model answer: The key unit in AOP is the aspect. An aspect pulls one crosscutting concern, like transactions or logging, out of the business code and applies it in many places. The Spring docs name declarative transaction management as its most important service: you write @Transactional, and an aspect opens and commits the transaction for you.

Why does @Transactional not work when a method calls another method in the same class?

Short answer: Spring applies @Transactional through a proxy that wraps your bean. Calls from outside go through the proxy, so the transaction starts. A call from inside the same class uses this, which is the real object, so it skips the proxy and no transaction begins. Fixes: move the method to a separate bean, or switch to AspectJ weaving, which changes the class at build or load time so the call needs no proxy.

@Service
class OrderService {
    public void placeOrder() {
        save(); // same-class call: skips the proxy, no transaction
    }
    @Transactional
    public void save() { /* ... */ }
}

Red flag answer: "It should work, it has the annotation." This tells the interviewer you treat annotations as magic.

Microservices questions on failure and data consistency

At this level interviewers want to hear what you do when a service call fails, not a definition of microservices. Both questions below are about failure: one about calls, one about data.

Every hop between services can fail, so each one needs a timeout and a plan.
Every hop between services can fail, so each one needs a timeout and a plan.

How do you stop one slow service from taking down the others?

Model answer: Set a timeout on every outgoing call, so a slow service cannot hold your threads forever. Retry only a few times, with backoff, and only for safe operations. Add a circuit breaker (Resilience4j is the common Java choice): after repeated failures it stops calling the service for a while and returns a fallback.

Red flag answer: "We retry until it works." Unlimited retries pile more load onto a service that is already struggling.

How do you keep data consistent when each service owns its database?

Model answer: Use a saga. Each service runs a local transaction and publishes an event. If a later step fails, earlier services run compensating actions, such as refunding a payment. The data becomes consistent over time, not instantly. That is eventual consistency.

Pros

  • Each service keeps its own schema and can deploy alone.
  • No locks held across the network, so one slow service does not block the rest.

Cons

  • You must write and test a compensating action for every step.
  • Users can briefly see an in-between state, which a single shared database would hide.

Red flag answer: "Use a distributed transaction across all services." Two-phase commit locks resources across services and fails badly when one is down.

Red flags that fail a 3-year Java candidate

Most rejections come from a few habits in how you answer, not from missing one fact. Say the interviewer asks how you would find a memory leak, and you have never done it. "I would take a heap dump and look at retained size, though I have only done it in practice projects" passes. "I have never needed to" does not.

Here is the do and don't list drawn from the 15 answers above:

  • Don't recite a definition and stop. Do add one example from code you wrote.
  • Don't say you never touched GC or threads. Do describe what you would check first.
  • Don't claim you used a pattern you cannot explain. Interviewers ask "why" twice.
  • Don't treat Spring annotations as magic. Do name the proxy or callback behind them.

If you also interview for frontend roles, my JavaScript interview questions for five years of experience covers browser and async JavaScript at a more senior level. This post stays on the JVM and Spring at three years. For a database-heavy round, the SQL Server interview questions use the same answer style.

Pick the three questions you could not answer aloud, write a one-minute answer to each with one example, and practise saying them to a friend before your next interview. Start with the ones that come with a code example above, equals and hashCode and the @Transactional self-call, because you can test those yourself in ten minutes.

ShareWhatsAppLinkedInX

Picked from the tags and topic of this post.

A bar chart showing increasing monthly salary across experience levels, indicating cyber security pay progression.
career growth8 min read

Cyber Security Salary Per Month in India: Role-by-Role for 2026

A fresher in cyber security in India earns about ₹30,000 to ₹58,000 per month in 2026. This post breaks down pay by role and experience level, and shows what a ₹4 LPA offer actually lands in your account.

Arnab Biswas
A resume page with a blank objective section, highlighting the emptiness that must be filled in six seconds.
career growth10 min read

How to Write a Career Objective for a Resume, With Fresher Examples

Two guides say a career objective should run 2-3 sentences, about 30-50 words. This guide adds a one-sentence simplification for freshers with one degree and one project, plus 12 examples for developer, data, QA and AI roles.

Arnab Biswas
A staircase with five steps and a flag at the top, representing the 50 DSA questions as a climbing path to coding round success.
career growth9 min read

50 DSA Interview Questions for 2026 Fresher Coding Rounds

The 50 DSA questions most likely to appear in 2026 IT-services and product-company coding rounds, with solutions and complexity. Start with arrays, strings, stacks and linked lists — services loops lean on them.

Arnab Biswas