Spring Boot 4.0 with JDK 21 and Virtual Threads: Boosting Performance
Spring Boot 4.0 introduces a milestone upgrade that requires JDK 21, switches to jakarta.* packages, offers first‑class virtual‑thread support, a built‑in declarative HTTP client, native retry annotations, API versioning, and modular auto‑configuration, all aimed at higher cloud‑native performance.
Spring Boot 4.0 is a milestone release that pushes Java development toward cloud‑native, null‑safety, and high performance.
1. Environment Upgrade: Embrace JDK 21 and Jakarta EE 11
JDK Requirement : JDK 21 or higher (JDK 21 LTS recommended). Running on JDK 17 is possible but virtual‑thread features need JDK 21+.
Namespace Change : The old javax.* packages are fully replaced by jakarta.*. For example, import javax.servlet.http.HttpServletRequest; must become import jakarta.servlet.http.HttpServletRequest;.
Servlet Container : Requires Tomcat 11+ or Jetty 12.1+.
2. Performance Core: Virtual Threads in Practice
Spring Boot 4.0 provides first‑class support for virtual threads. When enabled, each web request is handled by a lightweight virtual thread, allowing thousands of concurrent connections.
Configuration (application.yml) :
spring:
threads:
virtual:
enabled: true # Enables virtual threads for Tomcat thread pool
task:
scheduling:
virtual: true # Enables virtual threads for scheduled tasksPatch Note : Virtual threads are not fully compatible with the synchronized lock. If performance does not improve, replace synchronized with ReentrantLock:
// ❌ Avoid synchronized
public synchronized void oldMethod() { ... }
// ✅ Use ReentrantLock
private final Lock lock = new ReentrantLock();
public void newMethod() {
lock.lock();
try {
// business logic
} finally {
lock.unlock();
}
}3. New Feature: Declarative HTTP Client
The traditional RestTemplate boilerplate is replaced by a built‑in declarative client using the @HttpExchange annotation, similar to Spring Cloud OpenFeign.
Example Interface (fetching posts from JSONPlaceholder):
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.HttpExchange;
import java.util.List;
@HttpExchange(url = "https://jsonplaceholder.typicode.com")
public interface JsonPlaceholderService {
@GetExchange("/posts")
List<Post> getPosts();
@GetExchange("/posts/{id}")
Post getPostById(@PathVariable Long id);
}Configuration (Java) :
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.service.invoker.HttpServiceProxyFactory;
@Configuration
public class AppConfig {
@Bean
public JsonPlaceholderService jsonPlaceholderService() {
WebClient client = WebClient.builder()
.baseUrl("https://jsonplaceholder.typicode.com")
.build();
HttpServiceProxyFactory factory = HttpServiceProxyFactory
.builder(WebClientAdapter.forClient(client))
.build();
return factory.createClient(JsonPlaceholderService.class);
}
}Usage in a service:
@Service
public class MyService {
private final JsonPlaceholderService apiService;
public MyService(JsonPlaceholderService apiService) {
this.apiService = apiService;
}
public void run() {
Post post = apiService.getPostById(1L);
System.out.println(post.getTitle());
}
}4. Resilience Boost: Built‑in Retry Mechanism
Previously, retry required the spring-retry dependency. Spring Boot 4.0 now includes the @Retryable annotation in spring-core.
Retry Example :
import org.springframework.context.annotation.Configuration;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.retry.annotation.Retryable;
@Configuration
@EnableRetry
public class ResilientConfig { /* ... */ }
@Service
public class PaymentService {
@Retryable(maxAttempts = 3, backoff = @Backoff(delay = 1000, multiplier = 2))
public void callRemoteApi() {
System.out.println("Attempting remote API call...");
throw new RuntimeException("Network timeout");
}
}5. Essential Skill: API Version Control
Spring Boot 4.0 natively supports API versioning, allowing version differentiation via request headers, parameters, or media types without custom URL changes.
Controller Example :
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/user")
public class UserController {
@RequestMapping(value = "/info", version = "v1")
public String getInfoV1() {
return "{\"name\": \"old version user\"}";
}
@RequestMapping(value = "/info", version = "v2")
public String getInfoV2() {
return "{\"name\": \"new version user\", \"email\": \"[email protected]\"}";
}
}Version strategy can be defined in application.yml:
spring:
mvc:
apiversion:
enabled: true
# header: API-VERSION=v2
# or 'parameter' / 'media-type'
default-version: v16. Modular Auto‑Configuration
Earlier auto‑configuration was heavyweight and slowed GraalVM native image generation. Spring Boot 4.0 splits auto‑configuration into fine‑grained modules, speeding startup and simplifying custom starter development.
Impact : No code‑style changes for developers, but project startup becomes faster and custom starters are clearer.
7. Starter Project Setup (Pitfalls Guide)
When creating a new Spring Boot 4.0 project, the pom.xml must follow these rules:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- Parent must be Spring Boot 4.0.0 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.0.0-M1</version> <!-- use M1 or SNAPSHOT before final release -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-4-demo</name>
<properties>
<java.version>21</java.version> <!-- must be 21+ -->
</properties>
<dependencies>
<!-- Web starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Observability starter (new in 4.0) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-opentelemetry</artifactId>
</dependency>
<!-- Test starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>4.0.0-M1</version>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots><enabled>false</enabled></snapshots>
</repository>
</repositories>
</project>Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Senior Xiao Ying
Dedicated to sharing Java backend technical experience and original tutorials, offering career transition advice and resume editing. Recognized as a rising star in CSDN's Java backend community and ranked Top 3 in the 2022 New Star Program for Java backend.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
