Facebook Pixel

Spring Boot Interview Questions & Answers (2026)

5 min read
Spring Boot Interview Questions & Answers (2026)
On this page

TL;DR

The most common Spring Boot interview questions are:

  • What is Spring Boot, and why use it instead of the plain Spring Framework?

  • What is @SpringBootApplication?

  • How does auto-configuration work?

  • What are Spring Boot starters?

  • What is dependency injection, and why is constructor injection preferred?

  • What is an embedded server, and why is it useful?

  • @RestController vs @Controller

  • How do you validate requests with @Valid and @Validated?

  • How do you handle exceptions globally with @ControllerAdvice?

  • What is Spring Data JPA, and what does JpaRepository provide?

  • What is @Transactional, and where should it be used?

  • What is transaction propagation, and when would you use REQUIRES_NEW?

  • What is the N+1 query problem, and how do you fix it?

  • RestTemplate vs RestClient vs WebClient: when do you use each?

  • How do you secure a REST API in Spring Boot?

  • JWT vs OAuth2 vs session auth: which would you choose and why?

  • What are Spring Boot Actuator endpoints used for?

  • How do you expose metrics with Micrometer and Prometheus?

  • @WebMvcTest vs @DataJpaTest vs @SpringBootTest

  • How do you package and deploy a Spring Boot app (JAR vs WAR, Docker)?

  • What changed in Spring Boot 3 (Jakarta EE, native images, observability, virtual threads)?

  • How do service discovery, config server, and circuit breakers work in Spring Cloud microservices?

Below you will find one-line answers, deeper explanations, and working code for each, plus scenario-based questions, Spring Boot 3 specifics, and the questions to expect at each experience level. Code targets Spring Boot 3.x on Java 17 or later, with jakarta.* imports.

What is Spring Boot?

Spring Boot is an opinionated framework that simplifies building production-ready Spring applications via auto-configuration, embedded servers, and starter dependencies.

Spring Boot reduces boilerplate so you can get a runnable app fast: it auto-configures sensible defaults based on what is on the classpath, bundles an embedded web server (Tomcat by default, or Jetty or Undertow) so apps run as standalone JARs, and provides "starter" dependencies that pull in a tested set of libraries with one line. It is built for production: profiles, externalized configuration, health checks, metrics, and a rich ecosystem (Spring Data, Spring Security, Spring Cloud) make mainstream backend work much faster.

Interview tip: give the one-sentence definition first, then show a tiny code example. It proves both concept knowledge and hands-on ability.

Spring Boot Interview Questions & Answers

Spring Boot Interview Questions by Experience Level

Interviewers adjust the depth of their questions to your experience. A fresher is asked what an annotation does; a developer with 5 years of experience is asked why it broke in production; a senior engineer with 10 or more years is asked how they would design the system so it does not break. Use the lists below to decide where to spend your prep time. Every topic is answered later in this guide.

Spring Boot interview questions for freshers (0-2 years)

Expect definitions, core annotations, and a small REST API. You should be able to explain and write:

  • What Spring Boot is and how it differs from the Spring Framework

  • What @SpringBootApplication does, what starters are, and what auto-configuration means

  • Inversion of Control, dependency injection, and why constructor injection is preferred

  • @Component vs @Bean, the stereotype annotations, and bean scopes

  • application.properties vs application.yml, and what profiles are

  • @RestController vs @Controller, and @PathVariable vs @RequestParam

  • What the embedded server is, what Actuator does, and what DevTools is for

Spring Boot interview questions for 2-5 years experience

Expect questions about how things behave in a real codebase, plus debugging scenarios. Be ready for:

  • Validation, global exception handling, and consistent error responses (ProblemDetail)

  • Spring Data JPA, the N+1 problem, @Transactional propagation, and why a transaction did not roll back

  • Securing a REST API with Spring Security 6 (JWT, OAuth2 resource server, method security)

  • Test slices (@WebMvcTest, @DataJpaTest), @SpringBootTest, and Testcontainers

  • @ConfigurationProperties, configuration across environments, and secrets

  • RestClient vs WebClient, timeouts, caching, and @Async

  • Actuator, Micrometer metrics, Docker packaging, and "a bean is not being injected" style scenarios

Spring Boot interview questions for senior developers (7-10+ years experience)

Expect design trade-offs, production incidents, and platform decisions. Prepare to discuss:

  • How auto-configuration works internally and how you would write a custom starter

  • Migrating from Spring Boot 2 to 3 (Jakarta namespace, Spring Security 6, Hibernate 6)

  • GraalVM native images, virtual threads, and when each is (and is not) worth it

  • Observability with Micrometer Observation, tracing, and log correlation

  • Microservice resilience: timeouts, retries, circuit breakers, bulkheads, idempotency, and sagas

  • Diagnosing a service that is slow under load, and preventing cascading failures

  • System design: service boundaries, data ownership, multi-tenancy, and scaling the database

If you are also preparing for the core Java round, pair this guide with our Java developer interview preparation guide.

Core / Basic Spring Boot Questions

Each question below starts with the one-line answer so you can say it first in the interview, followed by a short explanation and a code example where it helps.

Why use Spring Boot instead of the Spring Framework?

Spring Boot is not a replacement for Spring; it is a layer on top of it that removes most of the setup work.

With the plain Spring Framework you choose compatible library versions, configure a DispatcherServlet, a DataSource, a transaction manager, and a web server yourself, and deploy a WAR to an external container. Spring Boot gives you managed dependency versions, auto-configuration that creates those beans for you, an embedded server so the app runs with java -jar, and production features such as Actuator. You still use the same Spring concepts (IoC container, beans, AOP, Spring MVC); Boot just wires them with sensible defaults that you can override.

Aspect

Spring Framework

Spring Boot

Configuration

Mostly manual (Java config or XML)

Auto-configuration with overridable defaults

Dependencies

You pick and align versions

Starters plus managed versions

Server

Deploy WAR to an external container

Embedded Tomcat, Jetty, or Undertow

Production features

Add yourself

Actuator health, metrics, info out of the box

What is @SpringBootApplication?

@SpringBootApplication is a meta-annotation that combines @SpringBootConfiguration (a specialized @Configuration), @EnableAutoConfiguration, and @ComponentScan.

Put this annotation on your main class to mark the app entry point. It enables auto-configuration and component scanning from that class's package downward, so your beans and configuration are discovered automatically. It is the standard bootstrap annotation for almost every Spring Boot app.

Code (main class):

@SpringBootApplication
public class DemoApplication {
  public static void main(String[] args) {
    SpringApplication.run(DemoApplication.class, args);
  }
}

When to use: On the primary application class in nearly all Spring Boot projects.
Common pitfall: Component scanning starts at the main class's package. If the main class sits in com.example.app.web, beans in com.example.app.service will not be found. Put it in the root package.

How does auto-configuration work?

Auto-configuration detects classes, beans, and properties at startup and conditionally registers beans using @Conditional rules to provide sensible defaults.

Spring Boot ships many auto-configuration classes (in spring-boot-autoconfigure) that are listed in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. Since Spring Boot 3.0, this file is the only registration mechanism; the old spring.factories entry for auto-configuration is no longer supported. Each class uses conditions such as @ConditionalOnClass, @ConditionalOnMissingBean, or @ConditionalOnProperty, so beans are created only when appropriate. Because of @ConditionalOnMissingBean, defining your own bean of the same type makes Boot back off.

Code (exclude an auto-configuration):

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class DemoApplication { /* ... */ }

How to see what was applied: start the app with --debug (or set debug=true) to print the conditions evaluation report, or call /actuator/conditions if that endpoint is exposed. This is the fastest way to answer "why was (or wasn't) this bean created?"

Senior follow-up: how would you write a custom starter? Create an auto-configuration class, guard it with conditions, and register it in the imports file:

@AutoConfiguration
@ConditionalOnClass(AuditClient.class)
@EnableConfigurationProperties(AuditProperties.class)
public class AuditAutoConfiguration {

  @Bean
  @ConditionalOnMissingBean
  AuditClient auditClient(AuditProperties props) {
    return new AuditClient(props.endpoint());
  }
}

// src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.example.audit.AuditAutoConfiguration

Common pitfall: Relying on auto-configuration without understanding it can hide missing dependencies or create unexpected beans. Know how to read the conditions report.

Spring Boot auto-configuration flow: classpath, auto-configuration classes, conditional checks, beans created

Spring Boot starters - what are they?

Starters are curated dependency bundles (for example spring-boot-starter-web) that pull in a compatible set of libraries so you do not manage each transitive dependency manually.

Instead of adding Jackson, Spring MVC, validation, and an embedded server individually, you include spring-boot-starter-web and get a set that is tested to work together. Versions come from Spring Boot's dependency management (the parent POM or the Gradle plugin), so you normally omit version numbers.

Pom example (Maven):

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>

When to use: Add the starter for the layer you need (web, data-jpa, security, validation, actuator, test).
Common pitfall: Adding starters you do not use triggers extra auto-configuration (for example, adding data-jpa without a database URL fails startup). Keep the classpath lean.

What are IoC and dependency injection, and why is constructor injection preferred?

Inversion of Control means the Spring container creates and wires your objects (beans); dependency injection is how it hands each bean the collaborators it needs.

Spring supports constructor, setter, and field injection. Constructor injection is the recommended default because dependencies can be final, the object is never half-initialized, required dependencies are obvious, and the class is easy to unit test without Spring. Since Spring 4.3, a class with a single constructor does not need @Autowired at all.

@Service
public class OrderService {

  private final OrderRepository orders;
  private final PaymentClient payments;

  // Single constructor: no @Autowired needed
  public OrderService(OrderRepository orders, PaymentClient payments) {
    this.orders = orders;
    this.payments = payments;
  }
}

Follow-up: Spring Boot 2.6 and later refuse to start when beans have circular dependencies. The right fix is usually to split responsibilities, not to set spring.main.allow-circular-references=true.

What is the difference between @Component and @Bean?

@Component goes on your own class so component scanning picks it up; @Bean goes on a method inside a @Configuration class and registers whatever that method returns.

Use @Component (or the more specific stereotypes @Service, @Repository, @Controller) for classes you write. Use @Bean when you need to configure a third-party class you cannot annotate, or when construction needs logic. @Repository also enables translation of persistence exceptions into Spring's DataAccessException hierarchy.

