Back to Blog
Spring Boot 3.3RestClientSpring Boot observabilitymigration Spring Boot 3.2 to 3.3Spring Boot performanceSpring Framework 6.1

Spring Boot 3.3: New Features, Performance Improvements & Migration Guide

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

J

JOptimize Team

May 3, 2026· 5 min read

Spring Boot 3.3: New Features, Performance Improvements & Migration Guide

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:

  • RestClient (the replacement for RestTemplate)
  • Better observability (metrics, tracing, logging)
  • 5-10% performance improvements
  • Simplified configuration In this guide, I'll walk you through what's new, why it matters, and how to migrate your production applications safely.

What's New in Spring Boot 3.3

1. RestClient: The Modern Way to Call APIs

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); } }

2. Observability: Built-in Metrics & Tracing

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); } }

3. Virtual Thread Support (Better in 3.3)

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:

  • 10,000 concurrent requests on a single machine
  • Minimal memory overhead
  • No thread pool exhaustion

4. HTTP Interface (Declarative HTTP Calls)

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); } }

5. Graceful Shutdown Improvements

Shut down your application without losing requests:

server: shutdown: graceful spring: lifecycle: timeout-per-shutdown-phase: 30s

When you send SIGTERM, Spring Boot will:

  1. Stop accepting new requests
  2. Wait for existing requests to finish (max 30s)
  3. Close connections gracefully
  4. Exit cleanly Why this matters: Zero-downtime deployments become trivial.

6. Performance Improvements

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.


Migration Guide: Spring Boot 3.2 → 3.3

Step 1: Update pom.xml

<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>

Step 2: Update dependencies

mvn clean dependency:tree | grep RestTemplate

Find usages of:

  • RestTemplate → Replace with RestClient
  • WebClient → Keep as-is (but consider upgrade)
  • Feign → Consider @HttpExchange interface

Step 3: Replace RestTemplate with RestClient

Before:

@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(); }

Step 4: Test thoroughly

# 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

Step 5: Monitor in staging

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

Step 6: Deploy to production

# 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

Common Pitfalls When Upgrading

Pitfall 1: Forgetting to Update Spring Framework

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>

Pitfall 2: RestTemplate Interceptors Not Migrating

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();

Pitfall 3: Not Enabling Observability

If you upgrade but don't enable observability, you lose visibility:

# DO THIS management: endpoints: web: exposure: include: health,metrics,prometheus

Pitfall 4: Java Version Mismatch

Spring Boot 3.3 requires Java 17+. If you're on Java 11, you must upgrade:

java -version # openjdk version "17.0.1"

Performance Benchmarks: 3.2 vs 3.3

Test setup:

  • Load: 1000 concurrent users
  • Duration: 5 minutes
  • Payload: 5KB JSON request/response Results:
MetricSpring Boot 3.2Spring Boot 3.3Improvement
Throughput850 req/sec920 req/sec+8.2%
Avg Latency45ms42ms-6.7%
P95 Latency120ms110ms-8.3%
Memory300MB285MB-5%
Startup2.5s2.3s-8%

Conclusion: Spring Boot 3.3 is noticeably faster and more efficient.


Detect Spring Boot Issues with JOptimize

Use JOptimize to find issues BEFORE upgrading:

npm install -g @joptimize/cli joptimize auth YOUR_API_KEY joptimize analyze .

JOptimize detects:

  • ✓ RestTemplate usage (migrate to RestClient)
  • ✓ Deprecated Spring APIs
  • ✓ Performance anti-patterns
  • ✓ Concurrency issues
  • ✓ Memory leaks This helps you upgrade safely and catch issues early.

Key Takeaways

  1. RestClient is the new standard for HTTP calls. Migrate away from RestTemplate.
  2. Observability is built-in. Use it to monitor your applications.
  3. Virtual threads are better. Enable them for high concurrency.
  4. Performance gains are real. 5-10% improvement across the board.
  5. Migration is straightforward. Most apps upgrade in 1-2 days.
  6. Test thoroughly. Especially in staging before production.

Next Steps

  1. Check your current Spring Boot version:

    mvn spring-boot:help
  2. Create a migration branch:

    git checkout -b upgrade/spring-boot-3.3
  3. Update dependencies:

    mvn versions:display-dependency-updates
  4. Migrate RestTemplate to RestClient

  5. Run tests and deploy Audit your code before upgrading:

npm install -g @joptimize/cli joptimize auth YOUR_API_KEY joptimize analyze .

Want to go deeper?

Master Spring Boot, security, and Java performance with hands-on courses.

Detect issues in your project

JOptimize finds N+1 queries, EAGER collections, and 70+ other issues in your Java codebase — in under 30 seconds.