---
title: "Spring Boot mit Virtual Threads: Migration und Benchmarks"
description: "Virtual Threads in Spring Boot 3.2+ aktivieren: Konfiguration, Benchmarks vs. Platform Threads, WebFlux-Migration und Stolpersteine in der Praxis."
keywords: "Spring Boot, Virtual Threads, Java 21, Performance, Tomcat, WebFlux"
url: "https://encircle360.com/de/blog/spring-boot-virtual-threads-praxis"
language: "de"
type: "article"
date: "2024-08-19"
reading_time_minutes: 6
categories: ["Softwareentwicklung"]
image: "https://cms.encircle360.com/assets/e1dcadeb-bac3-461b-b49e-aebb50cea869?width=1200&quality=80&format=webp"
author:
  name: "Patrick Hütter"
  role: "Gründer & Software-Architekt"
  company: "encircle360 GmbH"
  url: "https://encircle360.com/de/blog?author=patrick-huetter"
  linkedin: "https://www.linkedin.com/in/patrickhuetter/"
publisher:
  name: "encircle360 GmbH"
  url: "https://encircle360.com"
  email: "hello@encircle360.com"
  phone: "+49 214 736999-80"
  address: "Petersbergstraße 72, 51375 Leverkusen, DE"
  linkedin: "https://www.linkedin.com/company/encircle360"
alternates:
  en: "https://encircle360.com/en/blog/spring-boot-virtual-threads-practice"
citation: "Patrick Hütter (encircle360 GmbH): \"Spring Boot mit Virtual Threads: Migration und Benchmarks\", 2024-08-19, https://encircle360.com/de/blog/spring-boot-virtual-threads-praxis"
---

# Spring Boot mit Virtual Threads: Migration und Benchmarks

