Features You Shipped • Debugging • JPA at Work • Testing • Config and Deploys • 2026

Spring Boot Interview Questions for 3 Years Experience (2 to 4 Years)

Spring Boot interviews for three years of experience (two to four years) ask less about what an annotation means and more about how it behaved in your project: a scheduled job that ran twice, a cache that served stale data, a migration that broke startup, a test that passed on one database and failed on another. It is written for Spring Boot developers with about two to four years of real work, the stage where you own a feature end to end but someone else still designs the service. 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.

Owning Features 4 questions

Medium Behavioral round Mid-level Practice question

1. Walk me through a feature you built end to end in a Spring Boot service, from the ticket to running in production.

What the interviewer is really testing:
Whether you really owned the whole path, including tests, rollout and checking it after release, or only wrote the controller and handed it off.
Answer frame:

The ask: what the ticket said and the questions you asked before coding.

The build: the layers you touched, the data change and the tests you wrote.

The release: how it shipped, what you watched afterwards and what you'd do differently.

Sample spoken answer:

“At my last company I built a CSV export of orders for our support team. Before coding I asked how big an export could get, and the answer was up to a few hundred thousand rows, so loading everything into a list was out. I added an endpoint that streamed rows from a paged repository query straight into the response, with the filters passed as a small request object. I wrote a unit test for the row formatting, a web layer test for the filters and error cases, and one integration test against a real database for the query. It went out behind a config flag, first for two support users. I watched the logs and the query time for a day, found one filter missing an index, added it with a migration, then switched it on for everyone. Next time I'd ask about the index during planning, not after release.”

Red flag to avoid:

A story that ends at 'I raised the PR' with nothing about tests, rollout or checking it in production.

They may ask next:
  • What would you have done if the export needed to include data from another service?
  • How did you decide the feature was ready to switch on for everyone?
Say it in 60 seconds
Easy Behavioral round Mid-level Practice question

2. Tell me about code review feedback on your Spring Boot code that changed how you write it.

What the interviewer is really testing:
Whether you learn from review and can name a concrete habit that changed, rather than treating review as a gate to get past.
Answer frame:

The code: what you wrote and why it seemed fine at the time.

The comment: what the reviewer pointed out and the bug it would have caused.

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

Sample spoken answer:

“In my first year I wrote a service method that looked up a customer's loyalty points. If the customer wasn't found, I caught the exception, logged it and returned null. Around a call to another service I caught a general Exception and returned an empty list. A senior reviewer pointed out two things. Every caller now had to remember the null check, and one batch job didn't, so it would have crashed far away from the real cause. The empty list was worse: a failed call looked exactly like a customer with no orders. I changed it to throw a specific not-found exception, let our controller advice turn that into a 404 in one place, and let the remote failure travel up as an error instead of fake data. Since then I only catch an exception when I can really handle it, and I never turn a failure into an empty result.”

Red flag to avoid:

Saying you can't remember any feedback, or naming only formatting nits.

They may ask next:
  • How do you decide which exceptions become a 4xx and which become a 5xx?
  • Tell me about a review comment you disagreed with. What happened?
Say it in 60 seconds
Medium Behavioral round Mid-level Practice question

3. Tell me about a Spring Boot task you estimated badly. What did you miss, and how do you estimate now?

What the interviewer is really testing:
Whether you can own a miss honestly and name what you now check before giving a number.
Answer frame:

The estimate: the task and the number you gave.

The miss: the hidden work you didn't see and when you found it.

The change: what you raised once you knew, and what you check before estimating now.

Sample spoken answer:

“I was asked to add a 'preferred language' field to our customer API and said one day. The Java part really was an hour. What I missed was everything around it: the column needed a migration on a big table, two other services read our customer response and one of them failed on unknown fields, and the mobile app cached the old shape. It took most of a week. As soon as I found the second consumer on day two, I told my lead and gave a new estimate instead of going quiet. Now, before I give a number for any API change, I check who calls the endpoint, whether the data change needs a migration on a large table, and what tests exist around it. I also give a range and say what it depends on.”

Red flag to avoid:

Blaming the requirements or other teams without saying what you would check next time.

They may ask next:
  • How do you find out who else calls an endpoint you own?
  • What do you do when your lead pushes for a smaller number than you think is honest?
Say it in 60 seconds
Medium Situational round Mid-level Practice question

