Spring Boot 4.0 주요 변경사항과 새로운 기능 Spring Boot 4.0 Key Changes and New Features
Spring Boot 4.0 Key Changes and New Features
Spring Boot 4.0 was released in November 2025, built on Spring Framework 7.0. This major release brings significant improvements including first-class API versioning, enhanced Kotlin support, modular auto-configuration, and full Jakarta EE 11 alignment. This post covers the key changes, new features, and migration considerations.
1. Minimum Requirements
Spring Boot 4.0 raises the baseline requirements:
| Component | Minimum Version | Recommended |
|---|---|---|
| Java | 17 (LTS) | 25 |
| Kotlin | 2.2 | 2.2.20+ |
| Gradle | 8.14+ | 9.x |
| Maven | 3.9+ | - |
Key points:
- Java 17 remains the minimum to maintain broad industry compatibility
- Java 25 (September 2025) is fully supported with all its features
- Kotlin 2.2 baseline with K2 compiler support
- GraalVM 24 alignment for native image builds
2. Major Dependency Upgrades
Spring Portfolio
| Dependency | Version |
|---|---|
| Spring Framework | 7.0 |
| Spring Security | 7.0 |
| Spring Data | 2025.1 |
| Spring Integration | 7.0 |
| Spring Session | 4.0 |
| Spring Batch | 6.0 |
| Spring AMQP | 4.0 |
| Spring for Apache Kafka | 4.0 |
Third-Party Libraries
| Library | Version |
|---|---|
| Hibernate | 7.1 |
| Jackson | 3.0 |
| Tomcat | 11.0 |
| H2 Database | 2.4 |
| MongoDB Driver | 5.6.0 |
Jakarta EE 11 Alignment
Spring Boot 4.0 fully adopts Jakarta EE 11:
- Jakarta Servlet 6.1 - Modern web API support
- Jakarta Persistence 3.2 - Enhanced JPA features
- Jakarta Validation 3.1 - Updated Bean Validation
- Jakarta WebSocket 2.2 - WebSocket improvements
Note: Undertow is not supported because it is not yet compatible with Servlet 6.1. Use Tomcat or Jetty instead.
3. New Features
3.1 HTTP Service Clients
Spring Boot 4.0 provides first-class support for declarative HTTP clients. Define an interface, annotate it, and Spring generates the implementation at runtime.
@HttpExchange("/api/users")
public interface UserClient {
@GetExchange("/{id}")
User getUser(@PathVariable("id") Long id);
@GetExchange
List<User> getAllUsers();
@PostExchange
User createUser(@RequestBody User user);
@DeleteExchange("/{id}")
void deleteUser(@PathVariable("id") Long id);
}
Configure the client using @ImportHttpServices:
@Configuration
@ImportHttpServices(group = "user-service", types = UserClient.class)
public class HttpClientConfig {
@Bean
public RestClientHttpServiceGroupConfigurer configurer() {
return groups -> groups
.filterByName("user-service")
.forEachClient((group, builder) -> {
builder.baseUrl("https://api.example.com");
builder.defaultHeader("Accept", "application/json");
});
}
}
Use the client by injecting it:
@Service
@RequiredArgsConstructor
public class UserService {
private final UserClient userClient;
public User findUser(Long id) {
return userClient.getUser(id);
}
}
Benefits:
- No additional dependencies (part of
spring-web) - Virtual Threads ready for efficient blocking operations
- Replaces the need for OpenFeign in many cases
3.2 API Versioning
Spring Boot 4.0 introduces built-in API versioning support. Configure via application.yml:
spring:
mvc:
apiversion:
supported: 1.0, 2.0, 3.0
default: 1.0
use:
# Choose ONE strategy:
# Path segment: /api/v1/users
path-segment: 1
# OR Header: X-API-Version: 1.0
# header: X-API-Version
# OR Query param: ?version=1.0
# query-parameter: version
Use versioning in controllers:
@RestController
@RequestMapping("/api/users")
public class UserController {
// Available in v1.0 only
@GetExchange(value = "/{id}", version = "1.0")
public UserV1Response getUserV1(@PathVariable Long id) {
return userService.getUserV1(id);
}
// Available in v2.0 and later (baseline version)
@GetExchange(value = "/{id}", version = "2.0+")
public UserV2Response getUserV2(@PathVariable Long id) {
return userService.getUserV2(id);
}
}
For programmatic configuration:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureApiVersioning(ApiVersionConfigurer configurer) {
configurer
.useRequestHeader("API-Version")
.supportedVersions("1.0", "2.0", "3.0")
.defaultVersion("2.0");
}
}
3.3 OpenTelemetry Starter
A new spring-boot-starter-opentelemetry module simplifies observability setup:
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-opentelemetry'
}
management:
opentelemetry:
tracing:
export:
otlp:
enabled: true
endpoint: http://localhost:4317
logging:
export:
otlp:
enabled: true
3.4 JmsClient Support
Spring Boot 4.0 introduces JmsClient as a modern alternative to JmsTemplate:
@Service
@RequiredArgsConstructor
public class MessageService {
private final JmsClient jmsClient;
public void sendMessage(String destination, OrderEvent event) {
jmsClient.send(destination, event);
}
public OrderEvent receiveMessage(String destination) {
return jmsClient.receive(destination, OrderEvent.class);
}
}
3.5 Virtual Threading Support
Virtual threads are fully supported when running on Java 21+:
spring:
threads:
virtual:
enabled: true
This enables efficient handling of blocking I/O operations without the complexity of reactive programming.
4. Kotlin Support Improvements
4.1 Kotlin 2.2 Baseline with K2 Compiler
Spring Boot 4.0 requires Kotlin 2.2+, which includes:
- K2 compiler for faster compilation
- Improved code analysis
- Better IDE support
4.2 JSpecify Null-Safety
JSpecify annotations provide standardized null-safety across the Spring portfolio. Kotlin 2.x automatically translates these to Kotlin nullability:
// Spring APIs now have proper Kotlin nullability
// No more platform types!
val user: User = userRepository.findById(id) // Returns User, not User!
val maybeUser: User? = userRepository.findByEmail(email) // Nullable
4.3 Coroutines Context Propagation
Automatic context propagation for observability in coroutines:
spring:
reactor:
context-propagation: auto
@Service
class UserService(private val userRepository: UserRepository) {
// Tracing context automatically propagated
suspend fun findUser(id: Long): User {
return userRepository.findById(id)
}
}
4.4 Kotlin Serialization Module
New spring-boot-starter-kotlin-serialization for kotlinx.serialization support:
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-kotlin-serialization'
}
@Serializable
data class User(
val id: Long,
val name: String,
val email: String
)
4.5 BeanRegistrarDsl
Programmatic bean registration with Kotlin DSL:
class MyBeanRegistrar : BeanRegistrarDsl({
// Simple registration
registerBean<UserRepository>()
// Registration with options
registerBean(
name = "customService",
prototype = true,
lazyInit = true,
description = "Custom service bean"
) {
CustomService(ref<UserRepository>())
}
// Profile-specific registration
profile("production") {
registerBean { ProductionDataSource() }
}
profile("development") {
registerBean { DevelopmentDataSource() }
}
})
Import in configuration:
@Configuration
@Import(MyBeanRegistrar::class)
class AppConfig
5. Configuration Changes
Renamed Properties
| Old Property | New Property |
|---|---|
management.tracing.enabled |
management.tracing.export.enabled |
spring.dao.exceptiontranslation.enabled |
spring.persistence.exceptiontranslation.enabled |
New Properties
| Property | Default | Description |
|---|---|---|
logging.console.enabled |
true |
Enable/disable console logging |
management.tracing.export.enabled |
true |
Enable tracing export |
spring.threads.virtual.enabled |
false |
Enable virtual threads |
Example Configuration
spring:
threads:
virtual:
enabled: true
persistence:
exceptiontranslation:
enabled: true
management:
tracing:
export:
enabled: true
logging:
console:
enabled: true
level:
root: INFO
com.example: DEBUG
6. Code Examples
Complete HTTP Service Client Example
// Define the interface
@HttpExchange("/api/v1/products")
public interface ProductClient {
@GetExchange
List<Product> findAll();
@GetExchange("/{id}")
Product findById(@PathVariable Long id);
@GetExchange("/search")
List<Product> search(@RequestParam String name,
@RequestParam(required = false) String category);
@PostExchange
Product create(@RequestBody CreateProductRequest request);
@PutExchange("/{id}")
Product update(@PathVariable Long id, @RequestBody UpdateProductRequest request);
@DeleteExchange("/{id}")
void delete(@PathVariable Long id);
}
// Configuration
@Configuration
@ImportHttpServices(group = "products", types = ProductClient.class)
public class ProductClientConfig {
@Bean
public RestClientHttpServiceGroupConfigurer productClientConfigurer() {
return groups -> groups
.filterByName("products")
.forEachClient((group, builder) -> {
builder.baseUrl("https://api.store.example.com");
builder.defaultHeader("Authorization", "Bearer " + getToken());
builder.defaultHeader("Content-Type", "application/json");
});
}
private String getToken() {
// Token retrieval logic
return "your-api-token";
}
}
// Usage
@RestController
@RequestMapping("/products")
@RequiredArgsConstructor
public class ProductController {
private final ProductClient productClient;
@GetMapping
public List<Product> getProducts() {
return productClient.findAll();
}
@GetMapping("/{id}")
public Product getProduct(@PathVariable Long id) {
return productClient.findById(id);
}
}
API Versioning Example
@RestController
@RequestMapping("/api/orders")
public class OrderController {
private final OrderService orderService;
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
// V1: Basic order response
@GetMapping(value = "/{id}", version = "1.0")
public OrderResponseV1 getOrderV1(@PathVariable Long id) {
Order order = orderService.findById(id);
return new OrderResponseV1(
order.getId(),
order.getStatus(),
order.getTotal()
);
}
// V2+: Extended response with items
@GetMapping(value = "/{id}", version = "2.0+")
public OrderResponseV2 getOrderV2(@PathVariable Long id) {
Order order = orderService.findById(id);
return new OrderResponseV2(
order.getId(),
order.getStatus(),
order.getTotal(),
order.getItems(),
order.getCreatedAt(),
order.getUpdatedAt()
);
}
}
// Response DTOs
public record OrderResponseV1(Long id, String status, BigDecimal total) {}
public record OrderResponseV2(
Long id,
String status,
BigDecimal total,
List<OrderItem> items,
Instant createdAt,
Instant updatedAt
) {}
Kotlin BeanRegistrar Example
// Bean classes
class UserRepository(private val dataSource: DataSource)
class UserService(
private val userRepository: UserRepository,
private val emailService: EmailService
)
class EmailService(private val mailSender: MailSender)
// Bean registrar with DSL
class ApplicationBeanRegistrar : BeanRegistrarDsl({
// Infrastructure beans
registerBean<DataSource>(infrastructure = true) {
HikariDataSource().apply {
jdbcUrl = env.getProperty("spring.datasource.url")
username = env.getProperty("spring.datasource.username")
password = env.getProperty("spring.datasource.password")
}
}
// Repository layer
registerBean { UserRepository(ref()) }
// Service layer with multiple dependencies
registerBean {
UserService(
userRepository = ref(),
emailService = ref()
)
}
// Conditional registration
profile("!test") {
registerBean<MailSender> { SmtpMailSender() }
registerBean { EmailService(ref()) }
}
profile("test") {
registerBean<MailSender> { MockMailSender() }
registerBean { EmailService(ref()) }
}
// Prototype scoped bean
registerBean(prototype = true) {
RequestScopedService(ref())
}
})
// Configuration
@Configuration
@Import(ApplicationBeanRegistrar::class)
class AppConfiguration
7. Migration Guide
Upgrade Path
- Upgrade to Spring Boot 3.5 first if you are on an earlier version
- Update Java version to at least 17 (25 recommended)
- Update Kotlin version to 2.2+ if using Kotlin
- Update Gradle to 8.14+ or 9.x
- Run the OpenRewrite migration recipe
plugins {
id 'org.openrewrite.rewrite' version '7.x.x'
}
dependencies {
rewrite 'org.openrewrite.recipe:rewrite-spring:latest.release'
}
// Run: gradle rewriteRun
Breaking Changes
- JUnit 4 Removed: Only JUnit Jupiter 6 is supported
- Undertow Not Supported: Use Tomcat or Jetty
- RestTemplate Deprecation Warning:
RestClientis the recommended alternative (RestTemplate will be removed in Spring Framework 8) - Hibernate 7.1 Changes: Detached entities cannot be reassociated with a persistence context
- Jackson 3.0: Some API changes from Jackson 2.x
Property Migration
Update these properties in your application.yml:
# Before (Spring Boot 3.x)
management:
tracing:
enabled: true
spring:
dao:
exceptiontranslation:
enabled: true
# After (Spring Boot 4.0)
management:
tracing:
export:
enabled: true
spring:
persistence:
exceptiontranslation:
enabled: true
Support Timeline
| Version | OSS Support Until |
|---|---|
| Spring Boot 4.0 | November 2026 |
| Spring Boot 3.5 | June 2026 |
| Spring Framework 7.0 | November 2026 |
| Spring Framework 6.2 | June 2026 |
8. Conclusion
Spring Boot 4.0 brings substantial improvements for modern Java and Kotlin development. The declarative HTTP clients, built-in API versioning, and enhanced Kotlin support simplify common development tasks. The full Jakarta EE 11 alignment and JSpecify null-safety annotations provide a solid foundation for building robust applications.
For production migrations, start by upgrading to Spring Boot 3.5, validate your application, then proceed to 4.0. Use the OpenRewrite recipes to automate property and API migrations. Take advantage of virtual threads on Java 21+ for improved scalability without reactive complexity.
댓글남기기