_Von **Patrick Hütter**, Gründer & Software-Architekt bei [encircle360 GmbH](https://encircle360.com) · 19. August 2024 · 6 Min. Lesezeit · Kategorien: Softwareentwicklung_

> Mit einer einzigen Property macht Spring Boot 3.2+ Virtual Threads nutzbar -- wir zeigen die Migration, vergleichen Benchmarks mit Platform Threads und erklären, wann sich der Umstieg lohnt.

# Spring Boot mit Virtual Threads: Migration und Benchmarks

Als wir im September letzten Jahres [Java 21](https://encircle360.com/blog/java-21-lts-virtual-threads) vorgestellt haben, war die Vorfreude auf Virtual Threads in Spring Boot greifbar. Mit Spring Boot 3.2 (November 2023) und dem kürzlich erschienenen 3.3 (Mai 2024) ist diese Unterstützung nun Realität -- und nach mehreren Monaten produktivem Einsatz können wir bei encircle360 ein erstes Fazit ziehen.

Das Ergebnis vorweg: Für I/O-lastige Services hat sich der Umstieg gelohnt. Der Weg dorthin war überraschend einfach -- und die Stolpersteine lagen dort, wo wir sie nicht erwartet hatten.

## Eine Property, die alles verändert

Die Aktivierung von Virtual Threads in Spring Boot 3.2+ ist bemerkenswert unkompliziert:

```properties
# application.properties
spring.threads.virtual.enabled=true
```

Das war es. Eine Zeile. Aber was passiert unter der Haube?

Sobald diese Property gesetzt ist, ändert Spring Boot mehrere Infrastruktur-Komponenten gleichzeitig:

-   **Tomcat** verwendet für eingehende HTTP-Requests einen Virtual-Thread-per-Request-Executor statt des klassischen Thread-Pools
-   **Asynchrone Tasks** (`@Async`) werden auf Virtual Threads ausgeführt
-   **Scheduling** (`@Scheduled`) nutzt ebenfalls Virtual Threads
-   **Spring MVC Request Handling** profitiert automatisch, ohne Code-Änderungen

Der eingebettete Tomcat erzeugt also für jeden eingehenden Request einen neuen Virtual Thread -- keinen Pool, kein Tuning, keine `server.tomcat.threads.max`\-Konfiguration mehr. Die JVM übernimmt das Scheduling auf die darunterliegenden Carrier Threads.

## Voraussetzungen: Java 21 und Spring Boot 3.2+

Bevor man die Property setzt, müssen zwei Voraussetzungen erfüllt sein:

1.  **Java 21** als Runtime -- Virtual Threads sind seit [Java 21](https://encircle360.com/blog/java-21-lts-virtual-threads) ein finales Feature, kein Preview mehr
2.  **Spring Boot 3.2 oder neuer** -- wer noch auf der 3.0- oder 3.1-Linie ist, muss zuerst upgraden

Wer die [Spring-Boot-3-Migration](https://encircle360.com/blog/spring-boot-3-migration) bereits hinter sich hat und auf Java 21 läuft, kann Virtual Threads sofort aktivieren. Für alle anderen empfehlen wir, die Java-21-Migration als separaten Schritt durchzuführen -- nicht gemeinsam mit der Virtual-Thread-Aktivierung.

## Benchmark: Platform Threads vs. Virtual Threads

Wir haben einen typischen I/O-lastigen Service als Benchmark herangezogen: Ein REST-Controller, der eine Datenbank-Query und einen externen HTTP-Call ausführt. Zusammen erzeugen diese Aufrufe rund 100 ms Latenz pro Request.

```java
@RestController
@RequestMapping("/api/orders")
public class OrderController {

    private final OrderRepository orderRepository;
    private final RestClient restClient;

    public OrderController(OrderRepository orderRepository, RestClient.Builder builder) {
        this.orderRepository = orderRepository;
        this.restClient = builder.baseUrl("https://inventory.internal").build();
    }

    @GetMapping("/{id}")
    public OrderResponse getOrder(@PathVariable Long id) {
        // ~50 ms Datenbank-Query
        Order order = orderRepository.findById(id)
                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));

        // ~50 ms externer HTTP-Call
        InventoryStatus status = restClient.get()
                .uri("/api/stock/{sku}", order.getSku())
                .retrieve()
                .body(InventoryStatus.class);

        return new OrderResponse(order, status);
    }
}
```

Wir haben diesen Endpoint mit 2.000 gleichzeitigen Verbindungen über 60 Sekunden belastet. Die Ergebnisse:

| Metrik | Platform Threads (200 max) | Virtual Threads |
| --- | --- | --- |
| Durchsatz (req/s) | ~1.850 | ~14.200 |
| p50 Latenz | 108 ms | 105 ms |
| p99 Latenz | 1.240 ms | 142 ms |
| Fehlerrate | 2,3 % | 0,0 % |

Die p50-Latenz ist nahezu identisch -- ein einzelner Request wird nicht schneller. Aber der Durchsatz vervielfacht sich, weil Virtual Threads nicht im Pool aufgebraucht werden. Die p99-Latenz fällt drastisch, weil keine Requests mehr in der Queue auf einen freien Thread warten müssen. Und die Fehlerrate sinkt auf null, weil keine Connection-Timeouts durch Thread-Erschöpfung mehr auftreten.

## WebFlux durch blockierenden Code ersetzen

Der vielleicht spannendste Aspekt von Virtual Threads: Sie machen reaktiven Code für viele Anwendungsfälle überflüssig. In unserem [Virtual-Threads-Preview-Artikel](https://encircle360.com/blog/java-virtual-threads-preview) hatten wir diese Entwicklung bereits angedeutet. Jetzt haben wir sie in der Praxis umgesetzt.

Hier ein typischer WebFlux-Service, wie er vor der Migration aussah:

```java
// Vorher: WebFlux mit Reactor
@RestController
@RequestMapping("/api/users")
public class UserController {

    private final WebClient webClient;
    private final ReactiveUserRepository userRepository;

    @GetMapping("/{id}/profile")
    public Mono<UserProfile> getProfile(@PathVariable String id) {
        return userRepository.findById(id)
                .flatMap(user -> webClient.get()
                        .uri("/api/avatars/{hash}", user.getAvatarHash())
                        .retrieve()
                        .bodyToMono(AvatarInfo.class)
                        .map(avatar -> new UserProfile(user, avatar))
                )
                .switchIfEmpty(Mono.error(
                        new ResponseStatusException(HttpStatus.NOT_FOUND)));
    }
}
```

Und hier die migrierte Version mit Spring MVC und Virtual Threads:

```java
// Nachher: Spring MVC mit Virtual Threads
@RestController
@RequestMapping("/api/users")
public class UserController {

    private final RestClient restClient;
    private final UserRepository userRepository;

    @GetMapping("/{id}/profile")
    public UserProfile getProfile(@PathVariable String id) {
        User user = userRepository.findById(id)
                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));

        AvatarInfo avatar = restClient.get()
                .uri("/api/avatars/{hash}", user.getAvatarHash())
                .retrieve()
                .body(AvatarInfo.class);

        return new UserProfile(user, avatar);
    }
}
```

Der Unterschied in der Lesbarkeit ist offensichtlich. Kein `Mono`, kein `flatMap`, kein `switchIfEmpty`. Einfacher, sequentieller Code, der blockiert -- aber dank Virtual Threads trotzdem skaliert. Stack Traces sind wieder lesbar, Debugging funktioniert wie gewohnt, und jede JDBC-kompatible Datenbank funktioniert ohne reaktiven Treiber.

Wir haben in den vergangenen Monaten drei Services von WebFlux auf Spring MVC mit Virtual Threads migriert. Die Code-Komplexität sank messbar, die Performance blieb vergleichbar. Einzig bei echtem Streaming -- Server-Sent Events und WebSocket-Feeds -- behalten wir WebFlux bei, weil dort das reaktive Modell weiterhin Vorteile bietet.

## Stolpersteine in der Praxis

### synchronized und Pinning

Das Pinning-Problem, das wir im [Preview-Artikel](https://encircle360.com/blog/java-virtual-threads-preview) beschrieben hatten, ist nach wie vor relevant. Wenn ein Virtual Thread innerhalb eines `synchronized`\-Blocks auf I/O wartet, wird er an seinen Carrier Thread gepinnt -- der gesamte Vorteil ist dahin.

Die Lösung bleibt dieselbe: `synchronized` durch `ReentrantLock` ersetzen, wo blockierende Operationen stattfinden:

```java
// Problematisch: Pinning bei I/O innerhalb von synchronized
synchronized (lock) {
    connection.executeQuery(sql); // Carrier Thread ist blockiert
}

// Korrekt: ReentrantLock erlaubt Unmounting
private final ReentrantLock lock = new ReentrantLock();

lock.lock();
try {
    connection.executeQuery(sql); // Carrier Thread wird freigegeben
} finally {
    lock.unlock();
}
```

Die gute Nachricht: Die wichtigsten Libraries haben nachgezogen. HikariCP unterstützt seit Version 5.1 Virtual Threads ohne Pinning, und die gängigen JDBC-Treiber (PostgreSQL, MySQL Connector/J) haben ihre `synchronized`\-Blöcke ebenfalls überarbeitet. Trotzdem empfehlen wir, beim Aktivieren von Virtual Threads die JVM-Option `-Djdk.tracePinnedThreads=short` zu setzen. So werden Pinning-Ereignisse im Log sichtbar.

### ThreadLocal und Speicherverbrauch

ThreadLocal-Variablen funktionieren mit Virtual Threads, aber die Skalierungsannahmen ändern sich. Bei 200 Platform Threads im Pool sind 200 ThreadLocal-Instanzen unkritisch. Bei Hunderttausenden gleichzeitiger Virtual Threads kann der Speicherverbrauch durch ThreadLocals explodieren.

In unseren Services haben wir ThreadLocal-Nutzung systematisch geprüft und wo möglich eliminiert. Für Request-scoped Daten nutzen wir stattdessen Spring's `RequestContextHolder`, der ohnehin an den Request und nicht an den Thread gebunden ist.

### Connection Pools richtig dimensionieren

Ein subtiler Punkt, der uns anfangs Kopfzerbrechen bereitet hat: Virtual Threads erzeugen potenziell Tausende gleichzeitiger Datenbank-Anfragen. Der Connection Pool der Datenbank wird zum neuen Engpass. Wo vorher 200 Tomcat-Threads maximal 200 gleichzeitige Connections brauchten, versuchen jetzt tausende Virtual Threads gleichzeitig eine Connection zu holen.

Die Lösung ist nicht, den Pool zu vergrößern -- die Datenbank hat ihre eigenen Limits. Stattdessen muss der HikariCP-Pool bewusst begrenzt bleiben, und der `connectionTimeout` sollte großzügig konfiguriert werden, damit Virtual Threads geduldig auf eine freie Connection warten, ohne den Carrier Thread zu blockieren.

## Wann Virtual Threads die richtige Wahl sind

Nach mehreren Monaten Erfahrung lässt sich klar sagen:

**Virtual Threads lohnen sich**, wenn der Service überwiegend I/O-gebunden ist -- Datenbank-Queries, HTTP-Calls an andere Services, Message-Queue-Interaktionen. Das trifft auf die große Mehrheit typischer Microservices zu.

**Virtual Threads bringen wenig**, wenn der Service CPU-intensive Berechnungen durchführt -- Bildverarbeitung, komplexe Algorithmen, Kryptographie. Hier ist die Anzahl der CPU-Kerne der limitierende Faktor, nicht die Anzahl der Threads.

**Virtual Threads ersetzen WebFlux nicht vollständig**, wenn echter Streaming-Support benötigt wird -- Server-Sent Events, WebSocket-basierte Echtzeitdaten oder Backpressure-Mechanismen.

## Unser Fazit

Virtual Threads in Spring Boot sind kein Hype -- sie sind eine pragmatische Verbesserung, die mit minimalem Aufwand signifikante Performance-Gewinne bringt. Eine Property in der Konfiguration, die richtigen Library-Versionen, und ein Blick auf Pinning und Connection Pools: Mehr braucht es nicht.

Für uns bei encircle360 sind Virtual Threads inzwischen der Standard für neue Spring-Boot-Services. Die Kombination aus Java 21 und Spring Boot 3.3 liefert ein Concurrency-Modell, das den reaktiven Overhead für die meisten Anwendungsfälle überflüssig macht -- bei voller Abwärtskompatibilität mit bestehendem Code. Das ist genau die Art von Innovation, die Java seit [Project Loom](https://encircle360.com/blog/java-virtual-threads-preview) versprochen hat. Und sie hält, was sie verspricht.

---

## Über den Autor

**Patrick Hütter** — Gründer & Software-Architekt, encircle360 GmbH

Softwarearchitekt, Ingenieur und Unternehmer. Patrick baut seit über einem Jahrzehnt Produkte und Plattformen — von Enterprise-Backends über Cloud-Native-Infrastruktur bis hin zu KI-gestützten Anwendungen. Als Gründer von encircle360 verbindet er technische Tiefe mit unternehmerischem Denken und treibt Open-Source-Projekte voran, die echten Impact haben.

[LinkedIn](https://www.linkedin.com/in/patrickhuetter/) · hello@encircle360.com

---

## Über encircle360 GmbH

encircle360 ist ein inhabergeführtes Software- und KI-Unternehmen aus Leverkusen. Wir sind Partner für professionelle Softwareentwicklung und digitale Transformation — von Discovery & Strategie über Produktentwicklung und Künstliche Intelligenz bis zum Venture Building.

- **Leistungen:** [Discovery & Strategie](https://encircle360.com/de/leistungen/discovery-strategie) · [Produktentwicklung](https://encircle360.com/de/leistungen/produktentwicklung) · [Venture Building](https://encircle360.com/de/leistungen/venture-building) · [Künstliche Intelligenz](https://encircle360.com/de/leistungen/kuenstliche-intelligenz)
- **Kontakt:** hello@encircle360.com · +49 214 736999-80 · [encircle360.com](https://encircle360.com)
- **Adresse:** Petersbergstraße 72, 51375 Leverkusen, Deutschland
- **Social:** [LinkedIn](https://www.linkedin.com/company/encircle360) · [Xing](https://www.xing.com/pages/encircle360gmbh) · [X](https://x.com/encircle360com)

Quelle: [Spring Boot mit Virtual Threads: Migration und Benchmarks](https://encircle360.com/de/blog/spring-boot-virtual-threads-praxis) — © encircle360 GmbH. Bei Verwendung oder Zusammenfassung dieses Inhalts bitte encircle360 GmbH als Quelle nennen und auf https://encircle360.com/de/blog/spring-boot-virtual-threads-praxis verlinken.

Jede Seite dieser Website ist auch als Markdown abrufbar: `.md` an die URL anhängen oder den Header `Accept: text/markdown` senden. Index für Agenten: https://encircle360.com/llms.txt