4. Your ticket is a small change to an old Spring Boot endpoint that has no tests at all, and the deadline is Friday. Do you write tests first, or just make the change?

What the interviewer is really testing:
Whether you protect the behaviour you are about to change without turning a small ticket into a rewrite, and keep your lead informed.
Answer frame:

Pin what exists: a few tests around the behaviour you are touching, written before the change.

Stay small: no clean-up of the whole class inside this ticket.

Be open: time-box it, tell your lead, and raise the bigger gap as its own ticket.

Sample spoken answer:

“I'd write a few tests first, but only around the part I'm changing. With no tests, I can't tell whether my change breaks something the endpoint does today that nobody wrote down. So I'd spend maybe an hour or two on a web layer test that calls the endpoint the way clients do and checks the current response, including one error case. Those tests pin today's behaviour. Then I make my change and add a test for the new rule. What I wouldn't do is refactor the whole controller inside this ticket, because that's where small changes blow up. If writing even those first tests turns out to be hard, say the logic is tangled with static calls, I tell my lead early and we decide together. The bigger test gap goes into its own ticket so it's visible, not silently fixed or ignored.”

Red flag to avoid:

Either skipping tests because the change is small, or rewriting the whole endpoint without telling anyone.

They may ask next:
  • What would you do if one of those first tests showed the current behaviour is actually a bug?
  • How would you test logic that calls a static method or the current time directly?
Say it in 60 seconds

Testing 3 questions

Medium Behavioral round Mid-level Practice question

5. Before you raise a pull request for a new endpoint, how do you test your own work?

What the interviewer is really testing:
Whether you have a real testing habit across layers, and look at the SQL and logs yourself instead of leaving it to review and QA.
Answer frame:

Automated tests: unit tests for rules, a web layer test for the edges, one integration test for the query.

Run it: hit it locally with real requests, including bad input.

Look underneath: the SQL it runs, the logs it writes and your own diff.

Sample spoken answer:

“I start with unit tests for the service logic, because that's where most of the rules live and those tests are fast. Then a web layer test for the controller: the happy path, a validation failure returning 400, and a missing record returning 404. If I wrote a new query, I add one integration test against a real database in a container, since that's where surprises hide. Then I run the app locally and call the endpoint with curl, including a wrong type and an empty body. I switch on SQL logging while I do that, because it's the easiest way to catch an extra query per row. Last, I read my own diff top to bottom as if I were the reviewer. That usually catches a leftover debug log or a missing null case before anyone else sees it.”

Red flag to avoid:

Saying QA will test it, or that you only check it compiles and the happy path works.

They may ask next:
  • How do you see the SQL Hibernate runs without leaving it on in production?
  • What do you do when a test you wrote is flaky?
Say it in 60 seconds
Medium Technical round Mid-level Practice question

6. Your repository tests all passed on an in-memory H2 database, but the same query failed on PostgreSQL after deploy. What happened, and what did you change in your tests?

What the interviewer is really testing:
Whether you've learned that a stand-in database hides real differences, and know how to test against the real engine.
Answer frame:

Cause: H2 is a different engine, with its own SQL dialect, types and quirks.

Fix: run data tests against the same database you use in production, in a container.

Balance: keep most tests fast unit tests and use the container for query and mapping tests.

Sample spoken answer:

“We had a native query that used a PostgreSQL feature for JSON columns. H2 accepted a similar-looking version in its compatibility mode, so the test was green, but on the real database the syntax and the column type behaved differently and it threw an error on the first call. The lesson was that H2 is a different database pretending to be yours. We moved our repository tests to a real PostgreSQL started in a container with Testcontainers, the same major version as production, and let Flyway build the schema so the tests also checked our migrations. The tests got a bit slower, so we kept business rules in plain unit tests and used the container only for queries, mappings and migrations. We haven't had that class of bug since.”

Red flag to avoid:

Concluding that native queries are bad, instead of seeing that the test database was the problem.

They may ask next:
  • How do you keep container-based tests fast when there are many test classes?
  • What do you do with test data so tests don't depend on each other?
Say it in 60 seconds
Medium Coding round Mid-level Practice question

7. Show me how you'd write a test proving your create endpoint returns 400 with a clear error when the email is missing, and never calls the service.

What the interviewer is really testing:
Whether you test the unhappy path of your own endpoints, checking both the status and the error body, with a web layer test that stays fast.
Answer frame:

Setup: a web layer test for just this controller, with the service replaced by a mock.

