Spring / Spring Boot 4 Basics Interview Questions
1. What is Spring Boot 4 and when was it released?
Spring Boot 4.0 was released on November 20, 2025, making it the most structurally significant Spring release since the move to Jakarta EE in Spring Boot 3.0 (2022). Spring Boot 4.1 followed on June 10, 2026 and is the current recommended version for new projects.
Spring Boot 4 is described by the Spring team as the beginning of a new generation -- not an incremental update but a genuine architectural shift built on top of Spring Framework 7 and Jakarta EE 11.
| Property | Detail |
|---|---|
| GA release | November 20, 2025 (4.0.0) |
| Latest stable | 4.1.0 (June 10, 2026) |
| Underlying framework | Spring Framework 7 |
| Jakarta EE baseline | Jakarta EE 11 (Servlet 6.1, Persistence 3.2, Validation 3.1) |
| Minimum Java | Java 17 |
| First-class Java support | Java 25 |
| Minimum Kotlin | Kotlin 2.2 |
| Recommended for new projects | Spring Boot 4.1 |
Key headlines:
- Complete modularisation into 70+ focused JARs
- Native API versioning in Spring MVC and WebFlux
- JSpecify null safety across the entire portfolio
- Jackson 3 is the new JSON library (Jackson 2 removed)
- JUnit 4 removed entirely
- Undertow removed (no Servlet 6.1 support)
- Built-in retry and concurrency limits via @Retryable and @ConcurrencyLimit
2. What is the complete modularisation of Spring Boot 4 and why does it matter?
One of the most architecturally significant changes in Spring Boot 4 is the complete modularisation of the codebase into 70+ focused JAR modules. Previously, the two monolithic JARs -- spring-boot-autoconfigure and spring-boot-test-autoconfigure -- contained auto-configuration for every supported technology regardless of what your project actually used.
Why it matters:
- Smaller application footprint -- you only include modules for technologies you actually use
- Faster builds and startup times -- less classpath scanning and fewer classes to load
- Better GraalVM native images -- smaller reachability surface means smaller native binaries
- Cleaner IDE auto-complete -- no longer suggests classes from libraries you haven't included
- Clearer dependency management -- easier to reason about what is on the classpath and why
| Spring Boot 3 | Spring Boot 4 | What it contains |
|---|---|---|
| spring-boot-starter-web | spring-boot-starter-webmvc | Spring MVC + Tomcat |
| spring-boot-starter-web | spring-boot-starter-webflux | WebFlux (if reactive) |
| spring-boot-autoconfigure (monolith) | 70+ focused modules | One module per technology |
| spring-boot-test-autoconfigure (monolith) | spring-boot-starter-webmvc-test etc. | Test slices per technology |
<!-- Spring Boot 3 (monolithic starter) --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring Boot 4 (modular starters) --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webmvc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jackson</artifactId> </dependency> <!-- For gradual migration: classic starters bridge the gap --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-classic</artifactId> <!-- bundles old behaviour --> </dependency>
3. What is the Java version baseline in Spring Boot 4 and what Java features does it unlock?
Spring Boot 4 maintains Java 17 as the minimum baseline while adding first-class support for Java 25. The decision to keep Java 17 as the minimum was deliberate -- as Spring Framework project lead Juergen Hoeller stated, "the current industry consensus is clearly around a Java 17 baseline."
| Java version | Support level | Notable features |
|---|---|---|
| Java 17 | Minimum required | Text blocks, records, sealed classes, pattern matching preview |
| Java 21 | Strongly recommended | Virtual threads (stable), structured concurrency (preview), sequenced collections |
| Java 25 | First-class, tested | AOT compilation improvements, GraalVM native optimisations, vector API incubator |
| Java 26 | Supported in Boot 4.1 | Structured concurrency (preview), further platform improvements |
# application.properties -- enable virtual threads (Java 21+) spring.threads.virtual.enabled=true # When enabled, Spring Boot optimises internal task executors # to use lightweight virtual threads instead of platform threads # Making blocking code perform like reactive code without complexity # GraalVM native image in Spring Boot 4 requires: # GraalVM native-image version 25 or later # Build native image: # ./mvnw -Pnative native:compile # ./gradlew nativeCompile
Virtual threads and Spring Boot 4: with spring.threads.virtual.enabled=true, the framework automatically switches internal Tomcat thread pools, Spring MVC request handling, and @Async executors to use Java virtual threads. This delivers near-reactive throughput for blocking I/O code without requiring reactive programming paradigms.
4. What is Jakarta EE 11 and what changes does it bring in Spring Boot 4?
Spring Boot 4 moves the Jakarta EE baseline from version 9/10 (used in Boot 3.x) to Jakarta EE 11. This brings updated specifications for persistence, validation, servlets, and WebSockets.
| Specification | Version | Key changes |
|---|---|---|
| Jakarta Servlet | 6.1 | Required by Spring Boot 4; Undertow incompatible (removed) |
| Jakarta Persistence (JPA) | 3.2 | Detached entities no longer silently reassociated; stricter persistence context rules |
| Jakarta Bean Validation | 3.1 | Improved constraint validation and messaging |
| Jakarta WebSocket | 2.2 | Updated WebSocket API |
| Hibernate ORM | 7.1 | Managed version; stricter detached entity behaviour |
// All javax.* imports from Spring Boot 2.x are gone in Boot 4 // They must be jakarta.* (this was also true in Boot 3 but Boot 4 enforces it) // WRONG (will not compile in Boot 4): import javax.persistence.Entity; import javax.validation.constraints.NotNull; import javax.servlet.http.HttpServletRequest; // CORRECT: import jakarta.persistence.Entity; import jakarta.validation.constraints.NotNull; import jakarta.servlet.http.HttpServletRequest; // The OpenRewrite recipe handles automated migration: // Migrate_To_Jakarta_EE_10 recipe covers remaining javax.* -> jakarta.* // Hibernate 7.1 breaking change: detached entities // Boot 3: silently reassociated (lenient) // Boot 4: throws exception if you try to persist a detached entity without explicit merge @Transactional public void updateOrder(Order detachedOrder) { // Boot 3: orderRepo.save(detachedOrder) might silently merge // Boot 4: be explicit: orderRepo.save(entityManager.merge(detachedOrder)); }
Critical breaking change: Undertow has been removed entirely from Spring Boot 4 because it is not yet compatible with Servlet 6.1. Any application using Undertow as its embedded container must migrate to Tomcat 11 or Jetty 12.1 before upgrading to Spring Boot 4.
5. How does Spring Boot 4 auto-configuration work and what is the new @AutoConfiguration annotation?
Spring Boot's auto-configuration mechanism has been refined in version 4. The core concept remains: Spring Boot detects libraries on your classpath and automatically configures beans accordingly -- but the implementation is now spread across 70+ modules rather than a single monolithic JAR.
// Spring Boot 4 auto-configuration mechanics: // 1. Conditional annotations still drive auto-configuration: @AutoConfiguration @ConditionalOnClass(DataSource.class) @ConditionalOnMissingBean(DataSource.class) @EnableConfigurationProperties(DataSourceProperties.class) public class DataSourceAutoConfiguration { @Bean @ConditionalOnMissingBean public DataSource dataSource(DataSourceProperties props) { return props.initializeDataSourceBuilder().build(); } } // 2. Registration is now in META-INF/spring/ // org.springframework.boot.autoconfigure.AutoConfiguration.imports // (replaces META-INF/spring.factories from Spring Boot 2) // 3. @SpringBootApplication still enables component scanning // and auto-configuration: @SpringBootApplication public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } } // 4. Exclude specific auto-configuration: @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) public class NoDbApp { ... } // 5. Conditional annotations reference: // @ConditionalOnClass - class is on classpath // @ConditionalOnMissingBean - no bean of this type // @ConditionalOnProperty - property is set // @ConditionalOnWebApplication - is a web app // @ConditionalOnExpression - SpEL expression is true
Key change from Boot 3: the @AutoConfiguration annotation (introduced in Boot 2.7) is now the standard approach. It replaces the older pattern of annotating auto-configuration classes with plain @Configuration plus a @ConditionalOn*. The @AutoConfiguration annotation also sets proxyBeanMethods=false by default for better performance in Boot 4's modular world.
6. What is native API versioning in Spring Boot 4 and how do you use it?
API versioning has historically been one of the most DIY aspects of Spring development -- teams built custom URL paths, header filters, or RequestCondition hacks. Spring Framework 7 (and Spring Boot 4) makes API versioning a first-class feature with native annotation-based support in both Spring MVC and WebFlux.
| Strategy | Example | Configuration |
|---|---|---|
| Path-based | GET /api/v1/orders, GET /api/v2/orders | spring.mvc.apiversion.use-path=true |
| Header-based | X-API-Version: 1 | spring.mvc.apiversion.use-header=X-API-Version |
| Query parameter | GET /orders?version=1 | spring.mvc.apiversion.use-param=version |
| Media type (Accept header) | Accept: application/json;version=1 | spring.mvc.apiversion.use-media-type=true |
// Spring Boot 3: manual versioning (repetitive and fragile) @RestController @RequestMapping("/api/v1/products") public class ProductControllerV1 { ... } @RestController @RequestMapping("/api/v2/products") public class ProductControllerV2 { ... } // Spring Boot 4: native versioning @RestController @RequestMapping("/api/products") public class ProductController { @GetMapping(path = "/{id}", version = "1.0") public ProductV1 getProductV1(@PathVariable String id) { return productService.findV1(id); } @GetMapping(path = "/{id}", version = "2.0") public ProductV2 getProductV2(@PathVariable String id) { return productService.findV2(id); } } // Configuration in application.properties: # Header-based versioning: spring.mvc.apiversion.use-header=X-API-Version spring.mvc.apiversion.default=1 # Request: GET /api/products/123 with header X-API-Version: 2 # Routes to: getProductV2 // Deprecation handling (RFC 9745 compliant): @GetMapping(path = "/{id}", version = "1.0", deprecated = true) public ProductV1 getProductV1(@PathVariable String id) { // Spring automatically adds Deprecation response header return productService.findV1(id); }
RFC 9745 compliance: Spring Boot 4 automatically adds the Deprecation HTTP response header when a versioned endpoint is marked as deprecated, giving API consumers programmatic notification of deprecation without custom filter code.
7. What are JSpecify nullability annotations in Spring Boot 4 and why are they important?
Spring Boot 4 adopts JSpecify annotations (@Nullable, @NonNull, @NullMarked) portfolio-wide, deprecating Spring's own org.springframework.lang annotations. JSpecify is a collaborative standard backed by Google, JetBrains, Broadcom (Spring), Uber, Oracle, and OpenJDK.
| Annotation | Package | Meaning |
|---|---|---|
| @NonNull | org.jspecify.annotations | The element is never null; tooling enforces this contract |
| @Nullable | org.jspecify.annotations | The element may be null; callers must handle null |
| @NullMarked | org.jspecify.annotations | Applied at package/class level: all unannotated types are non-null by default |
| @NullUnmarked | org.jspecify.annotations | Opts a scope back out of null-marked assumptions |
// package-info.java: declare the whole package as null-safe @NullMarked package com.example.service; import org.jspecify.annotations.NullMarked; // Service class: unannotated types are non-null by default @Service public class OrderService { // Non-null by default (from @NullMarked) public Order createOrder(String productId, int quantity) { return new Order(productId, quantity); } // Explicitly mark where null is possible: public @Nullable Order findOrder(@Nullable String orderId) { if (orderId == null) return null; return orderRepository.findById(orderId).orElse(null); } } // Maven dependency: <dependency> <groupId>org.jspecify</groupId> <artifactId>jspecify</artifactId> <!-- Version managed by Spring Boot 4 BOM --> </dependency> // Kotlin benefit: JSpecify annotations are automatically // translated into Kotlin nullability by Kotlin 2.2 compiler // val order: Order? = service.findOrder(null) <- correct type // val order: Order = service.createOrder("p1", 2) <- non-null inferred
Tool support: IntelliJ IDEA 2025.3 ships first-class JSpecify support with full data-flow analysis. Build-time null checking via NullAway (requires JDK 21+) catches nullability violations at compile time. Previously fragmented null annotation libraries (Spring's own, JetBrains', FindBugs) are being consolidated around JSpecify.
8. What changed with Jackson in Spring Boot 4 and what are the migration considerations?
Spring Boot 4 migrates from Jackson 2 to Jackson 3 as the primary JSON library. This is one of the highest-risk breaking changes for teams upgrading, because several serialisation behaviours change silently -- no compiler error, no deprecation warning, just different JSON output.
| Change | Jackson 2 (Boot 3) | Jackson 3 (Boot 4) |
|---|---|---|
| Package namespace | com.fasterxml.jackson | tools.jackson |
| Group ID | com.fasterxml.jackson.core | tools.jackson |
| Date serialisation | Timestamps (epoch millis) by default | ISO-8601 strings by default (WRITE_DATES_AS_TIMESTAMPS=false) |
| Property ordering | Insertion order by default | Alphabetical by default (SORT_PROPERTIES_ALPHABETICALLY=true) |
| Null handling | Configurable | Default changed; review null serialisation tests |
// Jackson 3: package migration // Old (Jackson 2 / Spring Boot 3): import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.annotation.JsonProperty; // New (Jackson 3 / Spring Boot 4): import tools.jackson.databind.ObjectMapper; import tools.jackson.annotation.JsonProperty; // Date serialisation change: // Jackson 2 default: {"createdAt": 1732099200000} // Jackson 3 default: {"createdAt": "2025-11-20T10:00:00Z"} // If your API clients expect timestamps, configure explicitly: @Configuration public class JacksonConfig { @Bean public Jackson3BuilderCustomizer timestampCustomizer() { return builder -> builder .featuresToEnable(DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS); } } // Automated migration using OpenRewrite: // Run this recipe to handle package renames: // org.openrewrite.java.jackson.UpgradeJackson_2_3 // mvn rewrite:run -Drewrite.activeRecipes=org.openrewrite.java.jackson.UpgradeJackson_2_3 // Property ordering change: // Jackson 2: {"name":"Alice","id":"1","email":"a@b.com"} // Jackson 3: {"email":"a@b.com","id":"1","name":"Alice"} <- alphabetical! // Fix tests that compare exact JSON strings character by character
9. What are @Retryable and @ConcurrencyLimit in Spring Boot 4 and how do they work?
Spring Boot 4 (via Spring Framework 7) brings built-in resilience patterns directly into the Spring context. Previously, retry required the separate spring-retry dependency plus @EnableRetry. Spring Boot 4 ships these capabilities as first-class features.
// Spring Boot 3 (required separate dependency + @EnableRetry): // pom.xml: spring-retry dependency // Config class: @EnableRetry @Service public class StockService { @Retryable(value = {NetworkException.class}, maxAttempts = 2) public void updateStock() { ... } } // Spring Boot 4: works out of the box with spring-boot-starter-resilience // No @EnableRetry needed! @Service public class StockService { // Retry with exponential backoff and jitter @Retryable( includes = NetworkException.class, // exception types to retry on maxRetries = 3, // number of retry attempts backoff = @Backoff( delay = 500, // initial delay in ms multiplier = 2.0, // exponential multiplier random = true // add jitter ) ) public Order fetchOrderFromRemote(String orderId) { return remoteService.fetch(orderId); // automatically retried on NetworkException } // Bulkhead pattern: limit concurrent callers @ConcurrencyLimit(limit = 5) // max 5 concurrent executions public void processPayment(Payment payment) { // Only 5 threads can execute this simultaneously // 6th caller blocks until one slot frees up paymentGateway.process(payment); } // Combining both: @Retryable(includes = NetworkException.class, maxRetries = 3) @ConcurrencyLimit(limit = 10) public void updateStock() { externalInventoryApi.update(); } }
@ConcurrencyLimit implements the Bulkhead pattern from resilience engineering. It is particularly valuable with virtual threads (Java 21+), where it prevents a burst of cheap virtual threads from overwhelming a downstream service with simultaneous requests.
10. What are HTTP Service Clients in Spring Boot 4 and how do you define them?
HTTP Service Clients (also called declarative HTTP clients) allow you to define REST client interfaces using annotations, similar to Feign clients but built natively into Spring. Spring Boot 4 adds auto-configuration for @HttpExchange interfaces, making it easy to create and inject HTTP clients as Spring beans.
// Define a declarative HTTP client interface: public interface ProductClient { @GetExchange("/api/products/{id}") ProductDto getProduct(@PathVariable String id); @GetExchange("/api/products") List<ProductDto> getAllProducts( @RequestParam int page, @RequestParam int size ); @PostExchange("/api/products") ProductDto createProduct(@RequestBody CreateProductRequest request); @DeleteExchange("/api/products/{id}") void deleteProduct(@PathVariable String id); } // Spring Boot 4: auto-configuration via spring.http.clients.* # application.properties: spring.http.clients.product.base-url=https://products-api.example.com spring.http.clients.product.connect-timeout=5s spring.http.clients.product.read-timeout=10s // Use it like any other Spring bean: @Service @RequiredArgsConstructor public class OrderService { private final ProductClient productClient; // injected automatically public Order createOrder(String productId, int qty) { ProductDto product = productClient.getProduct(productId); // ... return new Order(product, qty); } } // Manual configuration (without auto-config): @Configuration public class ClientConfig { @Bean public ProductClient productClient(RestClient.Builder builder) { return HttpServiceProxyFactory .builderFor(RestClientAdapter.create( builder.baseUrl("https://products-api.example.com").build() )) .build() .createClient(ProductClient.class); } }
11. What are the key breaking changes removed in Spring Boot 4 that were deprecated in Boot 3?
Spring Boot 4 follows a strict deprecation policy: everything deprecated in Spring Boot 3.x is removed in 4.0 with no fallback. This is why the recommended migration path is to first upgrade to Boot 3.5 and fix all deprecation warnings before touching Boot 4.
| What was removed | Category | Replacement |
|---|---|---|
| JUnit 4 support | Testing | Use JUnit 5 (Jupiter) -- Boot 3 already required it |
| Undertow embedded server | Web | Tomcat 11 or Jetty 12.1 |
| spring-boot-starter-web (monolithic) | Modularisation | spring-boot-starter-webmvc or spring-boot-starter-webflux |
| spring-boot-autoconfigure (monolith JAR) | Modularisation | 70+ individual technology modules |
| javax.annotation / javax.inject support | Jakarta EE | jakarta.annotation / jakarta.inject |
| RestTemplate (discouraged) | HTTP Client | RestClient or HTTP Service Client (@HttpExchange) |
| Old Actuator property prefixes | Observability | Updated management.* property paths |
| AntPathMatcher for HTTP mappings (deprecated) | MVC | PathPatternParser (PathPattern-based matching) |
| spring.jackson2.* properties | JSON | spring.jackson.* (Jackson 3 config) |
# WRONG (Boot 3 style, removed in Boot 4): # spring.jackson2.serialization.write-dates-as-timestamps=false # CORRECT (Boot 4): spring.jackson.serialization.write-dates-as-timestamps=false // Migration script check: find deprecated patterns // Run OpenRewrite to automate many changes: // mvn rewrite:run \ // -Drewrite.activeRecipes=\ // org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_5,\ // org.openrewrite.java.spring.boot4.UpgradeSpringBoot_4_0 // RestTemplate to RestClient migration: // Spring Boot 3 (discouraged): RestTemplate rt = new RestTemplate(); String result = rt.getForObject("http://api/data", String.class); // Spring Boot 4 (recommended): RestClient client = RestClient.create(); String result = client .get() .uri("http://api/data") .retrieve() .body(String.class);
12. How does Spring Boot 4 handle dependency injection and what are the core stereotypes?
Spring Boot 4 retains the same core dependency injection (DI) model as all previous Spring versions. The DI container, stereotype annotations, and bean lifecycle are unchanged -- Spring Boot 4's changes are at the infrastructure and ecosystem level, not the DI model itself.
| Annotation | Use case | Specialisation of |
|---|---|---|
| @Component | Generic Spring-managed component | (base) |
| @Service | Business logic layer | @Component |
| @Repository | Data access layer; translates persistence exceptions | @Component |
| @Controller | Spring MVC controller (returns view names) | @Component |
| @RestController | REST API controller (returns response body) | @Controller + @ResponseBody |
| @Configuration | Bean factory class | @Component |
// Constructor injection (recommended in Spring Boot 4) @Service public class OrderService { private final OrderRepository repo; private final ProductClient client; private final EventPublisher events; // Spring Boot 4 recommends constructor injection: // - Makes dependencies explicit // - Enables final fields (immutability) // - No @Autowired annotation needed on single-constructor classes public OrderService( OrderRepository repo, ProductClient client, EventPublisher events) { this.repo = repo; this.client = client; this.events = events; } // With Lombok @RequiredArgsConstructor: // @Service // @RequiredArgsConstructor // public class OrderService { // private final OrderRepository repo; // private final ProductClient client; // ... // } } // @Primary: preferred bean when multiple implementations exist @Service @Primary public class DefaultOrderService implements OrderService { ... } // @Qualifier: inject specific implementation by name @Autowired @Qualifier("premiumOrderService") private OrderService orderService;
13. What is Spring Boot's application.properties / application.yml and how does configuration work?
Spring Boot externalises application configuration through application.properties or application.yml files, environment variables, system properties, and more. The configuration property source hierarchy determines which value wins when the same key appears in multiple places.
| Priority | Source |
|---|---|
| 1 (highest) | Command-line arguments (--server.port=8081) |
| 2 | SPRING_APPLICATION_JSON environment variable |
| 3 | OS environment variables |
| 4 | application-{profile}.properties/yml (active profile) |
| 5 | application.properties / application.yml |
| 6 (lowest) | @PropertySource annotations on @Configuration classes |
# application.yml (YAML format - preferred for complex config) server: port: 8080 servlet: context-path: /api spring: application: name: order-service datasource: url: jdbc:postgresql://localhost:5432/orders username: ${DB_USER} # reference env variable password: ${DB_PASSWORD} jpa: hibernate: ddl-auto: validate show-sql: false # Bind to a typed configuration class: # application.yml: app: order: max-items: 50 allowed-currencies: [USD, EUR, GBP] // @ConfigurationProperties class: @ConfigurationProperties(prefix = "app.order") @Validated // enables Bean Validation on the properties public record OrderProperties( @Min(1) @Max(1000) int maxItems, @NotEmpty List<String> allowedCurrencies ) {} // Register: @SpringBootApplication @EnableConfigurationProperties(OrderProperties.class) public class App { ... } // Inject: @Service @RequiredArgsConstructor public class OrderService { private final OrderProperties props; }
14. What are Spring Boot profiles and how do you use them for environment-specific configuration?
Profiles allow different configuration to be activated based on the deployment environment (dev, test, staging, prod). Spring Boot supports profile-specific property files, conditional bean registration, and profile activation via environment variables.
# src/main/resources/application.yml (base config, always loaded) spring: application: name: order-service # src/main/resources/application-dev.yml (loaded only in "dev" profile) spring: datasource: url: jdbc:h2:mem:devdb driver-class-name: org.h2.Driver logging: level: root: DEBUG # src/main/resources/application-prod.yml (loaded only in "prod" profile) spring: datasource: url: jdbc:postgresql://prod-db:5432/orders username: ${DB_USER} password: ${DB_PASSWORD} logging: level: root: WARN # Activate a profile: # 1. Environment variable: export SPRING_PROFILES_ACTIVE=prod # 2. JVM argument: -Dspring.profiles.active=prod # 3. application.properties: # spring.profiles.active=dev // Profile-conditional bean registration: @Configuration public class CacheConfig { @Bean @Profile("dev") // Only in dev: simple in-memory cache public CacheManager devCache() { return new ConcurrentMapCacheManager(); } @Bean @Profile("prod") // Only in prod: Redis cache public CacheManager prodCache(RedisConnectionFactory factory) { return RedisCacheManager.create(factory); } } // Multi-profile documents in single yml file (using --- separator): # application.yml: spring: config: activate: on-profile: test datasource: url: jdbc:h2:mem:testdb
15. How does Spring Boot 4 testing work with @SpringBootTest and test slices?
Spring Boot 4 provides a rich testing support framework. @SpringBootTest loads the full application context for integration tests, while test slice annotations load only a subset of the context for focused, faster unit tests.
| Annotation | Loads | Use case |
|---|---|---|
| @SpringBootTest | Full application context | End-to-end integration tests |
| @WebMvcTest | Only MVC layer (controllers, filters) | Test controllers without starting a server or database |
| @DataJpaTest | Only JPA/database layer | Test repositories; uses in-memory H2 by default |
| @DataMongoTest | Only MongoDB layer | Test MongoDB repositories |
| @RestClientTest | Only REST client components | Test HTTP Service Clients |
| @JsonTest | Only JSON serialisation components | Test Jackson serialisation/deserialisation |
| @WebFluxTest | Only WebFlux layer | Test reactive controllers |
// Integration test: full context @SpringBootTest @AutoConfigureMockMvc class OrderControllerIntegrationTest { @Autowired MockMvc mockMvc; @Autowired ObjectMapper objectMapper; @Test void createOrder_returns201() throws Exception { mockMvc.perform(post("/api/orders") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString( new CreateOrderRequest("product-1", 2)))) .andExpect(status().isCreated()) .andExpect(jsonPath("$.id").isNotEmpty()); } } // Slice test: only MVC layer (no database) @WebMvcTest(OrderController.class) class OrderControllerTest { @Autowired MockMvc mockMvc; @MockBean OrderService service; // mock the service @Test void getOrder_returns200() throws Exception { when(service.find("O-1")).thenReturn(new OrderDto("O-1", "PENDING")); mockMvc.perform(get("/api/orders/O-1")) .andExpect(status().isOk()) .andExpect(jsonPath("$.status").value("PENDING")); } } // Spring Boot 4: RestTestClient replaces the choice between // MockMvc and WebTestClient for most scenarios @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class OrderApiTest { @Autowired RestTestClient client; // new in Spring Boot 4 @Test void getOrders() { client.get().uri("/api/orders") .exchange() .expectStatus().isOk() .expectBodyList(OrderDto.class).hasSize(0); } }
16. What is Spring Boot Actuator and what does it provide in Boot 4?
Spring Boot Actuator adds production-ready monitoring and management endpoints to any Spring Boot application. In Boot 4, the observability story has been tightened around OpenTelemetry with dedicated configuration properties.
| Endpoint | HTTP | What it shows |
|---|---|---|
| /actuator/health | GET | Application health status (UP/DOWN) and component health details |
| /actuator/info | GET | Application information (version, description, git commit) |
| /actuator/metrics | GET | Micrometer metrics (JVM, HTTP, database, custom) |
| /actuator/metrics/{name} | GET | Individual metric with tags |
| /actuator/env | GET | All environment properties and their sources |
| /actuator/beans | GET | All Spring beans in the context |
| /actuator/mappings | GET | All @RequestMapping routes |
| /actuator/loggers | GET/POST | Get and change log levels at runtime |
| /actuator/threaddump | GET | JVM thread dump |
| /actuator/httptrace | GET | Last 100 HTTP request/response traces |
# application.yml: Actuator configuration in Boot 4 management: endpoints: web: exposure: include: health,info,metrics,loggers # expose specific endpoints # include: "*" # expose all (not recommended in production) endpoint: health: show-details: when-authorized opentelemetry: # Boot 4: new OTel-specific config namespace resource-attributes: service.name: order-service service.version: "2.1.0" tracing: sampling: probability: 1.0 # 100% sampling in dev; reduce in prod # Custom health indicator: @Component public class DatabaseHealthIndicator implements HealthIndicator { @Override public Health health() { try { jdbcTemplate.queryForObject("SELECT 1", Integer.class); return Health.up() .withDetail("database", "reachable") .build(); } catch (Exception e) { return Health.down() .withDetail("error", e.getMessage()) .build(); } } }
17. How does Spring Boot 4 handle data access with Spring Data JPA?
Spring Boot 4 auto-configures Spring Data JPA when spring-boot-starter-data-jpa (or its modular equivalent) is on the classpath. With Hibernate 7.1 as the managed ORM and Jakarta Persistence 3.2, there are some important behavioural changes from Boot 3.
// Entity class (Jakarta Persistence 3.2) import jakarta.persistence.*; // NOT javax.persistence in Boot 4 @Entity @Table(name = "orders") public class Order { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false) private String productId; @Enumerated(EnumType.STRING) private OrderStatus status; // Constructors, getters, setters... } // Repository interface: Spring Data handles implementation public interface OrderRepository extends JpaRepository<Order, Long> { // Method name query derivation: List<Order> findByStatus(OrderStatus status); List<Order> findByProductIdAndStatus(String productId, OrderStatus status); Optional<Order> findFirstByProductIdOrderByCreatedAtDesc(String productId); // JPQL query: @Query("SELECT o FROM Order o WHERE o.status = :status AND o.total > :minTotal") List<Order> findHighValueByStatus( @Param("status") OrderStatus status, @Param("minTotal") BigDecimal minTotal ); // Native SQL query: @Query(value = "SELECT * FROM orders WHERE status = ?1", nativeQuery = true) List<Order> findByStatusNative(String status); } # application.yml JPA configuration: spring: jpa: hibernate: ddl-auto: validate # validate, update, create, create-drop, none show-sql: false properties: hibernate: format_sql: true dialect: org.hibernate.dialect.PostgreSQLDialect
18. What is Spring Security in Spring Boot 4 and what are the key Boot 4 changes?
Spring Boot 4 ships with Spring Security 7, which introduces multi-factor authentication (MFA) and important changes to default security configurations -- particularly CSRF protection defaults that can silently break REST APIs.
// Spring Boot 4 / Spring Security 7: SecurityFilterChain @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http // REST APIs: disable CSRF (Spring Security 7 default changed!) // Boot 3: CSRF enabled by default; REST APIs often disabled it // Boot 4 / Security 7: review CSRF defaults -- new SameSite-based protection .csrf(csrf -> csrf .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) ) .authorizeHttpRequests(auth -> auth .requestMatchers("/actuator/health", "/api/public/**").permitAll() .requestMatchers("/api/admin/**").hasRole("ADMIN") .anyRequest().authenticated() ) .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.STATELESS) // for JWT/OAuth2 ) .oauth2ResourceServer(oauth2 -> oauth2 .jwt(jwt -> jwt .decoder(JwtDecoders.fromIssuerLocation(issuerUri)) ) ); return http.build(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } } // Spring Security 7: Multi-Factor Authentication // MFA is now a first-class concept in the security model // Configure in SecurityFilterChain with mfa() DSL // Previously required custom implementations
Key Spring Security 7 change for Boot 4: Spring Security 7 changes some CSRF and session management defaults. Teams migrating from Boot 3 should review their SecurityFilterChain configurations carefully -- previously-passing REST API tests may fail if CSRF defaults behave differently.
19. What is the Spring Boot starter parent (POM) and how do you set up a Spring Boot 4 project?
The spring-boot-starter-parent POM sets sensible defaults for Maven builds: Java version, encoding, plugin versions, and dependency management via the Spring Boot BOM. It is the recommended way to start a Spring Boot project.
<!-- Maven: spring-boot-starter-parent --> <project> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>4.1.0</version> <!-- Spring Boot 4.1 (current) --> <relativePath/> </parent> <groupId>com.example</groupId> <artifactId>order-service</artifactId> <version>1.0.0-SNAPSHOT</version> <packaging>jar</packaging> <properties> <java.version>17</java.version> <!-- minimum for Boot 4 --> </properties> <dependencies> <!-- Spring Boot 4 modular starters --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webmvc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <!-- includes JUnit 5 (Jupiter only in Boot 4) --> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <!-- builds executable JAR with embedded Tomcat --> </plugin> </plugins> </build> </project>
20. What is @SpringBootApplication and what does it combine?
@SpringBootApplication is a composed annotation that is the standard entry point for Spring Boot applications. It combines three key annotations that together enable auto-configuration, component scanning, and configuration capabilities.
// @SpringBootApplication = @SpringBootConfiguration // + @EnableAutoConfiguration // + @ComponentScan @SpringBootApplication // the one annotation to rule them all public class OrderServiceApplication { public static void main(String[] args) { SpringApplication.run(OrderServiceApplication.class, args); } } // What each constituent annotation does: // @SpringBootConfiguration: marks this as a Spring configuration class // (specialisation of @Configuration) // @EnableAutoConfiguration: tells Spring Boot to scan the classpath // and auto-configure beans based on what is found // e.g., finds spring-boot-starter-data-jpa -> auto-configures DataSource // @ComponentScan: scans the current package and sub-packages // for @Component, @Service, @Repository, @Controller, etc. // Default: scans the package of the annotated class // Customisations: @SpringBootApplication( // exclude specific auto-configurations: exclude = {DataSourceAutoConfiguration.class}, // scan additional packages: scanBasePackages = {"com.example.orders", "com.example.shared"} ) public class App { ... } // SpringApplication customisation in Boot 4: @SpringBootApplication public class App { public static void main(String[] args) { var app = new SpringApplication(App.class); app.setBannerMode(Banner.Mode.OFF); // disable ASCII art banner app.setLazyInitialization(true); // lazy init for faster startup app.run(args); } }
21. How do you build REST APIs with Spring Boot 4 using @RestController?
Building REST APIs in Spring Boot 4 uses the same @RestController + @RequestMapping model as previous versions, but with native API versioning, improved validation integration, and better null safety via JSpecify.
import jakarta.validation.Valid; import jakarta.validation.constraints.*; import org.jspecify.annotations.Nullable; @RestController @RequestMapping("/api/orders") @Validated public class OrderController { private final OrderService service; public OrderController(OrderService service) { this.service = service; } // GET all with pagination @GetMapping public Page<OrderDto> listOrders( @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "20") int size) { return service.findAll(PageRequest.of(page, size)); } // GET by ID @GetMapping("/{id}") public ResponseEntity<OrderDto> getOrder(@PathVariable String id) { return service.findById(id) .map(ResponseEntity::ok) .orElse(ResponseEntity.notFound().build()); } // POST: create new order @PostMapping @ResponseStatus(HttpStatus.CREATED) public OrderDto createOrder(@Valid @RequestBody CreateOrderRequest request) { return service.create(request); } // PUT: full update @PutMapping("/{id}") public OrderDto updateOrder( @PathVariable String id, @Valid @RequestBody UpdateOrderRequest request) { return service.update(id, request); } // DELETE @DeleteMapping("/{id}") @ResponseStatus(HttpStatus.NO_CONTENT) public void deleteOrder(@PathVariable String id) { service.delete(id); } // Native API versioning (Spring Boot 4): @GetMapping(path = "/{id}", version = "2.0") public OrderDtoV2 getOrderV2(@PathVariable String id) { return service.findByIdV2(id); } // Global exception handler: @ExceptionHandler(OrderNotFoundException.class) @ResponseStatus(HttpStatus.NOT_FOUND) public ErrorResponse handleNotFound(OrderNotFoundException ex) { return new ErrorResponse("ORDER_NOT_FOUND", ex.getMessage()); } }
22. How does Bean Validation work with Spring Boot 4?
Spring Boot 4 auto-configures Jakarta Bean Validation 3.1 when spring-boot-starter-validation is on the classpath. Validation annotations on request bodies, path variables, and method parameters are enforced automatically.
// DTO with validation constraints: public record CreateOrderRequest( @NotBlank(message = "Product ID is required") String productId, @Min(value = 1, message = "Quantity must be at least 1") @Max(value = 100, message = "Quantity cannot exceed 100") int quantity, @Email(message = "Invalid email address") @NotNull String customerEmail, @NotNull @Valid // cascade validation into nested object AddressRequest shippingAddress ) {} public record AddressRequest( @NotBlank String line1, @Nullable String line2, @NotBlank @Size(min=5, max=10) String postcode ) {} // Controller: @Valid triggers validation before method body: @PostMapping @ResponseStatus(HttpStatus.CREATED) public OrderDto createOrder(@Valid @RequestBody CreateOrderRequest request) { return service.create(request); } // Handle validation errors globally: @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(MethodArgumentNotValidException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public Map<String, String> handleValidation(MethodArgumentNotValidException ex) { Map<String, String> errors = new LinkedHashMap<>(); ex.getBindingResult().getFieldErrors() .forEach(err -> errors.put(err.getField(), err.getDefaultMessage())); return errors; } } // Service-level validation with @Validated: @Service @Validated // enables method-level validation public class OrderService { public Order getOrder(@NotBlank String id) { return repo.findById(id).orElseThrow(); } }
23. What is Spring Boot's embedded server and how do you configure it?
Spring Boot 4 ships with embedded web servers that start as part of the application JAR -- no external application server is needed. The supported embedded servers in Boot 4 are Tomcat 11 and Jetty 12.1 (Undertow was removed).
| Server | Starter | Notes |
|---|---|---|
| Tomcat 11 | spring-boot-starter-webmvc (default) | Default; Servlet 6.1 compatible; most widely used |
| Jetty 12.1 | Exclude Tomcat, add jetty starter | Servlet 6.1 compatible; good for low-memory environments |
| Undertow | REMOVED in Spring Boot 4 | Not compatible with Servlet 6.1 |
# Tomcat (default): no special configuration needed # application.yml Tomcat tuning: server: port: 8080 servlet: context-path: /api tomcat: max-threads: 200 min-spare-threads: 10 connection-timeout: 20s keep-alive-timeout: 60s # Switch from Tomcat to Jetty: <!-- pom.xml --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webmvc</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jetty</artifactId> </dependency> # SSL / HTTPS configuration: server: port: 8443 ssl: enabled: true key-store: classpath:keystore.p12 key-store-password: ${SSL_KEYSTORE_PASSWORD} key-store-type: PKCS12 key-alias: my-app # Virtual threads with Tomcat (Java 21+): spring: threads: virtual: enabled: true # Tomcat automatically uses virtual thread executor
24. What is Spring Boot's transaction management with @Transactional?
Spring Boot 4 auto-configures transaction management when a data source is on the classpath. The @Transactional annotation declaratively wraps methods in a database transaction, with Spring AOP managing commit and rollback.
// Service with transaction management: @Service public class OrderService { private final OrderRepository orderRepo; private final InventoryRepository inventoryRepo; private final EventPublisher events; // Default: propagation=REQUIRED, isolation=DEFAULT, rollbackFor=RuntimeException @Transactional public Order placeOrder(CreateOrderRequest request) { // All operations in one transaction: Order order = orderRepo.save(new Order(request)); inventoryRepo.reserveStock(request.productId(), request.quantity()); events.publish(new OrderPlacedEvent(order.getId())); return order; // Commits on successful return // Rolls back on RuntimeException } // Read-only transaction (performance optimisation): @Transactional(readOnly = true) public Optional<Order> findById(String id) { return orderRepo.findById(id); // Hibernate skips dirty checking for read-only transactions } // Custom rollback rules: @Transactional(rollbackFor = {BusinessException.class, IOException.class}) public void processPayment(String orderId) throws IOException { // Rolls back on checked IOException too } // Propagation types: @Transactional(propagation = Propagation.REQUIRES_NEW) public void auditLog(String message) { // Always runs in its own NEW transaction, // suspending any existing outer transaction auditRepo.save(new AuditEntry(message)); } }
| Propagation | Behaviour |
|---|---|
| REQUIRED (default) | Join existing transaction; create new if none |
| REQUIRES_NEW | Always create a new transaction; suspend existing |
| SUPPORTS | Join existing if present; run non-transactional if none |
| NOT_SUPPORTED | Run non-transactional; suspend existing |
| NEVER | Must run non-transactionally; error if transaction exists |
| MANDATORY | Must join existing; error if no transaction exists |
25. What is Spring Boot's caching abstraction and how do you use it?
Spring Boot 4 provides a caching abstraction that decouples application code from specific cache implementations. Using @EnableCaching and method-level annotations, you can cache method results and evict them declaratively.
// Enable caching: @SpringBootApplication @EnableCaching public class App { ... } // Service with caching: @Service public class ProductService { // Cache the result by productId: @Cacheable(value = "products", key = "#productId") public ProductDto findById(String productId) { return productRepo.findById(productId) // only called on cache miss .map(this::toDto) .orElseThrow(); } // Update the cache after saving: @CachePut(value = "products", key = "#result.id") public ProductDto save(Product product) { Product saved = productRepo.save(product); return toDto(saved); // returns updated value AND updates cache } // Evict from cache on delete: @CacheEvict(value = "products", key = "#productId") public void delete(String productId) { productRepo.deleteById(productId); } // Evict all cache entries: @CacheEvict(value = "products", allEntries = true) public void clearAll() { } } # Configuring the cache provider in application.yml: # Simple (in-memory, default when no other cache is configured): spring: cache: type: simple # Redis cache: spring: cache: type: redis redis: time-to-live: 600s data: redis: host: localhost port: 6379 # Caffeine (high-performance in-memory): spring: cache: type: caffeine caffeine: spec: maximumSize=1000,expireAfterWrite=300s
26. What is Spring Boot's observability stack in Boot 4 with Micrometer and OpenTelemetry?
Spring Boot 4 builds observability around Micrometer (metrics), Micrometer Tracing (distributed tracing), and OpenTelemetry (standard telemetry export). The management.opentelemetry.* property namespace is new in Boot 4.
| Pillar | Library | What it tracks |
|---|---|---|
| Metrics | Micrometer | HTTP request rates, JVM heap, GC, custom counters/gauges |
| Tracing | Micrometer Tracing + OTel | Distributed request traces across services |
| Logging | SLF4J + Logback/Log4j2 | Structured application logs |
| Health | Spring Boot Actuator | Component health and readiness |
// Custom metrics with Micrometer: @Service public class OrderService { private final Counter orderCounter; private final Timer orderTimer; public OrderService(MeterRegistry registry) { this.orderCounter = Counter.builder("orders.created") .description("Total orders created") .tag("environment", "prod") .register(registry); this.orderTimer = Timer.builder("orders.processing.time") .description("Time to process order") .register(registry); } public Order placeOrder(CreateOrderRequest request) { return orderTimer.record(() -> { Order order = doCreateOrder(request); orderCounter.increment(); return order; }); } } # application.yml: OpenTelemetry configuration (Boot 4) management: opentelemetry: resource-attributes: service.name: order-service service.version: "2.1.0" deployment.environment: production tracing: sampling: probability: 0.1 # 10% sampling in production metrics: export: prometheus: enabled: true # expose /actuator/prometheus # Export traces to Jaeger / Zipkin / OTel Collector: management.otlp.tracing.endpoint=http://otel-collector:4317/v1/traces
27. How does Spring Boot 4 support GraalVM native images?
Spring Boot 4 has first-class support for compiling applications to GraalVM native images -- ahead-of-time compiled binaries that start in milliseconds and use a fraction of the heap compared to JVM-based deployments. Boot 4 requires GraalVM native-image 25 or later.
# Requirements for native image in Spring Boot 4: # 1. GraalVM native-image 25 (or later) # 2. java.version >= 17 # 3. spring-boot-starter-parent as parent (or spring AOT plugin) # Build native image with Maven: ./mvnw -Pnative native:compile # Output: target/order-service (native binary) # Build native image with Gradle: ./gradlew nativeCompile # Output: build/native/nativeCompile/order-service # Build Docker image with native binary (Buildpacks): ./mvnw spring-boot:build-image -Pnative # Creates a container image using Cloud Native Buildpacks // Native image hints for reflection (when AOT analysis misses something): @ImportRuntimeHints(MyRuntimeHints.class) @SpringBootApplication public class App { ... } public class MyRuntimeHints implements RuntimeHintsRegistrar { @Override public void registerHints(RuntimeHints hints, ClassLoader cl) { // Register class for reflection: hints.reflection() .registerType(MyDynamicClass.class, MemberCategory.INVOKE_DECLARED_METHODS); // Register resource file: hints.resources().registerPattern("templates/*.html"); } } # Native image startup comparison: # JVM mode: ~2-4 seconds startup, ~256MB heap # Native mode: ~50-100ms startup, ~50MB memory # Trade-off: Build time 5-10 minutes; no JIT optimisation at runtime
28. What is Spring WebFlux and reactive programming in Spring Boot 4?
Spring WebFlux is Spring's reactive web framework, built on Project Reactor. It is an alternative to Spring MVC for building non-blocking, asynchronous web applications. Spring Boot 4 supports both Spring MVC (servlet-based) and WebFlux (reactive) in the same framework.
| Aspect | Spring MVC | Spring WebFlux |
|---|---|---|
| Programming model | Imperative / blocking | Reactive / non-blocking |
| Thread model | One thread per request (or virtual threads) | Event loop; fewer threads handle more connections |
| Return types | Object, ResponseEntity | Mono |
| Persistence | Spring Data JPA (blocking) | Spring Data R2DBC (reactive) |
| Best for | Traditional CRUD, simple blocking I/O | High concurrency, streaming, SSE, WebSocket |
// WebFlux reactive controller: @RestController @RequestMapping("/api/orders") public class OrderReactiveController { private final OrderReactiveService service; // Mono<T>: 0 or 1 item (like Optional) @GetMapping("/{id}") public Mono<ResponseEntity<OrderDto>> getOrder(@PathVariable String id) { return service.findById(id) .map(ResponseEntity::ok) .defaultIfEmpty(ResponseEntity.notFound().build()); } // Flux<T>: 0 to N items (like Stream) @GetMapping public Flux<OrderDto> listOrders() { return service.findAll(); } // Server-Sent Events (SSE) streaming: @GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<OrderDto> streamOrders() { return service.streamNewOrders() // infinite stream of new orders .delayElements(Duration.ofSeconds(1)); } // R2DBC (reactive database): // Replace JpaRepository with ReactiveCrudRepository } // Starter for WebFlux: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> // WebFlux native API versioning (Boot 4): @GetMapping(path = "/{id}", version = "2.0") public Mono<OrderDtoV2> getOrderV2(@PathVariable String id) { return service.findByIdV2(id); }
29. What is Spring Boot's exception handling with @RestControllerAdvice?
@RestControllerAdvice provides a centralised, global exception handling mechanism for all controllers. It replaces the need to put @ExceptionHandler methods in every controller class.
// Centralised exception handler: @RestControllerAdvice public class GlobalExceptionHandler { private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); // Handle custom domain exception: @ExceptionHandler(OrderNotFoundException.class) @ResponseStatus(HttpStatus.NOT_FOUND) public ErrorResponse handleNotFound(OrderNotFoundException ex) { return new ErrorResponse( "ORDER_NOT_FOUND", ex.getMessage(), Instant.now() ); } // Handle Bean Validation failures: @ExceptionHandler(MethodArgumentNotValidException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public ValidationErrorResponse handleValidation(MethodArgumentNotValidException ex) { Map<String, String> fieldErrors = ex.getBindingResult() .getFieldErrors() .stream() .collect(Collectors.toMap( FieldError::getField, FieldError::getDefaultMessage, (a, b) -> a // keep first on duplicate key )); return new ValidationErrorResponse("VALIDATION_FAILED", fieldErrors); } // Handle illegal argument: @ExceptionHandler(IllegalArgumentException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public ErrorResponse handleIllegalArg(IllegalArgumentException ex) { return new ErrorResponse("INVALID_REQUEST", ex.getMessage(), Instant.now()); } // Catch-all for unexpected exceptions: @ExceptionHandler(Exception.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public ErrorResponse handleGeneral(Exception ex, HttpServletRequest request) { log.error("Unhandled exception for {}: {}", request.getRequestURI(), ex.getMessage(), ex); return new ErrorResponse("INTERNAL_ERROR", "An unexpected error occurred", Instant.now()); } } public record ErrorResponse(String code, String message, Instant timestamp) {}
30. How do you use Spring Data MongoDB and other NoSQL stores in Spring Boot 4?
Spring Boot 4 auto-configures connections to MongoDB, Redis, Cassandra, Elasticsearch, and other NoSQL stores when their starters are on the classpath. The programming model mirrors Spring Data JPA but without SQL.
// MongoDB entity: import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; @Document(collection = "products") // MongoDB collection name public record Product( @Id String id, // MongoDB ObjectId String name, BigDecimal price, String category, Instant createdAt ) {} // MongoDB repository: public interface ProductRepository extends MongoRepository<Product, String> { List<Product> findByCategory(String category); List<Product> findByPriceLessThan(BigDecimal maxPrice); // MongoDB query annotation: @Query("{ \"category\": ?0, \"price\": { \"$lt\": ?1 } }") List<Product> findByCategoryAndMaxPrice(String category, BigDecimal maxPrice); } # application.yml: MongoDB connection: spring: data: mongodb: uri: mongodb://localhost:27017/products-db # or: host: localhost port: 27017 database: products-db username: ${MONGO_USER} password: ${MONGO_PASS} # application.yml: Redis connection: spring: data: redis: host: localhost port: 6379 password: ${REDIS_PASS} timeout: 2s // Redis template usage: @Service public class SessionService { private final RedisTemplate<String, Object> redisTemplate; public void store(String key, Object value, Duration ttl) { redisTemplate.opsForValue().set(key, value, ttl); } }
31. How does Spring Boot 4 handle async processing with @Async?
@Async makes a method execute in a separate thread asynchronously, returning a CompletableFuture (or void) immediately while processing continues in the background. Spring Boot 4 automatically uses virtual threads when enabled.
// Enable async processing: @SpringBootApplication @EnableAsync public class App { ... } // Async service: @Service public class EmailService { // Fire-and-forget (no return value): @Async public void sendWelcomeEmail(String email) { // Runs in separate thread; caller continues immediately emailGateway.send(email, "Welcome!", buildWelcomeBody(email)); } // Async with result: @Async public CompletableFuture<EmailStats> getEmailStats(String userId) { EmailStats stats = emailGateway.fetchStats(userId); // slow I/O return CompletableFuture.completedFuture(stats); } } // Caller: @Service public class RegistrationService { public User register(RegisterRequest req) { User user = userRepo.save(new User(req)); emailService.sendWelcomeEmail(user.email()); // returns immediately return user; // registration complete; email sends in background } // Parallel async calls: public DashboardData loadDashboard(String userId) throws Exception { CompletableFuture<OrderStats> orders = orderService.getStats(userId); CompletableFuture<EmailStats> emails = emailService.getEmailStats(userId); CompletableFuture<PaymentInfo> payment = paymentService.getInfo(userId); // Wait for all 3 to complete (run in parallel) CompletableFuture.allOf(orders, emails, payment).join(); return new DashboardData(orders.get(), emails.get(), payment.get()); } } # Custom async executor thread pool: spring: task: execution: pool: core-size: 5 max-size: 20 queue-capacity: 100 thread-name-prefix: async-
32. What is Spring Boot's externalized configuration with @Value and @ConfigurationProperties?
Spring Boot 4 provides two ways to inject configuration values into beans: @Value for simple individual values and @ConfigurationProperties for structured, type-safe groups of related properties.
# application.yml: app: payment: gateway-url: https://payment.example.com/api timeout-seconds: 30 retry-attempts: 3 supported-currencies: [USD, EUR, GBP, JPY] api: rate-limit: 1000 // Method 1: @Value for simple individual values @Service public class RateLimiter { @Value("${api.rate-limit}") private int rateLimit; @Value("${api.rate-limit:500}") // default value if not configured private int rateLimitWithDefault; @Value("${MISSING_PROP:#{null}}") // null default private String optionalProp; @Value("${spring.application.name}") private String appName; } // Method 2: @ConfigurationProperties for grouped values (recommended) @ConfigurationProperties(prefix = "app.payment") @Validated public record PaymentProperties( @NotBlank URL gatewayUrl, @Positive int timeoutSeconds, @Min(1) @Max(10) int retryAttempts, @NotEmpty List<String> supportedCurrencies ) {} // Register and inject: @EnableConfigurationProperties(PaymentProperties.class) @SpringBootApplication public class App { ... } @Service @RequiredArgsConstructor public class PaymentService { private final PaymentProperties config; // type-safe injection public void processPayment(String currency) { if (!config.supportedCurrencies().contains(currency)) { throw new UnsupportedCurrencyException(currency); } } }
33. What is Spring Boot's messaging support with Kafka in Boot 4?
Spring Boot 4 ships with Spring for Apache Kafka 4.0, which introduces Share Consumer support for Kafka Queues -- allowing multiple consumers in a group to share records from the same partition rather than partitions being locked to single consumers.
// Producer: @Service public class OrderEventProducer { private final KafkaTemplate<String, OrderEvent> kafkaTemplate; public void publishOrderCreated(Order order) { OrderEvent event = new OrderEvent(order.id(), "ORDER_CREATED", Instant.now()); kafkaTemplate.send("orders-topic", order.id(), event); } } // Consumer: @Service public class OrderEventConsumer { @KafkaListener( topics = "orders-topic", groupId = "order-processing-group", concurrency = "3" // 3 consumer threads ) public void handleOrderEvent( @Payload OrderEvent event, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic, Acknowledgment ack) { try { processEvent(event); ack.acknowledge(); // manual acknowledgement } catch (Exception e) { log.error("Failed to process event: {}", event, e); // send to DLT (dead letter topic) } } } # application.yml: Kafka configuration spring: kafka: bootstrap-servers: localhost:9092 consumer: group-id: order-service auto-offset-reset: earliest key-deserializer: org.apache.kafka.common.serialization.StringDeserializer value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer properties: spring.json.trusted.packages: "com.example.events" producer: key-serializer: org.apache.kafka.common.serialization.StringSerializer value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
34. How do you schedule tasks in Spring Boot 4 with @Scheduled?
Spring Boot 4 provides task scheduling via @EnableScheduling and @Scheduled annotations. In Boot 4, the auto-configuration for task scheduling supports multiple TaskDecorator beans, enabling better observability and context propagation.
// Enable scheduling: @SpringBootApplication @EnableScheduling public class App { ... } @Service public class ReportScheduler { // Fixed rate: every 60 seconds (interval from start of last execution) @Scheduled(fixedRate = 60_000) public void generateHourlyMetrics() { metricsService.compute(); } // Fixed delay: 30 seconds AFTER last execution completes @Scheduled(fixedDelay = 30_000) public void cleanExpiredSessions() { sessionService.evictExpired(); } // Cron expression: every day at 2:30 AM @Scheduled(cron = "0 30 2 * * *") public void dailyReport() { reportService.generateDailyReport(); } // Cron with zone: @Scheduled(cron = "0 0 8 * * MON-FRI", zone = "Europe/London") public void morningDigest() { digestService.send(); } // Initial delay: wait 5s after startup before first run @Scheduled(initialDelay = 5_000, fixedRate = 60_000) public void warmUpCache() { cacheService.preload(); } } # Cron syntax: second minute hour day-of-month month day-of-week # "0 */5 * * * *" = every 5 minutes # "0 0 * * * *" = every hour on the hour # "0 0 9-17 * * MON-FRI" = every hour 9am-5pm Mon-Fri # Spring Boot 4: multiple TaskDecorator beans supported # (propagates MDC logging context and security context to scheduled threads) @Bean public TaskDecorator mdcTaskDecorator() { return runnable -> () -> { MDC.put("task", "scheduled"); try { runnable.run(); } finally { MDC.clear(); } }; }
35. What is Spring AI and how does it integrate with Spring Boot 4?
Spring AI is a first-class Spring ecosystem project that reached 1.0 GA in May 2025 and is now on a 2.x development track. It provides a model-agnostic abstraction layer for integrating LLMs, vector stores, and AI pipelines into Spring Boot 4 applications.
| Concept | Description |
|---|---|
| ChatModel | Abstraction over LLM providers (OpenAI, Gemini, Anthropic, Mistral, Ollama, etc.) |
| EmbeddingModel | Generates vector embeddings from text |
| VectorStore | Auto-configured integration with 10+ vector databases (pgvector, Pinecone, Chroma, Weaviate, etc.) |
| ChatClient | Fluent API for building prompt chains, advisors, and tool calling |
| Tool calling | LLMs can invoke Spring beans as tools (@Tool annotation) |
| MCP support | Model Context Protocol server integration |
| RAG pipelines | QuestionAnswerAdvisor for retrieval-augmented generation |
// Spring AI: ChatClient in Spring Boot 4 @Service public class CustomerSupportService { private final ChatClient chatClient; public CustomerSupportService(ChatClient.Builder builder, VectorStore vectorStore) { this.chatClient = builder .defaultSystem("You are a helpful customer support agent.") .defaultAdvisors( // RAG: retrieve relevant documents before answering new QuestionAnswerAdvisor(vectorStore), new MessageChatMemoryAdvisor(new InMemoryChatMemory()) ) .build(); } public String answer(String question) { return chatClient.prompt() .user(question) .call() .content(); } } // Tool calling: LLM can call Spring beans as tools @Service public class OrderTools { @Tool(description = "Get the current status of an order by its ID") public String getOrderStatus(String orderId) { return orderService.findById(orderId).status().name(); } } # application.yml: Spring AI OpenAI configuration spring: ai: openai: api-key: ${OPENAI_API_KEY} chat: options: model: gpt-4o-mini temperature: 0.7
36. How does Spring Boot 4 handle logging configuration?
Spring Boot 4 auto-configures Logback as the default logging framework (with SLF4J as the facade). Log4j2 and JUL are also supported. Boot 4 enhances structured logging for production observability.
# Logging configuration in application.yml: logging: level: root: INFO # global default level com.example.service: DEBUG # package-specific level org.springframework.security: WARN # reduce security noise org.hibernate.SQL: DEBUG # log SQL statements org.hibernate.orm.jdbc.bind: TRACE # log SQL parameters file: name: /var/log/order-service/app.log logback: rollingpolicy: max-file-size: 100MB max-history: 30 pattern: console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n" file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%X{traceId},%X{spanId}] - %msg%n" // Structured logging (JSON) for production / log aggregation: logging: structured: format: console: ecs # Elastic Common Schema file: logstash # Logstash JSON format # Output: machine-readable JSON logs for ELK stack // Using SLF4J in application code: @Service public class OrderService { private static final Logger log = LoggerFactory.getLogger(OrderService.class); // Or with Lombok: // @Slf4j // public class OrderService { public Order createOrder(CreateOrderRequest req) { log.debug("Creating order for product: {}", req.productId()); Order order = repo.save(new Order(req)); log.info("Order created successfully: id={}, product={}", order.id(), order.productId()); return order; } } // Change log level at runtime via Actuator: // POST /actuator/loggers/com.example.service // Body: {"configuredLevel": "DEBUG"}
37. What is Spring Boot 4 migration from Boot 3: complete checklist and common pitfalls?
Migrating to Spring Boot 4 requires addressing several categories of change. Here is a practical checklist with the most common pitfalls teams encounter.
| Category | Action | Common pitfall |
|---|---|---|
| Pre-migration | Upgrade to Spring Boot 3.5 and fix ALL deprecation warnings | Deprecations from 3.x are hard-deleted in 4.0 -- skipping this step guarantees breakage |
| Java | Confirm Java 17 minimum; recommend Java 21 for virtual threads | Using Java 11 will fail at startup |
| Kotlin | Upgrade to Kotlin 2.2 if using Kotlin | Kotlin null-safety compilation failures from JSpecify annotations |
| Dependencies | Update BOM version to 4.x.x; rename starters (webmvc, webflux) | Old spring-boot-starter-web still resolves but triggers a deprecation |
| Jackson | Migrate from com.fasterxml.jackson to tools.jackson; audit JSON tests | Silent date format and property ordering changes break JSON contracts |
| Server | Remove Undertow dependency; migrate to Tomcat 11 or Jetty 12.1 | Application fails to start if Undertow is still on classpath |
| Persistence | Review Hibernate 7.1 detached entity behaviour | Lenient reassociation silently broken; explicit merge required |
| Security | Review Spring Security 7 CSRF defaults | REST API tests fail silently after CSRF default change |
| javax.* imports | Run OpenRewrite Migrate_To_Jakarta_EE_10 recipe | Any remaining javax.* imports cause ClassNotFoundException at runtime |
| Testing | Replace JUnit 4 tests (removed); update MockMvc to RestTestClient | JUnit 4 is gone; vintage engine not included in spring-boot-starter-test |
# Maven: run OpenRewrite migration recipes mvn rewrite:run \ -Drewrite.activeRecipes=\ org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_5,\ org.openrewrite.java.spring.boot4.UpgradeSpringBoot_4_0,\ org.openrewrite.java.jackson.UpgradeJackson_2_3 # What OpenRewrite handles automatically: # - Package renames (javax -> jakarta) # - Jackson package renames (com.fasterxml -> tools.jackson) # - Deprecated API replacements # - Configuration property renames # # What OpenRewrite CANNOT handle: # - Jackson date serialisation default change # - JSON property ordering default change # - Hibernate detached entity behaviour # - Spring Security CSRF default changes # (these require manual review of integration tests)
38. What is Spring Boot DevTools and how does it improve development productivity?
Spring Boot DevTools accelerates development by providing automatic application restart, LiveReload, and property overrides for development environments. It is available only on the development classpath and has no effect in production.
<!-- Add DevTools (automatically excluded from production builds): --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> <!-- excluded from fat JAR / image --> </dependency> # What DevTools provides: # 1. Automatic restart: detects classpath changes and restarts the app # (much faster than cold start -- uses two ClassLoaders) # 2. LiveReload: refreshes browser on static resource changes # 3. Property overrides for development: # - Disables caching (Thymeleaf, Jackson) # - Enables H2 console # - Sets DEBUG logging for web and SQL # application.yml (DevTools default overrides in dev): # spring.thymeleaf.cache=false (disabled by DevTools) # spring.jackson.serialization.indent_output=true # logging.level.web=DEBUG # spring.h2.console.enabled=true # DevTools remote restart (for Docker/remote dev): spring: devtools: remote: secret: ${DEVTOOLS_SECRET} # Enable in IDE to push class changes to running remote container # Disable specific DevTools features: spring: devtools: restart: enabled: false # disable auto-restart livereload: enabled: false # disable LiveReload server additional-exclude: "static/**,public/**" # dont restart on these changes
39. How does Spring Boot 4 support containerised deployments with Docker?
Spring Boot 4 has first-class container support via Cloud Native Buildpacks (CNB), Layered JARs, and GraalVM native image Docker images. The Spring Boot Maven/Gradle plugins can produce optimised Docker images without a Dockerfile.
# Option 1: Buildpacks (recommended -- no Dockerfile needed) # Maven: ./mvnw spring-boot:build-image # Produces: docker.io/library/order-service:1.0.0 # With custom image name and registry: ./mvnw spring-boot:build-image \ -Dspring-boot.build-image.imageName=gcr.io/my-project/order-service:1.0.0 # Native image with Buildpacks: ./mvnw spring-boot:build-image -Pnative # Option 2: Traditional Dockerfile with Layered JAR # First, enable layered JAR: <configuration> <layers> <enabled>true</enabled> </layers> </configuration> # Dockerfile (multi-stage, layered JAR): FROM eclipse-temurin:17-jre AS builder WORKDIR /app COPY target/*.jar app.jar RUN java -Djarmode=tools -jar app.jar extract --layers --launcher FROM eclipse-temurin:17-jre WORKDIR /app # Copy layers in order of least-to-most frequently changed: COPY --from=builder /app/extracted/dependencies ./ COPY --from=builder /app/extracted/spring-boot-loader ./ COPY --from=builder /app/extracted/snapshot-dependencies ./ COPY --from=builder /app/extracted/application ./ EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar"] # Docker Compose for local development: # compose.yml (Spring Boot 4 auto-detects compose.yml): services: postgres: image: postgres:17 environment: POSTGRES_DB: orders POSTGRES_USER: dev POSTGRES_PASSWORD: dev ports: ["5432:5432"] redis: image: redis:8 ports: ["6379:6379"] # spring.docker.compose.enabled=true (default) auto-starts compose services
40. What is the Spring Boot 4 vs Spring Boot 3 comparison and what are the key takeaways?
Understanding the differences between Spring Boot 3 and Spring Boot 4 is the most common interview question for teams evaluating or migrating to Boot 4. Here is a structured comparison of every major dimension.
| Dimension | Spring Boot 3.x | Spring Boot 4.x |
|---|---|---|
| Release date | November 2022 (3.0), ongoing | November 20, 2025 (4.0) |
| Spring Framework | Spring Framework 6 | Spring Framework 7 |
| Jakarta EE | Jakarta EE 9 / 10 | Jakarta EE 11 (Servlet 6.1) |
| Minimum Java | Java 17 | Java 17 (same) |
| Best Java | Java 21 | Java 25 |
| Kotlin minimum | Kotlin 1.7+ | Kotlin 2.2 |
| Modularisation | Monolithic spring-boot-autoconfigure JAR | 70+ focused technology modules |
| API versioning | DIY (custom paths, filters, conditions) | Native: @GetMapping(version="2.0") |
| JSON library | Jackson 2 (com.fasterxml.jackson) | Jackson 3 (tools.jackson) |
| Null safety | spring.lang annotations | JSpecify portfolio-wide |
| Embedded servers | Tomcat 10, Jetty 11, Undertow 2 | Tomcat 11, Jetty 12.1 (Undertow REMOVED) |
| Retry/resilience | External spring-retry + @EnableRetry | Built-in @Retryable + @ConcurrencyLimit |
| HTTP clients | RestTemplate (legacy), WebClient | RestClient, HTTP Service Clients (@HttpExchange) |
| Test client | MockMvc or WebTestClient | RestTestClient (unified) |
| JUnit 4 | Supported via Vintage engine | REMOVED |
| Spring Security | Spring Security 6 | Spring Security 7 (MFA built-in) |
| Kafka | Spring Kafka 3.x | Spring Kafka 4.0 (Share Consumer) |
| GraalVM | native-image 23+ | native-image 25+ |
| Spring AI | Not included | Production-ready 1.x; heading to 2.x |
