Debugging • Testing • Collections and Streams • Concurrency at Work • Owning Features • 2026

Java Interview Questions for 3 Years Experience (2 to 4 Years)

Java interviews for 3 years of experience care less about definitions and more about what you did: a bug you traced, a feature you shipped, how you test your own code, how you use the debugger and the build tools, and what code review taught you. It is written for Java developers with about two to four years of real work, the stage where you own features end to end but someone else still designs the system. Each question shows what the interviewer is checking, the shape of a good answer and a short spoken answer. Swap the stories for your own before the interview.

Search all questions by round, difficulty and level, or save the ones you want to practice.

Debugging 2 questions

Medium Behavioral round Mid-level Practice question

1. Walk me through a NullPointerException you traced in your own code. How did you find where the null actually came from?

What the interviewer is really testing:
Whether you follow a null back to its source instead of wrapping the crash site in a null check and moving on.
Answer frame:

Read the trace: the top frame in your own code, and which reference on that line was null.

Walk it back: where that value was set, and which input or path left it empty.

Fix the source: validate at the boundary or change the contract, then add a test for that input.

Sample spoken answer:

“We had an NPE in our invoice export that only hit a few customers. The stack trace pointed at a line that chained three getters, and newer JDKs print which call returned null, so I could see it was the billing address. I didn't just add a null check there. I walked back to where the customer was loaded and found that customers created through an old import had no billing address at all, while our code assumed every customer had one. So the real fix was two parts: the export falls back to the main address when billing is missing, and the import now rejects records without an address. I added a unit test with a customer that has no billing address. The lesson I kept is that a null check at the crash site usually just moves the bug somewhere quieter.”

Red flag to avoid:

Saying you fixed it by adding a null check on the line that crashed, with no idea why the value was null.

They may ask next:
  • How do you decide whether a method should return null, an empty collection or an Optional?
  • What would you change in the code so this kind of null fails early, at the boundary?
Say it in 60 seconds
Medium Technical round Mid-level Practice question

2. Names with accents look fine when you run the code on your laptop, but come out garbled when the same code runs on the server. What would you check?

What the interviewer is really testing:
Whether you know that text becomes bytes only through a character encoding, and that code relying on the platform default behaves differently from machine to machine.
Answer frame:

Cause: somewhere bytes and text are converted without naming a charset, so the machine's default is used.

Where to look: getBytes and new String with no charset, readers and writers on files and streams, then the database and HTTP layers.

Fix: name UTF-8 explicitly wherever text crosses a boundary, and add a test with accented characters.

Sample spoken answer:

“Garbled accents almost always mean bytes were turned into text, or text into bytes, with a different encoding from the one they were written in. The usual cause is code that doesn't name a charset, like getBytes with no argument, new String on a byte array, or an old FileReader, so it uses the machine's default. Before Java 18 that default came from the operating system settings, so my laptop and a minimal server image could differ. Since Java 18 the default is UTF-8 for most APIs, but I still pass StandardCharsets.UTF_8 so the code doesn't depend on it. When I hit this, the culprit was a CSV export built with getBytes. I also check the other boundaries: the database column and connection settings, and the charset in the HTTP Content-Type header. Then I add a test with a name like José, so it can't quietly come back.”

Code:
byte[] bytes = text.getBytes(StandardCharsets.UTF_8);
String back = new String(bytes, StandardCharsets.UTF_8);

try (BufferedReader in = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
    // read lines
}
Red flag to avoid:

Blaming the server or the font, or fixing it by stripping accents out of the data.

They may ask next:
  • Why does broken text sometimes show as question marks and sometimes as two odd characters in place of one?
  • How would you repair data that was already saved with the wrong encoding?
Say it in 60 seconds

Everyday Java 4 questions

Medium Technical round Mid-level Practice question

3. A total in one of your reports suddenly came out negative once the data grew. The code sums with int. What happened, and how do you guard against it?

What the interviewer is really testing:
Whether you know that int overflow is silent in Java and where it hides in ordinary code, like mapToInt sums and unit conversions.
Answer frame:

Cause: an int tops out a little above two billion, and going past it wraps round to a negative number with no error.

Where it hides: mapToInt(...).sum() returns an int, and so do multiplications like days times milliseconds per day.

