Spring Boot Interview Questions
IoC, REST APIs, JPA, Security, Kafka & microservices with Spring Boot
← Learn this topic from scratch firstWhy Spring & Spring Boot
What problem does the Spring IoC container solve that plain Java object creation doesn't?
beginnerPlain Java means every class manually constructs and wires its own dependencies with `new`, so swapping an implementation (e.g. a mock in tests) means editing every call site. The IoC container inverts that: objects declare what they need via constructor parameters, and the container looks up or creates the right implementation and injects it, so wiring lives in one place (configuration/annotations) instead of scattered across the codebase.
What does 'auto-configuration' actually do under the hood in Spring Boot?
beginnerAt startup, Spring Boot scans `META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports` for a list of `@Configuration` classes, each guarded by conditional annotations like `@ConditionalOnClass` or `@ConditionalOnMissingBean`. It only activates a configuration if its trigger condition is true (e.g. an embedded Tomcat bean is only created if `spring-boot-starter-web` is on the classpath and you haven't already defined your own server bean), which is why adding a starter dependency is often the only setup step needed.
IoC Container & Dependency Injection
Why is constructor injection preferred over field injection in modern Spring code?
beginnerConstructor injection makes dependencies explicit and required — the object literally cannot be constructed without them, which also makes the class trivially testable with plain `new` calls in unit tests, no Spring context needed. Field injection (`@Autowired` on a field) hides the dependency graph, allows a half-constructed object with null dependencies to exist, and forces reflection to set up in tests.
What's the difference between `@Component`, `@Bean`, and `@Configuration`?
beginner`@Component` is a class-level annotation that tells component scanning to register that class itself as a bean. `@Bean` is a method-level annotation used inside a `@Configuration` class, for when you need to construct a bean yourself (e.g. a third-party class you don't own, or one needing custom setup logic) rather than annotate the class directly. `@Configuration` marks a class as a source of `@Bean` definitions and ensures those methods are CGLIB-proxied so calling one `@Bean` method from another still returns the same singleton instance.
Beans, Configuration & Lifecycle
What is the default bean scope in Spring, and when would you use `prototype` instead?
beginnerProThe default scope is `singleton` — exactly one instance per Spring container, shared by every injection point. `prototype` creates a brand-new instance every time the bean is requested, which matters for stateful, non-thread-safe objects (e.g. a mutable builder-like helper) where sharing one instance across concurrent requests would cause data races.
What's the correct way to run code right after a bean's dependencies are injected?
beginnerProImplement `InitializingBean.afterPropertiesSet()`, or more commonly, annotate a method with `@PostConstruct` — Spring guarantees this runs after all `@Autowired`/constructor dependencies are set, but before the bean is handed out to other beans. This is the right place for validation or warm-up logic that needs the bean's dependencies to already be present.
Core Annotations & Auto-Configuration
What exactly does `@SpringBootApplication` bundle together?
beginnerProIt's a meta-annotation combining three: `@Configuration` (this class can define beans), `@EnableAutoConfiguration` (turn on the classpath-conditional auto-config scanning), and `@ComponentScan` (scan this package and sub-packages for `@Component`/`@Service`/`@Repository`/`@Controller` classes). Understanding this matters because moving your main class to a different package can silently break component scanning for classes outside its scan radius.
How would you exclude one specific auto-configuration class from being applied?
beginnerProAdd `exclude = {SomeAutoConfiguration.class}` to `@SpringBootApplication` or `@EnableAutoConfiguration`, or set `spring.autoconfigure.exclude` in `application.properties`. This is the standard escape hatch when Boot's default wiring for something (e.g. a specific DataSource auto-configuration) conflicts with a custom bean you want full control over.
Properties, Profiles & Starters
How do Spring profiles let the same codebase behave differently in dev vs production?
beginnerProYou define `application-dev.properties` and `application-prod.properties` alongside the base `application.properties`; whichever profile is active (`spring.profiles.active=dev`) has its file's properties layered on top of the base ones, overriding any matching keys. Beans can also be profile-scoped with `@Profile("dev")` so, for example, a fake in-memory email sender only exists in dev while a real SMTP client backs production.
What is a Spring Boot 'starter', and why does adding one dependency configure so much automatically?
beginnerProA starter (e.g. `spring-boot-starter-data-jpa`) is just a Maven/Gradle dependency descriptor that transitively pulls in every library that feature area needs (Hibernate, the JPA API, a connection pool) plus nothing else — it doesn't contain code itself. The 'automatic' part comes from auto-configuration reacting to those libraries now being on the classpath, not from the starter doing anything special.
Building REST APIs
What's the difference between `@RequestBody` and `@RequestParam`?
beginnerPro`@RequestBody` deserializes the entire HTTP request body (typically JSON) into a Java object via Jackson, used for POST/PUT payloads. `@RequestParam` reads a single named value from the URL query string or form data, used for simple scalar inputs like `?page=2`. Mixing them up — expecting a `@RequestParam` when the client actually sent a JSON body — is a common source of 400 errors.
Why should a REST controller return `ResponseEntity<T>` instead of just `T`?
beginnerProReturning the bare object always yields HTTP 200 with the object serialized as the body, giving you no control over status code or headers. `ResponseEntity<T>` lets the same method return 201 Created with a `Location` header after a POST, or 404 with an empty body when a lookup misses — the status code becomes an explicit part of the API contract instead of an accident of always-200.
DTOs & Entity Mapping
Why not just return JPA `@Entity` objects directly from a REST controller?
beginnerProEntities carry lazy-loaded relationships that can throw `LazyInitializationException` once serialized outside a transaction, they leak internal DB columns you may not want public (audit fields, foreign keys), and every schema change becomes an uncontrolled API contract change. A DTO is a plain object shaped exactly like the API response you intend, decoupling your database schema from your public contract.
What's a practical downside of hand-writing entity-to-DTO mapping code everywhere?
beginnerProIt's repetitive and easy to forget a field when the entity changes, silently dropping data from responses. Libraries like MapStruct generate the mapping code at compile time from an interface you declare, so a missing-field mistake becomes a compile-time signal (or at least a single place to check) instead of a runtime surprise.
Spring Data JPA & Hibernate
What is the N+1 query problem, and how does it show up with Spring Data JPA?
beginnerProIf you fetch a list of N parent entities and then access a lazy-loaded child collection on each one inside a loop, Hibernate issues one query for the parents plus one additional query per parent to fetch their children — N+1 queries total instead of 1 or 2. It's fixed with an explicit `JOIN FETCH` in a JPQL query or an `@EntityGraph`, which tells Hibernate to pull the association in the same query.
What's the difference between `save()` triggering an INSERT vs an UPDATE in Spring Data JPA?
beginnerPro`save()` checks whether the entity's `@Id` is null (or matches Hibernate's configured 'new entity' detection) — if so, it INSERTs; if the ID is already set and Hibernate believes the row exists, it UPDATEs (technically via a SELECT-then-decide, or directly if the entity is 'detached' and assumed persistent). This is why passing an entity with a manually-set ID that doesn't exist yet can behave unexpectedly depending on the ID generation strategy.
Relationships & Queries
Why does Hibernate default `@OneToMany` to `FetchType.LAZY` but `@ManyToOne` to `FetchType.EAGER`?
intermediateProA `@ManyToOne` points to a single row, so eagerly loading it is cheap and usually needed anyway (e.g. an Order almost always needs its Customer). A `@OneToMany` can point to an unbounded collection, so eagerly loading it by default could silently pull thousands of rows for every parent fetched — Hibernate defaults it to lazy so you opt in explicitly when you actually need the collection.
How do Spring Data JPA's derived query methods like `findByEmailAndStatus` actually work?
intermediateProSpring parses the method name at startup, splitting on keywords (`findBy`, `And`, `OrderBy`) and mapping each segment to an entity field, then generates the equivalent JPQL/SQL automatically — no method body is written. This only works for reasonably simple queries; anything with joins across multiple unrelated conditions or dynamic filters is better as an explicit `@Query` or a Criteria API query.
Exception Handling & Validation
What does `@ExceptionHandler` combined with `@RestControllerAdvice` give you that a plain try/catch doesn't?
intermediateProIt centralizes error-to-HTTP-status mapping in one place instead of repeating try/catch blocks in every controller method — throw a domain exception anywhere in the call stack, and the `@RestControllerAdvice` class intercepts it globally and converts it into a consistent JSON error response with the right status code, keeping controllers focused on the happy path.
How does Bean Validation (`@Valid` + `@NotNull`/`@Size`) actually get triggered on a REST request?
intermediateProAnnotating a `@RequestBody` parameter with `@Valid` tells Spring MVC to run the object through the Bean Validation provider (Hibernate Validator) right after deserialization, checking every constraint annotation on its fields. If any fail, Spring throws `MethodArgumentNotValidException` before your controller method body even runs, which you then handle via `@ExceptionHandler` to shape the 400 response.
Testing APIs & Dockerized Postgres
Why use Testcontainers instead of an in-memory database like H2 for integration tests?
intermediateProH2 doesn't perfectly replicate Postgres-specific SQL dialects, functions, or constraint behaviors, so a query that passes against H2 in tests can still fail against real Postgres in production — a false sense of safety. Testcontainers spins up an actual throwaway Postgres Docker container for the test run, so the SQL your code executes is validated against the exact same database engine production uses.
What's the difference between `@SpringBootTest` and `@WebMvcTest`?
intermediatePro`@SpringBootTest` boots the entire application context — every bean, real or test-doubled — which is slow but gives full integration coverage. `@WebMvcTest` loads only the web layer (controllers, `@ControllerAdvice`, Jackson config) and mocks everything below it (services, repositories), making it much faster for testing request/response mapping and validation in isolation.
Spring Security Fundamentals
What happens the instant you add `spring-boot-starter-security` to a project with zero configuration?
intermediateProEvery endpoint immediately requires authentication, a default in-memory user named `user` is created with a randomly generated password printed to the console log, and both form-login and HTTP Basic auth are enabled with CSRF protection on. This 'secure by default' stance means a REST API needs explicit configuration to loosen these defaults for its intended auth strategy rather than starting wide open.
What is a `SecurityFilterChain`, and where does it sit in a request's lifecycle?
intermediateProIt's an ordered list of servlet filters (CORS handling, CSRF checks, authentication, authorization) that every incoming HTTP request passes through before it ever reaches your `@RestController`. You define it as a `@Bean` returning `HttpSecurity`-built configuration, and Spring Boot can support multiple chains matched by URL pattern (e.g. a stateless chain for `/api/**`, a stateful one for `/admin/**`).
Authentication with JWT
Why is a JWT-based API typically configured as stateless, and what does that change?
intermediateProA JWT carries the user's identity and claims signed inside the token itself, so the server doesn't need to keep a session store to know who's making a request — it just verifies the signature on each call. This means `SessionCreationPolicy.STATELESS` is set in the security config, load balancing becomes trivial (any server instance can validate any token), but it also means there's no server-side session to instantly invalidate on logout.
What actually happens if you decode a JWT's payload without a library — is it 'safe' to just read?
intermediateProYes and no: the payload is only Base64-encoded, not encrypted, so anyone can decode and read its claims without the secret key — never put sensitive data like passwords or PII directly in a JWT payload. What the signature protects against is tampering: changing a claim (like a role) invalidates the signature, so the server will reject a modified token even though the attacker could read the original.
Authorization & Role-Based Access Control
What's the difference between a Spring Security 'role' and an 'authority'?
intermediateProAn authority is the raw granted permission string (e.g. `ROLE_ADMIN`, `SCOPE_read`), while a 'role' is really just a naming convention — Spring Security's role-based helpers (`hasRole("ADMIN")`) automatically prepend `ROLE_` when checking against authorities. Understanding this prevents a common bug: calling `hasRole("ROLE_ADMIN")` accidentally checks for `ROLE_ROLE_ADMIN` and always fails.
Why does `@PreAuthorize` on a method sometimes get silently ignored?
intermediateProMethod security only applies through Spring's proxy mechanism, so a `@PreAuthorize`-annotated method called from another method in the SAME class bypasses the proxy entirely (it's a plain Java method call, not going through the bean), and the check never fires. The call has to come from a different bean — through the proxy — for the annotation to take effect.
Refresh Tokens & Session Lifecycle
Why use a short-lived access token plus a separate long-lived refresh token instead of one long-lived token?
intermediateProA short access token (minutes) limits the damage window if it's ever stolen — it expires quickly regardless. The refresh token lives longer but is only ever sent to a single dedicated token-refresh endpoint (never to regular API endpoints), so it's exposed to far fewer network paths, and it can be revoked server-side (stored, rotated, or blacklisted) unlike a stateless access token.
What is refresh token rotation, and what attack does it defend against?
intermediateProEvery time a refresh token is used, the server issues a brand-new refresh token and invalidates the old one. If a stolen refresh token is ever replayed by an attacker after the legitimate user already rotated it, the old token is rejected — and detecting that reuse is itself a signal the token was compromised, letting you revoke the whole token family immediately.
Securing Exceptions & Custom Errors
Why does Spring Security return 401 for one failure and 403 for another, and how do you customize each?
advancedPro401 Unauthorized means authentication itself failed or is missing (no valid credentials at all), handled by a custom `AuthenticationEntryPoint`. 403 Forbidden means the caller IS authenticated but lacks permission for this specific resource, handled by a custom `AccessDeniedHandler`. Confusing the two in your error responses misleads API consumers about whether they need to log in again or simply don't have access.
Why can leaking a raw exception message in a security error response be dangerous?
advancedProA raw stack trace or exception message can reveal internal details — class names, SQL fragments, whether a username exists at all (letting an attacker enumerate valid accounts by comparing 'user not found' vs 'wrong password' responses). Production error handlers should return a generic, consistent message for authentication failures regardless of the specific internal cause.
Caching with Redis
What does `@Cacheable` actually do the first time vs subsequent times a method is called with the same argument?
advancedProThe first call executes the method body normally and Spring stores its return value in the configured cache (e.g. Redis) keyed by the method's arguments. Subsequent calls with the same argument skip the method body entirely and return the cached value directly — which is why side-effecting methods should never be annotated `@Cacheable`, since the side effect wouldn't happen on cache hits.
Why does a cache need an eviction/TTL strategy instead of just caching forever?
advancedProWithout expiry, cached data drifts out of sync with the real database the moment the underlying row changes, serving stale data indefinitely. A TTL (`@Cacheable` with a configured Redis expiry) bounds how stale data can get, and explicit `@CacheEvict` on update/delete operations invalidates the cache immediately when you know the data changed, rather than waiting out the TTL.
Introduction to Microservices
What's the core trade-off microservices make compared to a monolith?
advancedProYou trade deployment/scaling independence and fault isolation (one service crashing doesn't take down the whole system) for massively increased operational complexity: network calls replace in-process method calls (with all their failure modes — timeouts, partial failures), data consistency across services becomes eventual instead of transactional, and you need service discovery, distributed tracing, and API gateways just to operate what used to be a single deployable.
Why is 'a service per database' considered a core microservices principle rather than an implementation detail?
advancedProIf two services share one database, they're still coupled at the schema level — one team can't change a table without coordinating with every other service reading it, which defeats the independent-deployability goal microservices exist for. Each service owning its own database forces communication to happen through versioned APIs or events instead of a shared schema.
Event-Driven Architecture with Kafka
What's the fundamental difference between a service calling another service's REST API vs publishing a Kafka event?
advancedProA REST call is synchronous and tightly coupled — the caller needs the callee to be up right now and waits for a response, and adding a new consumer means changing the producer's code to call it too. Publishing a Kafka event decouples them completely: the producer doesn't know or care who's listening, new consumers can be added later with zero changes to the producer, and if a consumer is temporarily down, the event just waits in the topic.
Why might using Kafka introduce eventual consistency bugs that a direct REST call wouldn't have?
advancedProBecause the producer's job is considered 'done' the moment the event is published, not when every consumer has processed it — there's a window where downstream systems haven't caught up yet. A UI that reads from a downstream service's data right after triggering an event can see stale state until that consumer finishes processing, which needs to be designed around (e.g. optimistic UI updates) rather than assumed away.
API Gateway & Service Communication
What cross-cutting concerns does an API Gateway centralize that would otherwise be duplicated in every microservice?
advancedProAuthentication/token validation, rate limiting, request logging, and routing/load balancing to the right downstream service — without a gateway, every single microservice would need to reimplement (and keep in sync) its own copy of each of these concerns, multiplying both code duplication and the chance of inconsistent behavior between services.
Why is client-side load balancing (each service instance discovering peers itself) sometimes preferred over a centralized load balancer?
advancedProA centralized load balancer is a single hop every request must pass through, adding latency and becoming a potential bottleneck/single point of failure at scale. Client-side load balancing (via a service registry like Eureka) lets each caller query the registry once and then call instances directly, spreading the discovery load and removing the extra network hop per request.
Docker & Docker Compose
Why is Docker Compose useful for local development of a multi-service Spring Boot app even though production uses Kubernetes?
advancedProCompose lets you define the app plus its dependencies (Postgres, Redis, Kafka) as one `docker-compose.yml` and bring the entire stack up with a single command, matching production's container-based topology closely enough to catch integration issues early, without needing the operational overhead of a full Kubernetes cluster just to develop locally.
What's the practical difference between `CMD` and `ENTRYPOINT` in a Dockerfile for a Spring Boot app?
advancedPro`ENTRYPOINT` defines the fixed command that always runs (e.g. `java -jar app.jar`), while `CMD` supplies default arguments that CAN be overridden at `docker run` time. A common pattern sets `ENTRYPOINT` to the java invocation and leaves `CMD` empty or for default JVM flags, so operators can override just the flags without needing to know or repeat the full java command.
Capstone — A Production-Ready System
When integrating JWT auth, JPA persistence, Redis caching, and Kafka events into one production service, what's the most common integration bug?
advancedProOrdering and transactionality: publishing a Kafka event before the database transaction that created the underlying row has actually committed means a consumer can react to an event for data that doesn't exist yet if it reads too fast. The safe pattern is publishing the event only after the transaction commits (e.g. via a transactional outbox or `@TransactionalEventListener(phase = AFTER_COMMIT)`).
Why does a 'production-ready' checklist include health checks and structured logging, not just working business logic?
advancedProWorking logic under manual testing doesn't tell you anything about how the service behaves under real failure conditions in production — health checks let orchestrators like Kubernetes detect and restart a stuck instance automatically, and structured (JSON) logs are what actually get searched during an incident, unlike free-text logs which don't scale past a handful of instances.
Logging
Why does log.info("msg={}", value) use a placeholder instead of string concatenation?
intermediateProThe `{}` placeholder defers building the actual string until the logging framework confirms this log level is enabled — string concatenation (`"msg=" + value`) always runs regardless of whether the log line will even be emitted, wasting work on every disabled DEBUG/TRACE call in production.
Why is logging every request at INFO level a bad default for a production service?
intermediateProINFO is meant for significant business events worth a permanent record; logging every routine request at INFO drowns real signal in noise and costs real money in log ingestion/storage at scale — request-level detail belongs at DEBUG, enabled only when actively investigating something.
Transactions with @Transactional
Why does calling a @Transactional method from another method in the same class not start a transaction?
intermediatePro@Transactional works via a Spring-generated proxy that intercepts calls to the bean from OUTSIDE — calling `this.method()` from within the same class bypasses the proxy entirely and just runs as a plain Java call, with no transaction ever started.
Does @Transactional roll back on every exception by default?
intermediateProNo — by default it only rolls back on unchecked exceptions (RuntimeException and subclasses). A checked exception does NOT trigger a rollback unless you explicitly configure `@Transactional(rollbackFor = Exception.class)`, which is a common source of silent partial-write bugs.
Scheduling with @Scheduled
Why does @Scheduled run on a single shared thread by default, and why does that matter?
intermediateProSpring's default scheduler uses one thread for all @Scheduled methods, so one slow-running scheduled task delays every other scheduled task in the application — fixed by configuring a dedicated TaskScheduler bean with an appropriately sized thread pool.
If you deploy 3 replicas of a service with a nightly @Scheduled job, how many times does it actually run?
intermediateProThree times — once per instance, independently — since @Scheduled has no built-in cluster coordination. Running a job exactly once across a cluster requires a distributed lock (via Redis or a database row) or a dedicated clustered scheduler like Quartz.
Async Programming with @Async
Why does an @Async method silently run synchronously when called from within the same class?
intermediatePro@Async, like @Transactional, only works through Spring's proxy — a self-invocation (calling from inside the same class) bypasses the proxy and the method just runs on the current thread with no error, which is easy to miss since nothing fails loudly.
Why return CompletableFuture instead of void from an @Async method?
intermediateProA void @Async method is pure fire-and-forget — the caller has no way to know when it finished or get its result. Returning CompletableFuture<T> lets the caller compose multiple async calls together and retrieve the result once it's ready, without blocking immediately.
File Upload
Why is trusting a file's Content-Type header or extension as proof of what it actually is a security risk?
intermediateProBoth the Content-Type header and the file extension are entirely client-controlled and trivially spoofed — a client can rename a malicious executable to `.jpg` and set any Content-Type it wants. Real validation checks the file's actual byte signature (magic numbers), not just claimed metadata.
Why doesn't saving uploaded files to the application server's local disk work in a real production deployment?
intermediateProWith more than one instance, a file saved by whichever instance handled the upload request is invisible to every other instance, and local disk gets wiped on every container restart unless it's a persistent volume — production systems upload to shared object storage (S3 or similar) instead.
Spring Boot Actuator
How does /actuator/health decide whether to report UP or DOWN?
advancedProIt aggregates every HealthIndicator bean Spring Boot auto-detects (database, disk space, Redis, and any custom ones you write) — if even one indicator reports DOWN, the whole aggregate endpoint reports DOWN, which is exactly what a Kubernetes readiness probe checks before routing traffic.
Why is exposing /actuator/env or /actuator/heapdump to the public internet a real security risk?
advancedPro/actuator/env can leak configuration values including secrets, and /actuator/heapdump hands out a full memory dump an attacker can mine for credentials and session tokens — production configuration should expose only health/info/metrics publicly and require authentication for anything sensitive.
Aspect-Oriented Programming (AOP)
What do @Transactional, @Async, and @PreAuthorize have in common under the hood?
advancedProAll three are built-in Spring AOP aspects sharing the exact same proxy-based interception mechanism — which is also why all three share the identical self-invocation limitation: calling any of them from within the same class bypasses the proxy entirely.
Why is @Around the only AOP advice type that can change a method's return value, and what's the risk in writing one?
advancedPro@Around wraps the entire method call and explicitly controls whether/how `joinPoint.proceed()` is invoked, so it can inspect or replace the return value — but forgetting to call `proceed()` silently skips the real method entirely, with no error indicating why.
Performance Optimization
Why should you measure with real data (like Actuator metrics) before optimizing a 'slow' endpoint?
advancedProOptimizing whatever looks slow without measuring routinely wastes time fixing something that was never the actual bottleneck — the real slow path is often unglamorous, like a synchronous external API call buried three layers deep, which only real per-endpoint response-time data reveals.
What's the telltale symptom that distinguishes connection-pool exhaustion from other performance problems?
advancedProEverything is fast under light traffic, then intermittently and unpredictably slow specifically once concurrent requests exceed the pool size, because extra requests simply wait for a connection to free up rather than failing outright — unlike N+1 queries or missing indexes, which are consistently slow regardless of concurrency.
Production Best Practices
Why does a service need graceful shutdown configured, not just a health check?
advancedProWithout it, Kubernetes' termination signal during a routine deployment hard-kills in-flight requests instead of letting them finish, producing user-visible errors on every single deployment — `server.shutdown=graceful` stops accepting new requests immediately while letting existing ones complete within a timeout.
Why should environment-specific behavior live in profile-specific properties files instead of if-checks in code?
advancedProConfig-file-based environment differences (application-prod.properties vs application-dev.properties) are visible and auditable in one place, while scattered if-checks bury environment-dependent behavior inside business logic where it's easy to miss and easy to get wrong during a deploy.