@Configuration
public class ClientConfig {

  @Bean
  RestClient inventoryClient(RestClient.Builder builder) {
    return builder.baseUrl("http://inventory-service").build();
  }
}

What bean scopes does Spring support?

The default scope is singleton (one instance per container); prototype creates a new instance each time the bean is requested; web apps add request, session, application, and websocket scopes.

Singletons must be thread-safe because every request shares them, so keep them stateless. A classic follow-up: injecting a prototype bean into a singleton gives you one instance forever, because injection happens once. Use ObjectProvider<T> (call getObject() each time) or a scoped proxy when you really need a fresh instance.

application.properties vs application.yml, and what are profiles?

Both files hold the same externalized configuration; YAML is hierarchical and easier to read for nested settings, while properties files are flat. Profiles let you activate environment-specific configuration such as dev or prod.

Profile-specific files follow the application-{profile}.yml naming pattern and override the base file when that profile is active. Activate a profile with spring.profiles.active=prod, the SPRING_PROFILES_ACTIVE environment variable, or a command-line argument. Beans can also be limited to a profile with @Profile("dev").

@Value vs @ConfigurationProperties?

@Value injects a single property; @ConfigurationProperties binds a whole group of properties to a typed, validatable object and is the better choice for anything beyond one or two values.

@Validated
@ConfigurationProperties(prefix = "app.payments")
public record PaymentsProperties(
    @NotBlank String baseUrl,
    @NotNull Duration timeout) {}

// Register it, for example on the main class:
@SpringBootApplication
@ConfigurationPropertiesScan
public class DemoApplication { /* ... */ }

With Spring Boot 3, a record (or a class with a single constructor) binds through its constructor without needing @ConstructorBinding. Relaxed binding means app.payments.base-url in YAML and APP_PAYMENTS_BASEURL as an environment variable both map to baseUrl.

What are CommandLineRunner and ApplicationRunner?

Both are callback interfaces whose run method executes once, after the application context has started.

CommandLineRunner receives raw String... arguments, while ApplicationRunner receives parsed ApplicationArguments. They are handy for seeding data or warm-up tasks. For heavy work, prefer listening for ApplicationReadyEvent or running it asynchronously so startup is not blocked.

What is Spring Boot DevTools?

DevTools is a development-only dependency that restarts the app automatically when classes change, supports LiveReload, and applies development-friendly defaults such as disabling template caching.

DevTools is disabled automatically when the app runs as a fully packaged JAR with java -jar, and it should be declared as an optional (Maven) or developmentOnly (Gradle) dependency so it never ships to production.

Embedded server vs external server - when to choose which?

Use an embedded server (runnable JAR) for faster development, simpler deployment, and microservices; use an external server (WAR) when your organization requires centralized servlet containers.

By default Spring Boot packages an embedded Tomcat (Jetty and Undertow are alternatives) so you can run java -jar app.jar. For environments that deploy to a shared application server, build a WAR and extend SpringBootServletInitializer. Embedded servers are the standard choice for cloud and container deployments.

Code (WAR bootstrapping):

@SpringBootApplication
public class WarApplication extends SpringBootServletInitializer {
  @Override
  protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
    return application.sources(WarApplication.class);
  }
}

When to use: Embedded for microservices and containers; external WAR for legacy app-server policies.
Common pitfall: Forgetting to change packaging to war and to mark the embedded Tomcat starter as provided when deploying to an external servlet container.

Most-asked Spring Boot annotations (cheat sheet)

Annotation

What it does

@SpringBootApplication

Bootstraps the app: configuration, auto-configuration, component scan

@Component, @Service, @Repository, @Controller

Stereotypes picked up by component scanning

@RestController

@Controller plus @ResponseBody for JSON APIs

@Configuration / @Bean

Java configuration class / method that registers a bean

@Autowired, @Qualifier, @Primary

Injection and choosing between multiple candidate beans

@Value, @ConfigurationProperties

Inject one property / bind a typed group of properties

@Profile, @ConditionalOnProperty

Register beans only for certain profiles or property values

@Transactional

Declarative transaction boundaries (proxy-based)

@EnableCaching / @Cacheable

Turn on caching / cache a method's result

@EnableAsync / @Async, @EnableScheduling / @Scheduled

Asynchronous methods and scheduled jobs

Core question

One-line answer

Tags

What is @SpringBootApplication?

Meta-annotation combining @SpringBootConfiguration, @EnableAutoConfiguration, @ComponentScan.

annotation, bootstrap

How does auto-configuration work?

Conditional configuration classes create beans based on classpath, existing beans, and properties.

auto-config, ConditionalOnClass, ConditionalOnMissingBean

What are Spring Boot starters?

Curated dependency bundles with managed versions.

starter, dependency-management

Embedded server vs external server?

Embedded JAR for microservices and cloud; WAR on an external servlet container for legacy app servers.

deployment, packaging, Tomcat

Web & REST (controllers, validation, error handling)

Each question below begins with a one-line answer, followed by a short explanation, code, and the pitfalls interviewers like to probe.

@RestController vs @Controller

@RestController returns response bodies (JSON/XML); @Controller returns views (templates) unless you add @ResponseBody.

Table (quick compare)

Aspect

@RestController

@Controller

Primary use

REST APIs - returns body (JSON/XML)

Server-side rendered views (Thymeleaf/JSP) or mixed

Implicit behavior

Combines @Controller + @ResponseBody

Requires @ResponseBody on methods to return raw bodies

Typical return types

POJOs, records, ResponseEntity<>

String view names, ModelAndView

When to choose

Microservices, API backends

Traditional MVC apps, pages + forms

Example

@RestController with @GetMapping("/api")

@Controller with @GetMapping("/") returning "index"

When to use: Prefer @RestController for JSON REST services and @Controller when rendering HTML templates.
Common pitfall: Mixing view logic and API endpoints in the same controller. Keep concerns separated.

@PathVariable vs @RequestParam?

@PathVariable reads a value from the URL path (/orders/42); @RequestParam reads a query string or form parameter (/orders?status=OPEN).

Use path variables to identify a resource and request parameters for optional filtering, sorting, and paging. @RequestBody is the third option: it deserializes the JSON body into an object.

@GetMapping("/api/orders/{id}")
public OrderDto get(@PathVariable Long id) { /* ... */ }

@GetMapping("/api/orders")
public Page<OrderDto> list(@RequestParam(defaultValue = "OPEN") OrderStatus status,
                           Pageable pageable) { /* ... */ }

Validation with @Valid / @Validated

Use @Valid on request bodies and @Validated on classes to trigger Jakarta Bean Validation checks (for example @NotNull, @Size).

Add spring-boot-starter-validation, annotate your DTO fields (@NotBlank, @Email, @Size), and put @Valid on the controller parameter. Invalid bodies throw MethodArgumentNotValidException, which you should handle centrally. @Validated is Spring's variant: it supports validation groups and enables method-level validation on Spring beans (for example validating @RequestParam values or service method arguments).

Common usage patterns

  • public ResponseEntity<?> create(@Valid @RequestBody UserDto dto)

  • Use @Validated when you need validation groups or method-level validation for Spring components.

Common pitfalls

  • Forgetting @Valid on the controller parameter: validation will not run.

  • Forgetting the validation starter: since Spring Boot 2.3 it is no longer included in the web starter.

  • Returning raw validation errors to clients without normalizing them.

Exception handling with @ControllerAdvice

Use @RestControllerAdvice (or @ControllerAdvice) with @ExceptionHandler methods to centralize API error mapping and return consistent error responses.

Instead of try/catch in controllers, create a global handler that maps exceptions (for example MethodArgumentNotValidException or your own OrderNotFoundException) to the right HTTP status and a structured body. In Spring Boot 3 the idiomatic body is ProblemDetail, Spring's implementation of the RFC 9457 "problem details" format (the successor to RFC 7807). Setting spring.mvc.problemdetails.enabled=true makes Spring MVC's own exceptions use that format too.

Common patterns

  • Handle validation exceptions and return a field-level error list.

  • Map domain exceptions to 4xx and unexpected exceptions to 5xx with logging and correlation IDs.

Common pitfall: Returning raw stack traces or internal exception messages to clients. Always sanitize.

Code block - sample controller + validation + service + global exception handler

Below is a compact example you can paste into a minimal Spring Boot 3 app. It includes a DTO with validation, a @RestController endpoint, a simple service, and a @RestControllerAdvice that returns ProblemDetail responses.

// UserDto.java
package com.example.demo.dto;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

public record UserDto(
  @NotBlank(message = "name is required") String name,
  @Email(message = "invalid email") String email,
  @Size(min = 8, message = "password must be at least 8 characters") String password
) {}

// UserService.java
package com.example.demo.service;

import com.example.demo.dto.UserDto;
import org.springframework.stereotype.Service;

@Service
public class UserService {
  public String createUser(UserDto dto) {
    // persist the user and return its id
    return "user-id-123";
  }
}

// UserController.java
package com.example.demo.controller;

import com.example.demo.dto.UserDto;
import com.example.demo.service.UserService;
import jakarta.validation.Valid;
import java.util.Map;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/users")
public class UserController {
  private final UserService service;

  public UserController(UserService service) {
    this.service = service;
  }

  @PostMapping
  public ResponseEntity<Map<String, String>> createUser(@Valid @RequestBody UserDto dto) {
    String id = service.createUser(dto);
    return ResponseEntity.status(HttpStatus.CREATED).body(Map.of("id", id));
  }
}

// GlobalExceptionHandler.java
package com.example.demo.exception;

import java.util.List;
import java.util.Map;
import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionHandler {

  @ExceptionHandler(MethodArgumentNotValidException.class)
  public ProblemDetail handleValidation(MethodArgumentNotValidException ex) {
    ProblemDetail problem = ProblemDetail.forStatusAndDetail(
        HttpStatus.BAD_REQUEST, "Request validation failed");
    List<Map<String, String>> errors = ex.getBindingResult().getFieldErrors().stream()
        .map(fe -> Map.of("field", fe.getField(),
                          "message", String.valueOf(fe.getDefaultMessage())))
        .toList();
    problem.setProperty("errors", errors);
    return problem;
  }

  @ExceptionHandler(Exception.class)
  public ProblemDetail handleAll(Exception ex) {
    // log the exception with a correlation id; never echo internals to the client
    return ProblemDetail.forStatusAndDetail(
        HttpStatus.INTERNAL_SERVER_ERROR, "Internal server error");
  }
}