Guard: use long, and Math.addExact or multiplyExact where an overflow must fail loudly.

Sample spoken answer:

“Java's int holds a bit over two billion, and when a sum goes past that it doesn't throw anything, it just wraps round to a big negative number. In my case a report added up item quantities across all orders with mapToInt and sum, and that sum is an int, so once the data grew enough the total flipped negative. The fix was mapToLong, so the sum is a long. Then I looked for the same pattern elsewhere and found a timeout worked out as days times 24 times 60 times 60 times 1000, all in int, which overflows from 25 days up. Writing 24L so the maths happens in long fixed it, though Duration.ofDays reads better. Where a wrong number would be worse than a crash, I use Math.addExact or multiplyExact, which throw an ArithmeticException on overflow.”

Code:
long totalQty = orders.stream()
    .mapToLong(Order::quantity) // mapToInt(...).sum() would be an int
    .sum();

long timeoutMs = Duration.ofDays(days).toMillis();
int checked = Math.addExact(a, b); // throws ArithmeticException on overflow
Red flag to avoid:

Expecting Java to throw an exception on overflow by itself, or fixing it by switching to double.

They may ask next:
  • How would you write a test that would have caught this before the data grew?
  • Why would switching the total to double be the wrong fix?
Say it in 60 seconds
Medium Technical round Mid-level Practice question

4. Have you handled money or exact decimals in Java? Why not double, and what traps did BigDecimal have for you?

What the interviewer is really testing:
Whether you have used BigDecimal for real and know its sharp edges, not just that double is imprecise.
Answer frame:

Why not double: binary floating point can't hold most decimal fractions exactly, so sums drift.

Creating values: from a String or valueOf, never the double constructor.

Traps: equals checks scale, divide needs a rounding mode, results need a set scale.

Sample spoken answer:

“Yes, on a billing module. Double can't represent values like 0.1 exactly, so adding many of them drifts by tiny amounts, and those show up as a total that's off in the last decimal place. BigDecimal fixed that, but it had its own traps. The first was creating it with new BigDecimal of a double, which carries the double's error into it. I use a String or BigDecimal.valueOf instead. The second was equals: 2.0 and 2.00 aren't equal because equals checks scale too, so I compare with compareTo. The third was divide: if the result doesn't terminate, like dividing by three, it throws an ArithmeticException unless you tell it how to round, usually with a scale and a rounding mode. We agreed on one rounding mode with finance and put it in one helper.”

Red flag to avoid:

Saying double is fine for money if you round at the end.

They may ask next:
  • Which rounding mode would you choose for money, and who should decide that?
  • Would you ever store money as a long number of minor units instead?
Say it in 60 seconds
Medium Technical round Mid-level Practice question

5. How have you used Java enums in your code beyond a list of constants, for example with fields or behaviour?

What the interviewer is really testing:
Whether you use enums as small classes to remove scattered if-else chains, and know the ordinal trap.
Answer frame:

Fields and methods: each constant carries its own data, set in a constructor.

Replace branching: logic that switched on a string moves into the enum.

Care points: persist by name not ordinal; EnumMap and EnumSet for lookups.

Sample spoken answer:

“In a subscription feature we had plan names as strings and if-else chains in several places deciding seat limits. I turned it into an enum where each plan holds its seat limit as a field and has a method that says whether a team size is allowed. That removed the scattered checks, and adding a plan became one line. When a new plan got added, the compiler also pointed at every switch that didn't cover it, because we used switch expressions with no default branch. Two things I watch for: we store the enum's name in the database, never the ordinal, because someone reordering the constants would silently change what saved numbers mean. And for maps keyed by an enum I use EnumMap, which is compact and keeps the declaration order.”

Code:
enum Plan {
    BASIC(1), TEAM(10), BUSINESS(50);

    private final int seats;
    Plan(int seats) { this.seats = seats; }

    boolean allows(int users) { return users <= seats; }
}
Red flag to avoid:

Saving enums by ordinal, or never having used an enum for anything but constants.

They may ask next:
  • How would you look up a Plan from a string safely when the input might be invalid?
  • When would you give each enum constant its own method body?
Say it in 60 seconds
Medium Technical round Mid-level Practice question

