Spring Boot interviews for experienced Java developers go far beyond "what is a bean?" Hiring managers want to see that you can ship production APIs—configure auto-wiring correctly, reason about transactions and lazy loading, secure endpoints with OAuth2/JWT, debug slow queries, and explain how your service behaves under load. Loops for 5–10 years of experience often blend framework depth with system design: caching, messaging, deployment on Kubernetes, and trade-offs between monolith modules and microservices.
Below are 45 questions aimed at Spring Boot interview questions for experienced candidates—including senior and 10 years experienced loops. Answers are written so you can learn while prepping: tables, small code samples, with elaborate answers; technical sections include a strong answer sample you can say aloud. Pair this guide with OOP interview questions for SOLID and design-pattern depth, Java interview questions part 1 and part 2 for JVM and concurrency fundamentals, Kafka interview questions for event streaming and messaging depth, Kubernetes interview questions for deploying services to clusters, PostgreSQL interview questions for JPA datasource and query tuning depth, full stack developer interviews for React/Spring integration, Maven interview questions for build and BOM imports, Selenium interview questions for UI test automation alongside @SpringBootTest, and SQL technical interviews when JPA questions turn into query plans.
Tested on: Ubuntu 25.04 (Plucky Puffin); kernel 6.14.0-37-generic; OpenJDK 21.0.9.
Interview context and how to prepare
What do Spring Boot interviews test for experienced developers?
Spring Boot is the default way most Java teams bootstrap web applications, batch jobs, and microservices. Interviewers are not checking whether you memorized every starter artifact—they want proof you can build and operate Spring applications.
Typical skill layers:
| Layer | What interviewers probe |
|---|---|
| Core & DI | Beans, scopes, @Configuration, component scanning |
| Auto-config | How starters wire defaults; when to override |
| Web layer | REST design, validation, exception handling |
| Data | Spring Data JPA, transactions, N+1, migrations |
| Security | Filter chain, JWT/OAuth2, method security |
| Testing | Slice tests, Testcontainers, mocking boundaries |
| Production | Actuator, metrics, profiles, health checks |
| Modern Java | Records as DTOs, virtual threads, Spring Boot 3 / Jakarta |
| Experience | What changes in the room |
|---|---|
| 3–5 years | CRUD APIs, basic JPA, starter dependencies |
| 5–8 years | Transactions, security, testing strategy, performance |
| 8–10+ years | System design, microservices boundaries, operability, mentoring |
Spring Boot vs Spring Framework — what is the difference?
Spring Framework is the core inversion-of-control container: beans, AOP, MVC, transaction abstraction, and integration modules. You can build apps with plain Spring, but you assemble a lot manually—XML or Java config, embedded server setup, dependency versions.
Spring Boot sits on top and optimizes for convention over configuration:
| Concern | Spring Framework alone | Spring Boot |
|---|---|---|
| Dependency versions | You align JAR versions | BOM / parent POM (spring-boot-dependencies) |
| Embedded server | You wire Tomcat/Jetty | spring-boot-starter-web brings Tomcat by default |
| Auto-configuration | Manual @Configuration |
Classpath-driven @EnableAutoConfiguration |
| Run command | Deploy WAR to external server | java -jar app.jar or spring-boot:run |
| Production features | Add Actuator yourself | spring-boot-starter-actuator |
Spring Boot does not replace Spring concepts—you still need to understand beans, @Transactional, and MVC. Boot removes boilerplate so you focus on business code.
What is a typical Spring Boot interview loop for 5–10 years experience?
Loops vary by company (product, bank, consultancy, startup), but experienced Java Spring Boot patterns often look like this:
| Round | Duration | Focus |
|---|---|---|
| Recruiter / HM | 30 min | Projects, team size, cloud exposure |
| Java fundamentals | 45–60 min | Often ties to Java part 2—collections, concurrency, JVM |
| Spring Boot deep dive | 60–90 min | JPA, security, REST, configuration, testing |
| System design | 45–60 min | Microservice boundaries, messaging, caching, deployment |
| Live coding / review | 45–60 min | Fix a controller, write a repository query, debug a test |
| Behavioral | 30–45 min | Incidents, deadlines, code review culture |
For 10 years experienced roles, expect more architecture and operability—how you roll out config changes, handle schema migrations, and observe services in Kubernetes or VM fleets.
What is a realistic 4–6 week prep plan?
Treat prep as one reference application you can explain aloud—not fifty disconnected flashcards.
| Week | Focus | Concrete output |
|---|---|---|
| 1 | Core Boot — starters, auto-config, profiles | Run a small REST app; list beans with Actuator |
| 2 | Data — JPA, transactions, Flyway | CRUD + custom query; reproduce and fix N+1 |
| 3 | Security — JWT or OAuth2 resource server | Protect one endpoint; document filter chain |
| 4 | Testing — slices + Testcontainers | @WebMvcTest, @DataJpaTest, one integration test |
| 5 | Production — metrics, logging, virtual threads | Enable Actuator; try spring.threads.virtual.enabled on Java 21 |
| 6 | Scenarios — design + stories | Whiteboard order service; rehearse two production incidents |
Review Maven BOM imports if interviewers ask how Spring Boot aligns dependency versions.
Core Spring Boot and dependency injection
What does @SpringBootApplication do?
@SpringBootApplication is a composed annotation that combines three important switches:
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}Under the hood it typically includes:
| Annotation | Role |
|---|---|
@SpringBootConfiguration |
Marks the class as a source of @Bean definitions (specialized @Configuration) |
@EnableAutoConfiguration |
Imports auto-configuration classes based on classpath |
@ComponentScan |
Scans the package of the application class and sub-packages for stereotypes |
SpringApplication.run bootstraps the context, starts the embedded web server (if spring-boot-starter-web is present), and registers shutdown hooks.
Common follow-up: How do you exclude an auto-configuration? Use @SpringBootApplication(exclude = SomeAutoConfiguration.class) or spring.autoconfigure.exclude in properties.
A strong answer is:
@SpringBootApplicationenables component scanning and auto-configuration from my main class package downward, andSpringApplication.runstarts the embedded container and refreshes the application context.
How does Spring Boot auto-configuration work?
Auto-configuration is conditional bean registration. Spring Boot loads candidate configuration classes listed in:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(Boot 3.x)- Older Boot 2.x used
spring.factories
Each auto-config class uses @ConditionalOn* annotations, for example:
| Condition | Meaning |
|---|---|
@ConditionalOnClass |
Classpath contains a type (e.g. DataSource) |
@ConditionalOnMissingBean |
User has not defined their own bean of that type |
@ConditionalOnProperty |
Property flag is set |
@ConditionalOnWebApplication |
Running as servlet or reactive web app |
Example flow for JPA:
- Classpath has
spring-boot-starter-data-jpaand a JDBC driver. DataSourceAutoConfigurationcreates aDataSourceif you did not define one.HibernateJpaAutoConfigurationsets upEntityManagerFactorywhen JPA is on the classpath.
You override by defining your own @Bean or setting properties (spring.datasource.url, etc.). In interviews, mention you can inspect what applied via the conditions report (/actuator/conditions when exposed).
A strong answer is:
Auto-configuration registers beans only when classpath and properties match, and backs off when I define my own bean—so defaults are safe to start with but fully overridable.
What are Spring Boot starters and why use them?
Starters are dependency bundles that pull in libraries plus matching auto-configuration. They keep your pom.xml or build.gradle readable.
| Starter | Brings in (high level) |
|---|---|
spring-boot-starter-web |
Spring MVC, Jackson, embedded Tomcat |
spring-boot-starter-data-jpa |
Hibernate, Spring Data JPA, JDBC |
spring-boot-starter-security |
Spring Security filter chain |
spring-boot-starter-test |
JUnit, Mockito, AssertJ, Spring Test |
spring-boot-starter-actuator |
Production endpoints and metrics |
spring-boot-starter-validation |
Bean Validation (Hibernate Validator) |
Starters are not magic—they are curated Maven coordinates. Custom starters follow the same pattern for internal platforms.
Interview tip: connect starters to the Spring Boot BOM imported by spring-boot-starter-parent or spring-boot-dependencies so versions stay aligned—see Maven interview questions.
A strong answer is:
Starters group dependencies that work together and trigger the right auto-configuration, so I add one coordinate instead of juggling ten JAR versions manually.
What is the difference between @Component, @Service, @Repository, and @Controller?
All are stereotypes of @Component—they mark classes for component scanning and register them as Spring beans.
| Stereotype | Typical use | Special behavior |
|---|---|---|
@Component |
Generic bean | None |
@Service |
Business logic layer | Semantic only (documentation) |
@Repository |
Data access | Translates persistence exceptions to Spring DataAccessException |
@Controller |
MVC controller returning views | Pair with @ResponseBody or use @RestController |
@RestController |
REST API | @Controller + @ResponseBody on class |
Layering interviewers expect:
@RestController → @Service → @Repository / JpaRepositoryKeep controllers thin—validation and HTTP mapping only; business rules in services.
A strong answer is:
Stereotypes are all discoverable beans; the names document layer intent, and
@Repositoryadds persistence exception translation while@RestControlleris the REST entry point.
How does dependency injection work in Spring Boot?
Spring creates objects (beans) and wires dependencies through the container.
Common injection styles:
| Style | Example | Notes |
|---|---|---|
| Constructor | public OrderService(OrderRepo repo) |
Preferred—immutable, test-friendly |
| Field | @Autowired private OrderRepo repo |
Works but harder to test; avoid in new code |
| Setter | @Autowired setRepo(...) |
Optional dependencies |
@Autowired resolves by type; use @Qualifier when multiple beans share a type.
Scopes matter in experienced loops:
| Scope | Behavior |
|---|---|
singleton (default) |
One instance per container |
prototype |
New instance per injection |
request / session |
Web scopes for per-request state |
Circular dependencies: constructor injection between two singletons fails at startup—fix by redesigning boundaries or using @Lazy sparingly.
A strong answer is:
I use constructor injection so dependencies are explicit and required at startup; Spring resolves beans from the context by type and honors scope and qualifier metadata.
When do you use @Configuration and @Bean instead of @Component?
Use @Configuration classes when you need programmatic bean setup—third-party classes you cannot annotate, conditional beans, or complex factory logic.
@Configuration
public class JacksonConfig {
@Bean
@ConditionalOnProperty(name = "app.json.pretty", havingValue = "true")
ObjectMapper objectMapper() {
return JsonMapper.builder()
.enable(SerializationFeature.INDENT_OUTPUT)
.build();
}
}| Approach | When |
|---|---|
@Component on your class |
You own the type; let Spring construct it |
@Bean method in @Configuration |
You configure a library class or need explicit lifecycle |
| Auto-configuration | Framework provides sensible default |
@Configuration classes are proxied so @Bean methods stay singleton-safe when they call each other.
A strong answer is:
I put my own business types on
@Service/@Repository, and use@Configuration+@Beanwhen I integrate libraries or need conditional factory methods the framework cannot guess.
Configuration, profiles, and Spring Boot 3
application.properties vs application.yml — how do you choose?
Both feed the same Environment abstraction. Spring Boot loads, in order of precedence (higher wins):
- Command-line args
SPRING_APPLICATION_JSON- Servlet config / JNDI (legacy)
- OS environment variables
application-{profile}.properties|ymlapplication.properties|yml
| Format | Pros |
|---|---|
.properties |
Simple, flat, universal |
.yml |
Hierarchy without repeating prefixes; readable for nested config |
Example profile file: application-prod.yml activated with spring.profiles.active=prod.
Relaxed binding maps spring.datasource.url ↔ SPRING_DATASOURCE_URL in environment variables—important for Kubernetes and cloud deployment interviews.
A strong answer is:
I pick YAML when configuration is nested and repeated prefixes would clutter properties; either way I keep secrets out of git and override with environment-specific files or env vars in production.
What are Spring profiles and how do you use them?
Profiles label beans and configuration for environments—dev, test, staging, prod.
Ways to activate:
spring.profiles.active=dev,localOr @Profile("dev") on a @Configuration or @Bean:
@Configuration
@Profile("dev")
public class DevDataSourceConfig { /* in-memory H2 */ }| Use case | Profile pattern |
|---|---|
| Local H2 vs prod PostgreSQL | @Profile("dev") / @Profile("prod") |
| Mock payment gateway | @Profile("!prod") |
| Integration tests | @ActiveProfiles("test") on test class |
Avoid profile explosion—prefer externalized config (URLs, feature flags) over duplicating entire @Configuration classes per environment.
A strong answer is:
Profiles switch which beans and property files load per environment; I use them for environment-specific infrastructure while keeping business code identical across profiles.
How do you externalize secrets in a Spring Boot production deployment?
Never commit secrets to git. Common patterns:
| Pattern | How it works |
|---|---|
| Environment variables | SPRING_DATASOURCE_PASSWORD injected by K8s/VM |
| Secret managers | AWS Secrets Manager, Azure Key Vault, HashiCorp Vault |
| Spring Cloud Config | Central server with encrypted values |
| Kubernetes secrets | Mounted as files; bind with spring.config.import |
Spring Boot 2.4+ supports:
spring.config.import=optional:vault://Operational practices interviewers like:
- Rotate credentials without redeploying code
- Least-privilege DB users per service
- No secrets in Actuator env dump exposed publicly
A strong answer is:
Secrets live in the platform secret store or environment injection, referenced by property placeholders at runtime—never hard-coded and never logged by my application.
@ConfigurationProperties vs @Value — when do you use each?
| Feature | @Value("${app.page-size}") |
@ConfigurationProperties(prefix="app") |
|---|---|---|
| Binding | Single property | Groups related properties on a type-safe class |
| Validation | Manual | Works with @Validated + JSR-303 |
| Relaxed binding | Limited | pageSize ↔ page-size |
| IDE support | Weaker | Metadata in spring-configuration-metadata.json |
@ConfigurationProperties(prefix = "app.pagination")
public record PaginationProps(int pageSize, int maxPageSize) {}Enable with @EnableConfigurationProperties(PaginationProps.class) or @ConfigurationPropertiesScan.
Use @Value for one-off toggles; use @ConfigurationProperties for structured config you pass around as a bean.
A strong answer is:
I use
@ConfigurationPropertiesfor grouped, validated settings with clear prefixes, and reserve@Valuefor simple single-property injection.
What changed in Spring Boot 3 and the Jakarta EE namespace?
Spring Boot 3 requires Java 17+ and migrates javax.* enterprise APIs to jakarta.* (Servlet, JPA, Validation, etc.).
| Area | Impact |
|---|---|
| Java baseline | 17 minimum; teams adopt 21 for virtual threads |
| Jakarta packages | jakarta.persistence.Entity, jakarta.validation.Valid |
| Security | OAuth2 resource server improvements; lambda DSL defaults |
| Observability | Micrometer Observation API, native tracing hooks |
| AOT / native | GraalVM native image support via Spring AOT |
Migration interview talking points:
- Update imports in entities and controllers
- Confirm third-party libs support Jakarta
- Re-run integration tests and security config
Pair with Java part 2 for records and virtual threads on modern JDKs.
A strong answer is:
Boot 3 moves the ecosystem to Jakarta namespaces and Java 17+, and adds first-class observability and native compilation paths—I plan migrations by dependency compatibility and regression tests, not search-and-replace alone.
Spring Data JPA, transactions, and persistence
How does Spring Data JPA simplify data access?
Spring Data JPA generates repository implementations from interfaces—you declare queries by method name, @Query, or specifications.
public interface OrderRepository extends JpaRepository<Order, Long> {
List<Order> findByStatusOrderByCreatedAtDesc(OrderStatus status);
@Query("select o from Order o join fetch o.customer where o.id = :id")
Optional<Order> findWithCustomer(@Param("id") Long id);
}| Concept | Role |
|---|---|
JpaRepository |
CRUD + paging + batch helpers |
| Derived queries | findBy...And... parsed at startup |
@Query |
JPQL or native SQL when derived names are awkward |
| Projections | Interface or DTO views without loading full entities |
Under the hood: Spring Data creates a JDK proxy that delegates to EntityManager. You still need to understand JPA mapping, flush timing, and SQL—see SQL interviews for index and join depth.
A strong answer is:
Spring Data JPA removes boilerplate DAO code; I use repositories for query declaration but still own entity design, fetch strategy, and transaction boundaries in the service layer.
What is the N+1 problem in JPA and how do you fix it?
N+1 happens when you load N parent rows, then trigger one extra query per row for a lazy association—classic with OneToMany or ManyToOne defaults.
Symptoms:
- API looks fine in dev with little data
- Production list endpoint fires hundreds of SQL statements
- DB CPU spikes; latency grows with page size
Fixes:
| Fix | When |
|---|---|
join fetch in JPQL |
Controlled fetch in one query |
@EntityGraph |
Declarative fetch plan on repository method |
| DTO projection | Select only columns you need |
| Batch fetching | hibernate.default_batch_fetch_size |
| Redesign API | Avoid returning deep object graphs |
@Query("select o from Order o join fetch o.items where o.status = :status")
List<Order> findWithItems(@Param("status") OrderStatus status);Detect with spring.jpa.show-sql=true in dev, Hibernate statistics, or APM traces.
A strong answer is:
N+1 is lazy loading multiplied by row count; I fix it with explicit fetch joins or projections and verify with SQL logging or metrics—not by blindly switching everything to EAGER.
Explain @Transactional propagation and rollback rules.
@Transactional defines a unit of work bound to a persistence context (for JPA) and database connection.
Common propagation values:
| Propagation | Behavior |
|---|---|
REQUIRED (default) |
Join caller's transaction or start new |
REQUIRES_NEW |
Suspend caller; always new transaction |
NESTED |
Nested savepoint if supported |
SUPPORTS |
Run non-transactionally if none exists |
NOT_SUPPORTED |
Suspend existing transaction |
MANDATORY |
Must run inside existing transaction |
NEVER |
Must not run inside a transaction |
Rollback rules:
- Unchecked exceptions roll back by default
- Checked exceptions commit unless you set
rollbackFor - Self-invocation (
this.save()) bypasses the proxy—call through another bean or refactor
@Transactional(rollbackFor = Exception.class)
public void placeOrder(PlaceOrderCommand cmd) { /* ... */ }A strong answer is:
I put
@Transactionalon the service layer with explicit rollback rules, understand propagation when calling external systems, and avoid self-invocation traps that skip the transactional proxy.
Lazy vs eager loading — when do you use each?
| Fetch type | Behavior | Risk |
|---|---|---|
| LAZY (default on collections) | Loads association when accessed | N+1 if accessed in a loop |
| EAGER | Loads with parent | Over-fetching, cartesian products |
Default rule: prefer LAZY; fetch intentionally in queries for each use case.
EAGER on OneToMany is a common production incident—large joins and memory pressure.
For read-heavy screens, prefer read models: DTO queries, materialized views, or CQRS-style separation instead of tuning fetch types globally.
A strong answer is:
I keep associations lazy by default and fetch exactly what each endpoint needs with join fetch or projections—never global EAGER to paper over design gaps.
Why separate entity and DTO in Spring Boot APIs?
Entities map to database tables and carry JPA annotations, lazy associations, and lifecycle concerns. DTOs shape what crosses the API boundary.
Reasons to separate:
| Concern | Entity exposure risk |
|---|---|
| Serialization | Jackson may trigger lazy loads or expose internals |
| Security | Password hash or internal flags leak in JSON |
| API stability | Schema changes should not equal table redesign |
| Performance | DTOs carry only needed fields |
Java records make immutable DTOs concise:
public class RecordDtoDemo {
public record OrderResponse(Long id, String status, java.time.Instant createdAt) {}
public static void main(String[] args) {
var order = new OrderResponse(42L, "PAID", java.time.Instant.parse("2026-06-27T10:00:00Z"));
System.out.println(order.id() + " " + order.status());
}
}When you click Run, you should see 42 PAID on one line—the record exposes accessors without boilerplate getters.
Map with MapStruct, manual mappers, or Spring's mapping helpers—avoid exposing JpaRepository entities directly from controllers.
A strong answer is:
Entities model persistence; DTOs model contracts—I map between them so API evolution, security, and fetch strategy stay under my control.
How do you manage database schema migrations in Spring Boot?
Flyway and Liquibase version schema changes alongside application code.
Flyway example on classpath:
src/main/resources/db/migration/V1__create_orders.sql
src/main/resources/db/migration/V2__add_order_index.sqlspring.flyway.enabled=true
spring.jpa.hibernate.ddl-auto=validate| Setting | Production intent |
|---|---|
ddl-auto=validate |
Hibernate checks mapping matches DB; does not mutate schema |
ddl-auto=update |
Dev only—unsafe for prod |
| Flyway/Liquibase | Repeatable, reviewed SQL or XML changelogs |
Interview scenario: blue/green deploy with backward-compatible migrations (expand-contract pattern)—add column nullable first, deploy code, backfill, add constraint later.
A strong answer is:
I treat schema as code—Flyway migrations in the repo,
ddl-auto=validatein production, and backward-compatible steps so rolling deploys never break old instances.
How do you implement pagination with Spring Data?
Use Pageable and return Page<T> or Slice<T>:
@GetMapping("/orders")
public Page<OrderResponse> list(@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
Pageable pageable) {
return orderService.findOrders(pageable);
}| Return type | Use when |
|---|---|
Page<T> |
You need total count (extra COUNT query) |
Slice<T> |
Infinite scroll; next-page check only |
ScrollPosition (newer APIs) |
Keyset / seek pagination for large tables |
Sort: ?sort=createdAt,desc. Cap size in validation to prevent abuse.
For very large tables, prefer keyset pagination over deep OFFSET—see SQL interviews.
A strong answer is:
I expose page and size with sensible caps, use
Pagewhen totals matter andSliceor keyset when OFFSET becomes expensive at scale.
Spring Security
How does the Spring Security filter chain work?
Spring Security is a chain of servlet filters that run before your dispatcher servlet.
Simplified order:
- Security context persistence
- Logout / anonymous / session management
- Authentication filters (form login, Basic, Bearer token, etc.)
- Authorization (access rules, method security)
- Exception translation to 401/403
In Spring Boot 3, configure with a SecurityFilterChain bean:
@Bean
SecurityFilterChain apiSecurity(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/actuator/health").permitAll()
.anyRequest().authenticated())
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
return http.build();
}Stateless REST APIs typically disable CSRF and use token auth; session-based apps keep CSRF protection.
A strong answer is:
Requests pass through ordered security filters that authenticate credentials and enforce authorization before they reach my controller—I configure one
SecurityFilterChainbean per security domain.
Walk through JWT authentication in a Spring Boot REST API.
Typical stateless JWT flow:
| Step | Actor | Action |
|---|---|---|
| 1 | Client | POST credentials to /login or external IdP |
| 2 | Auth server | Validates user; issues signed JWT (access + optional refresh) |
| 3 | Client | Sends Authorization: Bearer <token> on API calls |
| 4 | Resource server | Validates signature, expiry, audience, issuer |
| 5 | Spring Security | Builds Authentication with granted authorities |
| 6 | Controller | @PreAuthorize or URL rules enforce fine-grained access |
Spring Boot setup:
spring-boot-starter-oauth2-resource-serverspring.security.oauth2.resourceserver.jwt.issuer-uripointing to IdP metadata
Validate: clock skew, key rotation (JWKS endpoint), never log full tokens.
A strong answer is:
The client presents a Bearer JWT; Spring's resource server validates issuer and signature, maps claims to authorities, and my authorization rules run before business logic executes.
OAuth2 resource server vs OAuth2 client — what is the difference?
| Role | Your app is… | Typical use |
|---|---|---|
| Resource server | API protecting resources | Validates incoming JWTs or opaque tokens |
| OAuth2 client | App calling other APIs on behalf of a user | Authorization code flow, token exchange |
| Authorization server | Issues tokens | Keycloak, Entra ID, Okta, or Spring Authorization Server |
A Spring Boot API is usually a resource server. A backend-for-frontend might be both client (to downstream APIs) and resource server (to the browser).
Confuse these in interviews and you will mix up redirect URIs with JWT issuer configuration.
A strong answer is:
My REST API is a resource server that validates tokens; if it must call Google or a partner API with user consent, I add OAuth2 client support for the outbound leg.
Do you need CSRF protection for REST APIs?
CSRF protects cookie-based sessions from cross-site form posts. For stateless JWT in Authorization header, CSRF risk is lower because browsers do not attach custom headers cross-origin without CORS.
| App style | CSRF |
|---|---|
| Session cookie auth | Enable CSRF (or use SameSite cookies + double-submit) |
| Bearer token in header | Often disabled for pure APIs |
| Browser cookie + SPA | Careful—use SameSite, CSRF tokens, or BFF pattern |
If you store JWT in HttpOnly cookies from a SPA, CSRF matters again—see full stack interviews for cookie vs header trade-offs.
A strong answer is:
I enable CSRF when authentication relies on session cookies; for header-based JWT APIs I disable CSRF but still enforce CORS and never move tokens into cookies without a CSRF strategy.
How does method-level security with @PreAuthorize work?
Enable with @EnableMethodSecurity. Annotate service or controller methods:
@PreAuthorize("hasRole('ADMIN') or #order.customerId == authentication.principal.id")
public OrderDto getOrder(Long orderId, Order order) { /* ... */ }Spring evaluates SpEL expressions after authentication. authentication.principal is your UserDetails or JWT claims adapter.
| Annotation | Use |
|---|---|
@PreAuthorize |
Check before method runs |
@PostAuthorize |
Check return value |
@Secured("ROLE_ADMIN") |
Simple role list |
Prefer service-layer enforcement so all entry points (REST, messaging, scheduler) share rules.
A strong answer is:
Method security applies SpEL checks on top of URL rules—I centralize authorization on services with
@PreAuthorizeso every caller path respects the same policy.
REST APIs, validation, and error handling
What are REST API best practices in Spring Boot?
| Practice | Detail |
|---|---|
| Resource URLs | Nouns (/orders/{id}), not verbs |
| HTTP verbs | GET read, POST create, PUT replace, PATCH partial, DELETE remove |
| Status codes | 201 + Location on create; 404 when missing; 409 on conflict |
| Idempotency | PUT/DELETE idempotent; POST is not—use keys for payments |
| Versioning | URL (/v1/), header, or media type—pick one and document |
| Pagination | Query params or Pageable; link headers optional |
Keep controllers thin:
@RestController
@RequestMapping("/api/v1/orders")
@RequiredArgsConstructor
public class OrderController {
private final OrderService orderService;
@PostMapping
ResponseEntity<OrderResponse> create(@Valid @RequestBody CreateOrderRequest req) {
OrderResponse created = orderService.create(req);
URI location = URI.create("/api/v1/orders/" + created.id());
return ResponseEntity.created(location).body(created);
}
}Document with springdoc-openapi or Spring REST Docs for consumer contracts.
A strong answer is:
I model resources with clear HTTP semantics, return correct status codes and DTOs, version intentionally, and document the API so frontend and mobile teams integrate without guesswork.
How do you validate request bodies with Bean Validation?
Add spring-boot-starter-validation and annotate DTOs:
public record CreateOrderRequest(
@NotNull Long customerId,
@NotEmpty @Size(max = 50) List<@NotNull @Positive Integer> itemIds
) {}Controller:
@PostMapping
public OrderResponse create(@Valid @RequestBody CreateOrderRequest req) { /* ... */ }Validation runs before your method body; failures throw MethodArgumentNotValidException—handle globally (next question).
Custom constraints: @interface + ConstraintValidator for domain rules (e.g. valid SKU format).
A strong answer is:
I validate at the boundary with JSR-303 annotations on DTOs, add custom validators for domain rules, and return consistent 400 responses with field-level errors.
How do you handle exceptions globally with @ControllerAdvice?
@ControllerAdvice centralizes exception mapping to HTTP responses:
@RestControllerAdvice
public class ApiExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
ResponseEntity<ProblemDetail> handleValidation(MethodArgumentNotValidException ex) {
ProblemDetail problem = ProblemDetail.forStatus(HttpStatus.BAD_REQUEST);
problem.setTitle("Validation failed");
// map field errors to problem properties
return ResponseEntity.badRequest().body(problem);
}
@ExceptionHandler(OrderNotFoundException.class)
ResponseEntity<ProblemDetail> handleNotFound(OrderNotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, ex.getMessage()));
}
}Prefer RFC 7807 Problem Details (ProblemDetail in Spring 6) over ad-hoc JSON error shapes.
Never leak stack traces to clients in production; log server-side with correlation IDs.
A strong answer is:
@ControllerAdvicemaps domain and validation exceptions to stable Problem Details responses while full diagnostics go to structured logs.
How do you choose HTTP status codes in Spring Boot APIs?
| Situation | Status |
|---|---|
| Success GET | 200 OK |
| Created resource | 201 Created + Location |
| Accepted async job | 202 Accepted |
| No content success | 204 No Content |
| Validation error | 400 Bad Request |
| Missing auth | 401 Unauthorized |
| Authenticated but forbidden | 403 Forbidden |
| Missing resource | 404 Not Found |
| Business rule conflict | 409 Conflict |
| Precondition failed (ETag) | 412 Precondition Failed |
| Rate limited | 429 Too Many Requests |
| Server error | 500 — only for unexpected failures |
Use domain exceptions mapped in @ControllerAdvice so controllers stay clean.
A strong answer is:
I map business outcomes to intentional status codes—404 for missing entities, 409 for conflicts, 400 for bad input—and reserve 500 for truly unexpected exceptions we alert on.
PUT vs PATCH — how do you implement partial updates?
| Verb | Semantics | Implementation |
|---|---|---|
| PUT | Replace entire resource | Client sends full representation |
| PATCH | Partial update | JSON Merge Patch or JSON Patch |
Spring example for partial update:
@PatchMapping("/{id}")
public OrderResponse patch(@PathVariable Long id,
@RequestBody Map<String, Object> updates) {
return orderService.applyPatch(id, updates);
}Production services often use explicit patch DTOs instead of open maps—type safety and validation.
Concurrency: use ETags or version columns (@Version) to prevent lost updates—return 409 on conflict.
A strong answer is:
PUT replaces the whole resource; PATCH applies a controlled partial update with validation and optimistic locking when multiple clients edit the same row.
Testing, Actuator, and observability
@SpringBootTest vs slice tests — when do you use each?
| Annotation | Loads | Use for |
|---|---|---|
@SpringBootTest |
Full context (slow) | End-to-end integration |
@WebMvcTest |
MVC layer only | Controller mapping, security, JSON |
@DataJpaTest |
JPA + in-memory or Testcontainers DB | Repositories, queries |
@JsonTest |
Jackson | Serialization contracts |
@RestClientTest |
REST clients | Outbound HTTP client behavior |
Rule: test narrow by default, widen when integration risk appears.
@SpringBootTest(webEnvironment = RANDOM_PORT) + TestRestTemplate or WebTestClient for full HTTP stack.
A strong answer is:
I use slice tests for fast feedback on controllers and repositories, and a small set of
@SpringBootTestor Testcontainers tests to prove the wiring that slices mock away.
How do @WebMvcTest and @DataJpaTest work?
@WebMvcTest (example):
@WebMvcTest(OrderController.class)
@Import(OrderService.class) // or @MockBean OrderService
class OrderControllerTest {
@Autowired MockMvc mockMvc;
@MockBean OrderRepository orderRepository;
@Test
void returns404WhenMissing() throws Exception {
when(orderRepository.findById(1L)).thenReturn(Optional.empty());
mockMvc.perform(get("/api/v1/orders/1"))
.andExpect(status().isNotFound());
}
}Only MVC infrastructure starts—no full database.
@DataJpaTest:
@DataJpaTest
class OrderRepositoryTest {
@Autowired OrderRepository orders;
@Autowired TestEntityManager em;
@Test
void findsByStatus() { /* persist + query */ }
}Uses @Transactional rollback by default; add Testcontainers for dialect-specific SQL.
A strong answer is:
@WebMvcTestexercises HTTP contracts with mocked collaborators;@DataJpaTestproves queries against a real persistence context without booting the entire application.
How do Testcontainers improve Spring Boot integration tests?
Testcontainers spins real PostgreSQL, Kafka, Redis, etc. in Docker during tests.
@SpringBootTest
@Testcontainers
class OrderApiIT {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16");
@DynamicPropertySource
static void registerProps(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
}Benefits:
- Catch SQL dialect and constraint issues H2 hides
- Test Flyway migrations realistically
- Reproducible CI with Docker available
Trade-off: slower than in-memory DB—keep the suite small and tagged.
A strong answer is:
Testcontainers gives me production-like dependencies in CI—I use it for migration and integration paths that H2 cannot represent faithfully.
What is Spring Boot Actuator and which endpoints matter in production?
Actuator exposes operational endpoints over HTTP or JMX:
| Endpoint | Purpose |
|---|---|
/actuator/health |
Liveness/readiness (K8s probes) |
/actuator/info |
Build/git metadata |
/actuator/metrics |
Micrometer metrics |
/actuator/prometheus |
Prometheus scrape format |
/actuator/loggers |
Tune log levels at runtime |
Secure aggressively:
management.endpoints.web.exposure.include=health,info,prometheus
management.endpoint.health.show-details=when_authorizedCustom health indicators implement HealthIndicator for downstream dependencies (DB, queue).
A strong answer is:
Actuator is how operators check health and scrape metrics—I expose the minimum endpoints, protect them with auth/network policy, and add custom health checks for critical dependencies.
How do logging and distributed tracing work in Spring Boot 3?
Spring Boot 3 uses Micrometer Observation to correlate logs, metrics, and traces.
Practices:
| Practice | Detail |
|---|---|
| Structured logging | JSON logs with traceId, spanId (Logback encoder) |
| Correlation IDs | Propagate X-Correlation-ID in filters |
| Tracing bridge | OpenTelemetry or Brave with Zipkin/Jaeger/OTLP export |
| MDC | Map diagnostic context in OncePerRequestFilter |
management.tracing.sampling.probability=1.0In incidents, you pivot from user report → trace ID → slow SQL span—interviewers want that workflow, not just "we use logs."
A strong answer is:
I emit structured logs and traces with shared correlation IDs so I can follow one request from gateway through controller, service, and database query during an outage.
Microservices, resilience, and messaging
Monolith vs microservices — when does Spring Boot fit each?
Spring Boot supports modular monoliths and microservices equally well—the question is operational cost.
| Approach | Spring Boot pattern |
|---|---|
| Modular monolith | Single deployable; modules as packages or Gradle/Maven multi-modules |
| Microservices | One service per bounded context; independent DB |
| Strangler fig | Extract hot paths gradually behind API gateway |
Extract a service when you need independent scaling, deployment, or team ownership—not because the monolith hit arbitrary line count.
Spring tools for microservices:
- Spring Cloud OpenFeign — declarative HTTP clients
- Spring Cloud Gateway — edge routing
- Spring Cloud Config — central configuration
See full stack monolith vs micro for product-level framing.
A strong answer is:
I start with a well-bounded Spring Boot monolith and split services only when scaling or ownership forces the operational investment—not by default.
Explain Spring Cloud Config, Gateway, and service discovery at interview level.
| Component | Role |
|---|---|
| Config Server | Git-backed central application.yml; clients refresh with /actuator/refresh or Spring Cloud Bus |
| Gateway | Reactive edge: routing, rate limiting, auth termination |
| Discovery (Eureka, Consul) | Service registry so clients find instances dynamically |
| Load balancing | Spring Cloud LoadBalancer with RestTemplate/WebClient |
Modern deployments often replace Eureka with Kubernetes DNS and Config Server with GitOps + sealed secrets—still know the Spring Cloud abstractions for legacy enterprise stacks.
A strong answer is:
Spring Cloud centralizes config and routing; in Kubernetes I may use native ingress and ConfigMaps instead, but the problems—dynamic endpoints and environment-specific settings—stay the same.
How do circuit breakers work with Resilience4j in Spring Boot?
Resilience4j implements fault tolerance patterns:
| Pattern | Purpose |
|---|---|
| Circuit breaker | Stop calling failing dependency; fail fast |
| Retry | Transient errors with backoff |
| Rate limiter | Protect your service |
| Bulkhead | Isolate thread pools |
@CircuitBreaker(name = "inventory", fallbackMethod = "fallbackStock")
public StockResponse checkStock(Long skuId) {
return inventoryClient.getStock(skuId);
}States: CLOSED → OPEN (after failure threshold) → HALF_OPEN (probe).
Expose metrics via Micrometer; tune thresholds from production data, not guesses.
A strong answer is:
I wrap outbound calls with circuit breakers and fallbacks so cascading failures do not take down my API, and I monitor breaker state to know when dependencies recover.
Modern Java, performance, and caching
How do virtual threads work in Spring Boot on Java 21?
Virtual threads (Project Loom) are lightweight threads scheduled on carrier platform threads—ideal for blocking I/O heavy workloads.
Enable in Spring Boot 3.2+:
spring.threads.virtual.enabled=trueTomcat and @Async can use virtual threads for request handling when configured.
| Workload | Fit |
|---|---|
| Many concurrent blocking JDBC/HTTP calls | Good candidate |
| CPU-bound computation | Stick to platform threads / pool sizing |
Caveats interviewers mention:
- Pinning on
synchronizedblocks (improving over JDK releases) - Thread-local assumptions in libraries
- Measure—not every app needs virtual threads on day one
Connect to Java part 2 virtual threads for JVM-level detail.
A strong answer is:
On Java 21 I enable Spring Boot virtual thread support for I/O-heavy APIs, load-test against platform-thread baselines, and watch for pinning or thread-local issues in dependencies.
What are GraalVM native images with Spring Boot?
Spring Native / AOT compiles apps ahead-of-time for faster startup and lower memory—useful for serverless and dense Kubernetes.
Trade-offs:
| Benefit | Cost |
|---|---|
| Fast cold start | Longer build times |
| Lower RSS | Reflection hints; limited dynamic behavior |
| Smaller containers | Some libraries need reachability metadata |
Process:
- Spring AOT generates reflection/proxy config at build time
native-maven-pluginor Gradle native build produces executable
Not every Spring app is a native candidate—validate with benchmarks before committing.
A strong answer is:
Native images trade build complexity for startup and memory wins—I use them when cold start or footprint dominates cost, after proving our Spring features and libraries are native-compatible.
How does Spring caching with @Cacheable work?
Enable with @EnableCaching. Annotate read methods:
@Cacheable(value = "orders", key = "#id")
public OrderDto findById(Long id) {
return orderRepository.findById(id).map(mapper::toDto).orElseThrow();
}
@CacheEvict(value = "orders", key = "#id")
public void delete(Long id) { orderRepository.deleteById(id); }Providers: Caffeine (local), Redis (distributed).
| Concern | Practice |
|---|---|
| Stale data | TTL + eviction on writes |
| Stampede | Sync cache load or probabilistic early expiration |
| Serialization | DTOs in Redis, not lazy entities |
A strong answer is:
I cache expensive reads with explicit keys and TTL, evict on mutations, and choose Redis when multiple instances need a shared cache.
Senior scenarios and final prep
Scenario: A Spring Boot API is slow in production — how do you debug?
Structured approach interviewers want:
| Step | Action |
|---|---|
| 1 | Confirm scope—one endpoint or global? spike vs gradual? |
| 2 | Check metrics (latency percentiles, error rate, thread pool, DB pool) |
| 3 | Pull traces for slow requests—identify DB, external HTTP, or GC |
| 4 | Inspect SQL—N+1, missing index, lock waits (SQL prep) |
| 5 | Review recent deploys—config, pool size, feature flag |
| 6 | Reproduce in staging with production-like data volume |
| 7 | Fix, load-test, roll out with canary |
Tools: Actuator metrics, APM (Datadog, New Relic), Hibernate SQL log (dev only), pg_stat_statements, heap dump if GC thrash.
A strong answer is:
I start with metrics and traces to see whether latency is database, downstream HTTP, or pool exhaustion, reproduce with realistic data, fix the root cause—often N+1 or missing index—and verify with load tests before full rollout.
What should you rehearse the week before a senior Spring Boot interview?
Checklist:
- Whiteboard one service: controller → service → repo → DB + auth filter
- Explain a transaction bug you fixed (rollback, propagation, self-invocation)
- Explain a security choice (JWT resource server, roles, method security)
- Walk through testing pyramid for that service
- Two STAR stories—production incident and performance win
- Trade-off answers: monolith vs micro, cache invalidation, optimistic locking
- Java fundamentals refresh—part 1 and part 2
- Build/deploy story—Maven/Gradle, Docker, CI, health probes
A strong answer is:
I rehearse one real Spring Boot system end to end—security, data, tests, and how we run it in production—so every answer ties to something I actually shipped, not generic framework definitions.
Pattern cheat sheet (quick reference)
| Need | Spring Boot starting point |
|---|---|
| REST API | spring-boot-starter-web + @RestController |
| Database access | spring-boot-starter-data-jpa + Flyway |
| Validation | spring-boot-starter-validation + @Valid |
| Security | spring-boot-starter-security + OAuth2 resource server |
| Testing | @WebMvcTest, @DataJpaTest, Testcontainers |
| Production ops | spring-boot-starter-actuator + Micrometer |
| Caching | @EnableCaching + Caffeine or Redis |
| Async / I/O scale | Virtual threads on Java 21 |
| Microservices edge | Spring Cloud Gateway + Config (or K8s native) |
| Resilience | Resilience4j circuit breaker |
References
Official Spring documentation
- Spring Boot documentation
- Spring Framework documentation
- Spring Security reference
- Spring Data JPA reference
- Spring Boot 3.0 migration guide
On-site prep
- Java interview questions — part 1
- Java interview questions — part 2
- Kafka interview questions
- Full stack developer interviews
- Maven interview questions
- SQL technical interview questions
- Azure developer interviews
- Git interview questions
- Interview Questions category
Summary
Experienced Spring Boot interviews test auto-configuration, JPA and transactions, security, testing, and production operability—not annotation trivia. Answer aloud and compare your structure to each section. Pair with Java part 1 and part 2, full stack interviews, and SQL interviews when persistence becomes execution plans.

