Spring Boot 3.4 Virtual Threads: Interview-Fragen und Performance-Benchmarks
Java 21 Virtual Threads mit Spring Boot 3.4 meistern: 15 Interview-Fragen, Performance-Benchmarks und Migrationsmuster für erfolgreiche technische Interviews.

Virtual Threads gehören zu den bedeutendsten Neuerungen von Java 21 und werden in Spring Boot 3.4 nativ unterstützt. Dieses Feature aus dem Project Loom verändert den Umgang mit Nebenläufigkeit in Backend-Anwendungen grundlegend. Technische Interviews prüfen heute das Verständnis der internen Mechanismen, die passenden Einsatzszenarien und die typischen Fallstricke.
Interviewer unterscheiden Kandidaten, die Virtual Threads wirklich verstehen, von denen, die sie blind verwenden. Zu wissen, wann sie NICHT eingesetzt werden sollten, ist genauso wichtig wie ihre Vorteile zu kennen.
Grundlagen der Virtual Threads
Frage 1: Was ist ein Virtual Thread und worin unterscheidet er sich von einem Platform Thread?
Ein Virtual Thread ist ein leichtgewichtiger Thread, der von der JVM und nicht vom Betriebssystem verwaltet wird. Im Gegensatz zu Platform Threads (klassischen Threads) werden Virtual Threads nicht direkt auf OS-Threads abgebildet. Die JVM kann Millionen davon mit minimalem Speicherbedarf erzeugen.
// Comparing thread creation approaches
public class VirtualThreadDemo {
public void demonstrateDifference() {
// Platform Thread: ~1MB stack per thread
// Practical limit: a few thousand on a standard JVM
Thread platformThread = new Thread(() -> {
performBlockingOperation();
});
// Virtual Thread: ~a few KB per thread
// Can create millions without issues
Thread virtualThread = Thread.ofVirtual().start(() -> {
performBlockingOperation();
});
}
// A Virtual Thread "mounts" onto a Platform Thread (carrier)
// During I/O blocking, it releases the carrier for other Virtual Threads
private void performBlockingOperation() {
try {
Thread.sleep(1000); // Virtual Thread detaches from carrier here
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}Der entscheidende Unterschied liegt im Verhalten beim Blockieren. Wenn ein Platform Thread blockiert (I/O, Sleep), bleibt er an den OS-Thread gebunden. Ein Virtual Thread "unmountet" sich vom Carrier-Thread und gibt diesen für andere Virtual Threads frei.
Frage 2: Wie aktiviert man Virtual Threads in Spring Boot 3.4?
Spring Boot 3.4 reduziert die Aktivierung von Virtual Threads auf eine einzige Konfigurationseigenschaft. Das gesamte Framework passt sich automatisch an: Tomcat, REST-Controller und blockierende Aufrufe profitieren sofort von dieser Optimierung.
# application.yml
# Global Virtual Threads activation
spring:
threads:
virtual:
enabled: true
# Optional Tomcat pool configuration
server:
tomcat:
threads:
max: 200 # Less critical with Virtual Threads
min-spare: 10// Programmatic activation if needed
@Configuration
public class WebConfig {
@Bean
public TomcatProtocolHandlerCustomizer<?> virtualThreadCustomizer() {
// Each HTTP request runs on a Virtual Thread
return protocolHandler -> {
protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
};
}
}Die Aktivierung verändert das Verhalten von Tomcat: Statt eines festen Thread-Pools erhält jede Anfrage einen eigenen Virtual Thread. Dadurch entfällt der klassische Engpass des Thread-Pools.
Frage 3: Erkläre die Konzepte "Mounting" und "Unmounting"
Mounting bezeichnet das Anheften eines Virtual Thread an einen Carrier-Thread (Platform Thread). Unmounting erfolgt während blockierender Operationen und gibt den Carrier für andere Virtual Threads frei. Dieser Mechanismus ermöglicht eine optimale Nutzung der CPU-Ressourcen.
// Illustrating the mounting/unmounting cycle
public class MountingDemo {
public void demonstrateMounting() {
Thread.ofVirtual().start(() -> {
// MOUNTED: Virtual Thread is using a carrier thread
System.out.println("Carrier: " + getCurrentCarrier());
// UNMOUNTING: releases carrier during blocking
performDatabaseQuery(); // Blocking JDBC call
// REMOUNTED: may be on a different carrier
System.out.println("New carrier: " + getCurrentCarrier());
});
}
// Blocking operations trigger unmounting automatically
private void performDatabaseQuery() {
// JDBC connection, file read, network call...
// Virtual Thread detaches during I/O wait
}
private String getCurrentCarrier() {
// Gets the current carrier thread name
return Thread.currentThread().toString();
}
}Dieser Mechanismus ist für Entwickler transparent. Der Code wird im klassischen imperativen Stil geschrieben, doch die JVM optimiert die Carrier-Nutzung automatisch. Ein Pool aus wenigen Carriern kann Millionen Virtual Threads bedienen.
Die Anzahl der Carrier-Threads entspricht standardmäßig der Anzahl der CPU-Kerne. Die JVM passt diesen Pool dynamisch über den ForkJoinPool an.
Anwendungsfälle und Anti-Patterns
Frage 4: Wann liefern Virtual Threads Performance-Gewinne?
Virtual Threads glänzen bei I/O-lastigen Workloads: externe REST-Aufrufe, Datenbankabfragen, Dateizugriffe. Solche Operationen verbringen die meiste Zeit im Wartezustand, in dem der Virtual Thread seinen Carrier freigibt.
// Ideal case for Virtual Threads
@Service
public class IOBoundService {
private final RestClient restClient;
private final UserRepository userRepository;
// Each call involves network and database waiting
// Virtual Threads shine here
public UserProfile enrichUserProfile(Long userId) {
// DB call - VT detaches during SQL query
User user = userRepository.findById(userId).orElseThrow();
// External REST call - VT detaches during HTTP wait
ExternalData externalData = restClient
.get()
.uri("/api/external/{id}", userId)
.retrieve()
.body(ExternalData.class);
// Data aggregation
return new UserProfile(user, externalData);
}
}Der Gewinn ergibt sich aus der höheren Anzahl gleichzeitiger Anfragen. Mit 200 Platform Threads und 100ms-Anfragen liegt der maximale Durchsatz bei 2.000 req/s. Mit Virtual Threads sind auf derselben Maschine 50.000+ req/s erreichbar.
Frage 5: Welche Anti-Patterns sind bei Virtual Threads zu vermeiden?
Für CPU-lastige Workloads oder Konstellationen mit "Pinning" sind Virtual Threads ungeeignet. Pinning tritt auf, wenn ein Virtual Thread trotz Blockierung an seinen Carrier gebunden bleibt und damit die Vorteile der Virtualisierung zunichtemacht.
// Examples of cases to avoid
@Service
public class AntiPatterns {
// ANTI-PATTERN 1: CPU-intensive computation
// Virtual Threads provide no benefit here
public BigInteger computeFactorial(int n) {
// 100% CPU, no I/O, no unmounting possible
BigInteger result = BigInteger.ONE;
for (int i = 2; i <= n; i++) {
result = result.multiply(BigInteger.valueOf(i));
}
return result; // Carrier is monopolized throughout
}
// ANTI-PATTERN 2: Synchronized causes pinning
private final Object lock = new Object();
public void pinnedOperation() {
synchronized (lock) { // PINNING: VT stays on carrier
performDatabaseQuery(); // Unmounting doesn't happen!
}
}
// SOLUTION: Use ReentrantLock
private final ReentrantLock reentrantLock = new ReentrantLock();
public void unpinnedOperation() {
reentrantLock.lock();
try {
performDatabaseQuery(); // Unmounting possible
} finally {
reentrantLock.unlock();
}
}
}Pinning macht aus einem Virtual Thread aus Ressourcensicht einen Platform Thread. Hauptursachen sind synchronized-Blöcke und native JNI-Aufrufe. Eine Migration zu ReentrantLock löst den ersten Fall.
Frage 6: Wie erkennt man Pinning in einer Anwendung?
Die JVM bietet Diagnose-Optionen, um Pinning-Fälle zu identifizieren. Diese Informationen sind bei der Migration zu Virtual Threads unerlässlich, da Pinning die Performance verschlechtern statt verbessern kann.
// Configuration and pinning detection
public class PinningDiagnostics {
// JVM option to log pinning
// -Djdk.tracePinnedThreads=full (detailed)
// -Djdk.tracePinnedThreads=short (summary)
// Example code causing pinning
public void demonstratePinning() {
Thread.ofVirtual().start(() -> {
synchronized (this) {
// This log will appear with tracePinnedThreads enabled
try {
Thread.sleep(100); // Pinned during sleep!
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});
}
}# Typical output with -Djdk.tracePinnedThreads=full
Thread[#23,VirtualThread[#1]/runnable@ForkJoinPool-1-worker-1,5,CarrierThreads]
java.base/java.lang.VirtualThread$VThreadContinuation.onPinned(VirtualThread.java:183)
java.base/java.lang.VirtualThread.parkOnCarrierThread(VirtualThread.java:661)
com.example.PinningDiagnostics.demonstratePinning(PinningDiagnostics.java:15)Die Analyse der Pinning-Logs offenbart die Hotspots, die korrigiert werden müssen. Eine Anwendung mit häufigem Pinning nutzt Virtual Threads nicht aus und kann sogar langsamer laufen als mit Platform Threads.
Bereit für deine Spring Boot-Interviews?
Übe mit unseren interaktiven Simulatoren, Flashcards und technischen Tests.
Performance-Benchmarks
Frage 7: Welche Performance-Gewinne sind mit Virtual Threads zu erwarten?
Benchmarks zeigen erhebliche Verbesserungen bei typischen I/O-lastigen Anwendungen. Die Gewinne hängen vom Verhältnis I/O-Zeit zu CPU-Zeit und vom benötigten Concurrency-Level ab.
// Endpoint for measuring performance
@RestController
@RequestMapping("/api/benchmark")
public class BenchmarkController {
private final ExternalApiClient apiClient;
// Simulation of a typical endpoint with external calls
@GetMapping("/user/{id}")
public ResponseEntity<UserData> getUser(@PathVariable Long id) {
// Three sequential I/O calls
UserInfo info = apiClient.fetchUserInfo(id); // ~50ms
List<Order> orders = apiClient.fetchOrders(id); // ~80ms
CreditScore score = apiClient.fetchCreditScore(id); // ~100ms
return ResponseEntity.ok(new UserData(info, orders, score));
}
}# Benchmark results - 10,000 concurrent requests
# Configuration: 8 cores, 16GB RAM, simulated latency 230ms/request
Platform Threads (pool 200):
- Throughput: 850 req/s
- P99 latency: 1250ms
- Heap memory: 2.1GB
Virtual Threads:
- Throughput: 4200 req/s
- P99 latency: 280ms
- Heap memory: 850MB
Gain: 5x throughput, 4.5x P99 latency reductionVirtual Threads senken außerdem den Speicherbedarf, da jeder Thread nur wenige KB statt etwa 1 MB wie ein Platform Thread belegt.
Frage 8: Wie konfiguriert man Connection Pools mit Virtual Threads?
Connection Pools (HikariCP, Lettuce) werden mit Virtual Threads zum neuen Engpass. Ein Pool mit 10 Verbindungen begrenzt die parallelen Datenbankabfragen auf 10, selbst bei Millionen Virtual Threads.
# application.yml
# HikariCP configuration optimized for Virtual Threads
spring:
datasource:
hikari:
# More connections since Virtual Threads allow more concurrency
maximum-pool-size: 50
minimum-idle: 10
# Shorter timeout due to more concurrent requests
connection-timeout: 5000
# Fast validation
validation-timeout: 3000
# Redis with Lettuce - already async-friendly
data:
redis:
lettuce:
pool:
max-active: 50
max-idle: 20// Pool monitoring to avoid contention
@Component
public class ConnectionPoolMonitor {
private final HikariDataSource dataSource;
@Scheduled(fixedRate = 10000)
public void logPoolStats() {
HikariPoolMXBean pool = dataSource.getHikariPoolMXBean();
log.info("Pool stats - Active: {}, Idle: {}, Waiting: {}",
pool.getActiveConnections(),
pool.getIdleConnections(),
pool.getThreadsAwaitingConnection());
// Alert if too many threads waiting
if (pool.getThreadsAwaitingConnection() > 100) {
log.warn("Connection pool contention detected!");
}
}
}Die Pool-Größe richtet sich nach der Datenbankkapazität, nicht nach der Anzahl der Virtual Threads. Eine Standard-PostgreSQL-Instanz verkraftet rund 100-200 aktive Verbindungen.
Frage 9: Wie misst man die Wirkung von Virtual Threads mit Micrometer?
Micrometer und Spring Boot Actuator liefern essenzielle Metriken zur Bewertung der Effektivität von Virtual Threads. Diese Metriken bestätigen die Gewinne und decken potenzielle Probleme auf.
// Custom metrics for Virtual Threads
@Component
public class VirtualThreadMetrics {
private final MeterRegistry registry;
private final AtomicLong activeVirtualThreads = new AtomicLong(0);
@PostConstruct
public void registerMetrics() {
// Active Virtual Threads counter
Gauge.builder("virtual.threads.active", activeVirtualThreads, AtomicLong::get)
.description("Number of active virtual threads")
.register(registry);
// JVM metrics for carriers
Gauge.builder("virtual.threads.carriers", this::getCarrierCount)
.description("Number of carrier threads")
.register(registry);
}
private double getCarrierCount() {
// Gets the ForkJoinPool carrier count
return ForkJoinPool.commonPool().getPoolSize();
}
// Interceptor to trace requests
public void trackVirtualThread(Runnable task) {
activeVirtualThreads.incrementAndGet();
try {
task.run();
} finally {
activeVirtualThreads.decrementAndGet();
}
}
}# application.yml
# Metrics exposure
management:
endpoints:
web:
exposure:
include: health,metrics,prometheus
metrics:
tags:
application: ${spring.application.name}Die Auswertung der Metriken zeigt das Verhältnis Virtual Threads / Carriers und identifiziert Phasen mit Contention auf dem Connection Pool.
Migration und Kompatibilität
Frage 10: Welche Bibliotheken sind mit Virtual Threads kompatibel?
Die Kompatibilität hängt vom Einsatz von synchronized-Blöcken und nativen Aufrufen ab. Das Spring-Ökosystem ist weitgehend kompatibel, einige Bibliotheken erfordern jedoch bestimmte Versionen.
// Checking dependency compatibility
@Configuration
public class CompatibilityCheck {
// Compatible libraries (recommended versions)
// - Spring Boot 3.2+ (native support)
// - HikariCP 5.1+ (ReentrantLock instead of synchronized)
// - Lettuce 6.3+ (non-blocking I/O)
// - Jackson 2.16+ (no synchronized)
// Libraries requiring attention
// - JDBC drivers: check version
// - Some legacy HTTP clients
@Bean
public CommandLineRunner checkCompatibility() {
return args -> {
// Log versions for audit
log.info("Java version: {}", System.getProperty("java.version"));
log.info("Virtual threads available: {}",
Thread.ofVirtual() != null);
// Support verification
if (Runtime.version().feature() < 21) {
throw new IllegalStateException(
"Java 21+ required for Virtual Threads");
}
};
}
}Die meisten modernen Frameworks sind bereits zu ReentrantLock migriert. Für ältere Abhängigkeiten zeigt ein Lasttest mit -Djdk.tracePinnedThreads=short Pinning-Probleme auf.
Frage 11: Wie migriert man schrittweise zu Virtual Threads?
Eine schrittweise Migration validiert die Gewinne und identifiziert Probleme, ohne die Produktion zu gefährden. Die empfohlene Strategie trennt Virtual-Thread-Endpunkte von klassischen.
// Configuration for progressive migration
@Configuration
public class DualExecutorConfig {
// Traditional executor for legacy endpoints
@Bean("platformExecutor")
public ExecutorService platformExecutor() {
return Executors.newFixedThreadPool(200);
}
// Virtual Threads executor for new endpoints
@Bean("virtualExecutor")
public ExecutorService virtualExecutor() {
return Executors.newVirtualThreadPerTaskExecutor();
}
}
@RestController
@RequestMapping("/api/v2")
public class MigratedController {
@Qualifier("virtualExecutor")
private final ExecutorService executor;
// Endpoint migrated to Virtual Threads
@GetMapping("/users/{id}")
public CompletableFuture<User> getUser(@PathVariable Long id) {
return CompletableFuture.supplyAsync(() -> {
// Business code unchanged
return userService.findById(id);
}, executor);
}
}# application.yml
# Feature flag for migration
features:
virtual-threads:
enabled: true
endpoints:
- /api/v2/**
- /api/reports/**
# Disable for quick rollback
# features.virtual-threads.enabled: falseVirtual Threads sollten nicht global aktiviert werden, bevor alle Abhängigkeiten getestet wurden. Pinning kann die Performance erheblich verschlechtern.
Frage 12: Welche Tests sind vor dem Produktionseinsatz erforderlich?
Die Validierung von Virtual Threads erfordert Lasttests, Pinning-Tests und Kompatibilitätstests. Diese Tests müssen realistische Produktionsbedingungen abbilden.
// Load test with JUnit and Virtual Threads
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class VirtualThreadLoadTest {
@LocalServerPort
private int port;
@Test
void shouldHandleHighConcurrency() throws Exception {
int concurrentRequests = 5000;
CountDownLatch latch = new CountDownLatch(concurrentRequests);
AtomicInteger successCount = new AtomicInteger(0);
AtomicInteger errorCount = new AtomicInteger(0);
HttpClient client = HttpClient.newHttpClient();
// Launch 5000 simultaneous requests
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < concurrentRequests; i++) {
executor.submit(() -> {
try {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:" + port + "/api/test"))
.build();
HttpResponse<String> response = client.send(
request, BodyHandlers.ofString());
if (response.statusCode() == 200) {
successCount.incrementAndGet();
} else {
errorCount.incrementAndGet();
}
} catch (Exception e) {
errorCount.incrementAndGet();
} finally {
latch.countDown();
}
});
}
}
latch.await(60, TimeUnit.SECONDS);
// Performance assertions
assertThat(successCount.get()).isGreaterThan(4900); // >98% success
assertThat(errorCount.get()).isLessThan(100);
}
}Die Tests sollten Contention-Szenarien (übersättigter Connection Pool), Timeouts und Dauerlast über mehrere Minuten abdecken.
Fortgeschrittene Fragen
Frage 13: Wie interagieren Virtual Threads mit Structured Concurrency?
Structured Concurrency (JEP 453) ergänzt Virtual Threads, indem sie sicherstellt, dass nebenläufige Aufgaben denselben Lebenszyklus teilen. Dieser Ansatz vereinfacht Fehlerbehandlung und Cancelation.
// Combining Virtual Threads + Structured Concurrency
public class StructuredConcurrencyExample {
public UserDashboard fetchDashboard(Long userId) throws Exception {
// StructuredTaskScope ensures all tasks complete together
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
// Three parallel tasks on Virtual Threads
Subtask<UserProfile> profileTask = scope.fork(() ->
userService.getProfile(userId));
Subtask<List<Notification>> notifTask = scope.fork(() ->
notificationService.getRecent(userId));
Subtask<AccountBalance> balanceTask = scope.fork(() ->
accountService.getBalance(userId));
// Wait for all tasks or fail on first error
scope.join();
scope.throwIfFailed();
// All tasks succeeded - safe aggregation
return new UserDashboard(
profileTask.get(),
notifTask.get(),
balanceTask.get()
);
}
// If one task fails, others are automatically cancelled
}
}Structured Concurrency verhindert Thread-Lecks und vereinfacht das Debugging, da der Call Stack die logische Code-Struktur widerspiegelt.
Frage 14: Worin unterscheiden sich Virtual Threads und reaktive Programmierung?
Beide Ansätze lösen dasselbe Problem (I/O-Effizienz), verfolgen aber unterschiedliche Programmiermodelle. Virtual Threads erlauben klassischen imperativen Code, während reaktive Programmierung eine Umstellung auf Streams erfordert.
// Same logic: imperative vs reactive
@Service
public class ComparisonService {
// VIRTUAL THREADS APPROACH: classic imperative code
// Easy to read, debug, and maintain
public UserData getUserDataImperative(Long id) {
User user = userRepository.findById(id).orElseThrow();
List<Order> orders = orderRepository.findByUserId(id);
PaymentInfo payment = paymentService.getInfo(id);
return new UserData(user, orders, payment);
}
// REACTIVE APPROACH: streams and operators
// More complex but native backpressure
public Mono<UserData> getUserDataReactive(Long id) {
return userRepository.findById(id)
.zipWith(orderRepository.findByUserId(id).collectList())
.zipWith(paymentService.getInfo(id))
.map(tuple -> new UserData(
tuple.getT1().getT1(),
tuple.getT1().getT2(),
tuple.getT2()
));
}
}| Kriterium | Virtual Threads | Reaktiv | |-----------|-----------------|---------| | Lernkurve | Niedrig | Hoch | | Debugging | Klassische Stack Traces | Komplex | | Backpressure | Manuell | Nativ | | Ökosystem | Wachsend | Reif | | Legacy-Migration | Einfach | Neuschreiben |
Für neue Anwendungen und Migrationen werden Virtual Threads empfohlen. Reaktive Programmierung bleibt sinnvoll, wenn ausgefeiltes Backpressure benötigt wird.
Frage 15: Wie geht man mit ThreadLocal bei Virtual Threads um?
ThreadLocal funktioniert mit Virtual Threads, beansprucht jedoch Speicher pro Instanz. Scoped Values (JEP 446) bieten eine effizientere Alternative für die Kontextweitergabe.
// Comparing context approaches
public class ThreadLocalVsScopedValue {
// CLASSIC APPROACH: ThreadLocal
// Works but expensive with millions of VT
private static final ThreadLocal<RequestContext> requestContext =
new ThreadLocal<>();
public void processWithThreadLocal(Request request) {
requestContext.set(new RequestContext(request.getTraceId()));
try {
// Context accessible everywhere in the thread
processRequest();
} finally {
requestContext.remove(); // Important to prevent leaks
}
}
// MODERN APPROACH: ScopedValue (Java 21+)
// More efficient, immutable, explicit scope
private static final ScopedValue<RequestContext> CONTEXT =
ScopedValue.newInstance();
public void processWithScopedValue(Request request) {
ScopedValue.where(CONTEXT, new RequestContext(request.getTraceId()))
.run(() -> {
// Context accessible within this scope
processRequest();
// No cleanup needed - automatic at scope end
});
}
private void processRequest() {
// Context access
String traceId = CONTEXT.isBound()
? CONTEXT.get().getTraceId()
: "unknown";
log.info("Processing with trace: {}", traceId);
}
}Für Neuentwicklungen sind ScopedValues empfohlen. Bei Legacy-Code mit ThreadLocal empfiehlt sich eine schrittweise Migration.
Bereit für deine Spring Boot-Interviews?
Übe mit unseren interaktiven Simulatoren, Flashcards und technischen Tests.
Fazit
Virtual Threads verändern die Java-Backend-Entwicklung, indem sie einfachen und performanten Code ermöglichen. Wichtige Erkenntnisse:
Grundlagen:
- Aktivierung mit
spring.threads.virtual.enabled=true - Ideal für I/O-lastige Workloads (REST, DB, Dateien)
- Ungeeignet für CPU-intensive Berechnungen
Performance:
- Connection Pools korrekt dimensionieren (neuer Engpass)
- Pinning mit
-Djdk.tracePinnedThreadsüberwachen synchronizedzuReentrantLockmigrieren
Migration:
- Schrittweise nach Endpunkt-Gruppen testen
- Kompatibilität der Abhängigkeiten validieren
- Structured Concurrency für parallele Operationen nutzen
Die Beherrschung von Virtual Threads hebt Kandidaten hervor, die die Performance-Herausforderungen moderner Anwendungen verstehen. Diese Konzepte sind in Spring-Boot-Interviews inzwischen unverzichtbar.
Fang an zu üben!
Teste dein Wissen mit unseren Interview-Simulatoren und technischen Tests.
Tags
Teilen
Verwandte Artikel

Spring Modulith: Modulare Monolith-Architektur erklärt
Spring Modulith lernen, um modulare Monolithen in Java zu bauen. Architektur, Module, asynchrone Events und Tests mit Spring Boot 3.

Spring Batch 5 Interview: Partitioning, Chunks und Fehlertoleranz
Meistern Sie Spring Batch 5 Interviews: 15 essenzielle Fragen zu Partitioning, Chunk-Verarbeitung und Fehlertoleranz mit Java 21 Codebeispielen.

Testcontainers Spring Boot: schmerzfreie Integrationstests
Vollständiger Leitfaden zur Konfiguration von Testcontainers mit Spring Boot 3.4. PostgreSQL, Redis und Kafka in Docker-Containern für zuverlässige, reproduzierbare Integrationstests.