6. You need to process a CSV file of a few gigabytes in a Java job. How do you read it without running out of memory?

What the interviewer is really testing:
Whether you stream data line by line with resources closed properly, instead of loading a whole file into a list.
Answer frame:

Stream it: BufferedReader or Files.lines, one line at a time, never readAllLines.

Close it: try-with-resources, because Files.lines holds the file open.

Write in batches: group records into chunks for the database, and report bad lines instead of failing the run.

Sample spoken answer:

“The trap is reading the whole file into a list, which works in testing and then dies on the real file. I read it as a stream of lines, with Files.lines or a BufferedReader, so only a little of the file is in memory at a time. Files.lines keeps the file open, so it goes inside try-with-resources. I parse each line, and instead of saving one row at a time I collect a few hundred records and write them as one batch, then clear the batch. Bad lines go to an error file with the line number rather than stopping a two-hour job halfway. On a real job I'd also use a proper CSV parser, because quoted fields with commas inside break a simple split.”

Code:
try (Stream<String> lines = Files.lines(path)) {
    lines.skip(1) // header
         .map(parser::parse)
         .forEach(batcher::add); // flushes every few hundred rows
}
batcher.flush();
Red flag to avoid:

Using readAllLines on a file of several gigabytes, or not closing the stream.

They may ask next:
  • How would you let the job restart from where it stopped if it fails halfway?
  • If the database writes turn out to be the slow part, what would you change?
Say it in 60 seconds

Collections and Streams 3 questions

Medium Coding round Mid-level Practice question

7. Sort a list of orders by status, then newest first, with orders that have no created date at the end. Write the comparator.

What the interviewer is really testing:
Whether you write sorting the way it is done in day-to-day code, with Comparator chaining, and think about nulls and enum order.
Answer frame:

Chain: Comparator.comparing for the first key, thenComparing for the next.

Direction and nulls: reverseOrder for newest first, wrapped in nullsLast.

Gotchas: enums sort by declaration order, and a null status would still throw.

Sample spoken answer:

“I'd build it with Comparator chaining rather than a hand-written compare method. First comparing on status. Status is an enum, so it sorts in the order the constants are declared, which is worth knowing because someone reordering the enum changes the sort. Then thenComparing on the created date, with reverseOrder so the newest comes first, wrapped in nullsLast so orders with no date drop to the bottom of their status group. If status itself could be null, I'd wrap that key too, otherwise comparing throws a NullPointerException. I like this style because each line reads like the requirement, and when the product owner asks for a third sort key it's one more thenComparing instead of rewriting nested if statements.”

Code:
List<Order> sorted = orders.stream()
    .sorted(Comparator.comparing(Order::status)
        .thenComparing(Order::createdAt,
            Comparator.nullsLast(Comparator.reverseOrder())))
    .toList();
Red flag to avoid:

Subtracting two values inside compare, which can overflow, or not noticing that nulls will crash the sort.

They may ask next:
  • How would you sort by status in a custom business order that isn't the enum's order?
  • Is List.sort stable, and why would that matter here?
Say it in 60 seconds
Medium Coding round Mid-level Practice question

8. Using streams, turn a list of orders into a map of customer id to total order amount. What happens if you use toMap and a customer has two orders?

What the interviewer is really testing:
Whether you know the collector traps you only learn by hitting them: duplicate keys and null values in toMap.
Answer frame:

Collector: toMap with a merge function, or groupingBy with a reducing downstream.

Duplicate keys: toMap without a merge function throws IllegalStateException.

Amounts: BigDecimal with add, not double, for money.

Sample spoken answer:

“I'd use toMap with three arguments: the customer id as the key, the amount as the value, and BigDecimal add as the merge function, so two orders for the same customer get summed. If you leave out the merge function, toMap throws an IllegalStateException the moment it meets a duplicate key. I've seen that pass in tests with one order per customer and then blow up on real data. The other trap is that toMap throws a NullPointerException if a value is null, so if an amount can be missing I filter those out first or map them to zero. groupingBy with a reducing collector gives the same result. I use BigDecimal because summing money in double drifts.”

Code:
Map<Long, BigDecimal> totals = orders.stream()
    .collect(Collectors.toMap(
        Order::customerId,
        Order::amount,
        BigDecimal::add));