Run it yourself:

  • Create a Spring Boot 3 project with Spring Web and Validation (spring-boot-starter-validation).

  • Place the files in the com.example.demo package and run ./mvnw spring-boot:run (or start it from your IDE).

  • POST this invalid JSON to http://localhost:8080/api/users and you will get a 400 with an errors array:

{ "name": "", "email": "nope", "password": "123" }
Request flow in a Spring Boot app: controller, service, repository

Quick checklist (do before an interview):

  • Validate DTOs with @Valid and test invalid payload responses.

  • Centralize API errors with @RestControllerAdvice and ProblemDetail.

  • Keep controllers thin: move logic to services.

  • Return appropriate status codes (201 for create, 400 for validation, 404 for not found, 409 for conflicts).

Data access & JPA

Snippet-first answers, short explanations, and copy-ready code so you can speak confidently about Spring Data JPA in interviews.

Spring Data JPA basics

Spring Data JPA provides repository abstractions (for example JpaRepository) that eliminate boilerplate CRUD and let you define query methods or JPQL/SQL queries.

Extend JpaRepository or CrudRepository to get ready-made CRUD, paging, and sorting. Define methods by naming convention (for example findByEmail) or with @Query for custom JPQL or native SQL. Repositories work on entities annotated with @Entity (from jakarta.persistence in Spring Boot 3) and typically return entities, Optional<T>, Page<T>, or projections/DTOs. Spring Boot 3 uses Hibernate 6 as the default JPA provider.

Common interview points: method-name queries, derived count/exists methods, Pageable/Sort, projections, and when to use native queries vs JPQL.
Pitfall: Do not put business logic in repositories. Keep them thin and testable.

Transactions - @Transactional propagation table (REQUIRED, REQUIRES_NEW, etc.)

Transaction propagation controls whether a method runs inside an existing transaction or starts a new one. Pick the propagation that matches the business intent.

Propagation table (quick reference)

Propagation

One-line meaning

When to use / note

REQUIRED

Join current tx or create one if none exists.

Default; typical for service methods.

REQUIRES_NEW

Suspend current tx and start a new independent tx.

Use for auditing or when you must commit regardless of the outer tx.

SUPPORTS

Run within current tx if present; otherwise run non-transactionally.

For read-only helpers reusable in or out of a tx.

NOT_SUPPORTED

Suspend current tx and execute non-transactionally.

For calls that must not participate.

MANDATORY

Must run inside an existing tx; throws an exception otherwise.

When the caller is responsible for creating the tx.

NEVER

Must not run inside a transaction; throws if a tx exists.

Rare; enforces non-transactional behavior.

NESTED

Run in a nested savepoint within the current tx.

Partial rollbacks on the same connection (requires JDBC savepoint support).

Common pitfalls:

  • Self-invocation: @Transactional works through a proxy, so calling a transactional method from another method in the same class bypasses it. The same applies to @Async and @Cacheable.

  • Private methods: annotations on private methods are ignored because the proxy cannot intercept them.

  • Checked exceptions: by default Spring rolls back only on unchecked exceptions (RuntimeException and Error). Use rollbackFor = Exception.class if a checked exception should roll back.

  • Expecting REQUIRES_NEW to roll back with the outer tx: the two are independent.

  • Using NESTED assuming distributed transactions: it uses savepoints on the same connection only.

N+1 problem & solutions (fetch join, EntityGraph)

The N+1 problem occurs when loading a list of parents runs one query, and then touching a lazy association runs one more query per parent. Solve it with fetch joins, @EntityGraph, batch fetching, or DTO projections.

Explanation & solutions:

  • Fetch join (JPQL): select p from Post p join fetch p.comments where p.id = :id loads parent and children in one query. Best when you know you need the relationship.

  • @EntityGraph: Declaratively tells JPA to fetch certain associations without writing custom JPQL. Useful on repository methods.

  • Batch fetching (Hibernate @BatchSize or hibernate.default_batch_fetch_size): Loads lazy collections for many parents in a few IN queries instead of one per parent.

  • DTO projection: Select only the fields you need into a DTO or interface projection, avoiding entity loading and lazy-loading problems.

  • Detect it: enable SQL logging or Hibernate statistics in tests and count the queries.

Common pitfall: Using join fetch on a collection together with pagination. Hibernate cannot page in SQL in that case and either warns and pages in memory or fails, depending on configuration. Prefer DTOs or a two-query strategy (page the IDs, then fetch associations) for paged results.

Code: repository method + @Transactional example

Repository examples

// Post.java (excerpt)
@Entity
public class Post {
  @Id @GeneratedValue
  private Long id;

  private String title;

  @OneToMany(mappedBy = "post", fetch = FetchType.LAZY)
  private List<Comment> comments = new ArrayList<>();
  // getters/setters...
}

// PostRepository.java
public interface PostRepository extends JpaRepository<Post, Long> {

  // 1) Fetch join JPQL to load post + comments in one query
  @Query("select p from Post p left join fetch p.comments where p.id = :id")
  Optional<Post> findByIdWithComments(@Param("id") Long id);

  // 2) EntityGraph alternative (works nicely with Spring Data)
  @EntityGraph(attributePaths = {"comments"})
  Optional<Post> findWithCommentsById(Long id);

  // 3) DTO projection example
  @Query("select new com.example.dto.PostSummary(p.id, p.title, count(c)) " +
         "from Post p left join p.comments c where p.id = :id group by p.id, p.title")
  Optional<PostSummary> findSummaryById(@Param("id") Long id);
}

Service / @Transactional examples

@Service
public class PostService {

  private final PostRepository repo;
  private final AuditService auditService; // separate bean, so REQUIRES_NEW goes through its proxy

  public PostService(PostRepository repo, AuditService auditService) {
    this.repo = repo;
    this.auditService = auditService;
  }

  // Default propagation (REQUIRED): joins or creates a transaction
  @Transactional
  public Long createPost(Post post) {
    Post saved = repo.save(post);
    // audit in a separate transaction so it commits even if the outer one rolls back
    auditService.recordCreation(saved.getId());
    return saved.getId();
  }
}

@Service
public class AuditService {
  // REQUIRES_NEW: starts a separate transaction that commits or rolls back independently
  @Transactional(propagation = Propagation.REQUIRES_NEW)
  public void recordCreation(Long postId) {
    // write audit record
  }
}

When to use & pitfalls demonstrated:

  • Use findByIdWithComments or @EntityGraph when you know you need the collection, to avoid N+1.

  • Use DTO projection when you only need a few fields and want to avoid fully initializing entities.

  • AuditService is a separate bean on purpose. If recordCreation lived in PostService, the call would be self-invocation and REQUIRES_NEW would silently be ignored.

What is spring.jpa.open-in-view, and should you disable it?

Open Session in View keeps the persistence context open for the whole web request so lazy associations can load in the controller or during JSON serialization. Spring Boot enables it by default and logs a warning at startup.

It hides LazyInitializationException but holds a database connection for the entire request and lets queries fire from the view layer, which hurts under load. Many teams set spring.jpa.open-in-view=false and load exactly what each endpoint needs in the service layer (fetch joins, entity graphs, or DTOs).

Quick checklist (prep for interviews)

  • Know when to use JpaRepository methods vs custom @Query.

  • Explain the N+1 problem and at least two fixes (fetch join, @EntityGraph, batch fetching, DTO projection).

  • Be ready to explain @Transactional propagation modes and an example where REQUIRES_NEW is appropriate.

  • Mention pitfalls: self-invocation, private transactional methods, checked exceptions, join fetch with pagination, and open-in-view.

JPA entity lifecycle

Security (modern Spring Security patterns)

Security questions often trip candidates up. Show that you know the Spring Security 6 configuration style and the trade-offs behind each choice.

How to secure a Spring Boot REST API - 3 steps

Secure a Spring Boot REST API by (1) configuring stateless authentication and authorization, (2) validating tokens properly, and (3) applying secure defaults (HTTPS, CORS, headers, minimal Actuator exposure).

Short explanation:

  1. Stateless auth & authorization: For APIs, use the OAuth2 Resource Server support to validate JWT access tokens, set SessionCreationPolicy.STATELESS, and authorize by scope or role at both URL and method level.

  2. Token validation: Verify signature, expiration, issuer, and audience before trusting any claim. Let Spring Security do this instead of hand-rolling it.

  3. Secure defaults: Disable CSRF only for pure token-based APIs, configure strict CORS, keep security headers on, and expose only the Actuator endpoints you need, behind authentication.

Common pitfalls: Disabling CSRF on an app that authenticates with cookies, storing tokens insecurely in the browser, and not validating token audience or issuer.

JWT vs OAuth2 vs session auth (comparison table)

Approach

What it is

Pros

Cons

When to use

JWT (self-contained tokens)

Signed JSON Web Token carrying claims; validated by the resource server.

Simple, scalable, no server session state, easy for microservices.

Revocation is non-trivial; key management; token size.

Microservices, mobile apps, stateless APIs.

OAuth2 (Authorization Server + Resource Server)

Standards-based delegated authorization with access and refresh tokens, scopes, and flows (authorization code, client credentials).

Best for delegated auth, third-party apps, SSO, and fine-grained scopes.

More moving parts; higher complexity to run.

Public APIs, third-party integration, enterprise SSO.

Session (server state)

Cookie-based session stored on the server (or in a shared store such as Redis).

Simple logout and revocation; mature CSRF protection.

Needs a shared session store to scale horizontally.

Monoliths, server-rendered sites, apps needing strong revocation.

Note that JWT and OAuth2 are not really alternatives: OAuth2 is a protocol, and its access tokens are very often JWTs. Saying this clearly in an interview scores points.

Security checklist (quick): validate iss and aud, check expiry, use TLS everywhere, rotate keys, keep access tokens short-lived, and use refresh tokens per OAuth2 patterns.

Code: modern Security DSL with JWT validation

