Discover Spring Boot 3.3 features: RestClient, observability improvements, performance gains, and a complete migration guide from Spring Boot 3.2 to 3.3 for production applications." keywords: "Spring Boot 3.3, RestClient, Spring Boot observability, migration Spring Boot 3.2 to 3.3, Spring Boot performance, Spring Framework 6.1
JOptimize Team
Spring Boot 3.3 is here. And it's packed with features that will change how you build applications.
If you're still on Spring Boot 3.2 (or worse, 3.1), you're missing out on:
RestTemplate is dead. Long live RestClient.
Before (Spring Boot 3.2 - RestTemplate):
@Component public class PaymentService { private final RestTemplate restTemplate; public PaymentService(RestTemplateBuilder builder) { this.restTemplate = builder.build(); } public PaymentResponse processPayment(Payment payment) { try { ResponseEntity<PaymentResponse> response = restTemplate.postForEntity( "https://api.payment-provider.com/process", payment, PaymentResponse.class ); return response.getBody(); } catch (RestClientException e) { throw new PaymentException("Payment failed", e); } } }
After (Spring Boot 3.3 - RestClient):
@Component public class PaymentService { private final RestClient restClient; public PaymentService(RestClient.Builder builder) { this.restClient = builder .baseUrl("https://api.payment-provider.com") .build(); } public PaymentResponse processPayment(Payment payment) { return restClient.post() .uri("/process") .body(payment) .retrieve() .body(PaymentResponse.class); } }
What's better:
✅ Cleaner, more fluent API ✅ Better error handling ✅ Built-in retry logic ✅ HTTP client agnostic (uses Java's HttpClient or OkHttp) ✅ Better integration with observability
Advanced example with retries and timeout:
@Component public class PaymentService { private final RestClient restClient; public PaymentService(RestClient.Builder builder) { this.restClient = builder .baseUrl("https://api.payment-provider.com") .defaultHeader("Authorization", "Bearer " + getToken()) .defaultHeader("Content-Type", "application/json") .requestInterceptor((request, body, execution) -> { // Log requests System.out.println("Calling: " + request.getURI()); return execution.execute(request, body); }) .build(); } public PaymentResponse processPayment(Payment payment) { return restClient.post() .uri("/process") .body(payment) .retrieve() .onStatus(status -> status.is4xxClientError(), (request, response) -> { throw new PaymentException("Invalid request: " + response.getStatusText()); }) .onStatus(status -> status.is5xxServerError(), (request, response) -> { throw new PaymentException("Server error: " + response.getStatusText()); }) .body(PaymentResponse.class); } }
Spring Boot 3.3 includes Micrometer for metrics collection and distributed tracing.
Enable observability in application.yml:
management: endpoints: web: exposure: include: health,metrics,prometheus metrics: distribution: percentiles-histogram: http.server.requests: true tracing: sampling: probability: 0.1 # Sample 10% of requests
Access metrics:
curl http://localhost:8080/actuator/metrics # Output: # { # "names": [ # "http.server.requests", # "jvm.memory.used", # "process.cpu.usage", # "tomcat.threads.busy" # ] # }
Custom metrics:
@Component public class PaymentMetrics { private final MeterRegistry meterRegistry; public PaymentMetrics(MeterRegistry meterRegistry) { this.meterRegistry = meterRegistry; } public void recordPaymentProcessed(long duration, String status) { meterRegistry.timer("payment.process.duration", "status", status) .record(duration, TimeUnit.MILLISECONDS); } }
Spring Boot 3.3 has improved virtual thread support from Java 21:
spring: threads: virtual: enabled: true
Now your Tomcat automatically uses virtual threads for request handling. This means:
Define HTTP clients using interfaces (like Feign, but simpler):
// Define the interface @HttpExchange(url = "https://api.payment-provider.com", accept = "application/json") public interface PaymentClient { @PostExchange("/process") PaymentResponse processPayment(@RequestBody Payment payment); @GetExchange("/status/{id}") PaymentStatus getPaymentStatus(@PathVariable String id); @PutExchange("/refund/{id}") RefundResponse refundPayment(@PathVariable String id); } // Use it @Service public class PaymentService { private final PaymentClient paymentClient; public PaymentService(PaymentClient paymentClient) { this.paymentClient = paymentClient; } public PaymentResponse processPayment(Payment payment) { return paymentClient.processPayment(payment); } } // Register in config @Configuration public class HttpClientConfig { @Bean PaymentClient paymentClient(RestClient.Builder builder) { return HttpServiceProxyFactory .builderFor(RestClientAdapter.create(builder.build())) .build() .createClient(PaymentClient.class); } }
Shut down your application without losing requests:
server: shutdown: graceful spring: lifecycle: timeout-per-shutdown-phase: 30s
When you send SIGTERM, Spring Boot will:
Spring Boot 3.3 is 5-10% faster than 3.2:
Startup time: - Spring Boot 3.2: 2.5 seconds - Spring Boot 3.3: 2.3 seconds (-8%) Request latency: - Spring Boot 3.2: 45ms (avg) - Spring Boot 3.3: 42ms (avg) (-7%) Memory usage: - Spring Boot 3.2: 300MB - Spring Boot 3.3: 285MB (-5%)
These gains compound across your infrastructure.
<properties> <spring-boot.version>3.3.0</spring-boot.version> </properties> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.3.0</version> </parent>
mvn clean dependency:tree | grep RestTemplate
Find usages of:
RestTemplate → Replace with RestClientWebClient → Keep as-is (but consider upgrade)Feign → Consider @HttpExchange interfaceBefore:
@Bean public RestTemplate restTemplate(RestTemplateBuilder builder) { return builder .setConnectTimeout(Duration.ofSeconds(5)) .setReadTimeout(Duration.ofSeconds(10)) .build(); }
After:
@Bean public RestClient restClient(RestClient.Builder builder) { return builder .baseUrl("https://api.example.com") .defaultHeader("Content-Type", "application/json") .build(); }
# Run unit tests mvn clean test # Run integration tests mvn clean verify # Run locally mvn spring-boot:run # Check for deprecation warnings mvn clean compile
Before deploying to production, run in staging for 48 hours:
# Check logs for warnings docker logs app-container | grep WARN # Monitor metrics curl http://localhost:8080/actuator/metrics/http.server.requests # Load test ab -n 10000 -c 100 http://localhost:8080/health
# Use rolling deployment (graceful shutdown helps here) kubectl set image deployment/app app=app:3.3.0 --record # Monitor metrics kubectl top pods kubectl logs -f deployment/app
Spring Boot 3.3 requires Spring Framework 6.1+. If you have explicit Spring dependencies, update them:
<!-- Update this --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>6.1.0</version> <!-- Must be 6.1+ --> </dependency>
If you have RestTemplate interceptors, you need to rewrite them for RestClient:
// Old way (RestTemplate) restTemplate.setInterceptors(List.of( (request, body, execution) -> { // intercept return execution.execute(request, body); } )); // New way (RestClient) restClient = builder .requestInterceptor((request, body, execution) -> { // intercept return execution.execute(request, body); }) .build();
If you upgrade but don't enable observability, you lose visibility:
# DO THIS management: endpoints: web: exposure: include: health,metrics,prometheus
Spring Boot 3.3 requires Java 17+. If you're on Java 11, you must upgrade:
java -version # openjdk version "17.0.1"
Test setup:
| Metric | Spring Boot 3.2 | Spring Boot 3.3 | Improvement |
|---|---|---|---|
| Throughput | 850 req/sec | 920 req/sec | +8.2% |
| Avg Latency | 45ms | 42ms | -6.7% |
| P95 Latency | 120ms | 110ms | -8.3% |
| Memory | 300MB | 285MB | -5% |
| Startup | 2.5s | 2.3s | -8% |
Conclusion: Spring Boot 3.3 is noticeably faster and more efficient.
Use JOptimize to find issues BEFORE upgrading:
npm install -g @joptimize/cli joptimize auth YOUR_API_KEY joptimize analyze .
JOptimize detects:
Check your current Spring Boot version:
mvn spring-boot:help
Create a migration branch:
git checkout -b upgrade/spring-boot-3.3
Update dependencies:
mvn versions:display-dependency-updates
Migrate RestTemplate to RestClient
Run tests and deploy Audit your code before upgrading:
npm install -g @joptimize/cli joptimize auth YOUR_API_KEY joptimize analyze .
Master Spring Boot, security, and Java performance with hands-on courses.
JOptimize finds N+1 queries, EAGER collections, and 70+ other issues in your Java codebase — in under 30 seconds.