Red flag to avoid:

Using the two-argument toMap and not knowing it fails on duplicate keys.

They may ask next:
  • How would you get the result sorted by customer id?
  • How would you change this to return the customer's largest order instead of the total?
Say it in 60 seconds
Medium Technical round Mid-level Practice question

9. Code that adds to a list crashes with UnsupportedOperationException, but only on one path. Why can that happen, and how do Arrays.asList, List.of and Collections.unmodifiableList differ?

What the interviewer is really testing:
Whether you know which list factories give fixed-size or read-only lists, since the List type hides the difference until runtime.
Answer frame:

Cause: the variable says List, but the object behind it doesn't support add, and that only shows at runtime.

The three: Arrays.asList is fixed-size but settable and backed by the array; List.of can't be changed at all and rejects nulls; unmodifiableList is a read-only view of a list that can still change underneath.

Habit: copy into a new ArrayList before changing a list you didn't create, and make clear what your own methods return.

Sample spoken answer:

“All of these are typed as List, so the compiler can't warn you. The crash comes when one path hands you a list that doesn't support add. In my case a method normally returned a new ArrayList, but its empty case returned List.of(), so callers that added to the result blew up only when nothing was found. The three differ like this. Arrays.asList is a fixed-size wrapper over the array: set works and writes through to the array, but add and remove throw. List.of can't be changed at all, and it throws a NullPointerException if you pass it a null. Collections.unmodifiableList is a read-only view, so the original list can still change behind it. My rule now is that if I need to change a list I didn't create, I copy it into a new ArrayList first.”

Code:
List<String> fixed = Arrays.asList("a", "b");
fixed.set(0, "z");   // fine, writes through to the array
fixed.add("c");      // UnsupportedOperationException

List<String> none = List.of();
none.add("x");       // UnsupportedOperationException

List<String> mine = new ArrayList<>(none); // safe to change
mine.add("x");
Red flag to avoid:

Assuming any List can be added to, or catching the exception instead of fixing who owns the list.

They may ask next:
  • What kind of list does Stream.toList() give you, compared with Collectors.toList()?
  • When would you return a read-only list from your own method, and when a fresh copy?
Say it in 60 seconds

Testing 3 questions

Medium Behavioral round Mid-level Practice question

10. Your ticket needs a change inside a very long method that has no tests. How do you make the change without breaking something?

What the interviewer is really testing:
Whether you protect existing behaviour before changing legacy code, and refactor in small, safe steps.
Answer frame:

Pin behaviour first: a few tests that capture what the method does today, even the odd parts.

Small steps: extract pieces with IDE refactorings, running the tests after each one.

Change and review: make the real change in the extracted piece, keep the refactor and the change easy to review.

Sample spoken answer:

“I had this with a pricing method of a few hundred lines that nobody wanted to touch. Before changing anything, I wrote tests that fed it real cases from production data and asserted whatever it returned today, even where the result looked odd, because other code might depend on that. Then I used the IDE's extract method refactoring to pull out the discount part, which was the bit my ticket touched, running the tests after each step. Once that piece was a small method with its own tests, the actual change was a few lines. I put the refactoring and the behaviour change in separate commits, so the reviewer could check each one. The odd behaviour I found, I raised as a question instead of quietly fixing it.”

Red flag to avoid:

Editing the method directly and relying on manual testing, or rewriting it from scratch inside a small ticket.

They may ask next:
  • How much refactoring is too much inside one ticket?
  • What do you do if the method is so tangled you can't even call it from a test?
Say it in 60 seconds
Medium Coding round Mid-level Practice question

11. How do you unit test a service class that calls a repository and an external API? Show me with JUnit and Mockito.

What the interviewer is really testing:
Whether you write real unit tests day to day: mocking the edges, testing behaviour, and not over-mocking.
Answer frame:

Isolate: mock the repository and client; test the class's own logic.

Arrange, act, assert: stub inputs, call the method, check the result.

Verify sparingly: only the interactions that matter, like the save.

Sample spoken answer:

“I use constructor injection, so the service takes the repository and the client as dependencies, which makes it easy to pass mocks. With the Mockito extension, I mark both as mocks and let InjectMocks build the service. In the test I stub the tax client to return a rate, call create, and assert on the total the service worked out. Then I verify the repository's save was called, because saving is part of the behaviour I care about. What I avoid is verifying every single call, because then the test breaks whenever someone refactors without changing behaviour. I also keep a few integration tests against a real database, because mocks can't tell you that a query is wrong.”

Code:
@ExtendWith(MockitoExtension.class)
class InvoiceServiceTest {
    @Mock InvoiceRepository repo;
    @Mock TaxClient taxClient;
    @InjectMocks InvoiceService service;

    @Test
    void addsTaxAndSaves() {
        when(taxClient.rateFor("NORTH")).thenReturn(new BigDecimal("0.20"));

        Invoice invoice = service.create("NORTH", new BigDecimal("100"));

        assertEquals(0, new BigDecimal("120").compareTo(invoice.total()));
        verify(repo).save(any(Invoice.class));
    }
}
Red flag to avoid:

Tests that mock the class under test itself, or assert nothing except that mocks were called.

They may ask next:
  • How would you test what happens when the tax client throws a timeout?
  • When would you use an argument captor instead of any()?
Say it in 60 seconds
Medium Technical round Mid-level Practice question

12. A test fails only when it runs close to midnight, and the code calls LocalDate.now(). How do you make that code testable?

What the interviewer is really testing:
Whether you know to inject time as a dependency, and have dealt with flaky tests caused by the real clock.
Answer frame:

Cause: the code and the test each read the real clock, and the date can change between them.

Fix: inject a java.time Clock and call now(clock).

In tests: Clock.fixed at an exact moment, including the edge cases like midnight.

Sample spoken answer:

“The problem is that the code reads the real clock, and so does the test, so near midnight they can land on different days. I've had exactly this with a trial-expiry check. The fix is to treat time as a dependency. The class takes a java.time Clock in its constructor, and every now call passes that clock. In production we wire in the system clock, in the test I pass Clock.fixed at a chosen instant and zone. That also lets me write the tests I actually care about, like one minute before midnight and one minute after, on the last day of the trial. It's a small change, and it removes a whole group of tests that fail now and then for no clear reason.”

Code:
class TrialService {
    private final Clock clock;
    TrialService(Clock clock) { this.clock = clock; }

    boolean isExpired(LocalDate endDate) {
        return LocalDate.now(clock).isAfter(endDate);
    }
}

// in the test
Clock fixed = Clock.fixed(Instant.parse("2026-03-01T23:59:00Z"), ZoneOffset.UTC);
assertFalse(new TrialService(fixed).isExpired(LocalDate.of(2026, 3, 1)));
Red flag to avoid:

Fixing it by adding a sleep, retrying the test, or marking it as ignored.

They may ask next:
  • What other things make Java tests flaky, apart from time?
  • How would you test code that uses random numbers?
Say it in 60 seconds

Errors and Logging 3 questions

Medium Technical round Mid-level Practice question

13. A third-party API you call fails now and then with timeouts or server errors. How did you add retries without making things worse?

What the interviewer is really testing:
Whether you know which failures are safe to retry and how to retry without hammering a struggling service or doing things twice.
Answer frame:

What to retry: timeouts and server errors, not client errors like a bad request.

How: a small number of attempts with backoff and some random jitter.

Safety: only retry calls that are safe to repeat, or send an idempotency key.

Sample spoken answer:

“I added retries to a shipping-label API that failed a few times a day. First I decided what to retry: timeouts and 5xx responses, because those might work on a second try, but never a 400, which will fail the same way every time. I capped it at three attempts with a growing wait between them plus a little randomness, so all our threads don't retry at the same moment and pile onto a service that's already struggling. The part people miss is whether the call is safe to repeat. Creating a label twice would have charged us twice, so we sent an idempotency key the provider supported. I also logged each retry with the attempt number, which later showed us when the provider was having a bad day.”

Red flag to avoid:

Retrying every exception in a tight loop with no limit, or retrying a payment call that isn't safe to repeat.

They may ask next:
  • How is a circuit breaker different from a retry, and when would you want one?
  • What happens to your service's threads while all these retries are waiting?
Say it in 60 seconds
Medium Behavioral round Mid-level Practice question