Spring Security 6 removed WebSecurityConfigurerAdapter; you now declare a SecurityFilterChain bean and use the lambda DSL with authorizeHttpRequests and requestMatchers. The simplest correct way to accept JWTs is the built-in OAuth2 Resource Server support (spring-boot-starter-oauth2-resource-server), which validates the signature against your identity provider's published keys.

// SecurityConfig.java
package com.example.security;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableMethodSecurity
public class SecurityConfig {

  @Bean
  SecurityFilterChain api(HttpSecurity http) throws Exception {
    http
      .csrf(csrf -> csrf.disable()) // token-based API, no cookies
      .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
      .authorizeHttpRequests(auth -> auth
          .requestMatchers("/actuator/health").permitAll()
          .requestMatchers(HttpMethod.GET, "/api/orders/**").hasAuthority("SCOPE_orders.read")
          .anyRequest().authenticated())
      .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
    return http.build();
  }
}
# application.yml: Spring discovers the signing keys from the issuer
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://auth.example.com/
// Method-level authorization (enabled by @EnableMethodSecurity)
@PreAuthorize("hasAuthority('SCOPE_orders.write')")
@PostMapping("/api/orders")
public ResponseEntity<OrderDto> create(@Valid @RequestBody CreateOrderRequest request) { /* ... */ }

By default, the scope (or scp) claim is mapped to authorities with a SCOPE_ prefix, which is why the rules above use SCOPE_orders.read.

If the interviewer asks you to write a JWT filter by hand: extend OncePerRequestFilter, read the Authorization: Bearer header, validate the token with a JWT library (signature, expiry, issuer, audience), build an Authentication with the user's authorities, set it on the SecurityContextHolder, and register the filter with addFilterBefore(..., UsernamePasswordAuthenticationFilter.class). Then explain why you would still prefer the built-in resource server in production: less custom security code to get wrong.

Key notes & best practices:

  • CSRF: Disable only for stateless token APIs. If you use cookie-based auth, keep CSRF on and use SameSite cookies.

  • Key management: Do not hardcode secrets. Prefer asymmetric keys (for example RS256) published through a JWKS endpoint, and rotate them.

  • Passwords: store them with a PasswordEncoder such as BCrypt (or the delegating encoder), never in plain text or with a fast hash.

  • Error handling: Return 401 for missing or invalid tokens and 403 for valid tokens without permission.

  • Scopes & roles: Map claims to GrantedAuthority carefully and never trust role claims from a token you did not validate.

How to test it: spring-security-test lets you fake a validated token in MockMvc tests, for example mockMvc.perform(get("/api/orders").with(jwt().authorities(new SimpleGrantedAuthority("SCOPE_orders.read")))).

Interview talking points & follow-ups

  • Be ready to explain refresh token flows, refresh token rotation, and revocation strategies.

  • Discuss asymmetric keys & JWKS and how a resource server validates tokens with public keys.

  • Mention rate limiting and brute-force protection for auth endpoints. Spring Security does not rate limit for you; that usually lives in the API gateway or a library such as Bucket4j.

  • If asked about SSO, explain how the OAuth2 authorization code flow (with PKCE for public clients) works and when to use it.

Microservices & Spring Cloud

Spring Boot microservices interview questions test whether you understand what goes wrong once one application becomes many: finding each other, sharing configuration, and surviving each other's failures.

Spring Boot vs Spring Cloud?

Spring Boot builds individual services quickly; Spring Cloud provides the distributed-systems building blocks (discovery, config, gateway, load balancing, circuit breakers) you need to run many services reliably.

Use Spring Boot to create runnable microservices (auto-configuration, starters, Actuator). When you need service discovery, centralized config, routing, and resilience across multiple services, add Spring Cloud components: Eureka or Consul for discovery, Spring Cloud Config, Spring Cloud Gateway, Spring Cloud LoadBalancer, and Spring Cloud Circuit Breaker with Resilience4j. The older Netflix pieces (Ribbon, Hystrix, Zuul) are retired, so name their replacements in interviews.

What is service discovery?

Service discovery lets services find each other dynamically instead of hardcoding host names: use a registry (Eureka, Consul) or the platform's DNS-based discovery (Kubernetes Services).

With a registry, each instance registers itself on startup and sends heartbeats; clients ask the registry for healthy instances and load-balance across them on the client side with Spring Cloud LoadBalancer. On Kubernetes, you usually skip Eureka and call http://inventory-service, letting the platform's DNS and Service objects handle it. Trade-off: a registry adds another system to operate, but gives you health-aware instance lists outside Kubernetes.

// Eureka server
@SpringBootApplication
@EnableEurekaServer
public class DiscoveryServerApplication { /* main method */ }

# Client service: with spring-cloud-starter-netflix-eureka-client on the classpath,
# the service registers automatically
spring:
  application:
    name: order-service
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

What is a config server?

A centralized config server (Spring Cloud Config) externalizes and versions configuration so every service loads its environment-specific properties from one place at startup.

Store properties in Git (or Vault) and point clients at the server with spring.config.import=optional:configserver:http://localhost:8888. Beans marked @RefreshScope can pick up changes by calling /actuator/refresh, and Spring Cloud Bus can broadcast that refresh to every instance. Keep secrets out of plaintext Git: use Vault or encrypted values. On Kubernetes, ConfigMaps and Secrets are a common alternative.

What is a circuit breaker?

A circuit breaker (Resilience4j in modern Spring) stops calling an unhealthy dependency after a failure threshold, fails fast with a fallback, and periodically lets trial calls through to see if it has recovered.

It has three states: closed (calls flow normally while failures are counted), open (calls are short-circuited to the fallback), and half-open (a few trial calls decide whether to close again). Combine it with timeouts, limited retries with backoff, and bulkheads. Know when to return cached data versus failing gracefully.

// Requires resilience4j-spring-boot3 and spring-boot-starter-aop
@Service
public class InventoryClient {

  private final RestClient restClient;

  public InventoryClient(RestClient.Builder builder) {
    this.restClient = builder.baseUrl("http://inventory-service").build();
  }

  @CircuitBreaker(name = "inventory", fallbackMethod = "stockFallback")
  @Retry(name = "inventory")
  public StockLevel stockFor(String sku) {
    return restClient.get()
        .uri("/api/stock/{sku}", sku)
        .retrieve()
        .body(StockLevel.class);
  }

  // Same parameters plus the exception
  private StockLevel stockFallback(String sku, Throwable ex) {
    return StockLevel.unknown(sku);
  }
}
resilience4j:
  circuitbreaker:
    instances:
      inventory:
        sliding-window-size: 20
        failure-rate-threshold: 50
        wait-duration-in-open-state: 30s
  retry:
    instances:
      inventory:
        max-attempts: 3
        wait-duration: 200ms

Senior follow-up: with the annotations, Resilience4j applies Retry as the outermost decorator by default, so each retry attempt is recorded by the circuit breaker. Only retry idempotent operations, and cap total time so retries do not multiply load on a struggling service.

What is an API gateway, and why use Spring Cloud Gateway?

An API gateway is the single entry point in front of your services that handles routing, authentication, rate limiting, and cross-cutting headers so each service does not have to.

Spring Cloud Gateway routes requests using predicates (path, host, header) and filters (rewrite path, add headers, circuit breaker, request rate limiter). It was built on Spring WebFlux, and newer releases also offer a Spring MVC-based variant. A common design is to validate tokens at the gateway and again in each service (defense in depth).

How do microservices communicate: REST, messaging, or both?

Use synchronous calls (REST or gRPC) when the caller needs an immediate answer, and asynchronous messaging (Kafka, RabbitMQ) when work can happen later or several services need to react to the same event.

Synchronous calls are simpler but couple availability: if the callee is down, so is the caller. Messaging decouples services and absorbs spikes, at the cost of eventual consistency, duplicate delivery, and harder debugging. Mention idempotent consumers and the transactional outbox pattern (write the event to an outbox table in the same database transaction, then publish it) to show senior-level awareness.

How do you handle transactions across microservices?

You generally avoid distributed two-phase commit and use the Saga pattern: a sequence of local transactions where each step publishes an event, and failures trigger compensating actions.

