Ch. 08

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.

63 interview questions21 quiz questions0 notes
your progress0%

Top 63 Spring Boot interview questions most asked first

  1. 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 DataSource when 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.properties or 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 EE jakarta.* 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?

  2. 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 @Bean methods, 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

    BeanFactory is the basic container; ApplicationContext extends 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
    • ApplicationContext extends BeanFactory
    • Owning the objects lets Spring apply proxies

    Likely follow-up: What is the difference between BeanFactory and ApplicationContext?

  3. 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 null dependency
    • 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 (@Autowired on a private field) is concise but hides dependencies, rules out final, 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 final fields 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 @Autowired required on a constructor? · What happens with a circular dependency and constructor injection?

  4. 4.What does @SpringBootApplication do, and how does component scanning decide what to pick up?easy

    It's a convenience annotation that combines three:

    • @SpringBootConfiguration: a specialized @Configuration, so the class can declare @Bean methods
    • @EnableAutoConfiguration: turns on Boot's auto-configuration
    • @ComponentScan: scans for @Component and 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 as com.acme.web is 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, use exclude = DataSourceAutoConfiguration.class or the spring.autoconfigure.exclude property.

    SpringApplication.run(...) then creates the ApplicationContext, 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 exclude or a property

    Likely follow-up: How would you exclude a single auto-configuration class?

  5. 5.How does Spring Boot auto-configuration work under the hood?mid

    @EnableAutoConfiguration loads the candidate auto-configuration classes listed in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports in every jar on the classpath. (Older Boot 2 versions used spring.factories for this.) Each one is an ordinary configuration class, annotated @AutoConfiguration, guarded by conditions:

    • @ConditionalOnClass: only if a class, for example a JDBC driver or DataSource, 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 @ConditionalOnMissingBean lets them back off: define your own DataSource and 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 --debug to print the condition evaluation report, or look at the Actuator conditions endpoint.

    What interviewers listen for
    • Candidates listed in AutoConfiguration.imports
    • Each is guarded by @Conditional... annotations
    • Your beans win via @ConditionalOnMissingBean
    • --debug prints the condition evaluation report

    Likely follow-up: How would you write your own starter? · How do you disable one auto-configuration?

  6. 6.What is the difference between @Component, @Service, @Repository and @Controller?easy

    All four mark a class as a bean for component scanning; @Service, @Repository and @Controller are 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 unchecked DataAccessException hierarchy
    • @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
    • @Repository adds exception translation
    • @Controller is detected as an MVC handler
    • @Service documents intent, no extra behavior

    Likely follow-up: What is DataAccessException and why is it unchecked?

  7. 7.What is the difference between @RestController and @Controller?easy

    @RestController is @Controller plus @ResponseBody applied 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's orders.html, with the model. That's for server-side rendered pages.

    With @RestController, the return value is written straight to the response body. An HttpMessageConverter serializes it, typically to JSON with Jackson, based on content negotiation with the request's Accept header. That's what you use for REST APIs.

    You can mix the two: a @Controller can annotate individual methods with @ResponseBody, or return a ResponseEntity, which is also written to the body. In practice I use @RestController for APIs and @Controller only when rendering HTML.

    What interviewers listen for
    • @RestController = @Controller + @ResponseBody
    • @Controller returns view names by default
    • Message converters serialize the body, e.g. Jackson
    • Content negotiation uses the Accept header
  8. 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 @PreDestroy are 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>, @Lookup method 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?

  9. 9.How does @Transactional work, and what are its default settings?mid

    @Transactional is declarative transaction management built on AOP proxies. Spring wraps the bean in a proxy; a call through it asks the PlatformTransactionManager (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 RuntimeException and Error only: a checked exception commits unless you add rollbackFor = 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 @EnableTransactionManagement isn'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 rollbackFor is set
    • Only calls through the proxy are intercepted

    Likely follow-up: Why might @Transactional silently not work? · What does readOnly = true actually do?

  10. 10.Why does @Transactional sometimes have no effect? Explain the self-invocation problem in this code.hard

    Spring applies @Transactional through a proxy in front of the bean, and only calls that come through the proxy are intercepted. When placeAll calls this.place(...), it's an ordinary Java call on the target object, so no transaction is started even though place is annotated.

    Other reasons it silently does nothing:

    • the method is private or final, which a class-based proxy can't override (since Spring 6, protected and 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 TransactionTemplate for programmatic control, or switch to AspectJ weaving. The same pitfall applies to @Async, @Cacheable and 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 @Async and @Cacheable

    Likely follow-up: How would you get a transaction per order here?

  11. 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-web brings Spring MVC, Jackson and embedded Tomcat; spring-boot-starter-data-jpa brings Spring Data JPA, Hibernate and the HikariCP pool; spring-boot-starter-test brings 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-parent POM or the spring-boot-dependencies BOM 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 be acme-spring-boot-starter. Boot 4 splits some starters more finely, for example spring-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. 12.When would you use @Configuration with @Bean instead of @Component, and what does proxyBeanMethods change?mid

    @Component is for your own classes: annotate the class and scanning finds it. @Bean methods 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 @Configuration class, Spring by default subclasses the class with CGLIB (full mode, proxyBeanMethods = true). If one @Bean method calls another, the call is intercepted and returns the existing singleton instead of creating a second instance.

    With proxyBeanMethods = false, or with @Bean methods in a plain @Component (lite mode), there's no interception: calling another @Bean method 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
    • @Component for your own classes, found by scanning
    • @Bean for third-party or custom-built objects
    • Full mode: inter-bean calls return the singleton
    • proxyBeanMethods = false makes them plain Java calls
    • Prefer method parameters over calling @Bean methods
  13. 13.What is the difference between @PathVariable and @RequestParam, and when do you use each?easy

    @PathVariable binds a segment of the URI template: /orders/{id} called as /orders/42 gives id = 42. @RequestParam binds 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:

    • @RequestParam is required by default; a missing one gives a 400. Use required = false, Optional<T>, or defaultValue, which makes it optional implicitly.
    • Type conversion is automatic, and a value that can't be converted, like /orders/abc for a Long, also gives a 400.
    • @RequestBody is 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
    • @RequestParam is required by default
    • defaultValue makes a param optional
    • @RequestBody binds the JSON payload
  14. 14.How do you handle exceptions globally in a Spring Boot REST API?mid

    I use a @RestControllerAdvice class (@ControllerAdvice plus @ResponseBody) with @ExceptionHandler methods. 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 as application/problem+json with type, title, status, detail and instance, plus any custom properties.

    Two useful pieces:

    • extend ResponseEntityExceptionHandler to get ProblemDetail responses for Spring MVC's own exceptions, or in Boot simply set spring.mvc.problemdetails.enabled=true
    • an @ExceptionHandler inside 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
    • @RestControllerAdvice with @ExceptionHandler methods
    • Map domain exceptions to proper status codes
    • ProblemDetail implements RFC 9457
    • ResponseEntityExceptionHandler covers MVC exceptions
    • Catch-all handler that never leaks stack traces

    Likely follow-up: How would you return all field errors from a failed @Valid?

  15. 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-sql or 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.items loads 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 @BatchSize or hibernate.default_batch_fetch_size loads the collections for many parents with one IN query, so N+1 becomes 1 + N/batch size
    • DTO projections when you only need a few columns

    Switching the association to EAGER is 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 fetch or @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?

  16. 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 BeanNameAware and ApplicationContextAware
    • BeanPostProcessor.postProcessBeforeInitialization from every post-processor
    • Initialization: @PostConstruct, then InitializingBean.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, then DisposableBean.destroy(), then a custom destroy method

    Prototype beans are initialized but never get destruction callbacks.

    In practice I use @PostConstruct and @PreDestroy (from jakarta.annotation in Boot 3), which the Spring docs recommend over the Spring-specific interfaces. I don't rely on transactional behavior inside @PostConstruct; an ApplicationRunner or an ApplicationReadyEvent listener is safer for startup work.

    What interviewers listen for
    • Instantiate, inject, then Aware callbacks
    • BeanPostProcessors run around initialization
    • @PostConstruct, then afterPropertiesSet, then init method
    • Proxies are usually created after initialization
    • @PreDestroy on shutdown, never for prototypes
  17. 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, test or prod.

    • Property files: application-prod.yml is loaded on top of application.yml when prod is active, overriding matching keys. In one multi-document YAML file, each document can use spring.config.activate.on-profile.
    • Beans: @Profile("dev") on a component or @Bean method registers it only for that profile; @Profile("!prod") negates.
    • Activation: SPRING_PROFILES_ACTIVE=prod as an environment variable, --spring.profiles.active=prod on the command line, or the property itself. With nothing active, the default profile applies.
    • Groups: spring.profiles.group.prod=proddb,prodmq activates several profiles at once.

    You can't set spring.profiles.active inside 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/shop
    What interviewers listen for
    • Named config and beans per environment
    • application-{profile}.yml overrides the base file
    • @Profile registers beans conditionally
    • Activate via SPRING_PROFILES_ACTIVE or a command-line arg
    • Falls back to the default profile
  18. 18.What is the difference between @Value and @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, retryCount and the env var APP_PAYMENTS_RETRYCOUNT all bind to the same field
    • conversion to Duration, DataSize, lists, maps and nested objects
    • validation with @Validated and Bean Validation constraints, failing fast at startup
    • IDE auto-completion from metadata generated by spring-boot-configuration-processor

    Register it with @EnableConfigurationProperties or @ConfigurationPropertiesScan; a record with a single constructor uses constructor binding automatically.

    I use @ConfigurationProperties for any group of related settings and keep @Value for 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
    • @Value injects one property and supports SpEL
    • @ConfigurationProperties binds a typed tree
    • Relaxed binding and rich type conversion
    • @Validated fails fast at startup
    • Records give immutable, constructor-bound config
  19. 19.Explain transaction propagation. How do REQUIRED, REQUIRES_NEW and NESTED differ?hard

    Propagation 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 throws UnexpectedRollbackException.
    • 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's DataSourceTransactionManager; JPA has no nested transactions, and JpaTransactionManager disallows them by default.

    The rest: SUPPORTS joins if present, NOT_SUPPORTED suspends, MANDATORY requires an existing one and NEVER forbids 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. 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 the FilterChainProxy bean.
    • FilterChainProxy holds one or more SecurityFilterChains, 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 ExceptionTranslationFilter and AuthorizationFilter near the end.
    • Authentication filters produce an Authentication and store it in the SecurityContextHolder, which is thread-local by default.
    • On a denial, ExceptionTranslationFilter sends unauthenticated users to the AuthenticationEntryPoint (a 401 or login redirect) and authenticated ones to the AccessDeniedHandler (a 403).

    In Spring Security 6 you configure all this by declaring SecurityFilterChain beans; WebSecurityConfigurerAdapter has 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
    • DelegatingFilterProxy delegates to FilterChainProxy
    • First matching SecurityFilterChain wins
    • Authentication stored in SecurityContextHolder
    • 401 via entry point, 403 via access-denied handler
  21. 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 by SimpleJpaRepository. You get save, 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 through customer. Keywords like GreaterThan, Between, Like, In, OrderBy, First or Top3, plus existsBy, countBy and deleteBy, 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 (returns Iterable), ListCrudRepository (returns List), PagingAndSortingRepository (adds Sort and Pageable), and JpaRepository, which combines them with JPA extras like flush, saveAllAndFlush and getReferenceById. Since Spring Data 3, PagingAndSortingRepository no longer extends CrudRepository.

    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
    • JpaRepository adds JPA extras to CRUD and paging
    • Switch to @Query when names get unwieldy
  22. 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.servlet and javax.validation become jakarta.*. This is the biggest migration effort, because third-party libraries also need Jakarta-compatible versions
    • Hibernate 6 as the JPA provider
    • Spring Security 6: WebSecurityConfigurerAdapter is gone; you declare SecurityFilterChain beans and use authorizeHttpRequests with requestMatchers
    • Observability through Micrometer Observation and Micrometer Tracing, replacing Spring Cloud Sleuth
    • GraalVM native images and ahead-of-time processing as a supported option
    • ProblemDetail error responses and declarative HTTP interface clients (@HttpExchange)
    • auto-configurations registered in AutoConfiguration.imports instead of spring.factories
    • trailing-slash matching off by default, so /orders/ no longer matches /orders

    Later 3.x releases added @ServiceConnection for Testcontainers (3.1), virtual threads and RestClient (3.2), and structured logging plus graceful shutdown by default (3.4). For migrating, spring-boot-properties-migrator reports renamed properties at startup, and OpenRewrite recipes automate much of the javax to jakarta change.

    What interviewers listen for
    • Java 17 and Spring Framework 6 baseline
    • javax.* moved to jakarta.*
    • Security 6: SecurityFilterChain beans 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. 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 @Valid on nested objects you want validated too.
    • Annotate the controller parameter with @Valid (or Spring's @Validated, which also supports validation groups). On failure, Spring throws MethodArgumentNotValidException, which becomes a 400.
    • Constraints placed directly on @RequestParam or @PathVariable parameters, like @Min(1) int page, trigger Spring MVC's built-in method validation (Spring 6.1+), which raises HandlerMethodValidationException.
    • Handle both in a @RestControllerAdvice to return field-level errors, ideally as a ProblemDetail.

    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 for
    • spring-boot-starter-validation brings Hibernate Validator
    • Constraints on the DTO, @Valid on the parameter
    • Failure raises MethodArgumentNotValidException: a 400
    • Constraints on params trigger method validation
    • Custom constraints via @Constraint
  24. 24.What is the difference between lazy and eager fetching in JPA, and what causes a LazyInitializationException?mid

    Eager loads an association together with the entity; lazy loads it on first access, through a proxy or a persistent collection wrapper. The JPA defaults: @ManyToOne and @OneToOne are EAGER, @OneToMany and @ManyToMany are 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.

    LazyInitializationException happens 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 things EAGER or 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. 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 only health is exposed over HTTP; you opt in with management.endpoints.web.exposure.include.

    /actuator/health aggregates the HealthIndicators Boot auto-configures for what you use, such as the database, disk space or Redis, into a status like UP or DOWN. Details are hidden by default (show-details is never); when-authorized is a sensible production setting.

    For Kubernetes there are liveness and readiness groups at /actuator/health/liveness and /actuator/health/readiness. Boot 3 enables them automatically when it detects Kubernetes, or anywhere with management.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 HealthIndicator bean. Endpoints like env and heapdump are sensitive, so I expose them sparingly, secure them, or move management to its own port with management.server.port.

    What interviewers listen for
    • Production endpoints under /actuator
    • Only health is exposed over HTTP by default
    • Health aggregates auto-configured HealthIndicators
    • Liveness and readiness groups for Kubernetes
    • Secure sensitive endpoints or use a separate port
  26. 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 (RequestMappingHandlerMapping for annotated controllers) finds the controller method matching the path, HTTP method, headers and media types, along with any HandlerInterceptors, whose preHandle runs.
    • A HandlerAdapter invokes the method. Argument resolvers fill in @PathVariable, @RequestParam and @RequestBody, the last through an HttpMessageConverter such as Jackson, and validation runs.
    • The return value is handled: for @ResponseBody or ResponseEntity, a message converter writes the body; for a view name, a ViewResolver renders a template.
    • Interceptors' postHandle and afterCompletion run.
    • If anything throws, the HandlerExceptionResolvers take over, which is how @ExceptionHandler and @ControllerAdvice get 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 @ResponseBody results
    • Exception resolvers power @ExceptionHandler

    Likely follow-up: What is the difference between a servlet filter and a HandlerInterceptor?

  27. 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, @After or @Around), with a pointcut saying where, such as execution(* 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 final or private methods or subclass final classes. 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. 28.What is the difference between @SpringBootTest, @WebMvcTest and @DataJpaTest?mid
    • @SpringBootTest loads the full application context: all your beans plus auto-configuration. By default it uses a mock web environment with no real server; webEnvironment = RANDOM_PORT starts 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, with MockMvc auto-configured. Services and repositories aren't loaded, so you replace them with @MockitoBean. Ideal for routing, validation, status codes and JSON shape.
    • @DataJpaTest loads only JPA: entities, repositories and a TestEntityManager. 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 @SpringBootTest integration 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
    • @SpringBootTest loads the full context
    • @WebMvcTest loads only the MVC slice, with MockMvc
    • @DataJpaTest loads JPA only and rolls back each test
    • Replace collaborators with @MockitoBean
    • Context caching rewards consistent test configs
  29. 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 Authorization header 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-server and set spring.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, exp and nbf, and the issuer, and scope claims become SCOPE_ authorities
    • use SessionCreationPolicy.STATELESS and disable CSRF, since no cookies carry credentials
    • authorize with requestMatchers or @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
  30. 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:

    • @Primary on 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>, or Map<String, PaymentGateway> keyed by bean name, for strategy-style code that chooses at runtime

    I use @Primary for "the normal implementation plus a special case", @Qualifier (often via a custom qualifier annotation) when callers must choose explicitly, and a Map of 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
    • @Primary marks the default candidate
    • @Qualifier picks explicitly and beats @Primary
    • Inject a List or Map of all implementations
  31. 31.What is ResponseEntity, and when would you return it instead of the body object?easy

    ResponseEntity<T> represents the whole HTTP response: status code, headers and body. Returning a plain object from a @RestController always gives a 200 with that body (unless @ResponseStatus says otherwise); ResponseEntity lets you decide per request.

    Typical uses:

    • 201 Created with a Location header 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(), or ResponseEntity.of(optional)
    • custom headers such as ETag, Cache-Control or Content-Disposition for downloads

    For errors I usually prefer throwing a domain exception and mapping it centrally in a @ControllerAdvice rather than building error responses in every controller. So my controllers return ResponseEntity for 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 Location
    • noContent() and notFound() builders
    • Prefer exceptions plus advice for errors
  32. 32.What is the difference between application.properties and application.yml?easy

    Both are Boot's default configuration files, loaded from the classpath root (and config/ locations) into the same Environment; the difference is syntax.

    • .properties: flat key=value lines that repeat the prefix: spring.datasource.url=..., spring.datasource.username=.... Lists use indexes like app.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, .properties takes 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
    • .properties wins if both exist in one location
    • Env vars and CLI args override both
  33. 33.When do you use @Query in Spring Data JPA, and what does @Modifying do?mid

    @Query lets 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 with nativeQuery = true for database-specific features. JPQL here is checked at startup; native SQL isn't.

    Bind values with named parameters (:status plus @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 @Transactional on 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, and flushAutomatically = true flushes 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 = true for SQL
    • Bind parameters, never concatenate input
    • @Modifying for bulk update and delete
    • Bulk queries need a transaction
    • Bulk updates bypass the persistence context
  34. 34.How do you implement pagination and sorting with Spring Data, and what is the difference between Page and Slice?mid

    Add a Pageable parameter to a repository method, like Page<Order> findByStatus(OrderStatus status, Pageable pageable), and Spring Data applies the limit, offset and sort. Build one with PageRequest.of(0, 20, Sort.by("createdAt").descending()). In a controller, Spring Data's web support resolves a Pageable parameter 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 a Pageable just 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 the Window and ScrollPosition scrolling API.

    What interviewers listen for
    • Pageable parameter adds limit, offset and sort
    • Page runs an extra count query
    • Slice only knows if a next slice exists
    • Page numbers are zero-based
    • Keyset pagination for deep or changing data
  35. 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 LazyInitializationException or 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 role or id
    • 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
  36. 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 (ApplicationEventPublisher plus @EventListener)
    • merge the two classes if they're really one responsibility

    As a stopgap, @Lazy on 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
    • @Lazy is a stopgap, not a fix
  37. 37.What is the difference between a servlet Filter and a Spring HandlerInterceptor?mid

    A servlet Filter belongs to the Servlet API and wraps the whole request before it reaches the DispatcherServlet. 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, a Filter bean is registered automatically; use FilterRegistrationBean to control URL patterns and order, and extend OncePerRequestFilter so it runs once per request.

    A HandlerInterceptor is Spring MVC's hook and runs inside the DispatcherServlet, after the handler has been chosen:

    • preHandle: before the controller; returning false stops processing
    • postHandle: after the controller, before view rendering; of limited use with @ResponseBody, since the response is already written by then
    • afterCompletion: 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 through WebMvcConfigurer.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
    • postHandle is of limited use with @ResponseBody
    • Register via WebMvcConfigurer.addInterceptors
  38. 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 (0 picks a random free port), server.servlet.context-path, server.ssl.*, compression, and Tomcat thread and connection limits under server.tomcat.*.
    • To switch to Jetty, exclude spring-boot-starter-tomcat and add spring-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 WebServerFactoryCustomizer bean.
    • 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=true makes 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
  39. 39.What is the difference between authentication and authorization in Spring Security?easy

    Authentication answers "who are you?"; authorization answers "are you allowed to do this?".

    In Spring Security, authentication filters extract credentials, such as a username and password, a bearer token or a session, and hand them to the AuthenticationManager. Usually a ProviderManager delegates to AuthenticationProviders; for passwords, DaoAuthenticationProvider loads the user through your UserDetailsService and checks the password with the PasswordEncoder. The resulting Authentication, holding the principal and its GrantedAuthoritys, is stored in the SecurityContextHolder.

    Authorization then uses those authorities: URL rules in authorizeHttpRequests, or method security such as @PreAuthorize("hasRole('ADMIN')"). Note that hasRole('ADMIN') checks for the authority ROLE_ADMIN, while hasAuthority matches the exact string.

    The HTTP status codes map onto the two: 401 Unauthorized really means "not authenticated", and 403 Forbidden means authenticated but not permitted.

    What interviewers listen for
    • Authentication is identity, authorization is permission
    • AuthenticationManager delegates to providers
    • UserDetailsService plus PasswordEncoder for passwords
    • hasRole adds the ROLE_ prefix
    • 401 means unauthenticated, 403 means forbidden
  40. 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 DelegatingPasswordEncoder from PasswordEncoderFactories.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 encode when 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. NoOpPasswordEncoder and {noop} are for demos only.

    What interviewers listen for
    • Adaptive salted hashes: bcrypt, scrypt, Argon2, PBKDF2
    • DelegatingPasswordEncoder with {id} prefixes
    • Prefixes allow migrating algorithms over time
    • Use matches, never compare hashes manually
    • Tune the work factor; no NoOp encoder in production
  41. 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; RestClient is its modern replacement.
    • WebClient: the reactive, non-blocking client from WebFlux, returning Mono and Flux. 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 to WebClient, built on the same infrastructure as RestTemplate. My default in a blocking MVC service.
    • HTTP interface clients: you declare a Java interface with @GetExchange or @PostExchange methods, and Spring generates the implementation on top of RestClient or WebClient, 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 for
    • RestTemplate: classic, in maintenance mode
    • WebClient: reactive, for WebFlux and streaming
    • RestClient: modern fluent synchronous client
    • HTTP interfaces generate clients declaratively
    • Use Boot builders and always set timeouts
  42. 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, CookieCsrfTokenRepository puts 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 Authorization header, because browsers don't attach that automatically. If the API authenticates with cookies, including a JWT stored in a cookie, keep CSRF on. SameSite cookies 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. 43.How does caching with @Cacheable work in Spring Boot, and what pitfalls should you watch for?mid

    Enable it with @EnableCaching on a configuration class. Boot then auto-configures a CacheManager for the provider it finds, such as Caffeine, Redis, Hazelcast or JCache, falling back to a simple ConcurrentHashMap that 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 with allEntries = true

    The default key comes from the parameters: none gives SimpleKey.EMPTY, one gives that value, several give a SimpleKey of all of them. Customize with SpEL (key = "#id"), and use condition or unless to control what's cached.

    Pitfalls:

    • it's proxy-based, so self-invocation skips the cache
    • keys need correct equals and hashCode
    • 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 = true lets 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
    • @EnableCaching plus an auto-configured CacheManager
    • 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. 44.How does @Async work in Spring, and what are its common pitfalls?mid

    @Async makes a method run on another thread: the caller returns immediately and the work is submitted to a TaskExecutor. Enable it with @EnableAsync. Boot auto-configures an executor named applicationTaskExecutor, a ThreadPoolTaskExecutor by default or a virtual-thread executor when spring.threads.virtual.enabled=true; tune the pool with spring.task.execution.pool.*.

    Return void for fire-and-forget, or CompletableFuture<T> so callers can compose or wait on the result.

    Pitfalls:

    • it's proxy-based: a self-invoked @Async method simply runs synchronously
    • exceptions from void methods never reach the caller; they go to an AsyncUncaughtExceptionHandler, which by default just logs them
    • the new thread doesn't inherit the caller's transaction, and thread-local context like the SecurityContext or logging MDC isn't propagated unless you configure it, for example with a TaskDecorator
    • 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 void or CompletableFuture
    • Self-invocation runs synchronously
    • No transaction or thread-local propagation by default
    • Void-method exceptions go to the uncaught handler
  45. 45.How do you run scheduled tasks in Spring Boot with @Scheduled?easy

    Add @EnableScheduling to a configuration class, then annotate a no-argument bean method:

    • fixedRate: run every N milliseconds, measured from the start of the previous run
    • fixedDelay: wait N milliseconds after the previous run finishes
    • initialDelay: wait before the first run
    • cron: Spring's cron format has six fields, starting with seconds, so 0 0 2 * * * means 2 AM every day; add zone to 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.size if 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
    • @EnableScheduling plus @Scheduled methods
    • 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. 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> and Flux<T>, and you get back-pressure and streaming. The catch: every layer must be non-blocking, such as R2DBC instead of JDBC and WebClient instead 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, Mono and Flux
    • 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. 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-configures MockMvc, 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.

    @MockitoBean and @MockitoSpyBean come from Spring Framework 6.2. Boot's older @MockBean and @SpyBean were deprecated in Boot 3.4 and removed in Boot 4.

    Then you stub with Mockito, perform a request, and assert on the status, headers and JSON with jsonPath. With a full @SpringBootTest you add @AutoConfigureMockMvc to get a MockMvc; for a real HTTP round trip, use RANDOM_PORT with an HTTP client instead. Recent versions also offer MockMvcTester for 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
    • @WebMvcTest auto-configures MockMvc
    • Exercises the MVC pipeline without a server
    • @MockitoBean replaces collaborators with mocks
    • @MockBean deprecated in 3.4, removed in Boot 4
    • Assert status and JSON with jsonPath
  48. 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:

    • @Testcontainers on the class and a static @Container field, 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 the DataSource points at it with no properties at all
    • before 3.1, or for extra settings, @DynamicPropertySource registers properties such as spring.datasource.url from 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
    • @ServiceConnection wires connection details automatically
    • @DynamicPropertySource for manual properties
    • Share containers to keep the context cache effective
  49. 49.What are the JPA entity lifecycle states, and what is the difference between persist and merge?hard

    An 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, find or a query. Changes are tracked by dirty checking and flushed automatically at commit, with no save call 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() calls persist if the entity looks new (a null id or null version, or Persistable.isNew()) and merge otherwise, and returns the managed instance, so always use the return value. Inside a transaction, an already-managed entity doesn't need save() at all.

    What interviewers listen for
    • New, managed, detached, removed
    • Managed entities are dirty-checked and flushed
    • persist attaches the same instance
    • merge returns a managed copy
    • save picks persist or merge; use its return value
  50. 50.In what order does Spring Boot resolve configuration properties from different sources?hard

    Boot builds the Environment from many property sources, and later sources override earlier ones. From lowest to highest precedence, the main ones are:

    • default properties set on SpringApplication
    • @PropertySource on configuration classes
    • config data files: application.properties and application.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 = ...), @DynamicPropertySource and @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 a config/application-prod.yml next to the jar overrides the packaged defaults.

    @PropertySource sits near the bottom and is added too late to affect early settings like logging.*. Environment variables map through relaxed binding: SPRING_DATASOURCE_URL sets spring.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
    • @PropertySource is near the bottom
  51. 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 the spring-boot- prefix is reserved for official starters.

    In the autoconfigure module:

    • write a class annotated @AutoConfiguration with @Bean methods
    • guard it with conditions: @ConditionalOnClass so it applies only when the library is present, @ConditionalOnMissingBean on each bean so users can override it, and @ConditionalOnProperty for an on/off switch
    • bind settings with a @ConfigurationProperties class under your own prefix, plus spring-boot-configuration-processor for 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 @ConditionalOnBean in 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.imports
    What interviewers listen for
    • Separate autoconfigure and starter modules
    • @AutoConfiguration listed in the imports file
    • @ConditionalOnMissingBean so users can override
    • Own prefix via @ConfigurationProperties
    • Test with ApplicationContextRunner
  52. 52.How does method-level security work in Spring Security?mid

    You enable it with @EnableMethodSecurity, which turns on @PreAuthorize, @PostAuthorize, @PreFilter and @PostFilter by default; @Secured and JSR-250 annotations like @RolesAllowed need securedEnabled = true or jsr250Enabled = 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
    • @PreFilter and @PostFilter filter 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
    • @EnableMethodSecurity enables pre/post annotations
    • @PreAuthorize evaluates SpEL before the call
    • Expressions can use arguments and beans
    • AOP-based, so self-invocation is not secured
    • Denial throws AccessDeniedException, a 403
  53. 53.What transaction isolation levels can you set with @Transactional, and which anomalies do they prevent?hard

    Isolation 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 InnoDB
    • READ_UNCOMMITTED: allows dirty reads of other transactions' uncommitted data
    • READ_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 too
    • SERIALIZABLE: 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 REQUIRED method 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 for
    • DEFAULT uses 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. 54.What does @Transactional(readOnly = true) actually do?mid

    It'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 DataSource can use it to send queries to a read replica.

    What it doesn't do: it doesn't reliably block writes. Some databases reject INSERT or UPDATE inside a read-only transaction, others don't. And if the method joins an existing read-write transaction with the default REQUIRED propagation, its read-only flag is ignored.

    Spring Data JPA's SimpleJpaRepository already marks its read methods readOnly = true. In my services I often put @Transactional(readOnly = true) at class level and override it with plain @Transactional on 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. 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 @RefreshScope can 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 @FeignClient interfaces. 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
  56. 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 CallNotPermittedException and 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. 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.name or logging.file.path.

    Configuration:

    • levels per logger: logging.level.root=warn, logging.level.com.acme=debug, or as environment variables like LOGGING_LEVEL_COM_ACME=DEBUG
    • built-in log groups such as web and sql: logging.level.sql=debug
    • change levels at runtime through the Actuator loggers endpoint
    • for full control, logback-spring.xml rather than logback.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 vars
    • logback-spring.xml for profile-aware config
    • Change levels at runtime via Actuator
  58. 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 to spring.lifecycle.timeout-per-shutdown-phase, 30 seconds by default. Then beans are destroyed: @PreDestroy methods 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 SIGTERM happen concurrently, so some requests can still arrive after shutdown begins. The documented fix is a preStop hook with a short sleep, so traffic stops being routed before the app stops accepting connections. Keep terminationGracePeriodSeconds (30 seconds by default) longer than the sleep plus the shutdown timeout, or the kubelet sends SIGKILL mid-drain.

    For your own background work, stop consumers cleanly in @PreDestroy or a SmartLifecycle, and set spring.task.execution.shutdown.await-termination if 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=graceful before
    • Use a preStop sleep on Kubernetes
    • Grace period must exceed sleep plus drain time
  59. 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 ReportBuilder for 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 call getObject() each time. My usual choice, since it's explicit and easy to test.
    • @Lookup method injection: a method annotated @Lookup that returns ReportBuilder; 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 new inside 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
    • @Lookup method injection also works
    • A prototype scoped proxy gives a new instance per call
  60. 60.What is Open Session in View in Spring Boot, and why do many teams disable it?hard

    spring.jpa.open-in-view is enabled by default in Spring Boot web apps. It registers an OpenEntityManagerInViewInterceptor that opens the JPA EntityManager at 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. 61.How do you prevent lost updates with JPA? Explain optimistic locking with @Version.hard

    A 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 in update 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 an OptimisticLockException, which Spring translates into ObjectOptimisticLockingFailureException. 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 a SELECT ... FOR UPDATE and 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
    • @Version adds a version check to updates
    • A conflict throws OptimisticLockException
    • Return 409 or retry; ETags across requests
    • Pessimistic FOR UPDATE for frequent conflicts
  62. 62.How do you run code once when a Spring Boot application starts?easy

    The standard options:

    • CommandLineRunner or ApplicationRunner beans: their run method is called after the context has started, just before the app is considered ready. CommandLineRunner receives the raw String[] arguments; ApplicationRunner gets parsed ApplicationArguments with option and non-option arguments. Order several with @Order.
    • An @EventListener(ApplicationReadyEvent.class) method, which fires once the application is ready to serve traffic.
    • @PostConstruct for 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 for
    • CommandLineRunner gets the raw String arguments
    • ApplicationRunner gets parsed ApplicationArguments
    • Runners execute after the context has started
    • ApplicationReadyEvent fires when ready for traffic
    • @PostConstruct is only for per-bean setup
  63. 63.What happens when you call SpringApplication.run()? Walk through Spring Boot startup.hard

    Roughly, 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 ApplicationContext matching 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, register BeanPostProcessors, 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 and CommandLineRunners
    • 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 Actuator startup endpoint (with BufferingApplicationStartup) 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 ApplicationReadyEvent fires

Prefer multiple choice? All 21 Spring Boot MCQs with answers →

esc