14. Tell me about a time the logs didn't help you debug a problem. What did you change about how you log after that?

What the interviewer is really testing:
Whether you learned to log for the person debugging at night: context, the right level, and full stack traces.
Answer frame:

The incident: what you needed to know and what the logs actually said.

The changes: request ids, the key business ids, full exceptions, sensible levels.

What you avoid now: logging secrets or personal data, and noisy logs nobody reads.

Sample spoken answer:

“We had failed payments one weekend and the log just said 'payment failed' about two hundred times, with no order id, no customer and no stack trace, because the code logged e.getMessage and dropped the exception. I couldn't tell which failures belonged to which request. Afterwards I made three changes in our service. We put a request id in the logging context at the start of each request, so every line from that request carries it. Error logs now pass the exception itself to the logger, so the full stack trace and cause are there. And each important log line has the ids someone would search for, like the order id. I also made sure we never log card numbers or tokens, and turned a few chatty info logs down to debug.”

Red flag to avoid:

Logging only e.getMessage, or saying you just add more println statements until you find it.

They may ask next:
  • How do you carry a request id across threads, for example into an async task?
  • How do you decide what goes at error level and what goes at warn?
Say it in 60 seconds
Medium Situational round Mid-level Practice question

15. While fixing a bug you find a method that catches Exception and does nothing. It isn't part of your ticket. What do you do?

What the interviewer is really testing:
Whether you balance code quality with scope: you don't ignore a hidden failure, but you don't rewrite things quietly either.
Answer frame:

Understand first: check the history and what that code is protecting against.

Low-risk step: at least log the exception, if that doesn't change behaviour.

Make it visible: raise a ticket or mention it in review rather than a silent rewrite.

Sample spoken answer:

“I wouldn't just leave it, because a swallowed exception is how you get data that's quietly wrong for months. But I also wouldn't rewrite it inside an unrelated fix. First I'd look at the history and the caller to understand why someone did it. Sometimes it was hiding a known noisy failure. If adding a warning log with the exception doesn't change behaviour, I'd do that in my change and call it out in the PR description, so the reviewer sees it. If the right fix means changing behaviour, like letting the error fail the request, I'd raise a separate ticket with what I found and talk to whoever owns that area, because that decision might affect callers I don't know about.”

Red flag to avoid:

Ignoring it because it's not your ticket, or silently changing its behaviour in an unrelated PR.

They may ask next:
  • What if the logs then show this exception thousands of times a day?
  • How would you convince a teammate who says it has always been like that?
Say it in 60 seconds

Concurrency at Work 2 questions

Hard Technical round Mid-level Practice question

16. A background task that refreshes a cache every few minutes stopped running days ago, and nothing was logged. It uses a ScheduledExecutorService. What happened?

What the interviewer is really testing:
Whether you know that a repeating task which throws is quietly stopped, and that an exception inside an executor task goes nowhere unless you handle it.
Answer frame:

Cause: if one run of scheduleAtFixedRate or scheduleWithFixedDelay throws, all later runs are suppressed.

Why it was silent: the exception is kept in the returned future, and nobody ever checks it.

Fix: catch and log inside the task body, and alert when the last successful refresh gets too old.

Sample spoken answer:

“With scheduleAtFixedRate or scheduleWithFixedDelay, if a single run throws an exception, the executor stops running that task. There are no more runs, and the exception sits inside the ScheduledFuture that nobody looks at, so nothing reaches the logs. That's exactly what we had. The cache refresh called another service that once sent back a malformed response, the parsing threw, and the cache stayed stale for days while everything looked healthy. The fix was to wrap the task body in a try-catch that logs the exception, so one bad run gets logged and the next run still happens. We also recorded the time of the last successful refresh and alerted when it got too old, because a job that stops quietly is worse than one that fails loudly.”

Code:
scheduler.scheduleWithFixedDelay(() -> {
    try {
        cache.refresh();
        lastSuccess = Instant.now();
    } catch (Exception e) {
        log.error("Cache refresh failed, will try again next run", e);
    }
}, 0, 5, TimeUnit.MINUTES);
Red flag to avoid:

Assuming the executor retries or logs failures by itself, or restarting the service without finding out why the task stopped.