Sagas can be choreographed (services react to each other's events) or orchestrated (a coordinator tells each service what to do). Pair them with idempotency keys so retries do not double-charge a customer, and with the outbox pattern so the database write and the event are never out of sync.

Code - sample WebClient call example vs RestTemplate snippet

RestTemplate (blocking, maintenance mode)

@Service
public class LegacyClientService {

  private final RestTemplate restTemplate;

  public LegacyClientService(RestTemplate restTemplate) {
    this.restTemplate = restTemplate;
  }

  public String getRemoteData(String id) {
    ResponseEntity<String> resp =
        restTemplate.getForEntity("http://service-b/api/data/{id}", String.class, id);
    return resp.getBody();
  }
}

RestClient (blocking, fluent API; Spring Framework 6.1 / Spring Boot 3.2 and later)

@Service
public class DataClient {

  private final RestClient restClient;

  public DataClient(RestClient.Builder builder) {
    this.restClient = builder.baseUrl("http://service-b").build();
  }

  public String getRemoteData(String id) {
    return restClient.get()
        .uri("/api/data/{id}", id)
        .retrieve()
        .body(String.class);
  }
}

WebClient (non-blocking, for reactive stacks and streaming)

@Service
public class ReactiveClientService {

  private final WebClient webClient;

  public ReactiveClientService(WebClient.Builder webClientBuilder) {
    this.webClient = webClientBuilder.baseUrl("http://service-b").build();
  }

  public Mono<String> getRemoteData(String id) {
    return webClient.get()
      .uri("/api/data/{id}", id)
      .retrieve()
      .onStatus(HttpStatusCode::is4xxClientError,
          resp -> Mono.error(new IllegalArgumentException("Bad request for " + id)))
      .bodyToMono(String.class)
      .timeout(Duration.ofSeconds(3));
  }
}

Usage notes:

  • RestTemplate still works but is in maintenance mode. For new blocking code (Spring MVC apps), use RestClient.

  • Use WebClient when your stack is reactive (WebFlux) or you need streaming. Pulling in WebFlux just to call block() in an MVC app adds complexity for little gain now that RestClient exists.

  • Spring also supports declarative HTTP interface clients (@GetExchange and friends) backed by RestClient or WebClient. The Spring Cloud team considers OpenFeign feature-complete, so HTTP interfaces are a good answer for "what would you use instead of Feign?"

Table: RestTemplate vs RestClient vs WebClient (side-by-side pros/cons)

Feature

RestTemplate

RestClient

WebClient

API type

Blocking, template methods

Blocking, fluent

Non-blocking, reactive

Typical use

Existing code

New Spring MVC services

WebFlux apps, streaming, high fan-out

Resource usage

One thread per in-flight call

One thread per in-flight call (cheap with virtual threads)

Few threads, event loop

Streaming support

Limited

Limited

First-class (Flux)

Error handling

ResponseErrorHandler

onStatus() on retrieve()

onStatus() + reactive error signals

Learning curve

Low

Low

Higher (Reactor, backpressure)

Interview talking points & best practices

  • Service discovery: explain client-side (Eureka + Spring Cloud LoadBalancer) vs server-side (gateway or Kubernetes Service) discovery.

  • Config management: mention Spring Cloud Config, @RefreshScope, and Vault for secrets.

  • Resilience: discuss circuit breakers (Resilience4j), retries with backoff and jitter, timeouts, and bulkheads.

  • Observability: trace calls with Micrometer Tracing (which replaced Spring Cloud Sleuth in Spring Boot 3) exporting to OpenTelemetry or Zipkin, and propagate trace IDs into logs.

  • Protocol considerations: consider gRPC for internal calls when low latency and streaming matter.

Quick checklist for interview answers

  • Define the role of each Spring Cloud component (discovery, config, gateway, load balancer, circuit breaker).

  • Show code: a RestClient or WebClient call with a timeout and a circuit breaker.

  • Describe trade-offs: the operational cost of a service registry vs the benefits of health-aware routing.

  • Mention data ownership (one database per service), sagas, and idempotency.

Spring Boot 3 Interview Questions (Jakarta, Native, Observability, Virtual Threads)

If you have worked on a Spring Boot 3 codebase, interviewers expect you to know what changed and why. These questions come up more and more in interviews for experienced developers.

What changed in Spring Boot 3, and what is the Jakarta EE migration?

Spring Boot 3 (built on Spring Framework 6) requires Java 17 or later and moved from Java EE javax.* packages to Jakarta EE jakarta.* packages.

The namespace change touches servlets, JPA, validation, and more, so javax.persistence.Entity becomes jakarta.persistence.Entity and javax.validation.constraints.NotNull becomes jakarta.validation.constraints.NotNull. Every library you use must also have a Jakarta-compatible version. Other notable changes: Spring Security 6 (no WebSecurityConfigurerAdapter, requestMatchers instead of antMatchers), Hibernate 6 as the JPA provider, auto-configuration registered only through the AutoConfiguration.imports file, trailing-slash URL matching turned off by default, ProblemDetail support, Micrometer Observation and Tracing replacing Spring Cloud Sleuth, and first-class GraalVM native image support.

// Spring Boot 2.x
import javax.persistence.Entity;
import javax.validation.constraints.NotBlank;

// Spring Boot 3.x
import jakarta.persistence.Entity;
import jakarta.validation.constraints.NotBlank;

How would you migrate? Upgrade to the latest Spring Boot 2.7 first and fix its deprecation warnings, move to Java 17, then upgrade to 3.x. Use automated refactoring such as OpenRewrite recipes for the javax to jakarta rename, add spring-boot-properties-migrator temporarily to report renamed properties, and upgrade third-party libraries to Jakarta-compatible versions. Run the full test suite at each step.

What are GraalVM native images in Spring Boot, and what are the trade-offs?

A native image compiles your application ahead of time into a standalone platform-specific executable, which starts in a fraction of the time of a JVM app and uses less memory.

Spring Boot 3 supports this through its AOT (ahead-of-time) engine, which analyzes the application context at build time and generates code and hints for GraalVM. Build with ./mvnw -Pnative native:compile (when using the Spring Boot parent) or ./gradlew nativeCompile, or produce a container with ./mvnw -Pnative spring-boot:build-image.

  • Pros: very fast startup, lower memory footprint, good fit for scale-to-zero, serverless, and CLI tools.

  • Cons: much longer builds; a closed-world assumption, so reflection, dynamic proxies, and resources must be known at build time (registered through RuntimeHints or @RegisterReflectionForBinding when libraries do not provide them); the bean set is fixed at build time, so @Profile and @ConditionalOnProperty cannot change which beans exist at runtime; and no JIT, so peak throughput for long-running services can be lower than on the JVM.

Senior answer: choose native when startup time and memory dominate cost; stay on the JVM for long-running, throughput-heavy services. Spring Boot 3.3 also added Class Data Sharing (CDS) support as a lighter-weight way to improve JVM startup.

How does observability work in Spring Boot 3 (Micrometer, tracing)?

Spring Boot 3 uses Micrometer for metrics and Micrometer Tracing for distributed tracing, both driven by the Micrometer Observation API, so one instrumentation point can produce a timer metric and a trace span.

Add spring-boot-starter-actuator, a tracing bridge (micrometer-tracing-bridge-otel or micrometer-tracing-bridge-brave), and an exporter (for example OTLP or Zipkin). Spring MVC and WebFlux endpoints, RestClient, WebClient, and RestTemplate (when built from the auto-configured builders) are instrumented automatically. The default sampling probability is 10%, controlled by management.tracing.sampling.probability. When tracing is on, recent Spring Boot 3 versions add trace and span IDs to log lines, which is how you correlate logs across services.

@Service
public class OrderProcessor {

  private final ObservationRegistry registry;

  public OrderProcessor(ObservationRegistry registry) {
    this.registry = registry;
  }

  public void process(Order order) {
    Observation.createNotStarted("orders.process", registry)
        .lowCardinalityKeyValue("channel", order.channel())
        .observe(() -> doProcess(order)); // timer metric + span
  }
}

Pitfall: only put low-cardinality values (channel, region, status) in metric tags. User IDs or order IDs in tags create a new time series per value and can take down your metrics backend.

How do virtual threads work with Spring Boot?

On Java 21 or later with Spring Boot 3.2 or later, setting spring.threads.virtual.enabled=true makes the embedded web server and Spring's task executors use virtual threads, so blocking code scales to many more concurrent requests without a reactive rewrite.

# application.yml (Java 21+, Spring Boot 3.2+)
spring:
  threads:
    virtual:
      enabled: true

Virtual threads are cheap, JVM-managed threads that unmount from their carrier thread while blocked on I/O. They help I/O-bound, thread-per-request apps; they do not make CPU-bound work faster.

  • Downstream limits still apply: 10,000 virtual threads still share your database connection pool. Size the pool deliberately and consider rate-limiting calls to fragile dependencies.

  • Pinning: on Java 21, blocking inside a synchronized block pins the virtual thread to its carrier. JDK 24 removed most of this pinning; on 21, prefer ReentrantLock around blocking calls.

  • ThreadLocal usage: caching expensive objects in ThreadLocal stops working well when each task gets a new thread.

  • Virtual threads vs WebFlux: virtual threads keep simple imperative code; WebFlux still makes sense for streaming and backpressure.

What about Spring Boot 4?

Spring Boot 4.0 was released in November 2025 on top of Spring Framework 7. It keeps Java 17 as the minimum, moves to Jakarta EE 11, splits auto-configuration into smaller technology-specific modules, adopts JSpecify null-safety annotations, and adds built-in API versioning. Everything in this guide about Spring Boot 3 concepts still applies; the main differences you will notice are dependency and package names (for example, test-slice annotations moved into technology-specific test modules) and removed deprecations such as @MockBean. If the job posting mentions Boot 4, read its migration guide before the interview.

Production readiness & monitoring

Make your service production-ready by exposing safe Actuator endpoints, instrumenting app behavior with Micrometer, and shipping metrics to a backend such as Prometheus with Grafana dashboards.

Production readiness is about observability, safe defaults, and operational hygiene, not just "it runs." Expose only the Actuator endpoints you need, secure them, emit meaningful metrics and traces, and alert on what users actually feel.

Actuator endpoints you should know (health, metrics)

Key Actuator endpoints are /actuator/health, /actuator/metrics, /actuator/prometheus, and /actuator/info, used for health checks, telemetry, and deployment validation.

Short notes:

  • /actuator/health - aggregate health, plus /actuator/health/liveness and /actuator/health/readiness probe groups (enabled automatically on Kubernetes, or with management.endpoint.health.probes.enabled=true).

  • /actuator/metrics - counters, timers, and gauges from Micrometer, such as http.server.requests and hikaricp.connections.active.

  • /actuator/prometheus - available when micrometer-registry-prometheus is on the classpath and the endpoint is exposed.

  • /actuator/info - build and git information.

  • Debugging endpoints: /actuator/threaddump, /actuator/heapdump, /actuator/loggers (change log levels at runtime), /actuator/conditions, /actuator/beans.

Security: By default only health is exposed over HTTP. Opt in with management.endpoints.web.exposure.include=health,info,prometheus, and never expose everything publicly. Use Spring Security, a separate management port, or network rules.

Custom health indicators & Micrometer examples

Add HealthIndicator implementations for domain checks, and use Micrometer Counter and Timer to emit business and performance metrics.

Custom HealthIndicator (example):

@Component
public class PaymentGatewayHealthIndicator implements HealthIndicator {

  private final PaymentGatewayClient client;

  public PaymentGatewayHealthIndicator(PaymentGatewayClient client) {
    this.client = client;
  }

  @Override
  public Health health() {
    try {
      boolean reachable = client.ping();
      return reachable
          ? Health.up().withDetail("paymentGateway", "reachable").build()
          : Health.down().withDetail("paymentGateway", "unreachable").build();
    } catch (Exception ex) {
      return Health.down(ex).build();
    }
  }
}

Spring Boot already contributes health indicators for the DataSource, Redis, Kafka, and others, so write custom ones only for dependencies Boot does not know about. Be careful about including external dependencies in the liveness probe: if a downstream outage fails liveness, Kubernetes will restart healthy pods for no reason.

Micrometer counters & timers (example):

@Service
public class OrderService {
  private final Counter ordersCreated;
  private final Timer processTimer;

  public OrderService(MeterRegistry registry) {
    this.ordersCreated = Counter.builder("orders.created")
        .description("New orders")
        .register(registry);
    this.processTimer = Timer.builder("orders.process.time")
        .publishPercentiles(0.5, 0.95)
        .register(registry);
  }

  public void process(Order order) {
    processTimer.record(() -> {
      // processing logic
      ordersCreated.increment();
    });
  }
}

Prometheus scraping: add micrometer-registry-prometheus, include prometheus in the exposed endpoints, and let the Prometheus server scrape /actuator/prometheus.

Checklist: production logging, profiles, secrets management (Vault)

Production logging

  • Use structured JSON logs for easier parsing. Spring Boot 3.4 added built-in structured logging (for example logging.structured.format.console=ecs); earlier versions typically use a Logback JSON encoder.

  • Include trace or correlation IDs in logs and propagate them across threads and services.

  • Use INFO in production and change levels temporarily through /actuator/loggers instead of redeploying.

Profiles & configuration

  • Use Spring profiles (application-prod.yml) to separate environment settings.

  • Keep production secrets out of property files and Git; inject them from Vault, a cloud secrets manager, or platform secrets.

  • Make config reloadable only where it is safe (Config Server + @RefreshScope, or Vault dynamic secrets).

Secrets management (Vault best practices)

  • Do not hardcode secrets; inject them at runtime from Vault or a KMS-backed secrets manager.

  • Use short-lived credentials where possible (for example, database credentials from Vault dynamic secrets).

  • Authenticate apps to Vault with an instance identity (IAM role, Kubernetes service account) rather than static tokens.

Micrometer in-app instrumentation, Prometheus scraping, and Grafana dashboards

Quick operational tips

  • Alert on symptoms, not just metrics: error rate, p95/p99 latency, and connection pool saturation.

  • Use the right probes: point Kubernetes readiness at /actuator/health/readiness and liveness at /actuator/health/liveness.

  • Enable graceful shutdown: server.shutdown=graceful (the default since Spring Boot 3.4) lets in-flight requests finish during deployments.

  • Add runbook links to alerts: include the playbook URL and trace ID for faster recovery.

Testing Spring Boot applications

Unit vs integration tests

Unit tests verify small, isolated units (fast, no Spring context); integration tests load Spring slices or the full context to validate wiring, persistence, and web layers.

Short explanation:

  • Unit tests: Plain JUnit 5 + Mockito for service logic and utilities. Constructor injection makes this easy because you just call new.

  • Integration tests (slices): @WebMvcTest and @DataJpaTest load focused parts of the framework with auto-configured helpers (MockMvc, TestEntityManager).

  • Full integration: @SpringBootTest boots the whole app (useful for end-to-end flows, but slower).

  • Pitfall: Relying only on full-context tests makes feedback slow. Favor many unit tests, targeted slice tests, and a few end-to-end tests.

@WebMvcTest, @DataJpaTest, @SpringBootTest usage examples

@WebMvcTest: Loads only the web layer (controllers, advice, Jackson, validation) and auto-configures MockMvc.

  • When: Test controllers, request validation, security rules, and serialization.

  • Pitfall: Services and repositories are not loaded, so you must mock them with @MockitoBean.

@DataJpaTest: Loads JPA components and, by default, replaces your DataSource with an embedded database if one is on the classpath. Each test runs in a transaction that rolls back.

  • When: Test repository queries, mappings, and @Entity behavior.

  • Tip: Test against the same database engine you run in production (with Testcontainers) so SQL dialect differences do not surprise you.

@SpringBootTest: Boots the full application context; use webEnvironment = RANDOM_PORT to run a real server for end-to-end tests.

  • When: Full integration tests, or when multiple layers must be validated together.

  • Pitfall: Slow. Use sparingly and avoid unnecessary context variations, since each distinct configuration creates a new cached context.

Code: MockMvc example

Use @WebMvcTest + MockMvc to assert controller behavior and validation without starting the full app. This uses the UserController and UserDto from the Web & REST section above.

Test using MockMvc (Spring Boot 3.4+)

// src/test/java/com/example/demo/controller/UserControllerTest.java
package com.example.demo.controller;

import com.example.demo.dto.UserDto;
import com.example.demo.service.UserService;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;

import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@WebMvcTest(UserController.class)
class UserControllerTest {

  @Autowired MockMvc mockMvc;
  @Autowired ObjectMapper objectMapper;

  @MockitoBean UserService userService;

  @Test
  void createUser_returns201_andId() throws Exception {
    var dto = new UserDto("Alice", "alice@example.com", "s3cr3t-pass");
    when(userService.createUser(dto)).thenReturn("id-123");

    mockMvc.perform(post("/api/users")
        .contentType(MediaType.APPLICATION_JSON)
        .content(objectMapper.writeValueAsString(dto)))
      .andExpect(status().isCreated())
      .andExpect(jsonPath("$.id").value("id-123"));
  }

  @Test
  void createUser_validationError_returns400() throws Exception {
    var dto = new UserDto("", "bad-email", "123"); // invalid
    mockMvc.perform(post("/api/users")
        .contentType(MediaType.APPLICATION_JSON)
        .content(objectMapper.writeValueAsString(dto)))
      .andExpect(status().isBadRequest())
      .andExpect(jsonPath("$.errors").exists());
  }
}

Version note: @MockitoBean (from spring-test) replaced Spring Boot's @MockBean, which was deprecated in Spring Boot 3.4 and removed in 4.0. On Spring Boot 3.0 to 3.3, use @MockBean from org.springframework.boot.test.mock.mockito. Because UserDto is a record, equals works on values, so the stub matches the deserialized request.

Run it yourself:

  • Create a Spring Boot project with Spring Web, Validation, and spring-boot-starter-test.

  • Add the controller, DTO, service, exception handler, and the test above.

  • Run ./mvnw test or use your IDE test runner.

Integration test against a real database with Testcontainers (Spring Boot 3.1+):

@SpringBootTest
@Testcontainers
class OrderRepositoryIT {

  @Container
  @ServiceConnection
  static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16");

  @Autowired OrderRepository orders;

  @Test
  void savesAndFindsOrder() {
    Order saved = orders.save(new Order("SKU-1", 2));
    assertThat(orders.findById(saved.getId())).isPresent();
  }
}

@ServiceConnection tells Spring Boot to wire the DataSource to the container automatically, replacing the older @DynamicPropertySource boilerplate.

Test pyramid showing many unit tests, fewer integration tests, and few end-to-end tests.

Quick checklist for interviews & real projects

  • Write fast unit tests for business logic (no Spring context).

  • Use @WebMvcTest for controller behavior, validation, and security rules (MockMvc).

  • Use @DataJpaTest or Testcontainers for repository testing against a real database engine.

  • Use a few @SpringBootTest(webEnvironment = RANDOM_PORT) tests for end-to-end behavior.

  • Keep tests deterministic: mock external calls, isolate data, and reset state between tests.

Deployment & CI/CD

Package, containerize, and automate: pick the packaging model (JAR/WAR), build a small Docker image, and wire a simple CI pipeline to run tests, build artifacts, and push images.

Packaging: JAR vs WAR

Use an executable JAR (embedded server) for cloud and container apps; use a WAR only for legacy app-server deployment policies.

Short explanation:

  • JAR (recommended): The Spring Boot Maven or Gradle plugin packages the app with an embedded server (Tomcat, Jetty, or Undertow) so you run java -jar app.jar. Easy for Docker and cloud.

  • WAR (legacy): Build a WAR to deploy to an external servlet container. Requires SpringBootServletInitializer and different packaging. Use when your organization mandates central app servers.

  • No Dockerfile at all: ./mvnw spring-boot:build-image (or ./gradlew bootBuildImage) builds an OCI image with Cloud Native Buildpacks.

Dockerfile example + GitHub Actions CI snippet

A minimal multi-stage Dockerfile keeps the runtime image small, and a GitHub Actions workflow runs tests, builds the JAR, builds the image, and pushes it to a registry.

Dockerfile (multi-stage, Maven)

# ----- build stage -----
FROM eclipse-temurin:21-jdk AS build
WORKDIR /workspace
COPY mvnw pom.xml ./
COPY .mvn .mvn
RUN ./mvnw -q dependency:go-offline

COPY src ./src
RUN ./mvnw -q -DskipTests package

# ----- runtime stage -----
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY --from=build /workspace/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/app.jar"]

Modern JVMs are container-aware by default, so they respect the container's CPU and memory limits. Tune heap with -XX:MaxRAMPercentage rather than a fixed -Xmx if you need to.

GitHub Actions workflow (ci.yml)

name: CI

on: [push, pull_request]

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    env:
      REGISTRY: ghcr.io
      IMAGE_NAME: ${{ github.repository }}
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up JDK 21
        uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: 21
          cache: maven

      - name: Build & test
        run: ./mvnw -B clean verify

      - name: Build Docker image
        run: docker build -t $REGISTRY/${IMAGE_NAME}:${{ github.sha }} .

      - name: Log in to registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ secrets.REGISTRY_USER }}
          password: ${{ secrets.REGISTRY_TOKEN }}

      - name: Push image
        run: docker push $REGISTRY/${IMAGE_NAME}:${{ github.sha }}