Act: post a JSON body without the email.

Assert: status 400, the error names the field, and the service was never called.

Sample spoken answer:

“I'd use a web layer test for just this controller, so Spring starts MVC, validation and my exception handler but not the database. The service is a mock. Then I post a body with only a name and check three things: the status is 400, the error body names the email field, which is the format our exception handler returns, and the service mock was never touched. That last check matters, because it proves validation stopped the request before any business code ran. I usually write one of these per required field or rule that matters, plus one for a malformed JSON body, since that hits a different exception. They run in a second or two, so they don't slow the build.”

Code:
@WebMvcTest(CustomerController.class)
class CustomerControllerTest {

    @Autowired MockMvc mvc;
    @MockitoBean CustomerService service; // @MockBean on older versions

    @Test
    void rejectsMissingEmail() throws Exception {
        mvc.perform(post("/customers")
                .contentType(MediaType.APPLICATION_JSON)
                .content("{\"name\":\"Sam\"}"))
            .andExpect(status().isBadRequest())
            .andExpect(jsonPath("$.errors[0].field").value("email"));

        verifyNoInteractions(service);
    }
}
Red flag to avoid:

Only asserting the status code, or starting the whole application and a database for a validation test.

They may ask next:
  • What does a malformed JSON body throw, and how does your handler turn it into a response?
  • When would you test this with the full application context instead?
Say it in 60 seconds

Web Layer 3 questions

Medium Technical round Mid-level Practice question

8. An endpoint that returns an Order with its OrderItems crashes with an infinite recursion error while writing JSON. What's going on, and how did you fix it?

What the interviewer is really testing:
Whether you understand how Jackson walks a two-way JPA relation, and prefer a response object over patching entities with annotations.
Answer frame:

Cause: Order has items, each item points back to its order, and Jackson follows both sides forever.

Quick patch: JSON annotations that skip one side of the relation.

Real fix: return a response object that holds only what the client needs.

Sample spoken answer:

“This is a two-way relation. The Order has a list of items and each OrderItem has a field pointing back to its Order. Jackson serializes the order, then each item, then the item's order, then its items again, until it overflows the stack. You can patch it with JSON annotations, like marking the back reference so Jackson skips it, or ignoring that field. But in my project the real fix was to stop returning entities. I added an OrderResponse record with a list of item records that carry only the product, quantity and price. That also stopped a second problem we had: serializing the entity was triggering lazy loads of relations nobody needed. Now the JSON shape is something we choose on purpose, and adding a column to the table doesn't silently change the API.”

Red flag to avoid:

Fixing it by making the relation one-way in the database model just to make the JSON work.

They may ask next:
  • Where do you do the mapping from entity to response, and why there?
  • What else can go wrong when a lazy relation is serialized after the transaction has ended?
Say it in 60 seconds
Easy Technical round Mid-level Practice question

9. Users uploading a larger PDF get an error before your controller method even runs. What's stopping it, and how did you handle it?

What the interviewer is really testing:
Whether you know upload limits sit in front of your code, in Spring and often in a proxy, and give users a clear error.
Answer frame:

Spring limit: the multipart max file size and max request size properties.

Proxy limit: a gateway or reverse proxy can reject large bodies first.

Clear error: catch the size exception and return a helpful message.

Sample spoken answer:

“Spring Boot has upload limits set by the spring.servlet.multipart properties, one for a single file and one for the whole request. The defaults are small, so a normal scanned PDF can go over. When that happens, the request fails while parsing, before the controller runs, so my code never saw it. I raised the limits to what the business actually needed, not to something huge, and added a handler in our controller advice for the max upload size exception, so users got a clear message saying the file limit instead of a generic error page. In another project the same symptom came from the reverse proxy in front of the app, which had its own body size limit and answered with 413 without the request ever reaching Spring. So I check both.”

Red flag to avoid:

Setting the limit to unlimited without thinking about memory, disk or abuse.

They may ask next:
  • How would you handle files much larger than you'd want to hold in memory?
  • How do you check that the uploaded file really is a PDF?
Say it in 60 seconds
Hard Coding round Mid-level Practice question

10. A booking request has a start date and an end date, and the end must be after the start. How would you validate that with Bean Validation instead of an if in the controller?

What the interviewer is really testing:
Whether you can write a custom class-level constraint, since the rule involves two fields and a field-level annotation can't see both.
Answer frame:

Class-level: the rule needs two fields, so the annotation goes on the class.

Validator: implement ConstraintValidator and leave null checks to @NotNull.

Error: attach the message to the end date field so the client can show it there.

Sample spoken answer:

“Since the rule compares two fields, I'd make a custom constraint that goes on the whole class, not on one field. The annotation points to a validator class, and the validator gets the whole request object. It returns true when either date is null, because @NotNull on the fields already reports that, and otherwise checks the end is after the start. Then I put the annotation on the request record, and the @Valid on the controller parameter triggers it like any other rule, so our existing exception handler turns it into a 400. One thing I'd add in the real version: a class-level error has no field name by default, so inside the validator I'd build the violation with the property node set to end date. That way the client can show the message under the right input.”

Code:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = DateRangeValidator.class)
public @interface ValidDateRange {
    String message() default "end date must be after start date";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

public class DateRangeValidator implements ConstraintValidator<ValidDateRange, BookingRequest> {
    @Override
    public boolean isValid(BookingRequest r, ConstraintValidatorContext ctx) {
        if (r == null || r.start() == null || r.end() == null) return true;
        return r.end().isAfter(r.start());
    }
}

@ValidDateRange
public record BookingRequest(@NotNull LocalDate start, @NotNull LocalDate end) {}
Red flag to avoid:

Putting the annotation on one field and trying to read the other field from there.

They may ask next:
  • How would you run this rule only on create and not on update?
  • Could the validator call a repository, for example to check the room is free? Should it?
Say it in 60 seconds

Data and JPA 4 questions

Medium Technical round Mid-level Practice question

11. Users say a paged list endpoint sometimes shows the same record on page 1 and page 2, and some records never appear. The query sorts by created date. What's wrong?

What the interviewer is really testing:
Whether you know that sorting on a non-unique column leaves the order of ties undefined, which breaks offset paging.
Answer frame:

Cause: many rows share a created date, and the database may return ties in any order on each query.

Fix: add a unique tie-breaker, like the id, to the sort.

Check: a test with several rows on the same timestamp across a page boundary.

Sample spoken answer:

“The sort isn't deterministic. Lots of rows were created in the same second, or even the same millisecond from a batch import, and the database only promises order on the columns you sort by. For ties it can return them in any order, and it can change between the query for page 1 and the query for page 2. With limit and offset, that means a tied row can show up on both pages while another one is skipped. The fix was to always add the id as a second sort key, so every row has one fixed position. In Spring Data I built the Pageable with a sort on created date descending and then id descending. I added a test that inserts ten rows with the same timestamp and checks that pages one and two never overlap.”

Code:
Sort sort = Sort.by(Sort.Direction.DESC, "createdAt")
        .and(Sort.by(Sort.Direction.DESC, "id"));
Pageable page = PageRequest.of(pageNumber, 20, sort);
Page<Order> result = orderRepository.findByCustomerId(customerId, page);
Red flag to avoid:

Blaming caching or the frontend without seeing that the sort itself allows ties.

They may ask next:
  • What else can make offset paging show duplicates when new rows keep arriving?
  • When would you switch to paging by 'rows after this id' instead of by offset?
Say it in 60 seconds
Medium Technical round Mid-level Practice question

12. You changed a Flyway migration file that had already run on staging, and now the app won't start there. Why, and what's the right fix?

What the interviewer is really testing:
Whether you know migrations are an append-only history checked by checksum, and fix forward instead of editing the past.
Answer frame:

Cause: Flyway stores a checksum for every applied migration and refuses to start when a file no longer matches.

Right fix: put the old file back and add a new migration with the change.

Repair: only when the edit changes nothing in the database, like a comment.

Sample spoken answer:

“Flyway keeps a history table with a checksum of every migration it has run. On startup it validates the files against that table, and because I'd edited one that staging had already applied, the checksum didn't match and it stopped the app. That's a feature, not a bug: it's telling me staging and my file now describe different schemas. The right fix was to restore the original file exactly and write a new migration with the next version number that makes my change. That way every environment walks the same steps in the same order. There is a repair command that updates the stored checksums, but I'd only use it when the edit really changes nothing in the database, like fixing a comment. Since then I treat any migration that has left my laptop as read-only.”

Red flag to avoid:

Suggesting you clean the staging database or turn off validation so the app starts.

They may ask next:
  • How would you add a NOT NULL column to a large table that's in use?
  • Two developers both create migration version 12 on different branches. What happens?
Say it in 60 seconds
Medium Technical round Mid-level Practice question

13. Two support agents edit the same customer at the same time and the second save silently wipes out the first one's change. How did you stop that?

What the interviewer is really testing:
Whether you know optimistic locking with a version column and handle the conflict as a clear response to the client.
Answer frame:

Problem: last write wins, because each save overwrites the whole row.

Version column: @Version makes the update check the version it read, and fail if someone changed it.

Client side: send the version with the edit and answer a conflict with 409.

Sample spoken answer:

“It was a classic lost update. Both agents loaded version one of the customer, both edited, and the second save overwrote everything the first had changed. I added a version field to the entity with @Version. Hibernate then adds the version to the update's where clause and bumps it, so if someone else saved in between, the update matches no rows and it throws an optimistic locking exception. The part that's easy to miss is the web layer. Our API loaded the entity fresh on every request, so the version always looked current. I added the version to the response and the update request, and checked it against the loaded entity before saving. Our exception handler turns the conflict into a 409, and the screen tells the agent someone else changed this customer and shows the latest data.”

Code:
@Entity
public class Customer {
    @Id @GeneratedValue
    private Long id;