They may ask next:
  • Would you catch Exception or Throwable inside the task, and why?
  • What's the difference between scheduleAtFixedRate and scheduleWithFixedDelay when one run takes longer than the period?
Say it in 60 seconds
Hard Technical round Mid-level Practice question

17. Two sign-up requests arrived at the same moment, both passed your 'does this email already exist' check, and you got two accounts. Would synchronized fix it? What did you do?

What the interviewer is really testing:
Whether you see that check-then-insert is a race, and that a lock inside one JVM can't fix it once the service runs on more than one instance.
Answer frame:

Why it happens: the check and the insert are two steps, and both requests can pass the check before either one inserts.

Why synchronized isn't enough: it only covers threads inside one JVM, and the service runs on several instances.

Fix: a unique constraint in the database, with the duplicate-key error turned into a clear response.

Sample spoken answer:

“The check and the insert are two separate steps, so two requests can both run the check, both see no account, and both insert. Synchronized on the method would close that gap inside one JVM, but we ran three instances behind a load balancer, so requests landing on different instances never share the lock. It would also make every sign-up wait in one line. What actually fixed it was a unique constraint on the email column, with emails stored in lowercase, so the database itself refuses the second insert. In the code I kept the friendly check for the normal case, and also caught the duplicate-key exception from the insert and returned the same 'email already registered' response. Before the constraint could go on, I had to clean up the duplicates that were already there.”

Red flag to avoid:

Saying synchronized fixes it, without noticing the service runs on more than one instance.

They may ask next:
  • How would you handle a rule like this that can't be written as a unique constraint?
  • What could go wrong when merging the duplicate accounts that already existed?
Say it in 60 seconds

Tools and Build 2 questions

Easy Technical round Mid-level Practice question

18. How do you use the debugger in your IDE day to day? Walk me through how you'd debug a bug you can reproduce locally.

What the interviewer is really testing:
Whether you debug efficiently with the tools, not just by adding print statements and redeploying.
Answer frame:

Reproduce: ideally as a failing test, so the loop is fast.

Targeted breakpoints: conditional breakpoints, exception breakpoints, evaluate expression.

Beyond local: remote debugging a test environment, and logs where a debugger can't go.

Sample spoken answer:

“First I try to reproduce the bug as a failing unit test, because then the loop is quick. Then I put a breakpoint just before where I think things go wrong and step through. The features I use most are conditional breakpoints, so it only stops for the one order id that fails instead of on every loop pass, and exception breakpoints, which stop the moment a particular exception is thrown, even if something catches it later. While paused I use evaluate expression to try a fix or check a value without restarting. For a bug that only shows up in a test environment, I've attached the debugger remotely over the debug port, but never on production, where logs and dumps are the tools.”

Red flag to avoid:

Saying you only debug with print statements, or have never set a breakpoint condition.

They may ask next:
  • What would you do if the bug disappears when you run it under the debugger?
  • Why is attaching a debugger to a production service a bad idea?
Say it in 60 seconds
Hard Technical round Mid-level Practice question

19. After a library upgrade, the build compiles but the app fails at runtime with NoSuchMethodError. What is going on, and how do you fix it?

What the interviewer is really testing:
Whether you understand compile-time versus runtime classpaths and have untangled a dependency conflict in Maven or Gradle.
Answer frame:

Cause: code compiled against one version of a class, but a different version is on the runtime classpath.

Find it: the dependency tree, to see which versions were pulled in and which one won.

Fix: pin one version centrally or exclude the stray one, then test the other library that wanted it.

Sample spoken answer:

“NoSuchMethodError means the code was compiled against a version of a class that has the method, but the version loaded at runtime doesn't. Usually that's a dependency conflict: two libraries need different versions of the same thing, and the build picked one. In Maven, the version closest to your project in the tree wins, not the newest, so an old transitive version can quietly win. When I hit it, I ran the dependency tree, found two versions of a JSON library, and saw the older one had won through a reporting library. I pinned the newer version in dependencyManagement and ran that library's tests, since it now got a version it wasn't built for. Gradle picks the highest version by default, so the same bug looks different there.”

Red flag to avoid:

Deleting the local repository and rebuilding until it goes away, without finding which versions clash.

They may ask next:
  • How is NoSuchMethodError different from ClassNotFoundException?
  • How would you stop this kind of conflict reaching production again?