Tips: add REGISTRY_USER and REGISTRY_TOKEN as repository secrets, tag images with the commit SHA so every deployment is traceable, and adjust the login step for Docker Hub, GCR, or ECR.

Deployment options (Heroku / ECS / EKS / Cloud Run) - pros/cons

Platform

Pros

Cons

When to pick

Heroku

Fast PaaS, simple git push, good developer experience

Less control, cost at scale

MVPs, small teams

ECS (Fargate)

AWS native, serverless containers, integrates with IAM

AWS configuration complexity

Teams on AWS wanting managed containers

EKS (Kubernetes)

Full control, powerful orchestration, huge ecosystem

Operational complexity, steep learning curve

Large-scale microservices with custom needs

Cloud Run

Serverless containers, scale to zero, simple

Cold starts, limited control over infrastructure

Event-driven APIs, cost-sensitive scaling (a good fit for native images)

Tip: always wire your health checks and readiness probe into the platform. Orchestration without good health checks is guesswork.

Scenario-Based Spring Boot Interview Questions

Experienced candidates are rarely asked "what is @Transactional?" They are asked what they would do when something breaks. A strong answer follows the same shape every time: how you would confirm the problem, the likely causes in order, the fix, and how you would prevent it from coming back.

Scenario 1: Your Spring Boot service is slow under load. How do you find the bottleneck?