    @Version
    private Long version;

    private String phone;
    // getters and setters
}
Red flag to avoid:

Suggesting synchronized in the service, which does nothing across two instances or two separate requests.

They may ask next:
  • When would you use a pessimistic lock instead?
  • How would you retry automatically, and when should you never do that?
Say it in 60 seconds
Medium Technical round Mid-level Practice question

14. A teammate put Lombok's @Data on every JPA entity. What problems has that caused in projects you've worked on?

What the interviewer is really testing:
Whether you know how generated equals, hashCode and toString clash with JPA identity and lazy loading.
Answer frame:

equals and hashCode: built from all fields, including an id that is null until save.

toString: walks lazy relations, causing extra queries, lazy errors or endless loops.

Instead: getters and setters, a careful equals, and relations left out of toString.

Sample spoken answer:

“@Data generates equals, hashCode and toString over every field, and all three bite with entities. We had a new entity added to a set, then saved. Saving gave it an id, the hashCode changed, and the set couldn't find it any more, so a remove silently did nothing. toString was the other one. A debug log printed an order, toString touched the lazy items list, and outside a transaction that threw a lazy initialization error. With a two-way relation it can even loop until the stack overflows. We switched entities to plain getters and setters, excluded relations from toString, and wrote equals by hand based on the id with a fixed hashCode, which is the usual safe pattern for entities. For request and response objects we just used records.”

Red flag to avoid:

Saying Lombok only saves typing and has no effect on how Hibernate behaves.

They may ask next:
  • Why does a fixed hashCode per class work for entities, even though it looks odd?
  • Where is @Data perfectly fine to use?
Say it in 60 seconds

Integrations 2 questions

Hard Technical round Mid-level Practice question

15. Your service calls another team's REST API. One morning that API gets very slow, and soon your own service stops answering any request. What happened?

What the interviewer is really testing:
Whether you know that a remote call without timeouts can tie up every request thread, and that you set timeouts and limits on purpose.
Answer frame:

Cause: each request thread waits on the slow call, and with no read timeout they all end up waiting.

Fix: explicit connect and read timeouts on the HTTP client, sized from what the caller can accept.

Protect: limit retries, and fail fast or fall back while the other side is down.

Sample spoken answer:

“Every incoming request to our service made a call to theirs on the same request thread. Their API didn't fail, it just got slow, and our HTTP client had no read timeout set, so each thread sat waiting. Once all the server's request threads were stuck, even endpoints that never called that API stopped responding. Depending on the client and version, the default can be no timeout at all, so I don't trust defaults. The fix was setting a connect timeout of about a second and a read timeout based on how long our caller could actually wait. We also removed a retry loop that tripled the load on a service that was already struggling, and added a small fallback for the one screen that could live without that data. After that, when their API slowed down, only the feature that needed it degraded.”

Red flag to avoid:

Suggesting more threads or a bigger server, without mentioning timeouts.

They may ask next:
  • How would you decide the right read timeout for a call like this?
  • What is a circuit breaker, and when would it help here?
Say it in 60 seconds
Medium Situational round Mid-level Practice question

16. Your feature needs data that lives in a table owned by another team, in the same database. Adding a repository for it would take ten minutes. Would you?

What the interviewer is really testing:
Whether you see the hidden coupling in reading another team's tables, and can talk to the owners instead of taking the shortcut quietly.
Answer frame:

The risk: their table becomes a hidden contract they don't know they have.

Talk first: ask the owners for an API, an event or a view meant for you.

If you must: read-only, agreed with them, written down and tracked.

Sample spoken answer:

“Probably not, at least not without asking. The repository is ten minutes, but it quietly turns their table into an API they don't know they have. The next time they rename a column or split the table, my feature breaks in production and neither side saw it coming. So I'd message their team first, explain what I need, and ask if there's an endpoint, an event or a view they're happy to support. Often there's already an endpoint and I just didn't know. If the deadline really can't wait, I'd agree a read-only query with them, ideally against a view they own, note it in the code and in a ticket, and make sure their team knows that the view has a consumer. The shortcut is fine when it's visible. It's the hidden version that hurts.”

Red flag to avoid:

Adding the repository without telling anyone because it's in the same database anyway.

They may ask next:
  • What would you do if the other team says they don't have time to build an endpoint?
  • How would you find out today whether some other service already reads your tables?
Say it in 60 seconds

Async and Scheduling 2 questions

Medium Technical round Mid-level Practice question

17. A nightly @Scheduled job that sends reminder emails worked fine, until the service was scaled to two instances and customers got every email twice. Why, and how did you fix it?

What the interviewer is really testing:
Whether you understand that @Scheduled runs inside each instance, and know the options for making a job run once across a cluster.
Answer frame:

Cause: each instance has its own scheduler, so each one runs the job.

Fix options: a shared lock in the database, or run the job in one place only.

Safety net: make the job idempotent so a second run does no harm.

Sample spoken answer:

“@Scheduled knows nothing about other instances. Each copy of the app starts its own scheduler, so with two instances the job ran twice at the same minute and both read the same list of customers. We fixed it in two layers. First, we added a lock in the shared database using a small lock library, so whichever instance grabs the lock runs the job and the other skips it. Second, we made the job safe to repeat: each reminder row now gets marked as sent inside the same transaction that queues the email, and the job only picks rows not yet marked. That second part matters because locks can expire, deploys can overlap, and someone might rerun the job by hand. I also added a log line with the instance name, so it's obvious who ran it.”

Red flag to avoid:

Suggesting a random delay on each instance, or turning scheduling off on one instance by hand.

They may ask next:
  • What happens to the lock if the instance holding it crashes halfway through?
  • When would you move a job like this out of the web service entirely?
Say it in 60 seconds
Medium Technical round Mid-level Practice question

18. You added @Async to a method that sends a welcome email, but the sign-up request still waits for the email to finish. What would you check?

What the interviewer is really testing:
Whether you know @Async works through a proxy and needs to be enabled, and what happens to errors and thread pools once it does work.
Answer frame:

Enabled: is async support switched on with @EnableAsync.

Proxy: is the method called from another bean, not from inside its own class.

After it works: errors from void methods only reach a handler, and the pool needs sensible limits.

Sample spoken answer:

“First I'd check that async support is switched on with @EnableAsync on a configuration class, because without it the annotation does nothing. In my case that was fine. The real issue was that the sign-up method called sendWelcomeEmail on this, inside the same service. @Async works through a proxy, so a call from inside the class skips it and runs on the same thread. I moved the email method into its own bean and injected it. Once it ran on a separate thread, two more things came up. An exception in a void async method no longer reaches the caller, it only goes to an uncaught exception handler, so I logged failures there properly. And I set the task pool's core size and queue capacity through the spring.task.execution properties, so a burst of sign-ups couldn't pile up without limit.”

Red flag to avoid:

Assuming @Async works on any method call, including calls to a method in the same class.

They may ask next:
  • How would you return a result from an @Async method?
  • Why can logging context like a request id go missing on the async thread?
Say it in 60 seconds

Debugging 3 questions

Medium Technical round Mid-level Practice question

19. Many requests run at once and their log lines are mixed together. How did you make it possible to follow one request through the logs?

What the interviewer is really testing:
Whether you've set up request correlation yourself and know where it breaks, such as on other threads.
Answer frame:

Id per request: take one from a header or create it, in a filter.

MDC: put it in the logging context, print it in the log pattern, clear it at the end.

Gaps: copy it to async threads and pass it to downstream calls.

Sample spoken answer:

“I added a servlet filter that runs once per request. It reads a request id header if the caller sent one, otherwise it creates a random id, puts it in the logging MDC and returns it in the response header. The log pattern prints that value on every line, so I can search one id and see the whole request. The filter clears it in a finally block, because request threads are reused and a leftover id would tag the next request's logs. The gap we hit was async work: MDC is per thread, so logs from our @Async email sender had no id until we added a task decorator that copies the context across. We also send the id on outgoing calls, so another team can find the same request in their logs. Newer setups can get this from tracing, but the idea is the same.”

Code:
@Component
public class RequestIdFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res,
                                    FilterChain chain) throws ServletException, IOException {
        String id = Optional.ofNullable(req.getHeader("X-Request-Id"))
                .orElse(UUID.randomUUID().toString());
        MDC.put("requestId", id);
        res.setHeader("X-Request-Id", id);
        try {
            chain.doFilter(req, res);
        } finally {
            MDC.remove("requestId");
        }
    }
}
Red flag to avoid:

Putting the id in the MDC without ever clearing it on a pooled thread.

They may ask next:
  • Should you trust a request id sent by the caller? What could go wrong?
  • What else would you put in the MDC, and what would you never put there?
Say it in 60 seconds
Medium Technical round Mid-level Practice question

20. A teammate added their own ObjectMapper bean to switch on one Jackson setting. Afterwards dates in every response changed format, and spring.jackson properties stopped working. Why?

What the interviewer is really testing:
Whether you know Boot's auto-configured beans back off when you define your own, and prefer customizing Boot's bean over replacing it.
Answer frame:

Back-off: Boot only creates its ObjectMapper when none exists, so the new bean replaced it.

Lost settings: Boot's defaults and every spring.jackson property went with it.

Fix: set the option with a property or a customizer bean, and keep Boot's mapper.

Sample spoken answer:

“Boot's Jackson auto-configuration only creates an ObjectMapper if the app doesn't already have one. The teammate's bean was a plain new ObjectMapper with one feature turned on, so Boot backed off completely. That mapper never saw Boot's own defaults or our spring.jackson settings in application.yml, which is why dates came out as arrays of numbers instead of ISO strings and the properties seemed to be ignored. The condition report in the startup debug output shows the Jackson configuration was skipped because a bean already existed, which confirmed it. The fix was to delete the bean and set that one feature with a spring.jackson property. For things a property can't express, you add a customizer bean that Boot applies to its own mapper, so you add to its setup instead of replacing it. I now treat defining any bean Boot normally creates as a warning sign.”