Say it in 60 seconds

Owning Features 3 questions

Medium Behavioral round Mid-level Practice question

20. Walk me through a feature you owned end to end in a Java service, from the ticket to running in production.

What the interviewer is really testing:
Whether you really owned the work at this level: clarifying requirements, designing within the service, testing, releasing and checking it afterwards.
Answer frame:

Before code: questions you asked and edge cases you found in the ticket.

Build and test: how you split the work and what you tested at each level.

Release and after: how it went out, what you watched, what you'd do differently.

Sample spoken answer:

“The one I'd pick is a bulk refund feature for support staff. Before coding I went back to the product owner with questions the ticket didn't answer: what happens if one refund in the batch fails, and can the same order be refunded twice. We agreed each refund stands alone, and the whole thing had to be safe to retry, so I used the order id as an idempotency key. I built it as a new endpoint plus a small job that processes the batch, with unit tests for the rules and an integration test against a test database. It shipped behind a feature flag to two support staff first. The next morning I checked the logs and found one refund stuck on a provider timeout, which led to adding a retry. Then we opened it to everyone.”

Red flag to avoid:

Describing only the coding part, as if the work ended when the pull request was merged.

They may ask next:
  • What would you do differently if you built it again?
  • How did you know the feature was actually working after the release?
Say it in 60 seconds
Easy Behavioral round Mid-level Practice question

21. Tell me about code review feedback on your Java code that changed how you write it.

What the interviewer is really testing:
Whether you take feedback well and can name a concrete habit it changed, not a vague 'I learned a lot'.
Answer frame:

The comment: what the reviewer said and what your code did.

Your reaction: whether you agreed straight away or asked why.

The habit: what you now do differently, with a small example.

Sample spoken answer:

“Early on I had a method that returned null when a customer had no orders. A senior reviewer asked me to return an empty list instead, and pointed out that three callers already had null checks and a fourth didn't, which was a crash waiting to happen. At first I thought it was a small style point, but when I looked at the callers I saw it was right. Now my habit is that methods returning collections never return null, and for a single value that may be missing I return Optional so the caller has to deal with it. Another comment from the same reviewer was to name tests after the behaviour, like refunds are rejected after thirty days, and I've kept that as well.”

Red flag to avoid:

Saying you've never had useful feedback, or describing review comments as nitpicking.

They may ask next:
  • Tell me about a review comment you disagreed with. What did you do?
  • What do you look for now when you review someone else's code?
Say it in 60 seconds
Medium Behavioral round Mid-level Practice question

22. Tell me about a Java task you estimated badly. What did you miss, and how do you estimate differently now?

What the interviewer is really testing:
Whether you own a missed estimate honestly, raised it early, and changed how you break down work.
Answer frame:

The task: what you estimated and how far off it was.

What you missed: the hidden work, like legacy code, tests, data or reviews.

Now: break it down, spike unknowns, and say early when it's slipping.

Sample spoken answer:

“I estimated two days to add a new export format to our reporting service. It took about two weeks. I'd looked only at the code I'd write, and missed that the existing export code had no tests, so I had to add some before I could safely change it. On top of that, the real data had encodings and very large files the test data didn't. What I did right was tell my lead on day three, not day ten, with what I'd found and a new estimate. What I do differently now is break the task into steps before giving a number, include testing and review time, and when there's a real unknown, ask for half a day to look first and give the estimate after.”

Red flag to avoid:

Blaming the product owner or the codebase entirely, or saying you have never missed an estimate.

They may ask next:
  • How do you respond when someone pushes you for a smaller estimate?
  • What do you do when you realise halfway through that you'll miss the date?
Say it in 60 seconds
Were you asked something else? Share it A person checks every question before it goes on the site. No name is shown.
For the call itself

You practiced these. On the real call, ClapAssist helps with the rest.

ClapAssist is an AI interview assistant for Mac and Windows. It listens to the interview on your computer and shows you what to say, in short lines you can read while you talk. Your live interview audio and screen are never stored. Your resume and notes are saved to your account so the app fills them in on any computer. It stays out of screen share on every plan, including Free; only you can see it.

Download with 10 free minutes
Mac and Windows · Stays out of screen share · No card