Measure before you change anything: find out whether time is spent waiting (on the database, a downstream service, or a pool) or computing (CPU, garbage collection), then fix the biggest wait first.

  1. Confirm and locate: look at p95/p99 latency in http.server.requests per endpoint, and at a few distributed traces for slow requests. Traces show immediately whether time is spent in SQL, an HTTP call, or your own code.

  2. Check saturation: hikaricp.connections.pending above zero means requests are queuing for a database connection (HikariCP's default pool size is 10). Also check busy web server threads (Tomcat's default maximum is 200), CPU, and GC pause times. Take a thread dump from /actuator/threaddump: if most threads are waiting in the same place, you have found the bottleneck.

  3. Usual culprits: N+1 queries or missing indexes; connections held too long (open-in-view, or remote calls made inside a @Transactional method); downstream calls without timeouts; synchronous work that could be cached or made asynchronous; excessive logging or large JSON payloads.

  4. Fix and verify: apply one change at a time and re-run a load test (Gatling, JMeter, or k6) against a production-like environment to prove the improvement.

Always set explicit timeouts on outbound calls. A missing timeout is the most common reason one slow dependency makes the whole service slow:

@Bean
RestClient inventoryClient(RestClient.Builder builder) {
  var factory = new SimpleClientHttpRequestFactory();
  factory.setConnectTimeout(Duration.ofSeconds(2));
  factory.setReadTimeout(Duration.ofSeconds(3));
  return builder
      .baseUrl("http://inventory-service")
      .requestFactory(factory)
      .build();
}

What not to say: "I would increase the thread pool and the connection pool." Bigger pools often move the bottleneck to the database and make things worse. Explain that you would size pools based on measurements.

Scenario 2: A bean isn't being injected. How do you debug it?

Read the startup failure message first. Spring Boot's failure analyzer usually tells you exactly which constructor parameter could not be satisfied, and the cause is almost always one of a handful of issues.

  • NoSuchBeanDefinitionException ("required a bean of type ... that could not be found"):

    • The class is outside the component-scan base package (the package of your @SpringBootApplication class and its sub-packages).

    • The class is missing a stereotype annotation (@Service, @Component) or @Bean method.

    • A condition did not match: the bean is behind @Profile, @ConditionalOnProperty, or an auto-configuration that backed off. Run with --debug or check /actuator/conditions.

    • In a test slice (@WebMvcTest), services are intentionally not loaded; mock them with @MockitoBean.

  • NoUniqueBeanDefinitionException: two beans of the same type exist. Mark one @Primary, or inject by @Qualifier.

  • Field is null at runtime: the object was created with new, so Spring never managed it, or you are using field injection in a class Spring does not own.

  • Circular dependency error: two beans need each other through their constructors. Refactor responsibilities instead of re-enabling circular references.

@Configuration
public class PaymentConfig {

  @Bean
  @Primary
  PaymentGateway stripeGateway() { return new StripeGateway(); }

  @Bean
  PaymentGateway fallbackGateway() { return new ManualReviewGateway(); }
}

@Service
public class RefundService {
  private final PaymentGateway gateway;

  public RefundService(@Qualifier("fallbackGateway") PaymentGateway gateway) {
    this.gateway = gateway;
  }
}

/actuator/beans lists every bean in the context, which confirms whether your bean exists at all.

Scenario 3: @Transactional isn't rolling back. What went wrong?

Check three things in order: whether the call actually went through the Spring proxy, whether the exception type triggers a rollback, and whether the exception was swallowed.

@Service
public class TransferService {

  public void transferAll(List<Transfer> transfers) {
    transfers.forEach(this::transfer); // self-invocation: proxy bypassed, NO transaction
  }

  @Transactional
  public void transfer(Transfer t) throws InsufficientFundsException {
    debit(t);
    credit(t);
    // A checked exception here does NOT roll back by default
  }
}
  • Self-invocation: calls through this skip the proxy. Move the transactional method to another bean, or put @Transactional on the outer public method.

  • Checked exceptions: use @Transactional(rollbackFor = Exception.class) or throw an unchecked exception.

  • Swallowed exceptions: a try/catch that logs and continues means the transaction commits.

  • Other causes: private methods, a method called on an object not created by Spring, or a table using a non-transactional storage engine.

Scenario 4: How would you secure this REST API?

Walk through it in layers: who is calling (authentication), what they may do (authorization), what they send (input validation), and what the platform exposes (transport and operational hardening).

  1. Authentication: accept OAuth2 access tokens (JWTs) from your identity provider through oauth2ResourceServer().jwt(), with a stateless session policy (see the Security section for the full configuration).

  2. Authorization: coarse URL rules in the SecurityFilterChain, plus @PreAuthorize on service or controller methods for fine-grained checks. Also check ownership ("can this user see this order?"), because role checks alone do not prevent broken object-level authorization.

  3. Input: validate every request DTO with Bean Validation, use parameterized queries (Spring Data does this for you), and never bind request bodies directly to entities.

  4. Transport and defaults: HTTPS only, strict CORS, security headers on, CSRF disabled only because no cookies are used.

  5. Operations: expose only health publicly, keep secrets in a secrets manager, add rate limiting at the gateway, log authentication failures, and scan dependencies for known vulnerabilities.

@Service
public class OrderQueryService {

  private final OrderRepository orders;

  public OrderQueryService(OrderRepository orders) {
    this.orders = orders;
  }

  @PreAuthorize("hasAuthority('SCOPE_orders.read')")
  public OrderDto findForCurrentUser(Long orderId, Jwt jwt) {
    Order order = orders.findById(orderId).orElseThrow(OrderNotFoundException::new);
    if (!order.getCustomerId().equals(jwt.getSubject())) {
      throw new OrderNotFoundException(); // do not reveal that the order exists
    }
    return OrderDto.from(order);
  }
}

Scenario 5: How do you manage configuration across dev, staging, and production?

Keep one artifact and change only configuration: shared defaults in application.yml, environment differences in profiles or environment variables, typed and validated binding with @ConfigurationProperties, and secrets injected at runtime.

# application.yml
app:
  payments:
    base-url: http://localhost:8089
    timeout: 3s
---
spring:
  config:
    activate:
      on-profile: prod
app:
  payments:
    base-url: https://payments.internal.example.com
    timeout: 2s
@Validated
@ConfigurationProperties(prefix = "app.payments")
public record PaymentsProperties(
    @NotBlank String baseUrl,
    @NotNull Duration timeout) {}
  • Activate the profile from outside the artifact: SPRING_PROFILES_ACTIVE=prod.

  • Override without rebuilding: environment variables and command-line arguments take precedence over packaged files, so APP_PAYMENTS_BASEURL overrides app.payments.base-url.

  • Secrets: never in Git. Inject them from Vault, a cloud secrets manager, or Kubernetes Secrets.

  • Fail fast: @Validated properties stop the app at startup when a required value is missing, instead of failing on the first real request.

  • Many services: centralize with Spring Cloud Config or Kubernetes ConfigMaps so a change does not require editing every repository.

Scenario 6: A downstream service keeps timing out and is taking your service down with it. What do you do?

Contain the failure: short timeouts so threads are released, a circuit breaker so you stop calling a dependency that is already failing, a bulkhead so one dependency cannot use every thread, and a fallback so users get a degraded answer instead of an error.

  • Immediate: add or tighten timeouts on the client (see Scenario 1).

  • Circuit breaker: Resilience4j @CircuitBreaker with a fallback, as shown in the Microservices section.

  • Bulkhead: limit concurrent calls to that dependency (Resilience4j @Bulkhead) so the rest of the service keeps working.

  • Retries with care: only for idempotent calls, few attempts, exponential backoff with jitter.

  • Longer term: cache results that can be slightly stale, or move the interaction to asynchronous messaging if the caller does not need an immediate answer.

Advanced topics & system design (senior-level)

Senior interviews expect system-level thinking: resilience patterns, observability, scaling strategies, and trade-offs for distributed systems. For the design round itself, see our guide to system design interviews for senior engineers.

Designing resilient microservices

Design resilient microservices with timeouts, retries with backoff, circuit breakers, bulkheads, and graceful degradation.

Short explanation:

  • Timeouts: Set sensible request timeouts to avoid resource exhaustion.

  • Retries with jitter/backoff: Retry transient errors only; avoid thundering herds.

  • Circuit breakers: Fail fast when a downstream is unhealthy and provide fallbacks.

  • Bulkheads: Partition resources (thread pools, semaphores) per downstream to prevent cascading failures.

  • Idempotency: Accept an idempotency key on write endpoints so client retries do not create duplicates.

  • Graceful degradation: Return cached or reduced functionality when dependencies fail.

Design note: discuss trade-offs (user experience vs complexity) and where each pattern belongs: client-side, in the gateway, or in the service.

Observability: tracing, correlation IDs, OpenTelemetry

Combine traces, logs, and metrics, linked by trace IDs, so you can follow one request across services.

Short explanation:

  • Correlation: with Micrometer Tracing, the W3C traceparent header is propagated automatically and trace IDs appear in logs. For systems outside tracing, pass a request ID header (for example X-Request-ID) and log it.

  • Tracing: export spans through OpenTelemetry (OTLP) or Zipkin; choose a sampling rate that balances cost and visibility.

  • Metrics: emit business and system metrics with Micrometer and scrape them with Prometheus or push them through OTLP.

  • Why: observability is how you troubleshoot latency and errors in production instead of guessing.

Multi-tenant strategies & DB scaling

Choose shared schema, schema-per-tenant, or database-per-tenant based on isolation needs, scale, and operational cost.

Options:

  • Single DB, shared schema: Easiest to manage; use a tenant_id column on every table; lowest isolation, so every query must filter by tenant.

  • Schema per tenant: Better isolation, manageable with migration tooling (Flyway or Liquibase run per schema); a good middle ground.

  • DB per tenant: Highest isolation; highest operational cost; best for strict compliance needs.

  • DB scaling tips: read replicas for read-heavy traffic, partitioning and sharding for large tables, caching (Redis) for hot data, and connection pools sized per instance so the total across all instances stays below the database's connection limit.

