Spring Boot interview questions & answers
The Java backend framework interviewers ask about most: dependency injection, auto-configuration, REST APIs, Spring Data JPA, transactions, security and testing.
Top 63 Spring Boot interview questions most asked first
1.What is Spring Boot, and what does it add on top of the Spring Framework?easy
Spring Boot is an opinionated layer on top of the Spring Framework for building stand-alone, production-ready applications quickly. Spring itself gives you the IoC container, MVC, transactions and data access, but you wire and configure most of it yourself. Boot adds:
- Auto-configuration: it inspects the classpath, your beans and your properties, then configures sensible defaults, like a
DataSourcewhen a JDBC driver is present - Starters: curated dependency sets such as
spring-boot-starter-web, with versions managed by Boot's dependency BOM - Embedded server: Tomcat, Jetty or Netty inside an executable jar, so you just run
java -jar app.jar - Externalized configuration:
application.propertiesor YAML, profiles, environment variables - Production features through Actuator: health checks, metrics, info
Boot doesn't replace Spring; it's still Spring underneath, with the boilerplate removed and every default overridable. Spring Boot 3 requires Java 17 and moved from
javax.*to the Jakarta EEjakarta.*packages.What interviewers listen for- Opinionated layer on Spring, not a replacement
- Auto-configuration based on classpath, beans and properties
- Starters with managed dependency versions
- Embedded server, runnable with
java -jar - Actuator and externalized config for production
Likely follow-up: What changed in Spring Boot 3 compared with 2.x?
- Auto-configuration: it inspects the classpath, your beans and your properties, then configures sensible defaults, like a
2.What are Inversion of Control and dependency injection in Spring?easy
Inversion of Control means your objects don't create or look up their own dependencies; the framework does. In Spring, the container (the
ApplicationContext) reads configuration metadata such as annotations and@Beanmethods, creates the objects, called beans, wires them together and manages their lifecycle.Dependency injection is how Spring implements IoC: a class declares what it needs through constructor parameters, setters or fields, and the container supplies matching beans.
Why it matters:
- Loose coupling: a service depends on an interface, not a concrete class
- Testability: in a unit test you pass a mock through the constructor, no container needed
- Central configuration: swapping an implementation is a config change, not a code change
- Cross-cutting concerns: because Spring owns the objects, it can wrap them in proxies for transactions, security and caching
BeanFactoryis the basic container;ApplicationContextextends it with events, i18n, resource loading and eager creation of singletons.What interviewers listen for- Container creates and wires objects, not the class itself
- DI via constructor, setter or field
- Loose coupling and easy testing with mocks
ApplicationContextextendsBeanFactory- Owning the objects lets Spring apply proxies
Likely follow-up: What is the difference between
BeanFactoryandApplicationContext?3.Constructor, setter or field injection: which do you use, and why?easy
I default to constructor injection, which is also what the Spring team recommends. Dependencies are passed in when the object is created, so:
- fields can be
final, making the bean immutable and safe to share between threads - the object can never exist half-built with a
nulldependency - it's trivially unit-testable:
new OrderService(mockRepo), no reflection, no Spring - a constructor with eight parameters is a visible smell that the class does too much
Since Spring 4.3, a class with a single constructor doesn't even need
@Autowired.Setter injection is for genuinely optional dependencies that have a sensible default.
Field injection (
@Autowiredon a private field) is concise but hides dependencies, rules outfinal, and forces tests to use reflection or a Spring context. I avoid it outside test classes. A side effect of constructor injection is that circular dependencies fail fast at startup, which is usually what you want.What interviewers listen for- Constructor injection is the recommended default
- Enables
finalfields and immutability - Unit tests can construct the class without Spring
- Setter injection for optional dependencies
- Field injection hides dependencies and hurts testing
Likely follow-up: Is
@Autowiredrequired on a constructor? · What happens with a circular dependency and constructor injection?- fields can be
4.What does
@SpringBootApplicationdo, and how does component scanning decide what to pick up?easyIt's a convenience annotation that combines three:
@SpringBootConfiguration: a specialized@Configuration, so the class can declare@Beanmethods@EnableAutoConfiguration: turns on Boot's auto-configuration@ComponentScan: scans for@Componentand its stereotypes
Component scanning starts from the package of the annotated class and covers all its sub-packages. That's why the main class belongs in a root package like
com.acme.shop. A controller in a sibling package such ascom.acme.webis silently not found, and you get a 404 or a missing-bean error.You can widen it with
scanBasePackages, but restructuring packages is usually cleaner. To turn off one auto-configuration, useexclude = DataSourceAutoConfiguration.classor thespring.autoconfigure.excludeproperty.SpringApplication.run(...)then creates theApplicationContext, applies auto-configuration, starts the embedded server and calls any runners.// Same as @SpringBootConfiguration + @EnableAutoConfiguration + @ComponentScan @SpringBootApplication public class ShopApplication { public static void main(String[] args) { SpringApplication.run(ShopApplication.class, args); } }What interviewers listen for- Combines configuration, auto-configuration and component scan
- Scans the main class package and its sub-packages
- Keep the main class in the root package
- Exclude auto-configurations via
excludeor a property
Likely follow-up: How would you exclude a single auto-configuration class?
5.How does Spring Boot auto-configuration work under the hood?mid
@EnableAutoConfigurationloads the candidate auto-configuration classes listed inMETA-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.importsin every jar on the classpath. (Older Boot 2 versions usedspring.factoriesfor this.) Each one is an ordinary configuration class, annotated@AutoConfiguration, guarded by conditions:@ConditionalOnClass: only if a class, for example a JDBC driver orDataSource, is on the classpath@ConditionalOnMissingBean: only if you haven't defined that bean yourself@ConditionalOnProperty: only if a property has a given value
Auto-configurations are processed after your own bean definitions, so
@ConditionalOnMissingBeanlets them back off: define your ownDataSourceand Boot's default disappears. That's the core idea: sensible defaults that yield to explicit configuration.To see what was applied and why, start the app with
--debugto print the condition evaluation report, or look at the Actuatorconditionsendpoint.What interviewers listen for- Candidates listed in
AutoConfiguration.imports - Each is guarded by
@Conditional...annotations - Your beans win via
@ConditionalOnMissingBean --debugprints the condition evaluation report
Likely follow-up: How would you write your own starter? · How do you disable one auto-configuration?
6.What is the difference between
@Component,@Service,@Repositoryand@Controller?easyAll four mark a class as a bean for component scanning;
@Service,@Repositoryand@Controllerare themselves meta-annotated with@Component. The differences are mostly semantic, with a little behavior:@Component: a generic bean@Service: the business-logic layer. It adds no behavior today, but it documents intent and is a handy target for aspects@Repository: the data-access layer. It enables exception translation, so technology-specific persistence exceptions are converted into Spring's uncheckedDataAccessExceptionhierarchy@Controller: the web layer. Spring MVC detects it as a request handler; its methods return view names unless they're@ResponseBody
Using the right stereotype makes the layering readable and lets aspects or test slices target a layer. Note that Spring Data repository interfaces don't need
@Repository: Spring Data creates and registers their implementations for you.What interviewers listen for- All are specializations of
@Component @Repositoryadds exception translation@Controlleris detected as an MVC handler@Servicedocuments intent, no extra behavior
Likely follow-up: What is
DataAccessExceptionand why is it unchecked?7.What is the difference between
@RestControllerand@Controller?easy@RestControlleris@Controllerplus@ResponseBodyapplied to every handler method.With plain
@Controller, a method's return value is treated as a view name: return"orders"and a view resolver renders a template, say Thymeleaf'sorders.html, with the model. That's for server-side rendered pages.With
@RestController, the return value is written straight to the response body. AnHttpMessageConverterserializes it, typically to JSON with Jackson, based on content negotiation with the request'sAcceptheader. That's what you use for REST APIs.You can mix the two: a
@Controllercan annotate individual methods with@ResponseBody, or return aResponseEntity, which is also written to the body. In practice I use@RestControllerfor APIs and@Controlleronly when rendering HTML.What interviewers listen for@RestController=@Controller+@ResponseBody@Controllerreturns view names by default- Message converters serialize the body, e.g. Jackson
- Content negotiation uses the
Acceptheader
8.What bean scopes does Spring support, and which is the default?easy
The default is singleton: one instance per bean definition per container. It isn't the GoF singleton; you can define two beans of the same class, and each context gets its own instances.
- prototype: a new instance every time the bean is requested or injected. Spring creates and initializes it, then forgets it, so destruction callbacks such as
@PreDestroyare not called. - Web-aware scopes: request (one per HTTP request), session (one per HTTP session), application (one per
ServletContext) and websocket.
Because a singleton is shared by all request threads, it should be stateless or hold only thread-safe state; a mutable field on a service is a classic concurrency bug.
A common trap is injecting a prototype or request-scoped bean into a singleton: injection happens once, so you keep reusing the same instance. Fix it with
ObjectProvider<T>,@Lookupmethod injection, or a scoped proxy.What interviewers listen for- Singleton is the default: one per container
- Prototype: new instance each time, no destroy callbacks
- Request, session, application and websocket scopes for web
- Singletons must be stateless or thread-safe
Likely follow-up: What happens when you inject a prototype bean into a singleton?
- prototype: a new instance every time the bean is requested or injected. Spring creates and initializes it, then forgets it, so destruction callbacks such as
9.How does
@Transactionalwork, and what are its default settings?mid@Transactionalis declarative transaction management built on AOP proxies. Spring wraps the bean in a proxy; a call through it asks thePlatformTransactionManager(for JPA,JpaTransactionManager) to start or join a transaction, invokes your method, then commits, or rolls back if the method throws.Defaults:
- propagation
REQUIRED: join the current transaction or start a new one - isolation
DEFAULT: the database's own default level - read-write, and a timeout of the underlying system's default
- rollback on
RuntimeExceptionandErroronly: a checked exception commits unless you addrollbackFor = Exception.class
I put it on service methods, which define the unit of work, rather than on controllers or repositories. Boot auto-configures transaction management, so
@EnableTransactionManagementisn't needed. And because it's proxy-based, only calls that come in through the proxy are transactional.@Transactional(rollbackFor = Exception.class) public void transfer(long from, long to, BigDecimal amount) throws InsufficientFundsException { accounts.debit(from, amount); // both run in one transaction accounts.credit(to, amount); }What interviewers listen for- Implemented with AOP proxies and a transaction manager
- Default propagation REQUIRED, isolation DEFAULT
- Rolls back on unchecked exceptions and errors only
- Checked exceptions commit unless
rollbackForis set - Only calls through the proxy are intercepted
Likely follow-up: Why might
@Transactionalsilently not work? · What doesreadOnly = trueactually do?- propagation
10.Why does
@Transactionalsometimes have no effect? Explain the self-invocation problem in this code.hardSpring applies
@Transactionalthrough a proxy in front of the bean, and only calls that come through the proxy are intercepted. WhenplaceAllcallsthis.place(...), it's an ordinary Java call on the target object, so no transaction is started even thoughplaceis annotated.Other reasons it silently does nothing:
- the method is
privateorfinal, which a class-based proxy can't override (since Spring 6,protectedand package-private methods work with class-based proxies) - the object isn't a Spring bean, for example it was created with
new - the exception is checked, so it commits, or you swallow it inside the method
- it's called from
@PostConstruct, before the proxy is fully usable
Fixes: move the transactional method to another bean (cleanest), inject the bean's own proxy lazily, use
TransactionTemplatefor programmatic control, or switch to AspectJ weaving. The same pitfall applies to@Async,@Cacheableand method security.@Service public class OrderService { public void placeAll(List<Order> orders) { orders.forEach(this::place); // plain call on "this" } @Transactional public void place(Order order) { /* ... */ } }What interviewers listen for- The proxy only intercepts external calls
this.method()bypasses the proxy entirely- Private or final methods and non-beans are not advised
- Fix: move to another bean or use
TransactionTemplate - Same issue for
@Asyncand@Cacheable
Likely follow-up: How would you get a transaction per order here?
- the method is
11.What are Spring Boot starters, and how does Boot manage dependency versions?easy
A starter is a dependency descriptor: a mostly empty jar whose POM pulls in everything needed for one capability.
spring-boot-starter-webbrings Spring MVC, Jackson and embedded Tomcat;spring-boot-starter-data-jpabrings Spring Data JPA, Hibernate and the HikariCP pool;spring-boot-starter-testbrings JUnit Jupiter, Mockito, AssertJ and Spring's test support. Bean Validation is its own starter,spring-boot-starter-validation.Versions come from Boot's dependency management: the
spring-boot-starter-parentPOM or thespring-boot-dependenciesBOM in Maven, or the Boot Gradle plugin. You declare starters without versions and get a set of library versions that were tested together; you can still override one through its version property.Starters and auto-configuration are separate ideas: the starter puts libraries on the classpath, and auto-configuration reacts to what it finds. Official starters are named
spring-boot-starter-*; a third-party one should beacme-spring-boot-starter. Boot 4 splits some starters more finely, for examplespring-boot-starter-webmvc.<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <!-- no version: managed by Spring Boot --> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>What interviewers listen for- A starter is a curated dependency set for one capability
- Versions come from the Boot BOM or parent POM
- Declare starters without versions
- Starters add jars; auto-configuration reacts to them
- Third-party naming:
acme-spring-boot-starter
12.When would you use
@Configurationwith@Beaninstead of@Component, and what doesproxyBeanMethodschange?mid@Componentis for your own classes: annotate the class and scanning finds it.@Beanmethods are for when you build the object yourself: third-party classes you can't annotate, objects that need construction logic, or several differently configured beans of the same type. The method's return value becomes a bean, named after the method by default.In a
@Configurationclass, Spring by default subclasses the class with CGLIB (full mode,proxyBeanMethods = true). If one@Beanmethod calls another, the call is intercepted and returns the existing singleton instead of creating a second instance.With
proxyBeanMethods = false, or with@Beanmethods in a plain@Component(lite mode), there's no interception: calling another@Beanmethod is a normal Java call that creates a new object. Lite mode avoids the CGLIB subclass, which is why Boot's own auto-configurations use it. The safe habit in any mode is to declare dependencies as method parameters rather than calling other bean methods.@Configuration public class AppConfig { @Bean public Clock clock() { return Clock.systemUTC(); } @Bean public InvoiceService invoiceService(Clock clock) { // injected, not called return new InvoiceService(clock); } }What interviewers listen for@Componentfor your own classes, found by scanning@Beanfor third-party or custom-built objects- Full mode: inter-bean calls return the singleton
proxyBeanMethods = falsemakes them plain Java calls- Prefer method parameters over calling
@Beanmethods
13.What is the difference between
@PathVariableand@RequestParam, and when do you use each?easy@PathVariablebinds a segment of the URI template:/orders/{id}called as/orders/42givesid = 42.@RequestParambinds a query parameter or form field:/orders?status=PAID&page=2.The REST convention: path variables identify a resource (
/users/7/orders/42); request params filter, sort or paginate a collection and are often optional.Details worth knowing:
@RequestParamis required by default; a missing one gives a 400. Userequired = false,Optional<T>, ordefaultValue, which makes it optional implicitly.- Type conversion is automatic, and a value that can't be converted, like
/orders/abcfor aLong, also gives a 400. @RequestBodyis the third option: it deserializes the JSON body into an object for POST or PUT payloads.- Names are inferred from parameter names only when code is compiled with
-parameters; Boot's Maven parent and Gradle plugin enable that, otherwise write@PathVariable("id").
@GetMapping("/users/{userId}/orders") public List<OrderDto> list(@PathVariable Long userId, @RequestParam(defaultValue = "ALL") String status, @RequestParam(required = false) Integer limit) { return orderService.find(userId, status, limit); }What interviewers listen for- Path variables identify the resource
- Request params filter, sort or paginate
@RequestParamis required by defaultdefaultValuemakes a param optional@RequestBodybinds the JSON payload
14.How do you handle exceptions globally in a Spring Boot REST API?mid
I use a
@RestControllerAdviceclass (@ControllerAdviceplus@ResponseBody) with@ExceptionHandlermethods. Spring MVC routes an exception thrown by any controller to the most specific matching handler, and I turn it into a consistent error response with the right status: 404 for a not-found domain exception, 400 for validation failures, 409 for conflicts.For the body, Spring 6 supports
ProblemDetail, the RFC 9457 "problem details" format, served asapplication/problem+jsonwithtype,title,status,detailandinstance, plus any custom properties.Two useful pieces:
- extend
ResponseEntityExceptionHandlerto get ProblemDetail responses for Spring MVC's own exceptions, or in Boot simply setspring.mvc.problemdetails.enabled=true - an
@ExceptionHandlerinside a controller applies only to that controller and takes precedence over the global advice
I also keep a catch-all handler that logs the error and returns a generic 500 without leaking stack traces.
@RestControllerAdvice public class ApiExceptionHandler { @ExceptionHandler(OrderNotFoundException.class) public ProblemDetail notFound(OrderNotFoundException ex) { ProblemDetail pd = ProblemDetail.forStatusAndDetail( HttpStatus.NOT_FOUND, ex.getMessage()); pd.setTitle("Order not found"); pd.setProperty("orderId", ex.getOrderId()); return pd; } }What interviewers listen for@RestControllerAdvicewith@ExceptionHandlermethods- Map domain exceptions to proper status codes
ProblemDetailimplements RFC 9457ResponseEntityExceptionHandlercovers MVC exceptions- Catch-all handler that never leaks stack traces
Likely follow-up: How would you return all field errors from a failed
@Valid?- extend
15.What is the N+1 select problem in JPA, and how do you fix it?mid
You run one query to load N parent rows, then JPA fires one extra query per parent as soon as you touch a lazy association. Here, 100 orders means 101 round trips. The code looks innocent, which is why it's so common; the SQL log (
spring.jpa.show-sqlor Hibernate's SQL logger) gives it away.Fixes, in the order I reach for them:
- Fetch join in JPQL:
select o from Order o join fetch o.itemsloads orders and items in one query - Entity graph:
@EntityGraph(attributePaths = "items")on a repository method, the same effect without writing JPQL - Batch fetching: Hibernate's
@BatchSizeorhibernate.default_batch_fetch_sizeloads the collections for many parents with oneINquery, so N+1 becomes 1 + N/batch size - DTO projections when you only need a few columns
Switching the association to
EAGERis not a fix: it still issues extra queries and loads data you often don't need. Also be careful combining a collection fetch join with pagination, because Hibernate then paginates in memory.List<Order> orders = orderRepository.findAll(); // 1 query for (Order o : orders) { System.out.println(o.getItems().size()); // +1 query per order }What interviewers listen for- One query for parents, N more for lazy children
- Spot it in the SQL log
- Fix with
join fetchor@EntityGraph - Batch fetching cuts N queries down to a few
- Switching to EAGER does not fix it
Likely follow-up: Why is a collection fetch join a problem with pagination?
- Fetch join in JPQL:
16.Walk me through the lifecycle of a Spring bean.mid
For a singleton, the container roughly does this:
- Instantiate the bean by calling its constructor; constructor injection happens here
- Populate properties: setter and field injection
- Aware callbacks such as
BeanNameAwareandApplicationContextAware BeanPostProcessor.postProcessBeforeInitializationfrom every post-processor- Initialization:
@PostConstruct, thenInitializingBean.afterPropertiesSet(), then a custom init method postProcessAfterInitialization, which is typically where AOP proxies are created, so other beans may receive a proxy rather than the raw object- The bean is in use
- On context shutdown:
@PreDestroy, thenDisposableBean.destroy(), then a custom destroy method
Prototype beans are initialized but never get destruction callbacks.
In practice I use
@PostConstructand@PreDestroy(fromjakarta.annotationin Boot 3), which the Spring docs recommend over the Spring-specific interfaces. I don't rely on transactional behavior inside@PostConstruct; anApplicationRunneror anApplicationReadyEventlistener is safer for startup work.What interviewers listen for- Instantiate, inject, then Aware callbacks
- BeanPostProcessors run around initialization
@PostConstruct, thenafterPropertiesSet, then init method- Proxies are usually created after initialization
@PreDestroyon shutdown, never for prototypes
17.What are Spring profiles, and how do you use them to configure different environments?easy
A profile is a named set of configuration and beans that's active only in some environments, like
dev,testorprod.- Property files:
application-prod.ymlis loaded on top ofapplication.ymlwhenprodis active, overriding matching keys. In one multi-document YAML file, each document can usespring.config.activate.on-profile. - Beans:
@Profile("dev")on a component or@Beanmethod registers it only for that profile;@Profile("!prod")negates. - Activation:
SPRING_PROFILES_ACTIVE=prodas an environment variable,--spring.profiles.active=prodon the command line, or the property itself. With nothing active, thedefaultprofile applies. - Groups:
spring.profiles.group.prod=proddb,prodmqactivates several profiles at once.
You can't set
spring.profiles.activeinside a profile-specific document. My rule of thumb: use profiles for structural differences, like a stub client versus a real one, and inject real secrets and URLs from the environment rather than committed profile files.spring: datasource: url: jdbc:postgresql://localhost:5432/shop --- spring: config: activate: on-profile: prod datasource: url: jdbc:postgresql://db.internal:5432/shopWhat interviewers listen for- Named config and beans per environment
application-{profile}.ymloverrides the base file@Profileregisters beans conditionally- Activate via
SPRING_PROFILES_ACTIVEor a command-line arg - Falls back to the
defaultprofile
- Property files:
18.What is the difference between
@Valueand@ConfigurationProperties, and which do you prefer?mid@Value("${app.timeout}")injects a single property into a field or parameter. It supports defaults like${app.timeout:5s}and SpEL expressions, but keys become strings scattered through the code, there's no real validation, and relaxed binding is limited.@ConfigurationProperties(prefix = "app.payments")binds a whole tree of properties to a type-safe object, ideally an immutable record:- relaxed binding:
app.payments.retry-count,retryCountand the env varAPP_PAYMENTS_RETRYCOUNTall bind to the same field - conversion to
Duration,DataSize, lists, maps and nested objects - validation with
@Validatedand Bean Validation constraints, failing fast at startup - IDE auto-completion from metadata generated by
spring-boot-configuration-processor
Register it with
@EnableConfigurationPropertiesor@ConfigurationPropertiesScan; a record with a single constructor uses constructor binding automatically.I use
@ConfigurationPropertiesfor any group of related settings and keep@Valuefor a one-off value or when I genuinely need SpEL.@ConfigurationProperties(prefix = "app.payments") @Validated public record PaymentProperties( @NotBlank String baseUrl, Duration timeout, @Min(0) int retryCount) {}What interviewers listen for@Valueinjects one property and supports SpEL@ConfigurationPropertiesbinds a typed tree- Relaxed binding and rich type conversion
@Validatedfails fast at startup- Records give immutable, constructor-bound config
- relaxed binding:
19.Explain transaction propagation. How do
REQUIRED,REQUIRES_NEWandNESTEDdiffer?hardPropagation decides what a transactional method does when a transaction may already be running.
REQUIRED(default): join the existing transaction, or start one. All participants share one physical transaction. If an inner method throws and marks it rollback-only, everything rolls back; if the outer method catches that exception and tries to commit anyway, Spring throwsUnexpectedRollbackException.REQUIRES_NEW: suspend the outer transaction and run in an independent one that commits or rolls back on its own. Good for an audit record that must survive a business rollback. It holds a second connection while the outer one waits, so it can exhaust the pool under load.NESTED: one physical transaction with a savepoint; the inner part can roll back to the savepoint while the outer continues. It's meant for JDBC'sDataSourceTransactionManager; JPA has no nested transactions, andJpaTransactionManagerdisallows them by default.
The rest:
SUPPORTSjoins if present,NOT_SUPPORTEDsuspends,MANDATORYrequires an existing one andNEVERforbids one. And propagation only applies to calls through the proxy, so the inner method must live on another bean.@Transactional(propagation = Propagation.REQUIRES_NEW) public void recordAudit(AuditEvent event) { auditRepository.save(event); // commits even if the caller rolls back }What interviewers listen for- REQUIRED joins or starts one physical transaction
- Inner rollback-only leads to
UnexpectedRollbackException - REQUIRES_NEW suspends the outer and uses its own connection
- NESTED uses savepoints, meant for JDBC
- Only applies to calls through the proxy
20.How does Spring Security work in a servlet application? Describe the filter chain.mid
Spring Security is built on servlet filters, so it runs before a request ever reaches the
DispatcherServlet.- The servlet container calls a
DelegatingFilterProxy, which bridges into the Spring context and delegates to theFilterChainProxybean. FilterChainProxyholds one or moreSecurityFilterChains, each with a request matcher. Only the first matching chain handles the request, so specific chains like/api/**must come before general ones.- Each chain is an ordered list of filters: CSRF protection, authentication filters (form login, HTTP Basic, bearer tokens), then
ExceptionTranslationFilterandAuthorizationFilternear the end. - Authentication filters produce an
Authenticationand store it in theSecurityContextHolder, which is thread-local by default. - On a denial,
ExceptionTranslationFiltersends unauthenticated users to theAuthenticationEntryPoint(a 401 or login redirect) and authenticated ones to theAccessDeniedHandler(a 403).
In Spring Security 6 you configure all this by declaring
SecurityFilterChainbeans;WebSecurityConfigurerAdapterhas been removed.@Bean SecurityFilterChain api(HttpSecurity http) throws Exception { http.securityMatcher("/api/**") .authorizeHttpRequests(auth -> auth .requestMatchers("/api/public/**").permitAll() .anyRequest().authenticated()) .httpBasic(Customizer.withDefaults()); return http.build(); }What interviewers listen for- Servlet filters that run before the DispatcherServlet
DelegatingFilterProxydelegates toFilterChainProxy- First matching
SecurityFilterChainwins - Authentication stored in
SecurityContextHolder - 401 via entry point, 403 via access-denied handler
- The servlet container calls a
21.How do Spring Data JPA repositories work, and what are derived query methods?easy
You declare an interface such as
OrderRepository extends JpaRepository<Order, Long>and Spring Data generates the implementation at runtime: a proxy backed bySimpleJpaRepository. You getsave,findById,findAll,deleteById,count, paging and sorting for free.Derived queries are parsed from the method name:
findByCustomerEmailAndStatus(String email, OrderStatus status)becomes a JPQL query that joins throughcustomer. Keywords likeGreaterThan,Between,Like,In,OrderBy,FirstorTop3, plusexistsBy,countByanddeleteBy, are supported. The name is checked when the repository is created, usually at startup, so a typo in a property name fails fast.The hierarchy:
CrudRepository(returnsIterable),ListCrudRepository(returnsList),PagingAndSortingRepository(addsSortandPageable), andJpaRepository, which combines them with JPA extras likeflush,saveAllAndFlushandgetReferenceById. Since Spring Data 3,PagingAndSortingRepositoryno longer extendsCrudRepository.When a method name gets long and unreadable, I switch to
@Query.public interface OrderRepository extends JpaRepository<Order, Long> { List<Order> findByCustomerEmailAndStatus(String email, OrderStatus status); Optional<Order> findFirstByCustomerIdOrderByCreatedAtDesc(Long customerId); boolean existsByReference(String reference); long countByStatus(OrderStatus status); }What interviewers listen for- Just an interface; Spring Data generates a proxy
- Derived queries are parsed from method names
- Invalid names fail when the repository is created
JpaRepositoryadds JPA extras to CRUD and paging- Switch to
@Querywhen names get unwieldy
22.What are the main changes in Spring Boot 3 compared with Spring Boot 2?mid
- Java 17 baseline and Spring Framework 6
- Jakarta EE namespace:
javax.persistence,javax.servletandjavax.validationbecomejakarta.*. This is the biggest migration effort, because third-party libraries also need Jakarta-compatible versions - Hibernate 6 as the JPA provider
- Spring Security 6:
WebSecurityConfigurerAdapteris gone; you declareSecurityFilterChainbeans and useauthorizeHttpRequestswithrequestMatchers - Observability through Micrometer Observation and Micrometer Tracing, replacing Spring Cloud Sleuth
- GraalVM native images and ahead-of-time processing as a supported option
ProblemDetailerror responses and declarative HTTP interface clients (@HttpExchange)- auto-configurations registered in
AutoConfiguration.importsinstead ofspring.factories - trailing-slash matching off by default, so
/orders/no longer matches/orders
Later 3.x releases added
@ServiceConnectionfor Testcontainers (3.1), virtual threads andRestClient(3.2), and structured logging plus graceful shutdown by default (3.4). For migrating,spring-boot-properties-migratorreports renamed properties at startup, and OpenRewrite recipes automate much of thejavaxtojakartachange.What interviewers listen for- Java 17 and Spring Framework 6 baseline
javax.*moved tojakarta.*- Security 6:
SecurityFilterChainbeans only - Micrometer Tracing, native images and AOT
- ProblemDetail and HTTP interface clients
Likely follow-up: How would you plan a migration from Boot 2.7 to 3.x?
23.How do you validate request payloads in a Spring Boot REST API?easy
With Bean Validation (Jakarta Validation, implemented by Hibernate Validator), added through
spring-boot-starter-validation.- Put constraints on the request DTO:
@NotBlank,@Email,@Size,@Positive,@Past,@Pattern, and@Validon nested objects you want validated too. - Annotate the controller parameter with
@Valid(or Spring's@Validated, which also supports validation groups). On failure, Spring throwsMethodArgumentNotValidException, which becomes a 400. - Constraints placed directly on
@RequestParamor@PathVariableparameters, like@Min(1) int page, trigger Spring MVC's built-in method validation (Spring 6.1+), which raisesHandlerMethodValidationException. - Handle both in a
@RestControllerAdviceto return field-level errors, ideally as aProblemDetail.
For rules that span several fields, I write a custom constraint with
@Constraint(validatedBy = ...). Rules that need the database, like "email already registered", belong in the service layer: DTO validation checks shape, the domain checks business rules.public record CreateUserRequest( @NotBlank @Size(max = 50) String name, @NotBlank @Email String email, @Min(18) int age) {} @PostMapping("/users") public UserDto create(@Valid @RequestBody CreateUserRequest req) { return userService.create(req); }What interviewers listen forspring-boot-starter-validationbrings Hibernate Validator- Constraints on the DTO,
@Validon the parameter - Failure raises
MethodArgumentNotValidException: a 400 - Constraints on params trigger method validation
- Custom constraints via
@Constraint
- Put constraints on the request DTO:
24.What is the difference between lazy and eager fetching in JPA, and what causes a
LazyInitializationException?midEager loads an association together with the entity; lazy loads it on first access, through a proxy or a persistent collection wrapper. The JPA defaults:
@ManyToOneand@OneToOneare EAGER,@OneToManyand@ManyToManyare LAZY.My rule is to make everything lazy, including
@ManyToOne(fetch = FetchType.LAZY), and fetch what each use case needs explicitly with a fetch join, an entity graph or a DTO projection. An eager mapping can't be switched off per query, so it loads data you don't need and causes extra queries when you load lists.LazyInitializationExceptionhappens when you touch a lazy association after the persistence context has closed, typically outside the transaction; a classic case is Jackson serializing an entity in the controller. The fixes are to load what you need inside the transactional service method, map to a DTO there, or use a fetch join. Making thingsEAGERor relying on open-session-in-view just hides the problem.@Entity public class Order { @ManyToOne(fetch = FetchType.LAZY) // default would be EAGER private Customer customer; @OneToMany(mappedBy = "order") // LAZY by default private List<OrderItem> items = new ArrayList<>(); }What interviewers listen for- ToOne defaults to EAGER, ToMany to LAZY
- Prefer LAZY everywhere and fetch per use case
- Lazy loading needs an open persistence context
- The exception comes from access outside the transaction
- Fix with fetch joins or DTOs, not EAGER
25.What is Spring Boot Actuator, and how do you use it for health checks?easy
Actuator adds production-ready endpoints under
/actuator:health,info,metrics,env,loggers,threaddump,prometheus(with the Micrometer Prometheus registry) and more. By default onlyhealthis exposed over HTTP; you opt in withmanagement.endpoints.web.exposure.include./actuator/healthaggregates theHealthIndicators Boot auto-configures for what you use, such as the database, disk space or Redis, into a status likeUPorDOWN. Details are hidden by default (show-detailsisnever);when-authorizedis a sensible production setting.For Kubernetes there are liveness and readiness groups at
/actuator/health/livenessand/actuator/health/readiness. Boot 3 enables them automatically when it detects Kubernetes, or anywhere withmanagement.endpoint.health.probes.enabled=true. Keep external systems like the database out of liveness, or a database outage restarts every pod.You can add a custom
HealthIndicatorbean. Endpoints likeenvandheapdumpare sensitive, so I expose them sparingly, secure them, or move management to its own port withmanagement.server.port.What interviewers listen for- Production endpoints under
/actuator - Only
healthis exposed over HTTP by default - Health aggregates auto-configured
HealthIndicators - Liveness and readiness groups for Kubernetes
- Secure sensitive endpoints or use a separate port
- Production endpoints under
26.Walk me through what happens when an HTTP request hits a Spring MVC application.mid
- The embedded server (Tomcat by default) accepts the request and runs it through the servlet filter chain: Spring Security, CORS, logging filters.
- It reaches the
DispatcherServlet, Spring MVC's front controller. - A
HandlerMapping(RequestMappingHandlerMappingfor annotated controllers) finds the controller method matching the path, HTTP method, headers and media types, along with anyHandlerInterceptors, whosepreHandleruns. - A
HandlerAdapterinvokes the method. Argument resolvers fill in@PathVariable,@RequestParamand@RequestBody, the last through anHttpMessageConvertersuch as Jackson, and validation runs. - The return value is handled: for
@ResponseBodyorResponseEntity, a message converter writes the body; for a view name, aViewResolverrenders a template. - Interceptors'
postHandleandafterCompletionrun. - If anything throws, the
HandlerExceptionResolvers take over, which is how@ExceptionHandlerand@ControllerAdviceget invoked.
The whole request is handled on one container thread, unless you use async request processing.
What interviewers listen for- Filters run before the DispatcherServlet
- HandlerMapping picks the controller method
- HandlerAdapter resolves arguments and invokes it
- Message converters write
@ResponseBodyresults - Exception resolvers power
@ExceptionHandler
Likely follow-up: What is the difference between a servlet filter and a
HandlerInterceptor?27.What is Spring AOP, and how does it use proxies?mid
AOP applies cross-cutting concerns, like transactions, security, caching, logging or metrics, without putting that code in every method. An aspect combines advice, the code to run (
@Before,@AfterReturning,@AfterThrowing,@Afteror@Around), with a pointcut saying where, such asexecution(* com.acme.service..*(..))or@annotation(com.acme.Timed).Spring AOP is proxy-based and works at runtime. When a bean matches a pointcut, the container hands out a proxy instead of the raw object:
- a JDK dynamic proxy when proxying through interfaces
- a CGLIB proxy, a generated subclass, when proxying the class; Spring Boot uses class-based proxies by default (
spring.aop.proxy-target-class=true)
The consequences: only method executions on Spring beans can be advised; calls must come through the proxy, so self-invocation isn't intercepted; and CGLIB can't override
finalorprivatemethods or subclassfinalclasses. For anything beyond that, such as advising constructors or non-Spring objects, you need full AspectJ weaving.@Aspect @Component public class TimingAspect { @Around("@annotation(com.acme.Timed)") public Object time(ProceedingJoinPoint pjp) throws Throwable { long start = System.nanoTime(); try { return pjp.proceed(); } finally { log.info("{} took {} ns", pjp.getSignature(), System.nanoTime() - start); } } }What interviewers listen for- Aspect = advice plus pointcut
- Runtime proxies: JDK interface-based or CGLIB subclass
- Boot defaults to CGLIB class-based proxies
- Only method calls through the proxy are advised
- AspectJ weaving for anything beyond that
28.What is the difference between
@SpringBootTest,@WebMvcTestand@DataJpaTest?mid@SpringBootTestloads the full application context: all your beans plus auto-configuration. By default it uses a mock web environment with no real server;webEnvironment = RANDOM_PORTstarts the embedded server for true end-to-end HTTP tests. The most realistic and the slowest.@WebMvcTest(OrderController.class)is a slice: only the MVC layer, meaning controllers,@ControllerAdvice, filters, converters and JSON support, withMockMvcauto-configured. Services and repositories aren't loaded, so you replace them with@MockitoBean. Ideal for routing, validation, status codes and JSON shape.@DataJpaTestloads only JPA: entities, repositories and aTestEntityManager. Each test is transactional and rolled back, and it swaps in an embedded database by default;@AutoConfigureTestDatabase(replace = Replace.NONE)keeps your real one, for example a Testcontainers database.
Slices keep tests fast and focused. I write many unit and slice tests and fewer
@SpringBootTestintegration tests. Spring caches contexts between test classes with the same configuration, so needlessly varying mocks and properties slows the suite down.What interviewers listen for@SpringBootTestloads the full context@WebMvcTestloads only the MVC slice, with MockMvc@DataJpaTestloads JPA only and rolls back each test- Replace collaborators with
@MockitoBean - Context caching rewards consistent test configs
29.How would you secure a stateless REST API with JWTs using Spring Security?hard
In a stateless API every request carries a bearer token in the
Authorizationheader and the server keeps no session. Instead of hand-writing a JWT filter, I use Spring Security's OAuth2 Resource Server support:- add
spring-boot-starter-oauth2-resource-serverand setspring.security.oauth2.resourceserver.jwt.issuer-uri, so Spring discovers the issuer's public keys (the JWK set) - enable
oauth2ResourceServer(o -> o.jwt(...)): each request's token is checked for a valid signature,expandnbf, and the issuer, andscopeclaims becomeSCOPE_authorities - use
SessionCreationPolicy.STATELESSand disable CSRF, since no cookies carry credentials - authorize with
requestMatchersor@PreAuthorize("hasAuthority('SCOPE_orders:write')")
Design points: keep access tokens short-lived with refresh tokens, because a JWT can't easily be revoked before it expires. Don't put secrets in the payload; it's encoded, not encrypted. Prefer asymmetric signatures so services only hold the public key. And let a dedicated authorization server, such as Keycloak or Spring Authorization Server, issue tokens.
@Bean SecurityFilterChain api(HttpSecurity http) throws Exception { return http .authorizeHttpRequests(a -> a.anyRequest().authenticated()) .oauth2ResourceServer(o -> o.jwt(Customizer.withDefaults())) .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .csrf(csrf -> csrf.disable()) .build(); }What interviewers listen for- Bearer token per request, no server session
- Use Resource Server support, not a hand-rolled filter
- Validates signature, expiry and issuer via the JWK set
- Stateless sessions, CSRF off for token-only APIs
- Short-lived tokens, because JWTs are hard to revoke
- add
30.What happens when two beans of the same type are candidates for injection, and how do you resolve it?easy
If a dependency matches more than one bean and Spring can't narrow it down, startup fails with
NoUniqueBeanDefinitionException. Ways to resolve it:@Primaryon one bean makes it the default whenever there's ambiguity@Qualifier("stripe")at the injection point picks a specific bean by qualifier or bean name, and it takes precedence over@Primary- Parameter-name fallback: if the parameter name matches a bean name, Spring uses that bean, but a rename silently breaks it
- Inject them all:
List<PaymentGateway>, orMap<String, PaymentGateway>keyed by bean name, for strategy-style code that chooses at runtime
I use
@Primaryfor "the normal implementation plus a special case",@Qualifier(often via a custom qualifier annotation) when callers must choose explicitly, and aMapof strategies when the choice depends on request data.@Component("stripe") class StripeGateway implements PaymentGateway { } @Primary @Component("paypal") class PaypalGateway implements PaymentGateway { } @Service class CheckoutService { CheckoutService(@Qualifier("stripe") PaymentGateway gateway) { } }What interviewers listen for- Ambiguity causes
NoUniqueBeanDefinitionException @Primarymarks the default candidate@Qualifierpicks explicitly and beats@Primary- Inject a
ListorMapof all implementations
31.What is
ResponseEntity, and when would you return it instead of the body object?easyResponseEntity<T>represents the whole HTTP response: status code, headers and body. Returning a plain object from a@RestControlleralways gives a 200 with that body (unless@ResponseStatussays otherwise);ResponseEntitylets you decide per request.Typical uses:
- 201 Created with a
Locationheader after a POST:ResponseEntity.created(uri).body(dto) - 204 No Content after a DELETE:
ResponseEntity.noContent().build() - 404 for a missing resource:
ResponseEntity.notFound().build(), orResponseEntity.of(optional) - custom headers such as
ETag,Cache-ControlorContent-Dispositionfor downloads
For errors I usually prefer throwing a domain exception and mapping it centrally in a
@ControllerAdvicerather than building error responses in every controller. So my controllers returnResponseEntityfor successful responses that aren't a plain 200, and bare DTOs otherwise.What interviewers listen for- Wraps status, headers and body
- A plain return value means 200
created(uri)for a 201 with LocationnoContent()andnotFound()builders- Prefer exceptions plus advice for errors
- 201 Created with a
32.What is the difference between
application.propertiesandapplication.yml?easyBoth are Boot's default configuration files, loaded from the classpath root (and
config/locations) into the sameEnvironment; the difference is syntax..properties: flatkey=valuelines that repeat the prefix:spring.datasource.url=...,spring.datasource.username=.... Lists use indexes likeapp.hosts[0]=.....yml: hierarchical, which is more readable for nested settings and lists, and one file can hold several documents separated by---, for example one per profile.
Trade-offs: YAML is whitespace-sensitive, so a bad indent can silently move a key under the wrong parent, and YAML files can't be loaded with
@PropertySource. Properties files are harder to get wrong. If both formats exist in the same location,.propertiestakes precedence.Either way, environment variables and command-line arguments override the same keys, which is how you change settings per deployment without rebuilding.
What interviewers listen for- Same binding, different syntax
- YAML is hierarchical and supports multiple documents
- YAML is indentation-sensitive
.propertieswins if both exist in one location- Env vars and CLI args override both
33.When do you use
@Queryin Spring Data JPA, and what does@Modifyingdo?mid@Querylets you write the query yourself when a derived method name would be unreadable, or when you need joins, aggregates, a fetch join or a DTO projection. It takes JPQL by default, written against entity and field names rather than tables, or native SQL withnativeQuery = truefor database-specific features. JPQL here is checked at startup; native SQL isn't.Bind values with named parameters (
:statusplus@Param) or positional ones (?1); never concatenate user input into the string.For bulk updates and deletes add
@Modifying. Two things to know:- the query needs a transaction, either the caller's or
@Transactionalon the method, because declared queries get no transaction configuration of their own - a bulk JPQL statement goes straight to the database and bypasses the persistence context, so managed entities already loaded are now stale.
@Modifying(clearAutomatically = true)clears the context afterwards, andflushAutomatically = trueflushes pending changes first.
public interface OrderRepository extends JpaRepository<Order, Long> { @Query("select o from Order o join fetch o.items where o.customer.id = :customerId") List<Order> findWithItems(@Param("customerId") Long customerId); @Modifying(clearAutomatically = true) @Query("update Order o set o.status = :status where o.createdAt < :before") int expireOlderThan(@Param("status") OrderStatus status, @Param("before") Instant before); }What interviewers listen for- JPQL by default,
nativeQuery = truefor SQL - Bind parameters, never concatenate input
@Modifyingfor bulk update and delete- Bulk queries need a transaction
- Bulk updates bypass the persistence context
- the query needs a transaction, either the caller's or
34.How do you implement pagination and sorting with Spring Data, and what is the difference between
PageandSlice?midAdd a
Pageableparameter to a repository method, likePage<Order> findByStatus(OrderStatus status, Pageable pageable), and Spring Data applies the limit, offset and sort. Build one withPageRequest.of(0, 20, Sort.by("createdAt").descending()). In a controller, Spring Data's web support resolves aPageableparameter straight from?page=0&size=20&sort=createdAt,desc; page numbers are zero-based.Page<T>runs an extra count query, so it knows the total number of elements and pages. Useful for "page 3 of 12" UIs, but the count can be expensive on large tables.Slice<T>skips the count; it fetches one extra row just to know whether there's a next slice. Good for "load more" and infinite scroll.- A
List<T>return type with aPageablejust applies the limit, with no metadata.
Offset pagination slows down on deep pages, because the database still walks the skipped rows, and it can skip or repeat rows when data changes. For large or fast-moving tables I prefer keyset pagination,
where id > :lastId order by id, which recent Spring Data versions support through theWindowandScrollPositionscrolling API.What interviewers listen forPageableparameter adds limit, offset and sortPageruns an extra count querySliceonly knows if a next slice exists- Page numbers are zero-based
- Keyset pagination for deep or changing data
35.Why shouldn't you expose JPA entities directly from REST controllers, and how do you use DTOs instead?mid
Returning entities couples your API contract to your database schema, and causes practical problems:
- Lazy loading: Jackson touches lazy associations while serializing, which throws
LazyInitializationExceptionor fires surprise queries - Infinite recursion with bidirectional relationships: order, items, order, items...
- Over-exposure: internal fields such as password hashes or audit columns leak unless you remember to hide each one
- Mass assignment: binding a request body onto an entity lets a client set fields like
roleorid - every schema refactor becomes a breaking API change
So I use DTOs, usually Java records, with separate request and response types per use case and validation annotations on the request types. Mapping happens in the service layer, inside the transaction, by hand for small cases or with a mapper like MapStruct for larger ones. For read-heavy endpoints, Spring Data projections (interface projections, or records populated by a JPQL constructor expression) fetch only the columns you need.
The cost is some mapping code; the payoff is an API that can evolve independently of the schema.
What interviewers listen for- Entities couple the API to the schema
- Lazy loading and recursion break serialization
- Risk of leaking fields and mass assignment
- Records as request and response DTOs
- Projections fetch only the needed columns
- Lazy loading: Jackson touches lazy associations while serializing, which throws
36.What is a circular dependency in Spring, and how do you fix one?mid
It's when bean A needs B and B needs A, directly or through a longer chain. With constructor injection neither can be created first, so startup fails with
BeanCurrentlyInCreationException, and Boot's failure analyzer prints the cycle.Spring can technically resolve a cycle between singletons that use setter or field injection, by handing one bean an early reference to the other before it's fully initialized. But since Spring Boot 2.6, circular references are prohibited by default, so those fail too unless you set
spring.main.allow-circular-references=true, which should be a last resort.A cycle usually signals a design problem, so the real fixes are:
- extract the shared logic into a third bean that both depend on
- decouple with an interface or with application events (
ApplicationEventPublisherplus@EventListener) - merge the two classes if they're really one responsibility
As a stopgap,
@Lazyon one injection point injects a lazy proxy, which breaks the cycle at startup without changing the design.What interviewers listen for- A needs B and B needs A
- Constructor cycles fail with
BeanCurrentlyInCreationException - Boot 2.6+ prohibits circular references by default
- Fix the design: extract a bean or use events
@Lazyis a stopgap, not a fix
37.What is the difference between a servlet
Filterand a SpringHandlerInterceptor?midA servlet
Filterbelongs to the Servlet API and wraps the whole request before it reaches theDispatcherServlet. It sees every request, including static resources and error dispatches, can wrap or replace the request and response, and knows nothing about which controller will run. Spring Security, CORS, request logging and compression live here. In Boot, aFilterbean is registered automatically; useFilterRegistrationBeanto control URL patterns and order, and extendOncePerRequestFilterso it runs once per request.A
HandlerInterceptoris Spring MVC's hook and runs inside theDispatcherServlet, after the handler has been chosen:preHandle: before the controller; returningfalsestops processingpostHandle: after the controller, before view rendering; of limited use with@ResponseBody, since the response is already written by thenafterCompletion: after everything, even on exceptions; good for cleanup and timing
Because it knows the
HandlerMethod, an interceptor can read the controller's annotations. Register it throughWebMvcConfigurer.addInterceptors. Rule of thumb: generic HTTP concerns go in filters, MVC-aware ones in interceptors.What interviewers listen for- Filter: Servlet API, before the DispatcherServlet
- Interceptor: Spring MVC, after handler mapping
- Interceptors know the handler method
postHandleis of limited use with@ResponseBody- Register via
WebMvcConfigurer.addInterceptors
38.How does Spring Boot run with an embedded server, and how would you change or configure it?easy
With the web starter, Boot puts Tomcat on the classpath and starts it programmatically inside your application, instead of you deploying a WAR into an external server. The Boot build plugin packages an executable "fat" jar containing your code, its dependencies and the server, so you run it with
java -jar app.jar, which suits containers well.- Configure it with properties:
server.port(0picks a random free port),server.servlet.context-path,server.ssl.*, compression, and Tomcat thread and connection limits underserver.tomcat.*. - To switch to Jetty, exclude
spring-boot-starter-tomcatand addspring-boot-starter-jetty. WebFlux apps use Reactor Netty by default. Undertow was an option in Boot 3 but was dropped in Boot 4. - For programmatic tweaks, register a
WebServerFactoryCustomizerbean. - You can still build a WAR for an external container by extending
SpringBootServletInitializer.
On Java 21 with Boot 3.2 or later,
spring.threads.virtual.enabled=truemakes Tomcat handle requests on virtual threads.What interviewers listen for- Tomcat is embedded by default for Spring MVC
- Executable fat jar, run with
java -jar - Configure via
server.*properties - Swap to Jetty by excluding the Tomcat starter
- WebFlux defaults to Reactor Netty
- Configure it with properties:
40.How should passwords be stored in a Spring Security application?easy
Never in plain text, and never with a fast hash like MD5 or plain SHA-256. Use an adaptive, salted one-way function through a
PasswordEncoder: bcrypt, scrypt, Argon2 or PBKDF2. They're deliberately slow and tunable, and each hash includes a random salt, so identical passwords produce different hashes.The usual setup is the
DelegatingPasswordEncoderfromPasswordEncoderFactories.createDelegatingPasswordEncoder(). It stores hashes with an id prefix, like{bcrypt}$2a$10$..., encodes new passwords with bcrypt, and can still verify the other supported formats. That makes migrating algorithms possible: verify with the old one, then re-encode with the new one on the next successful login.Rules: call
encodewhen a user registers,matches(raw, stored)when checking, and never compare hashes yourself. Tune the work factor so a verification takes a noticeable amount of time on your hardware (Spring's docs suggest around one second), balanced against login throughput.NoOpPasswordEncoderand{noop}are for demos only.What interviewers listen for- Adaptive salted hashes: bcrypt, scrypt, Argon2, PBKDF2
DelegatingPasswordEncoderwith{id}prefixes- Prefixes allow migrating algorithms over time
- Use
matches, never compare hashes manually - Tune the work factor; no NoOp encoder in production
41.Which HTTP clients does Spring offer for calling other services, and which would you choose today?mid
RestTemplate: the classic synchronous, template-style client. Still common, but in maintenance mode;RestClientis its modern replacement.WebClient: the reactive, non-blocking client from WebFlux, returningMonoandFlux. The right choice in a WebFlux app or for streaming. It works from MVC too, but calling.block()everywhere defeats the point.RestClient(Spring Framework 6.1, Boot 3.2+): a synchronous client with a fluent API similar toWebClient, built on the same infrastructure asRestTemplate. My default in a blocking MVC service.- HTTP interface clients: you declare a Java interface with
@GetExchangeor@PostExchangemethods, and Spring generates the implementation on top ofRestClientorWebClient, a built-in alternative to OpenFeign.
Whichever I use, I build it from the Boot-provided builder, like the auto-configured
RestClient.Builder, so message converters and observability are applied. And I always set connect and read timeouts, because an unbounded call to a slow dependency is how thread pools get exhausted.What interviewers listen forRestTemplate: classic, in maintenance modeWebClient: reactive, for WebFlux and streamingRestClient: modern fluent synchronous client- HTTP interfaces generate clients declaratively
- Use Boot builders and always set timeouts
42.What is CSRF, how does Spring Security protect against it, and when is it safe to disable?mid
Cross-Site Request Forgery tricks a logged-in user's browser into sending a state-changing request to your site from a malicious page. The browser attaches the session cookie automatically, so without protection the server can't tell the forged request from a real one.
Spring Security enables CSRF protection by default for unsafe methods (POST, PUT, PATCH, DELETE). It requires a secret CSRF token, which a cross-site page can't read, in a form field or header, and compares it with the copy stored server-side, in the HTTP session by default. Since Spring Security 6 the token is randomized on each request to mitigate BREACH attacks. For single-page apps,
CookieCsrfTokenRepositoryputs the token in a cookie the frontend reads and echoes back as a header.It's reasonable to disable it for a stateless API authenticated only by a bearer token in the
Authorizationheader, because browsers don't attach that automatically. If the API authenticates with cookies, including a JWT stored in a cookie, keep CSRF on.SameSitecookies help, but as defense in depth, not a replacement.What interviewers listen for- Forged requests ride on automatically sent cookies
- Enabled by default for unsafe HTTP methods
- Token checked against a server-side copy
- Disable only for bearer-token APIs
- Cookie-based auth still needs CSRF protection
43.How does caching with
@Cacheablework in Spring Boot, and what pitfalls should you watch for?midEnable it with
@EnableCachingon a configuration class. Boot then auto-configures aCacheManagerfor the provider it finds, such as Caffeine, Redis, Hazelcast or JCache, falling back to a simpleConcurrentHashMapthat isn't meant for production.@Cacheable: on a hit, returns the cached value without calling the method; on a miss, calls it and stores the result@CachePut: always runs the method and updates the entry@CacheEvict: removes an entry, or everything withallEntries = true
The default key comes from the parameters: none gives
SimpleKey.EMPTY, one gives that value, several give aSimpleKeyof all of them. Customize with SpEL (key = "#id"), and useconditionorunlessto control what's cached.Pitfalls:
- it's proxy-based, so self-invocation skips the cache
- keys need correct
equalsandhashCode - local caches aren't shared between instances and go stale; set TTLs and size limits, or use Redis
- concurrent misses can stampede the backend;
sync = truelets only one thread compute the value
@Cacheable(cacheNames = "products", key = "#id", unless = "#result == null") public Product findById(long id) { return productRepository.findById(id).orElse(null); } @CacheEvict(cacheNames = "products", key = "#product.id") public Product update(Product product) { return productRepository.save(product); }What interviewers listen for@EnableCachingplus an auto-configuredCacheManager- A cache hit skips the method entirely
- Default key is derived from the parameters
- Self-invocation bypasses the cache
- Plan TTLs, eviction and multi-instance consistency
44.How does
@Asyncwork in Spring, and what are its common pitfalls?mid@Asyncmakes a method run on another thread: the caller returns immediately and the work is submitted to aTaskExecutor. Enable it with@EnableAsync. Boot auto-configures an executor namedapplicationTaskExecutor, aThreadPoolTaskExecutorby default or a virtual-thread executor whenspring.threads.virtual.enabled=true; tune the pool withspring.task.execution.pool.*.Return
voidfor fire-and-forget, orCompletableFuture<T>so callers can compose or wait on the result.Pitfalls:
- it's proxy-based: a self-invoked
@Asyncmethod simply runs synchronously - exceptions from
voidmethods never reach the caller; they go to anAsyncUncaughtExceptionHandler, which by default just logs them - the new thread doesn't inherit the caller's transaction, and thread-local context like the
SecurityContextor logging MDC isn't propagated unless you configure it, for example with aTaskDecorator - the default pool's queue is unbounded, so a burst of work piles up in memory; set a queue capacity and rejection policy for heavy use
For background work that must survive a restart, I'd use a message queue instead.
What interviewers listen for- Runs on a TaskExecutor thread; needs
@EnableAsync - Return
voidorCompletableFuture - Self-invocation runs synchronously
- No transaction or thread-local propagation by default
- Void-method exceptions go to the uncaught handler
- it's proxy-based: a self-invoked
45.How do you run scheduled tasks in Spring Boot with
@Scheduled?easyAdd
@EnableSchedulingto a configuration class, then annotate a no-argument bean method:fixedRate: run every N milliseconds, measured from the start of the previous runfixedDelay: wait N milliseconds after the previous run finishesinitialDelay: wait before the first runcron: Spring's cron format has six fields, starting with seconds, so0 0 2 * * *means 2 AM every day; addzoneto pin the time zone
Values can come from properties, for example
cron = "${jobs.cleanup.cron}".Gotchas: Boot's default scheduler has a single thread, so one slow job delays the others; raise
spring.task.scheduling.pool.sizeif needed. An exception in a repeating task is logged and the schedule carries on. Most importantly, in a multi-instance deployment every instance runs the job. For exactly-one-node semantics, use a distributed lock (ShedLock is a popular library), a Kubernetes CronJob, or a clustered scheduler like Quartz.@Component public class CleanupJob { @Scheduled(cron = "0 0 2 * * *", zone = "UTC") public void purgeExpiredSessions() { /* daily at 02:00 UTC */ } @Scheduled(fixedDelayString = "PT30S", initialDelay = 10_000) public void pollOutbox() { /* 30 s after the previous run ends */ } }What interviewers listen for@EnableSchedulingplus@Scheduledmethods- fixedRate counts from the start, fixedDelay from the end
- Spring cron has six fields, seconds first
- Default scheduler pool has a single thread
- Every instance runs it; use a lock for one node
46.Spring MVC or Spring WebFlux: how do they differ, and when would you choose WebFlux?hard
Spring MVC is built on the Servlet API with a thread-per-request model: each request holds a pool thread for its whole duration, including while it blocks on the database or a remote call. It's simple to write, debug and profile, and it works with the whole blocking ecosystem: JDBC, JPA and most client libraries.
Spring WebFlux is non-blocking and built on Project Reactor. A small, fixed number of event-loop threads (on Reactor Netty by default) serve many concurrent requests, because no thread waits on I/O. You return
Mono<T>andFlux<T>, and you get back-pressure and streaming. The catch: every layer must be non-blocking, such as R2DBC instead of JDBC andWebClientinstead of blocking clients. One blocking call on an event-loop thread stalls every request that thread serves.I'd pick WebFlux for high-concurrency, I/O-bound services like gateways, streaming, or fan-out to many slow backends. For a typical CRUD service on a relational database, MVC is the better fit, and on Java 21 virtual threads give MVC much of the scalability with a blocking style.
What interviewers listen for- MVC: Servlet, thread per request, blocking I/O
- WebFlux: event loop, non-blocking,
MonoandFlux - WebFlux needs non-blocking I/O end to end
- Blocking an event-loop thread stalls many requests
- Virtual threads narrow the gap for MVC
Likely follow-up: What happens if both the MVC and WebFlux starters are on the classpath?
47.How do you test a controller with MockMvc and mock its dependencies?mid
With
@WebMvcTest(OrderController.class), Spring loads only the web layer and auto-configuresMockMvc, which drives the real Spring MVC pipeline (routing, argument binding, validation, message conversion, exception handlers) without starting a server. Anything the controller depends on must be supplied, usually as a mock declared with@MockitoBean, which replaces or adds that bean in the test context.@MockitoBeanand@MockitoSpyBeancome from Spring Framework 6.2. Boot's older@MockBeanand@SpyBeanwere deprecated in Boot 3.4 and removed in Boot 4.Then you stub with Mockito,
performa request, and assert on the status, headers and JSON withjsonPath. With a full@SpringBootTestyou add@AutoConfigureMockMvcto get aMockMvc; for a real HTTP round trip, useRANDOM_PORTwith an HTTP client instead. Recent versions also offerMockMvcTesterfor AssertJ-style assertions.@WebMvcTest(OrderController.class) class OrderControllerTest { @Autowired MockMvc mvc; @MockitoBean OrderService orderService; @Test void returnsOrder() throws Exception { when(orderService.find(42L)).thenReturn(new OrderDto(42L, "PAID")); mvc.perform(get("/api/orders/42")) .andExpect(status().isOk()) .andExpect(jsonPath("$.status").value("PAID")); } }What interviewers listen for@WebMvcTestauto-configures MockMvc- Exercises the MVC pipeline without a server
@MockitoBeanreplaces collaborators with mocks@MockBeandeprecated in 3.4, removed in Boot 4- Assert status and JSON with
jsonPath
48.How do you write integration tests against a real database with Testcontainers in Spring Boot?hard
Testcontainers starts real services, like PostgreSQL, Kafka or Redis, in throwaway Docker containers from your JUnit tests, so you test against the same engine as production instead of H2, which differs in SQL dialect, types and locking behavior.
The setup:
@Testcontainerson the class and astatic@Containerfield, so the container starts once for the class@ServiceConnection(Boot 3.1+) on that field: Boot takes the host, port and credentials from the running container and creates the connection details, so theDataSourcepoints at it with no properties at all- before 3.1, or for extra settings,
@DynamicPropertySourceregisters properties such asspring.datasource.urlfrom the container - with
@DataJpaTest, add@AutoConfigureTestDatabase(replace = Replace.NONE)so Boot doesn't swap in an embedded database
To keep the suite fast, share containers across test classes, for example through a common base class or
@ImportTestcontainers, so Spring's context cache keeps working. Boot can also reuse the same container definitions at development time.@SpringBootTest @Testcontainers class OrderRepositoryIT { @Container @ServiceConnection static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine"); @Autowired OrderRepository orders; @Test void savesAndLoads() { /* runs against a real PostgreSQL */ } }What interviewers listen for- Real services in throwaway Docker containers
- Same engine as production, not H2
@ServiceConnectionwires connection details automatically@DynamicPropertySourcefor manual properties- Share containers to keep the context cache effective
49.What are the JPA entity lifecycle states, and what is the difference between
persistandmerge?hardAn entity is in one of four states relative to a persistence context:
- New (transient): just created with
new, unknown to JPA, no row yet - Managed: attached to the persistence context, after
persist,findor a query. Changes are tracked by dirty checking and flushed automatically at commit, with nosavecall needed - Detached: it was managed, but the context closed (the transaction ended) or it was cleared or evicted; changes are no longer tracked
- Removed: scheduled for deletion by
remove, deleted at flush
persist(entity)makes a new instance managed; the object you passed is the managed one.merge(entity)copies the state of a detached or new object onto a managed instance and returns that copy; the argument itself stays detached, which is a classic bug when code keeps using it.Spring Data's
save()callspersistif the entity looks new (a null id or null version, orPersistable.isNew()) andmergeotherwise, and returns the managed instance, so always use the return value. Inside a transaction, an already-managed entity doesn't needsave()at all.What interviewers listen for- New, managed, detached, removed
- Managed entities are dirty-checked and flushed
persistattaches the same instancemergereturns a managed copysavepicks persist or merge; use its return value
- New (transient): just created with
50.In what order does Spring Boot resolve configuration properties from different sources?hard
Boot builds the
Environmentfrom many property sources, and later sources override earlier ones. From lowest to highest precedence, the main ones are:- default properties set on
SpringApplication @PropertySourceon configuration classes- config data files:
application.propertiesandapplication.yml - OS environment variables
- Java system properties (
-Dkey=value) SPRING_APPLICATION_JSON- command-line arguments such as
--server.port=9090 - test sources such as
@SpringBootTest(properties = ...),@DynamicPropertySourceand@TestPropertySource, at the very top
Among config files, profile-specific files beat plain ones, and files outside the jar (the current directory or
./config/) beat those packaged inside. So aconfig/application-prod.ymlnext to the jar overrides the packaged defaults.@PropertySourcesits near the bottom and is added too late to affect early settings likelogging.*. Environment variables map through relaxed binding:SPRING_DATASOURCE_URLsetsspring.datasource.url.What interviewers listen for- Later property sources override earlier ones
- Command-line args beat env vars and files
- Env vars beat
application.yml - Profile-specific and external files beat packaged ones
@PropertySourceis near the bottom
- default properties set on
51.How would you build your own auto-configuration or custom starter?hard
I'd split it the way Boot does: an autoconfigure module with the code, and a starter module that only aggregates dependencies. I'd name it
acme-sms-spring-boot-starter, since thespring-boot-prefix is reserved for official starters.In the autoconfigure module:
- write a class annotated
@AutoConfigurationwith@Beanmethods - guard it with conditions:
@ConditionalOnClassso it applies only when the library is present,@ConditionalOnMissingBeanon each bean so users can override it, and@ConditionalOnPropertyfor an on/off switch - bind settings with a
@ConfigurationPropertiesclass under your own prefix, plusspring-boot-configuration-processorfor IDE metadata - list the class in
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports - order it with
@AutoConfiguration(after = ...)if it depends on other auto-configured beans
I'd test it with
ApplicationContextRunner, which asserts which beans exist under different properties and classpaths without booting a full app. And I'd only use@ConditionalOnBeanin auto-configuration classes, because it depends on which bean definitions exist at that point.@AutoConfiguration @ConditionalOnClass(SmsClient.class) @EnableConfigurationProperties(SmsProperties.class) public class SmsAutoConfiguration { @Bean @ConditionalOnMissingBean SmsClient smsClient(SmsProperties props) { return new SmsClient(props.apiUrl(), props.apiKey()); } } // Listed in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.importsWhat interviewers listen for- Separate autoconfigure and starter modules
@AutoConfigurationlisted in the imports file@ConditionalOnMissingBeanso users can override- Own prefix via
@ConfigurationProperties - Test with
ApplicationContextRunner
- write a class annotated
52.How does method-level security work in Spring Security?mid
You enable it with
@EnableMethodSecurity, which turns on@PreAuthorize,@PostAuthorize,@PreFilterand@PostFilterby default;@Securedand JSR-250 annotations like@RolesAllowedneedsecuredEnabled = trueorjsr250Enabled = true. It replaces the deprecated@EnableGlobalMethodSecurity, and Boot doesn't enable it for you.@PreAuthorize("hasRole('ADMIN')")evaluates a SpEL expression before the call and can reference arguments, like#userId == authentication.name@PostAuthorize("returnObject.owner == authentication.name")checks after the call, against the returned value@PreFilterand@PostFilterfilter collection arguments or results
Expressions can call a bean, for example
@access.canEdit(#id, authentication), which keeps complex rules in testable code.It's implemented with Spring AOP, so the proxy rules apply: self-invocation isn't checked, and only Spring beans are protected. A denial throws
AccessDeniedException, which becomes a 403. I use URL rules for coarse access and method security for domain rules like ownership.What interviewers listen for@EnableMethodSecurityenables pre/post annotations@PreAuthorizeevaluates SpEL before the call- Expressions can use arguments and beans
- AOP-based, so self-invocation is not secured
- Denial throws
AccessDeniedException, a 403
53.What transaction isolation levels can you set with
@Transactional, and which anomalies do they prevent?hardIsolation controls what a transaction can see of concurrent transactions.
@Transactional(isolation = ...)offers:DEFAULT(the default): use the database's own default, e.g. READ COMMITTED on PostgreSQL, Oracle and SQL Server, REPEATABLE READ on MySQL's InnoDBREAD_UNCOMMITTED: allows dirty reads of other transactions' uncommitted dataREAD_COMMITTED: no dirty reads, but reading a row twice can give different values (non-repeatable reads)REPEATABLE_READ: rows you've read stay stable, but the SQL standard still allows new matching rows to appear (phantom reads); some engines prevent those tooSERIALIZABLE: behaves as if transactions ran one at a time; the safest and slowest, and it produces serialization failures you must retry
Higher isolation means more locking or more aborted transactions. The level is applied when a transaction starts, so an inner
REQUIREDmethod that joins an existing transaction has its setting ignored by default. In practice I stay on READ COMMITTED and handle specific races with optimistic locking (@Version) or a pessimistic@Lock(LockModeType.PESSIMISTIC_WRITE)query.What interviewers listen forDEFAULTuses the database default level- READ_COMMITTED prevents dirty reads
- REPEATABLE_READ prevents non-repeatable reads
- SERIALIZABLE is safest but slowest, needs retries
- Prefer targeted locking over raising isolation
54.What does
@Transactional(readOnly = true)actually do?midIt's an optimization hint, not a guarantee that nothing gets written.
- With JPA and Hibernate, Spring sets the session's flush mode to manual, so Hibernate skips dirty checking and never flushes changes to managed entities at commit. That's a noticeable saving on large object graphs.
- The flag is also passed to the JDBC driver as a read-only hint, which some drivers and databases use for optimizations, and a routing
DataSourcecan use it to send queries to a read replica.
What it doesn't do: it doesn't reliably block writes. Some databases reject
INSERTorUPDATEinside a read-only transaction, others don't. And if the method joins an existing read-write transaction with the defaultREQUIREDpropagation, its read-only flag is ignored.Spring Data JPA's
SimpleJpaRepositoryalready marks its read methodsreadOnly = true. In my services I often put@Transactional(readOnly = true)at class level and override it with plain@Transactionalon the write methods.What interviewers listen for- A hint, not an enforcement mechanism
- Hibernate uses manual flush and skips dirty checking
- Passed to the JDBC driver as a hint
- Ignored when joining a read-write transaction
- Can drive read-replica routing
55.What does Spring Cloud provide for microservices? Explain Config Server, Gateway, OpenFeign and service discovery.mid
Spring Cloud is a family of projects that solve common distributed-system problems on top of Boot.
- Config Server serves centralized configuration, typically from a Git repository, per application and profile. Clients load it at startup with
spring.config.import=configserver:...; the old bootstrap context is disabled by default. Beans marked@RefreshScopecan pick up changes at runtime via/actuator/refresh, or on all instances with Spring Cloud Bus. - Service discovery with Eureka, Consul or Kubernetes: instances register themselves, clients look up healthy instances by service name, and Spring Cloud LoadBalancer picks one; it replaced Netflix Ribbon.
- Gateway is the edge service: routes are defined by predicates (path, host, headers) plus filters (auth, rate limiting, rewrites, retries). It comes in a reactive WebFlux flavor and a servlet Web MVC flavor.
- OpenFeign generates declarative HTTP clients from
@FeignClientinterfaces. It's now considered feature-complete, and the Spring team suggests Spring's own HTTP interface clients for new code.
On Kubernetes, the platform already provides much of this, like discovery, config maps and ingress, so I'd adopt only the pieces that add value.
What interviewers listen for- Config Server centralizes config, often Git-backed
- Clients import it via
spring.config.import - Discovery plus Spring Cloud LoadBalancer
- Gateway: routes, predicates and filters at the edge
- OpenFeign is feature-complete; consider HTTP interfaces
- Config Server serves centralized configuration, typically from a Git repository, per application and profile. Clients load it at startup with
56.How do you make service-to-service calls resilient with Resilience4j? Explain how a circuit breaker works.hard
Resilience4j is a lightweight fault-tolerance library, integrated with Boot through
resilience4j-spring-boot3(plus the AOP and Actuator starters) or through the Spring Cloud CircuitBreaker abstraction. Its modules are CircuitBreaker, Retry, RateLimiter, Bulkhead and TimeLimiter.A circuit breaker is a state machine:
- CLOSED: calls pass through and outcomes are recorded in a sliding window, count-based or time-based
- when the failure rate or slow-call rate crosses a threshold, after a minimum number of calls, it goes OPEN: calls fail immediately with
CallNotPermittedExceptionand the fallback runs, so the struggling dependency gets breathing room - after a wait duration it turns HALF_OPEN and lets a few trial calls through; if they succeed it closes, otherwise it opens again
Retries handle brief glitches, but only for idempotent calls and with backoff. The time limiter bounds latency, and a bulkhead caps concurrent calls so one slow dependency can't take all your threads. With annotations, Retry wraps the others by default, so it's the outermost layer. I monitor breaker states through Actuator and metrics.
@CircuitBreaker(name = "inventory", fallbackMethod = "stockUnknown") @Retry(name = "inventory") public StockLevel stock(String sku) { return inventoryClient.stock(sku); } // Same parameters plus the exception private StockLevel stockUnknown(String sku, Throwable ex) { return StockLevel.unknown(sku); }What interviewers listen for- CircuitBreaker, Retry, RateLimiter, Bulkhead, TimeLimiter
- CLOSED, OPEN and HALF_OPEN states
- An open breaker fails fast and runs the fallback
- Retry only idempotent calls, with backoff
- Bulkheads and timeouts protect your own threads
57.How does logging work in Spring Boot, and how do you configure it?easy
With the starters, your code logs through SLF4J and Boot uses Logback as the implementation; Boot's own internals use Commons Logging, routed to the same backend. By default it logs to the console at INFO level and writes no file unless you set
logging.file.nameorlogging.file.path.Configuration:
- levels per logger:
logging.level.root=warn,logging.level.com.acme=debug, or as environment variables likeLOGGING_LEVEL_COM_ACME=DEBUG - built-in log groups such as
webandsql:logging.level.sql=debug - change levels at runtime through the Actuator
loggersendpoint - for full control,
logback-spring.xmlrather thanlogback.xml, because the Spring variant supports<springProfile>and<springProperty> - structured logging is built in since Boot 3.4, for example
logging.structured.format.console=ecs, handy for log aggregation
Good practice: use parameterized messages like
log.info("Order {} placed", id), never log secrets or personal data, and carry a correlation id; with Micrometer Tracing, recent Boot versions add trace and span ids to log lines.What interviewers listen for- SLF4J API with Logback by default
- Console at INFO, no file unless configured
logging.level.*per logger, also via env varslogback-spring.xmlfor profile-aware config- Change levels at runtime via Actuator
- levels per logger:
58.How does graceful shutdown work in Spring Boot, especially on Kubernetes?hard
On
SIGTERM, Boot closes the application context. With graceful shutdown, the embedded server first stops accepting new requests and lets in-flight requests finish, for up tospring.lifecycle.timeout-per-shutdown-phase, 30 seconds by default. Then beans are destroyed:@PreDestroymethods run, connection pools close, executors shut down.It's on by default since Boot 3.4; on earlier versions you set
server.shutdown=graceful, because the old default was immediate. The readiness state also switches to refusing traffic.On Kubernetes, removing the pod from load balancers and sending
SIGTERMhappen concurrently, so some requests can still arrive after shutdown begins. The documented fix is apreStophook with a short sleep, so traffic stops being routed before the app stops accepting connections. KeepterminationGracePeriodSeconds(30 seconds by default) longer than the sleep plus the shutdown timeout, or the kubelet sendsSIGKILLmid-drain.For your own background work, stop consumers cleanly in
@PreDestroyor aSmartLifecycle, and setspring.task.execution.shutdown.await-terminationif queued async tasks must finish.What interviewers listen for- Stops new requests, drains in-flight ones
- Timeout per shutdown phase, 30 seconds by default
- Default since Boot 3.4;
server.shutdown=gracefulbefore - Use a
preStopsleep on Kubernetes - Grace period must exceed sleep plus drain time
59.A singleton injects a prototype-scoped bean but always gets the same instance. Why, and how do you fix it?hard
Dependencies are resolved once, when the singleton is created. Spring creates one
ReportBuilderfor that injection point and the singleton keeps the reference forever, so every call shares the same "prototype", and its state leaks between callers, possibly across threads.The fixes all ask the container for an instance at call time:
ObjectProvider<ReportBuilder>: inject the provider and callgetObject()each time. My usual choice, since it's explicit and easy to test.@Lookupmethod injection: a method annotated@Lookupthat returnsReportBuilder; Spring overrides it with CGLIB so each call fetches a new bean.- Scoped proxy:
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)injects a proxy, but for a prototype every method call on it hits a new instance, which is rarely what you want. Scoped proxies fit request and session scopes better. - Or question the design: if it's just a stateful helper, create it with
newinside the method.
Remember too that Spring never calls destruction callbacks on prototypes, so any cleanup is yours.
@Component @Scope("prototype") class ReportBuilder { /* holds per-report state */ } @Service class ReportService { private final ReportBuilder builder; // injected only once ReportService(ReportBuilder builder) { this.builder = builder; } }What interviewers listen for- Injection happens once, at singleton creation
- The prototype effectively becomes a singleton
- Use
ObjectProvider.getObject()per use @Lookupmethod injection also works- A prototype scoped proxy gives a new instance per call
60.What is Open Session in View in Spring Boot, and why do many teams disable it?hard
spring.jpa.open-in-viewis enabled by default in Spring Boot web apps. It registers anOpenEntityManagerInViewInterceptorthat opens the JPAEntityManagerat the start of each request and keeps it open until the request completes, so lazy associations can still be loaded in the controller or during JSON serialization, after the service transaction has ended. Boot logs a warning at startup if you haven't set the property explicitly.Why teams set
spring.jpa.open-in-view=false:- it hides N+1 problems: lazy loads in the web layer fire extra queries outside any transaction, often unnoticed
- it can keep a database connection tied up for the rest of the request, including slow serialization and network writes, which hurts pool capacity under load
- it blurs layers, because the web layer silently depends on the persistence context
With it off, lazy access outside a transaction throws
LazyInitializationException, which forces each use case to fetch what it needs in the service layer, with fetch joins, entity graphs or DTO projections. I disable it on new projects.What interviewers listen for- Enabled by default; Boot logs a warning
- Keeps the EntityManager open for the whole request
- Hides N+1 queries in the web layer
- Can hold a connection during serialization
- Disable it and fetch explicitly in services
61.How do you prevent lost updates with JPA? Explain optimistic locking with
@Version.hardA lost update happens when two users read the same row, both change it, and the second write silently overwrites the first.
With optimistic locking, you add an attribute annotated
@Version. JPA adds it to every update, as inupdate account set balance = ?, version = 4 where id = ? and version = 3, and increments it. If another transaction changed the row first, no row matches, and JPA throws anOptimisticLockException, which Spring translates intoObjectOptimisticLockingFailureException. No locks are held between read and write, so it scales well when conflicts are rare.Handling the conflict: return a 409 Conflict so the client reloads and retries, or retry the whole transaction automatically when the operation is safe to repeat. Across HTTP requests, expose the version (for example as an
ETag) and check the client's version on update, since each request loads a fresh entity.Pessimistic locking,
@Lock(LockModeType.PESSIMISTIC_WRITE)on a repository query, issues aSELECT ... FOR UPDATEand blocks other writers. It suits frequent conflicts or expensive retries, but keep those transactions short to limit contention and deadlocks.@Entity public class Account { @Id @GeneratedValue private Long id; @Version private Long version; // checked and incremented on every update private BigDecimal balance; }What interviewers listen for- Lost update: the last write silently wins
@Versionadds a version check to updates- A conflict throws
OptimisticLockException - Return 409 or retry; ETags across requests
- Pessimistic
FOR UPDATEfor frequent conflicts
62.How do you run code once when a Spring Boot application starts?easy
The standard options:
CommandLineRunnerorApplicationRunnerbeans: theirrunmethod is called after the context has started, just before the app is considered ready.CommandLineRunnerreceives the rawString[]arguments;ApplicationRunnergets parsedApplicationArgumentswith option and non-option arguments. Order several with@Order.- An
@EventListener(ApplicationReadyEvent.class)method, which fires once the application is ready to serve traffic. @PostConstructfor the initialization of a single bean. It runs during context creation, before transactional proxies are reliably usable, so it's for the bean's own setup rather than application-wide startup work.
An exception thrown from a runner fails startup, which is useful for mandatory checks. Long-running work in a runner delays readiness, because Boot only reports ready after all runners finish, so I move lengthy jobs to a background executor.
What interviewers listen forCommandLineRunnergets the raw String argumentsApplicationRunnergets parsedApplicationArguments- Runners execute after the context has started
ApplicationReadyEventfires when ready for traffic@PostConstructis only for per-bean setup
63.What happens when you call
SpringApplication.run()? Walk through Spring Boot startup.hardRoughly, in order:
- Deduce the application type from the classpath: servlet, reactive or none
- Load initializers and listeners, and publish
ApplicationStartingEvent - Prepare the
Environment: property sources from command-line args, environment variables, system properties and config files, plus active profiles.EnvironmentPostProcessors run here and the logging system is initialized - Print the banner and create the
ApplicationContextmatching the type, for example a servlet web server context - Refresh the context, where the core Spring work happens: parse configuration classes and component scanning, evaluate auto-configurations and their conditions, run
BeanFactoryPostProcessors, registerBeanPostProcessors, create the embedded web server, and instantiate all non-lazy singletons - Start the web server and other lifecycle beans, then publish
ApplicationStartedEvent - Call the
ApplicationRunners andCommandLineRunners - Publish
ApplicationReadyEvent; readiness switches to accepting traffic
If startup fails,
FailureAnalyzers turn common exceptions, like a port already in use, into readable messages. For slow startups, the Actuatorstartupendpoint (withBufferingApplicationStartup) shows where the time goes.What interviewers listen for- Deduce the app type, then prepare the Environment
- Create and refresh the ApplicationContext
- Auto-configuration is evaluated during refresh
- Embedded server created in refresh, then started
- Runners run, then
ApplicationReadyEventfires
No questions match that filter.
Prefer multiple choice? All 21 Spring Boot MCQs with answers →