Spring Boot 3.x for interviews: DI and bean lifecycle, auto-configuration, config precedence, REST, JPA and N+1, @Transactional rules, JWT and testing.
AMCompiled by Aditya Mishra · Technical Lead, BNP Paribas
factory methods, often for third-party classes; calls between @Bean methods return the same singleton (CGLIB proxy)
@Configuration(proxyBeanMethods = false)
“lite” mode, no proxy, faster startup
@Value("${app.name:demo}")
inject one property with a default
@ConfigurationProperties("app")
bind a typed, validatable group of properties
@Profile("!prod")
register the bean only for matching profiles
@Lazy, @DependsOn, @Order, @Import
defer creation, force order, sort lists, pull in config
Auto-configuration & starters
@EnableAutoConfiguration loads classes listed in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports (Boot 3 no longer reads them from spring.factories).
Each is guarded by conditions: @ConditionalOnClass, @ConditionalOnMissingBean, @ConditionalOnProperty, @ConditionalOnBean, @ConditionalOnWebApplication.
It backs off: define your own DataSource or ObjectMapper bean and Boot’s is skipped.
See why with --debug (condition evaluation report) or /actuator/conditions. Exclude with @SpringBootApplication(exclude = DataSourceAutoConfiguration.class) or spring.autoconfigure.exclude.
A custom starter is an @AutoConfiguration class, listed in that imports file, plus a starter POM.
Starter
Brings
spring-boot-starter-web
Spring MVC, embedded Tomcat, Jackson (renamed -webmvc in Boot 4)
@ConfigurationProperties(prefix = "app") // enable with @ConfigurationPropertiesScanrecord AppProps(Duration paymentTimeout, @DefaultValue("3") int retries) {}
java
Precedence, highest first: command-line args (--server.port=9000), SPRING_APPLICATION_JSON, Java system properties (-D), OS environment variables, profile-specific files outside the jar, application.yml outside the jar, profile-specific files inside it, application.yml inside it, @PropertySource, defaults. Test annotations such as @TestPropertySource beat all of them.
.properties wins over .yml in the same location.
Relaxed binding: app.payment-timeout, app.paymentTimeout and env var APP_PAYMENTTIMEOUT are the same key. spring.datasource.url becomes SPRING_DATASOURCE_URL.
Profiles: application-{profile}.yml, spring.profiles.active, groups (spring.profiles.group.prod=db,mq). Secrets come from the environment or a vault, never the jar.
query or form parameter; required by default; defaultValue = "0"
@RequestBody
JSON body via HttpMessageConverter (Jackson)
@RequestHeader, @CookieValue
header or cookie value
@ResponseStatus
fixed status for the handler or an exception class
ResponseEntity<T>
full control: ResponseEntity.created(uri).body(dto)
@CrossOrigin
CORS for one controller (use global config with Security)
Request flow: DispatcherServlet, HandlerMapping, HandlerAdapter, controller, then HttpMessageConverter writes the body.
Return DTOs, not entities: avoids lazy-loading errors, JSON cycles and leaking columns.
Blocking HTTP clients: RestClient (6.1) or RestTemplate; reactive: WebClient.
Validation & error handling
record CreateUser(@NotBlank String name, @NotNull @Email String email, @Min(18) int age) {}
java
@NotNull rejects null; @NotEmpty also rejects "" and empty collections; @NotBlank also rejects whitespace-only strings.
@Valid @RequestBody failures throw MethodArgumentNotValidException (400). Constraints placed directly on @RequestParam or @PathVariable raise HandlerMethodValidationException (built in since 6.1); a class-level @Validated switches to AOP validation and ConstraintViolationException.
Custom rule: a @Constraint(validatedBy = ...) annotation plus a ConstraintValidator. @Valid on a field cascades.
@RestControllerAdviceclass ApiErrors { @ExceptionHandler // type comes from the parameter ProblemDetail notFound(NotFoundException ex) { // RFC 9457 JSON body return ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, ex.getMessage()); } @ExceptionHandler ProblemDetail invalid(MethodArgumentNotValidException ex) { var pd = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, "Validation failed"); ex.getFieldErrors().forEach(e -> pd.setProperty(e.getField(), e.getDefaultMessage())); return pd; }}
java
spring.mvc.problemdetails.enabled=true makes Spring MVC’s own exceptions answer as ProblemDetail; extending ResponseEntityExceptionHandler does the same inside your advice.
Throw ResponseStatusException(HttpStatus.CONFLICT, "...") for one-offs. Unhandled errors fall through to Boot’s /error (BasicErrorController).
Spring Data JPA
public interface OrderRepository extends JpaRepository<Order, Long> { List<Order> findByStatusAndCreatedAtAfter(Status status, Instant since); Page<Order> findByCustomerId(Long customerId, Pageable pageable); @Query("select o from Order o join fetch o.items where o.status = :s") List<Order> findWithItems(@Param("s") Status status); // one query, no N+1 @EntityGraph(attributePaths = "items") List<Order> findByCustomerEmail(String email); // same fix, declarative @Modifying @Query("update Order o set o.status = :s where o.id in :ids") int updateStatus(@Param("s") Status s, @Param("ids") List<Long> ids);}
java
Hierarchy: Repository, CrudRepository/ListCrudRepository, PagingAndSortingRepository, JpaRepository (adds flush and batch methods). JpaSpecificationExecutor for dynamic filters.
Derived-query keywords: And, Or, Between, LessThan, Like, Containing, In, IsNull, IgnoreCase, OrderBy...Desc, findTop10By, existsBy, countBy. Projections: interfaces or DTO records.
@Modifying queries need a surrounding @Transactional.
save() persists a new entity or merges a detached one. Managed entities are dirty-checked and flushed at commit, with no save() call needed.
@Version gives optimistic locking; @Lock(LockModeType.PESSIMISTIC_WRITE) gives SELECT ... FOR UPDATE.
Association
Default fetch
@ManyToOne, @OneToOne
EAGER (make it LAZY)
@OneToMany, @ManyToMany, @ElementCollection
LAZY
N+1: one query for N parents, then one per parent for a lazy association. Fix with join fetch, @EntityGraph, hibernate.default_batch_fetch_size, or DTO projections. Spot it with logging.level.org.hibernate.SQL=debug.
Fetch-joining a collection with paging makes Hibernate paginate in memory; fetching two List collections at once throws MultipleBagFetchException.
LazyInitializationException means you touched a lazy association after the session closed: fetch it in the query or map to a DTO inside the transaction.
@Transactional rules
Propagation
Existing transaction
No transaction
REQUIRED (default)
join it
start one
REQUIRES_NEW
suspend it, start a new one
start one
NESTED
savepoint inside it (JDBC)
start one
SUPPORTS
join it
run without
NOT_SUPPORTED
suspend it
run without
MANDATORY
join it
throw
NEVER
throw
run without
It works through a proxy: only calls from outside the bean are intercepted. Self-invocation (this.save()) runs with no transaction. Fix: move the method to another bean, use TransactionTemplate, or AspectJ mode.
Proxies can’t intercept private methods, and CGLIB can’t override final ones. Since 6.0, protected and package-private methods work with class-based proxies.
Rollback happens by default only for RuntimeException and Error. Checked exceptions commit unless you set rollbackFor = Exception.class.
Swallowing an exception thrown from a joined REQUIRED method still marks the transaction rollback-only: the outer commit throws UnexpectedRollbackException.
readOnly = true is a hint (Hibernate skips dirty checking). Keep transactions in the service layer and short: no remote calls inside.
Filter chain: the servlet DelegatingFilterProxy calls FilterChainProxy, which runs the first matching SecurityFilterChain: exploit protection (CsrfFilter), then authentication filters (BearerTokenAuthenticationFilter, UsernamePasswordAuthenticationFilter, BasicAuthenticationFilter), then ExceptionTranslationFilter and AuthorizationFilter.
Authentication: AuthenticationManager (ProviderManager) asks AuthenticationProviders (such as DaoAuthenticationProvider with a UserDetailsService and PasswordEncoder); the result sits in SecurityContextHolder, a ThreadLocal by default.
Not authenticated gives 401 via AuthenticationEntryPoint; authenticated but not allowed gives 403 via AccessDeniedHandler.
Security 6: WebSecurityConfigurerAdapter is gone (declare SecurityFilterChain beans), authorizeHttpRequests plus requestMatchers replace authorizeRequests/antMatchers, and @EnableMethodSecurity enables @PreAuthorize("hasRole('ADMIN')").
hasRole("ADMIN") checks authority ROLE_ADMIN. Hash passwords with BCryptPasswordEncoder or the delegating encoder ({bcrypt} prefix).
JWT flow
The client authenticates with the authorization server and receives a signed JWT (header.payload.signature, Base64URL-encoded, not encrypted).
It sends Authorization: Bearer <token> on every request.
BearerTokenAuthenticationFilter extracts it; JwtDecoder verifies the signature against the JWKS from spring.security.oauth2.resourceserver.jwt.issuer-uri and checks exp, nbf and iss (aud only if configured).
JwtAuthenticationConverter maps scopes to SCOPE_* authorities (customize it to turn a roles claim into ROLE_*), then authorization runs as usual.
Keep access tokens short-lived with refresh tokens; revoking a JWT early needs a denylist. Never put secrets in the payload.
Actuator
Endpoint
Shows
/actuator/health
UP/DOWN; /health/liveness and /health/readiness probes (auto-enabled on Kubernetes)
/actuator/info
build and git info
/actuator/metrics, /actuator/prometheus
Micrometer metrics; Prometheus format needs micrometer-registry-prometheus
Switch each on with @EnableCaching, @EnableAsync, @EnableScheduling. All are proxy-based, so self-invocation skips them too.
Caching: @CachePut updates, @CacheEvict(allEntries = true) clears; condition, unless, sync = true. With no provider, Boot uses an in-memory ConcurrentHashMap; add Caffeine or Redis for TTLs and eviction.
@Async returns void or CompletableFuture<T> and runs on Boot’s ThreadPoolTaskExecutor (8 core threads; tune spring.task.execution.pool.*). Exceptions from void methods go to AsyncUncaughtExceptionHandler.
@Scheduled takes fixedRate, fixedDelay, initialDelay or a 6-field cron. The scheduler has one thread by default (spring.task.scheduling.pool.size), and every instance runs the job: use ShedLock or similar in a cluster.
resilience4j: circuitbreaker.instances.inventory: sliding-window-size: 20 failure-rate-threshold: 50 wait-duration-in-open-state: 10s retry.instances.inventory: max-attempts: 3 # includes the first call wait-duration: 200ms enable-exponential-backoff: true
yaml
Circuit breaker states: CLOSED, OPEN (calls fail fast with CallNotPermittedException), HALF_OPEN (a few trial calls). Defaults: count-based window of 100 calls, 50% failure threshold, 60 s open.
The fallback lives in the same class with the same parameters plus a trailing exception.
Default aspect order: Retry(CircuitBreaker(RateLimiter(TimeLimiter(Bulkhead(call))))), so each retry passes through the breaker. Also @Bulkhead, @RateLimiter, @TimeLimiter.
Quick answers
Spring vs Spring Boot? Spring is the framework (DI, MVC, data); Boot adds auto-configuration, starters, an embedded server and production features.
What does @SpringBootApplication do? Configuration, auto-configuration and component scanning from its package down.
How does auto-configuration work? Conditional @AutoConfiguration classes from the imports file that back off when you define your own bean.
@Component vs @Bean? Class-level scanning for your classes vs a factory method, typically for third-party classes.
Are singleton beans thread-safe? No. Keep them stateless; shared mutable state needs synchronization.
Run code at startup?CommandLineRunner or ApplicationRunner beans, or @EventListener(ApplicationReadyEvent.class).
@Value vs @ConfigurationProperties? One value (SpEL allowed) vs a typed, validated group with relaxed binding.
Why isn’t my @Transactional working? Self-invocation, a private method, a checked exception, or a bean Spring didn’t create.
Fix N+1?join fetch, @EntityGraph, batch fetching or DTO projections.
Swap Tomcat? Exclude spring-boot-starter-tomcat and add spring-boot-starter-jetty.
Boot 2 to 3 migration? Java 17, javax.* to jakarta.*, Security 6 filter-chain beans, Sleuth to Micrometer Tracing.
Secure a REST API? Stateless resource server validating JWTs, URL rules plus @PreAuthorize, HTTPS, CORS configured in Security.
Gotchas & traps
@Transactional, @Cacheable, @Async and @PreAuthorize are ignored on self-invocation and private methods.
Checked exceptions don’t roll back: a throws IOException method commits half its work.
spring.jpa.open-in-view is true by default (Boot logs a warning): lazy loading in the web layer hides N+1. Set it to false.
ddl-auto defaults to create-drop only for embedded databases. Never run update in production; use Flyway or Liquibase.
Lombok @Data on entities: equals, hashCode and toString touch lazy collections and bidirectional links (loops, exceptions).
GenerationType.IDENTITY disables Hibernate’s JDBC insert batching; SEQUENCE keeps it.
Beans outside the main class’s package aren’t scanned; @Value on a static field is never injected.
Spring 6.1 no longer reads parameter names from debug info: without -parameters (Boot’s build plugins set it), write @PathVariable("id").
Exposing env, heapdump or * actuator endpoints publicly leaks secrets.
Gotcha
Boot 3 moved to jakarta.*. Old javax.persistence or javax.validation imports fail to compile, or, if a stale jar is still on the classpath, compile and are silently ignored.