Coding tasks & whiteboard prompts (practical)

Many Spring Boot interviews include a take-home or live-coding task. Know what "done" looks like before you start.

Small REST API implementation task (acceptance criteria)

Task: Implement a simple Task Manager REST API with endpoints to create, get, update, and list tasks.
Acceptance criteria:

  • POST /api/tasks creates a task and returns 201 with its id. The title must not be empty.

  • GET /api/tasks/{id} returns 200 with the task or 404 if not found.

  • PUT /api/tasks/{id} updates title and description and returns 200.

  • GET /api/tasks supports pagination (page, size) and an optional status filter.

  • Use @Valid for input validation, correct status codes, and a @RestControllerAdvice for errors.

  • Include 3 unit tests for service logic and 2 MockMvc tests for controller endpoints.

Evaluation focus: API correctness, DTOs, validation, transaction handling, tests, and a clear README with run instructions.

Debugging a stacktrace - guided exercise

Diagnose a failing call by reading the stacktrace, identifying the root cause (for example LazyInitializationException or NullPointerException), and proposing a fix and a test.

Example stacktrace (shortened):

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.example.Post.comments: could not initialize proxy - no Session
  at org.hibernate.collection.spi.AbstractPersistentCollection.throwLazyInitializationException(...)
  ...
  at com.example.controller.PostController.getPost(PostController.java:45)

Debug steps:

  1. Read the exception: a lazy collection was accessed after the persistence context closed.

  2. Find the code line: PostController.getPost reads post.getComments(), or Jackson serializes the entity after the transaction ended.

  3. Fix options:

    • Use a fetch join in the repository (findByIdWithComments) or @EntityGraph.

    • Initialize what you need inside a @Transactional service method.

    • Map to a DTO in the service layer and return that instead of the entity.

  4. Test: add an integration test that loads the entity through the service and asserts the comments are present.

  5. Explain trade-offs: a fetch join on a collection complicates pagination; DTOs avoid entity lifecycle issues entirely. Turning on open-in-view hides the error but does not fix the design.

Study plan & cheat sheet (30/60/90 days)

Structured study: fundamentals first (30 days), then mid-level topics and testing (60 days), then system design, microservices, and mock interviews (90 days). If your interview is sooner, compress each block but keep the order.

30 / 60 / 90 breakdown (daily topics)

30 days - Fundamentals (about 1 hour a day)

  • Week 1: Spring Boot basics, IoC and DI, @SpringBootApplication, starters, running apps.

  • Week 2: Controllers, REST, validation, exception handling. Build a small API.

  • Week 3: JPA basics, repositories, transactions, pagination.

  • Week 4: Testing basics: unit tests + @WebMvcTest.

60 days - Intermediate (about 1 to 1.5 hours a day)

  • Week 5: Spring Security 6, OAuth2 resource server, and JWT flows.

  • Week 6: Microservices basics, RestClient and WebClient, Spring Cloud concepts.

  • Week 7: Observability: Actuator, Micrometer, tracing.

  • Week 8: Deployment: Docker, a basic CI pipeline, and GitHub Actions.

90 days - Advanced (about 1 to 2 hours a day)

  • Week 9: Resilience patterns: circuit breaker, retries, bulkheads.

  • Week 10: Spring Boot 3 specifics: Jakarta migration, virtual threads, native images.

  • Week 11: Performance tuning and troubleshooting (N+1, connection pools, the scenarios above).

  • Week 12: System design, mock interviews, whiteboard tasks, and a final cheat-sheet review.

Common pitfalls & quick fixes (Do's & Don'ts)

Prevent common mistakes by fixing these anti-patterns in your own projects before the interview. Interviewers reward clarity: present the issue, the fix, and the test you would write.

Do's

  • Do use constructor injection (avoid field injection).

  • Do validate inputs with @Valid and handle errors centrally.

  • Do set timeouts on every outbound call.

  • Do write small, focused tests: unit first, slices second.

  • Do explain trade-offs when choosing tools and patterns.

Don'ts

  • Don't put heavy logic in controllers.

  • Don't rely on @Transactional for private methods or self-invocation.

  • Don't block reactive stacks (avoid blocking calls in WebFlux).

  • Don't expose Actuator endpoints publicly without protection.

  • Don't return JPA entities directly from controllers.

1-page checklist: "Fix these before your interview"

  • Use constructor injection for beans.

  • Use DTOs for external API models; do not expose internal entities.

  • Add @Valid and test invalid payloads.

  • Add Actuator health and a secured Prometheus endpoint.

  • Add unit tests for key service methods.

  • Avoid LazyInitializationException by using DTOs or fetch strategies.

  • Build a small, multi-stage Docker image.

  • Write a README that explains how to run the app and the tests.

Quick-Fire Spring Boot Questions (Rapid Review)

Short answers for last-minute revision. Each is covered in more depth above.

  • How is Spring Boot different from Spring MVC? Spring MVC is the web framework for controllers and views; Spring Boot is the opinionated layer that packages and auto-configures Spring apps, including Spring MVC.

  • How do I validate request input in controllers? Use @Valid on @RequestBody DTOs and annotate fields with Jakarta Bean Validation constraints like @NotBlank and @Email.

  • How do I handle exceptions globally? Use @RestControllerAdvice with @ExceptionHandler methods that return ProblemDetail or a structured error body.

  • How do I test controllers? Use @WebMvcTest + MockMvc for controller slice tests and @SpringBootTest for end-to-end tests.

  • Should I use RestTemplate, RestClient, or WebClient? RestClient for new blocking code, WebClient for reactive or streaming, RestTemplate only in existing code.

  • Which Actuator endpoints should I expose? health, plus info, metrics, or prometheus behind authentication or on an internal port.

  • What is Spring Data JPA? Repository abstractions over JPA that provide CRUD, paging, sorting, and derived queries.

  • How do I prevent leaking DB credentials? Inject them at runtime from Vault, a secrets manager, or platform secrets; never commit them.

  • DTOs or entities in controllers? DTOs, to avoid exposing internal state and lazy-loading issues.

  • How do I debug LazyInitializationException? Find where the lazy association is accessed outside a transaction and load it explicitly (fetch join, entity graph) or map to a DTO.

  • How do I instrument metrics? Use Micrometer counters, timers, or the Observation API, export to Prometheus or OTLP, and visualize in Grafana.

  • How do I run integration tests with a real DB? Testcontainers with @ServiceConnection.

  • How do I enable caching? Add @EnableCaching and annotate methods with @Cacheable; Spring Boot auto-configures a provider such as Caffeine or Redis if present.

  • How do I run a method asynchronously? Add @EnableAsync and annotate a public method on another bean with @Async.

  • How do I schedule a job? Add @EnableScheduling and use @Scheduled(cron = "..."); with several instances, use a distributed lock so the job runs once.

Conclusion

Spring Boot interviews reward clarity, practical examples, and the ability to explain trade-offs. Lead with the one-line answer, back it with a small code sample, and show an operational mindset (testing, security, observability, deployment). For experienced roles, practice the scenario questions out loud: that is where most candidates lose points.

Quick takeaways

  • Lead with a one-line answer, then show a short example.

  • Practice the common patterns: auto-configuration, controllers, JPA, security, HTTP clients.

  • Know what changed in Spring Boot 3: Jakarta namespaces, Spring Security 6, Micrometer Observation, native images, and virtual threads.

  • Prepare 2-3 real mini-demos (Task API, secured endpoint, a fetch-join fix).

  • For 5+ years of experience, rehearse the scenarios: slow service, missing bean, failed rollback, cascading failure.

Reading answers is not the same as saying them under pressure. You can rehearse with Interview Sidekick: its AI mock interviews give you feedback on how you explain your answers, and its bank of 10,000+ real interview questions covers the behavioral and general rounds that come with most backend roles. There is a free tier, and no credit card is required.

Frequently Asked Questions

What are the most common Spring Boot interview questions?

The most common questions are what Spring Boot is and how it differs from Spring, what @SpringBootApplication does, how auto-configuration and starters work, @RestController vs @Controller, validation and global exception handling, Spring Data JPA and @Transactional, the N+1 problem, securing a REST API, Actuator, and testing with @WebMvcTest and @SpringBootTest.

What Spring Boot questions are asked for freshers?

Freshers are usually asked definitions and basics: what Spring Boot is, IoC and dependency injection, @Component vs @Bean, bean scopes, starters, auto-configuration, application.properties and profiles, @RestController, @PathVariable vs @RequestParam, and how to build a simple CRUD REST API with Spring Data JPA.

What Spring Boot questions are asked for 5 or 10 years of experience?

With around 5 years of experience, expect transactions and propagation, the N+1 problem, Spring Security configuration, testing strategy, and debugging scenarios such as a bean not being injected or a transaction not rolling back. With 10 or more years, expect microservice design, resilience patterns, observability, Spring Boot 3 migration, performance under load, and system design trade-offs.

What is new in Spring Boot 3 that interviewers ask about?

Spring Boot 3 requires Java 17, moved from javax.* to jakarta.* packages, ships Spring Security 6 and Hibernate 6, supports GraalVM native images through ahead-of-time processing, uses Micrometer Observation and Tracing for observability, supports ProblemDetail error responses, and (from 3.2 on Java 21) can run on virtual threads with spring.threads.virtual.enabled=true.

Which Spring Boot microservices questions should I prepare?

Prepare service discovery (Eureka or Kubernetes DNS), centralized configuration with Spring Cloud Config, API gateways, circuit breakers and retries with Resilience4j, synchronous vs asynchronous communication, the Saga and outbox patterns for data consistency, and distributed tracing with Micrometer Tracing.

How should I prepare for a Spring Boot interview?

Build one small but complete project (REST API, validation, JPA, security, tests, Docker), learn to explain each part in one sentence, study the Spring Boot 3 changes, and practice scenario questions out loud. Mock interviews help you find gaps in how you explain things before a real interviewer does.

4.8 from 2,500+ reviews

Turn failed interviews into offers accepted

No credit card required • Cancel anytime