Red flag to avoid:

Fixing each date field with its own format annotation without asking why the global settings stopped working.

They may ask next:
  • How do you find out why an auto-configuration did or didn't apply?
  • Which other Boot beans have you seen replaced by accident?
Say it in 60 seconds
Medium Technical round Mid-level Practice question

21. Created-at times from your service show up a few hours off in the app, but only when it runs on the server, never on your laptop. What did you find, and how did you fix it?

What the interviewer is really testing:
Whether you understand that a date-time without a zone takes on the zone of whichever machine made it, and store instants instead.
Answer frame:

Cause: LocalDateTime.now() uses the JVM's zone and then drops it, so the value means different moments on different machines.

Fix: store an Instant, in a column type that keeps the zone, with Hibernate's JDBC zone set to UTC.

API: send ISO-8601 with an offset, and convert to the user's zone only for display.

Sample spoken answer:

“My laptop ran in my local zone, while the server ran in UTC. We set createdAt with LocalDateTime.now(), which takes the clock time in whatever zone the JVM is in and then throws the zone away. The API sent it as a string with no offset, so the web app assumed it was the user's local time. On my laptop that happened to be true. On the server it was UTC shown as if it were local, so it looked off by exactly the zone difference. The fix had three parts. We switched the fields to Instant, so the value means one moment everywhere, and the column to a timestamp type with time zone. We set Hibernate's JDBC time zone to UTC, so it no longer depends on where the JVM runs. And the API now sends ISO-8601 with an offset, so the client converts to the user's zone for display. I also added a test that runs with a non-UTC default zone.”

Red flag to avoid:

Fixing it by setting the server's zone to match your laptop.

They may ask next:
  • When is LocalDate or LocalDateTime still the right type to use?
  • How would you fix times that were already saved with the wrong offset?
Say it in 60 seconds

Performance 1 question

Hard Technical round Mid-level Practice question

22. After you added @Cacheable to a method, testers saw old prices after an update, and once saw another user's data. What went wrong?

What the interviewer is really testing:
Whether you think about cache keys and eviction, not just the annotation, and spot a key that leaves out who is asking.
Answer frame:

Stale: nothing evicted or refreshed the entry when the price changed.

Wrong user: the key is built from the parameters, and the method read the user from somewhere else.

Also check: calls from inside the same class skip the cache, and cached objects can be changed by callers.

Sample spoken answer:

“Two separate bugs. The stale prices were because we cached reads but the update method never touched the cache, so an entry lived until it expired. I added an evict on the update, keyed the same way as the read. The other-user data was worse. By default the cache key is built from the method's parameters, and our method took only a product id, then read the current user from the security context inside the method to apply their personal discount. So the first user's discounted price got cached under the product id and served to everyone. Putting the user id into the key would have meant one entry per user per product, so instead we cached only the shared base price and applied the personal discount after, outside the cache. Now, before caching anything, I ask two questions: what exactly is the key, and what changes this data.”

Red flag to avoid:

Saying you'd just shorten the expiry time, without fixing the key or the eviction.

They may ask next:
  • Why can calling a @Cacheable method from inside the same class skip the cache?
  • What's the risk of caching a mutable entity object in an in-memory cache?
Say it in 60 seconds

Configuration 2 questions

Easy Technical round Mid-level Practice question

23. A property you read with @Value is null in one class, even though the property is set and other classes get it fine. What did you find?

What the interviewer is really testing:
Whether you know @Value is filled in by Spring on a managed bean, and when that happens, instead of doubting the property file.
Answer frame:

Not a bean: an object made with new never gets injected.

Too early: a field is filled after the constructor runs, so reading it in the constructor gives null.

Fix: take the value as a constructor parameter, or group it in a @ConfigurationProperties class.

Sample spoken answer:

“Since other classes got the value, I knew the property itself was fine, so the problem was how this class got it. There are three usual causes. One, the object was created with new somewhere, so Spring never touched it. Two, the field was static, and @Value doesn't fill static fields. Three, the value was read too early. That was ours: a client class had @Value on a field and used it in the constructor to build a URL. Spring creates the object first and fills fields after, so inside the constructor the field was still null. I moved the value to a constructor parameter with @Value on it, so it's there from the start and the field can be final. Later we grouped those settings into a @ConfigurationProperties record, which also let us validate them at startup.”

Code:
@Component
public class PaymentClient {
    private final String baseUrl;

    public PaymentClient(@Value("${app.payment.base-url}") String baseUrl) {
        this.baseUrl = baseUrl; // set before any method can use it
    }
}
Red flag to avoid:

Adding a null check with a hard-coded fallback URL instead of finding out why the value never arrived.

They may ask next:
  • What happens at startup if that property is missing completely?
  • How would you make the app refuse to start when a required setting is blank?
Say it in 60 seconds
Hard Technical round Mid-level Practice question

24. Every time your service is deployed, a handful of requests fail with errors for a few seconds, even though the new version is fine. What would you look at?

What the interviewer is really testing:
Whether you understand what happens to in-flight and new requests while one copy stops and another starts, and which Boot settings help.
Answer frame:

Old copy: is it shutting down gracefully, finishing in-flight requests first.

New copy: does it get traffic only once it is really ready.

The gap: the load balancer may keep sending to a stopping copy for a moment.

Sample spoken answer:

“I'd look at both ends of the deploy. On the old instance, when it gets the stop signal, is graceful shutdown on? It hasn't always been the default, so I check server.shutdown is set to graceful. Then Boot stops taking new requests and gives in-flight ones time to finish, up to the shutdown timeout, instead of cutting them off. On the new instance, I'd check the readiness check. If the platform sends traffic as soon as the process is up, the first requests hit an app that's still warming up. Pointing the readiness probe at the Actuator readiness health group fixed that for us. The last gap was the load balancer. It kept sending requests to the stopping instance for a second or two after shutdown began, so we added a short pause before shutdown starts. After those three changes, deploys stopped showing up in the error graph.”

Red flag to avoid:

Saying a few errors during a deploy are normal and can't be avoided.

They may ask next:
  • What's the difference between the liveness and readiness health groups?
  • How would you handle long-running requests or background jobs during shutdown?
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