{"id":664,"date":"2026-05-16T22:54:03","date_gmt":"2026-05-16T21:54:03","guid":{"rendered":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/?p=664"},"modified":"2026-06-16T22:58:39","modified_gmt":"2026-06-16T21:58:39","slug":"structured-concurrency-in-java-21-strukturierte-nebenlaeufigkeit-mit-structuredtaskscope","status":"publish","type":"post","link":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/?p=664","title":{"rendered":"Structured Concurrency in Java 21 \u2013 Strukturierte Nebenl\u00e4ufigkeit mit StructuredTaskScope"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Nebenl\u00e4ufigkeit in Java war lange von zwei Mustern gepr\u00e4gt:&nbsp;<code>ExecutorService<\/code>&nbsp;mit&nbsp;<code>Future<\/code>&nbsp;und&nbsp;<code>CompletableFuture<\/code>&nbsp;mit asynchronen Callbacks. Beide haben ihre Nachteile: Futures erfordern manuelle Synchronisation, und asynchrone Pipelines werden schnell un\u00fcbersichtlich. Mit Java 21 (JEP 453) wurde&nbsp;<strong>Structured Concurrency<\/strong>&nbsp;als Preview-Feature eingef\u00fchrt \u2013 ein Paradigma, das die Lebenszyklen nebenl\u00e4ufiger Tasks an den umgebenden Code kn\u00fcpft und Task-Abbruch sowie Fehlerbehandlung fundamental vereinfacht.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Hinweis:<\/strong>&nbsp;Structured Concurrency ist zum Zeitpunkt der Artikelerstellung noch ein Preview-Feature und wurde in Java 22 (JEP 462), Java 23 (JEP 480) und Java 24 (JEP 499) weiter als Preview gef\u00fchrt. Die API kann sich bis zur Finalisierung noch \u00e4ndern. Um die Beispiele auszuf\u00fchren, muss mit&nbsp;<code>--enable-preview<\/code>&nbsp;kompiliert und gestartet werden.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Das Problem klassischer Nebenl\u00e4ufigkeit<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Betrachten wir einen Service, der parallel drei externe APIs abfragt:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">ExecutorService executor = Executors.newFixedThreadPool(<span class=\"hljs-number\">3<\/span>);\n<span class=\"hljs-keyword\">try<\/span> {\n    Future&lt;<span class=\"hljs-built_in\">String<\/span>&gt; apiA = executor.submit(() -&gt; rufeApiA());\n    Future&lt;<span class=\"hljs-built_in\">String<\/span>&gt; apiB = executor.submit(() -&gt; rufeApiB());\n    Future&lt;<span class=\"hljs-built_in\">String<\/span>&gt; apiC = executor.submit(() -&gt; rufeApiC());\n\n    <span class=\"hljs-built_in\">String<\/span> ergebnisA = apiA.get();\n    <span class=\"hljs-built_in\">String<\/span> ergebnisB = apiB.get();\n    <span class=\"hljs-built_in\">String<\/span> ergebnisC = apiC.get();\n} <span class=\"hljs-keyword\">finally<\/span> {\n    executor.shutdown();\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Mehrere Probleme fallen auf:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Wenn\u00a0<code>apiA.get()<\/code>\u00a0eine Exception wirft, laufen die anderen Tasks weiter, ohne abgebrochen zu werden \u2013 Ressourcenverschwendung<\/li>\n\n\n\n<li>Der\u00a0<code>ExecutorService<\/code>\u00a0muss manuell heruntergefahren werden (sonst l\u00e4uft er ewig)<\/li>\n\n\n\n<li>Stacktraces sind schwer nachvollziehbar, weil Threads entkoppelt sind<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">StructuredTaskScope<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><code>StructuredTaskScope<\/code>&nbsp;l\u00f6st diese Probleme, indem es Tasks an einen definierten Scope bindet. Der Scope ist&nbsp;<code>AutoCloseable<\/code>&nbsp;\u2013 mit try-with-resources wird er automatisch geschlossen. Alle Tasks werden beim Verlassen des Scopes garantiert beendet.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">try<\/span> (<span class=\"hljs-keyword\">var<\/span> scope = <span class=\"hljs-keyword\">new<\/span> StructuredTaskScope.ShutdownOnFailure()) {\n    Supplier&lt;<span class=\"hljs-built_in\">String<\/span>&gt; apiA = scope.fork(() -&gt; rufeApiA());\n    Supplier&lt;<span class=\"hljs-built_in\">String<\/span>&gt; apiB = scope.fork(() -&gt; rufeApiB());\n    Supplier&lt;<span class=\"hljs-built_in\">String<\/span>&gt; apiC = scope.fork(() -&gt; rufeApiC());\n\n    scope.join();           <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">em<\/span>&gt;<\/span>\/\/ Wartet auf alle Tasks und wirft bei Fehlern<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">em<\/span>&gt;<\/span><\/span>\n    scope.throwIfFailed();  <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">em<\/span>&gt;<\/span>\/\/ Propagiert Exceptions<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">em<\/span>&gt;<\/span><\/span>\n\n    <span class=\"hljs-built_in\">String<\/span> ergebnis = apiA.get() + apiB.get() + apiC.get();\n    System.out.println(ergebnis);\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Das Verhalten ist radikal anders als bei&nbsp;<code>ExecutorService<\/code>:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Schl\u00e4gt ein einzelner Task fehl, werden\u00a0<strong>alle anderen automatisch abgebrochen<\/strong><\/li>\n\n\n\n<li>Der Scope wird durch try-with-resources automatisch geschlossen<\/li>\n\n\n\n<li>Der aufrufende Thread wird von\u00a0<code>join()<\/code>\u00a0blockiert \u2013 keine versteckten Thread-Leaks mehr<\/li>\n\n\n\n<li>Die Eltern-Kind-Beziehung der Threads bleibt erhalten \u2013 Stacktraces zeigen den vollst\u00e4ndigen Aufrufbaum<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">ShutdownOnSuccess \u2013 Der schnellste gewinnt<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Neben&nbsp;<code>ShutdownOnFailure<\/code>&nbsp;gibt es&nbsp;<code>ShutdownOnSuccess<\/code>, das sofort alle anderen Tasks abbricht, sobald ein Task erfolgreich war \u2013 ideal f\u00fcr redundante API-Abfragen:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">try<\/span> (<span class=\"hljs-keyword\">var<\/span> scope = <span class=\"hljs-keyword\">new<\/span> StructuredTaskScope.ShutdownOnSuccess&lt;<span class=\"hljs-built_in\">String<\/span>&gt;()) {\n    scope.fork(() -&gt; rufeApiSpiegel1());\n    scope.fork(() -&gt; rufeApiSpiegel2());\n    scope.fork(() -&gt; rufeApiSpiegel3());\n\n    scope.join();\n    <span class=\"hljs-built_in\">String<\/span> ergebnis = scope.result();\n    System.out.println(<span class=\"hljs-string\">\"Schnellste Antwort: \"<\/span> + ergebnis);\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Sobald die erste API antwortet, werden die beiden anderen Tasks abgebrochen \u2013 ohne manuelles&nbsp;<code>cancel()<\/code>&nbsp;oder Timeout-Logik.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Scoped Values als Erg\u00e4nzung<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Structured Concurrency wird oft mit&nbsp;<strong>Scoped Values<\/strong>&nbsp;kombiniert, um Kontext wie Request-IDs oder Benutzerinformationen ohne&nbsp;<code>ThreadLocal<\/code>&nbsp;weiterzureichen. Scoped Values wurden ebenfalls in Java 21 als Preview eingef\u00fchrt (JEP 446) und sind bis einschlie\u00dflich Java 24 weiterhin im Preview-Status:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">static<\/span> <span class=\"hljs-keyword\">final<\/span> ScopedValue&lt;String&gt; REQUEST_ID = ScopedValue.newInstance();\n\nScopedValue.where(REQUEST_ID, <span class=\"hljs-string\">\"abc-123\"<\/span>)\n    .run(() -&gt; {\n        <span class=\"hljs-keyword\">try<\/span> (<span class=\"hljs-keyword\">var<\/span> scope = <span class=\"hljs-keyword\">new<\/span> StructuredTaskScope.ShutdownOnFailure()) {\n            scope.fork(() -&gt; verarbeiteMitRequestId(REQUEST_ID.get()));\n            scope.join();\n        }\n    });\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Jeder geforkte Task erbt den Scoped Value des Eltern-Threads automatisch \u2013 ohne explizites Kopieren.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Virtual Threads als Unterbau<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Structured Concurrency entfaltet seine volle Kraft mit Virtual Threads \u2013 den leichtgewichtigen Threads, die Java 21 ebenfalls mitbrachte:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">try<\/span> (<span class=\"hljs-keyword\">var<\/span> executor = Executors.newVirtualThreadPerTaskExecutor()) {\n    <span class=\"hljs-keyword\">try<\/span> (<span class=\"hljs-keyword\">var<\/span> scope = <span class=\"hljs-keyword\">new<\/span> StructuredTaskScope.ShutdownOnFailure()) {\n        scope.fork(() -&gt; rufeApiA());\n        scope.fork(() -&gt; rufeApiB());\n        scope.fork(() -&gt; rufeApiC());\n        scope.join();\n    }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Der&nbsp;<code>ExecutorService<\/code>-Ersatz hier nutzt Virtual Threads: jeder&nbsp;<code>scope.fork()<\/code>&nbsp;erzeugt einen neuen Virtual Thread, der nur minimale Ressourcen verbraucht. Zehntausende parallele Aufgaben sind damit m\u00f6glich \u2013 mit Platform Threads undenkbar.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Benutzerdefinierte Error-Handler<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><code>ShutdownOnFailure<\/code>&nbsp;behandelt Fehler automatisch, aber f\u00fcr granulare Kontrolle l\u00e4sst sich ein eigener&nbsp;<code>StructuredTaskScope<\/code>-Subtyp schreiben:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">BestEffortScope<\/span>&lt;<span class=\"hljs-title\">T<\/span>&gt; <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">StructuredTaskScope<\/span>&lt;<span class=\"hljs-title\">T<\/span>&gt; <\/span>{\n    <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">final<\/span> <span class=\"hljs-keyword\">List<\/span>&lt;T&gt; results = <span class=\"hljs-keyword\">new<\/span> ArrayList&lt;&gt;();\n\n    @Override\n    <span class=\"hljs-keyword\">protected<\/span> void handleComplete(Subtask<span class=\"hljs-meta\">&lt;?<\/span> extends T&gt; subtask) {\n        <span class=\"hljs-keyword\">if<\/span> (subtask.state() == Subtask.State.SUCCESS) {\n            results.add(subtask.get());\n        } <span class=\"hljs-keyword\">else<\/span> {\n            &lt;em&gt;<span class=\"hljs-comment\">\/\/ Fehler ignorieren, mit erfolgreichen Ergebnissen weitermachen&lt;\/em&gt;<\/span>\n            log.warn(<span class=\"hljs-string\">\"Task fehlgeschlagen: {}\"<\/span>,\n                subtask.<span class=\"hljs-keyword\">exception<\/span>().getMessage());\n        }\n    }\n\n    @Override\n    <span class=\"hljs-keyword\">public<\/span> BestEffortScope&lt;T&gt; join() throws InterruptedException {\n        super.join();\n        <span class=\"hljs-keyword\">return<\/span> this;\n    }\n\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">List<\/span>&lt;T&gt; results() { <span class=\"hljs-keyword\">return<\/span> results; }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Dieser \u201eBest Effort&#8220;-Scope sammelt erfolgreiche Ergebnisse und ignoriert Fehler \u2013 n\u00fctzlich, wenn mehrere optionale Datenquellen abgefragt werden.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Vergleich der Ans\u00e4tze<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Kriterium<\/th><th class=\"has-text-align-left\" data-align=\"left\">ExecutorService<\/th><th class=\"has-text-align-left\" data-align=\"left\">StructuredTaskScope<\/th><\/tr><\/thead><tbody><tr><td>Task-Abbruch bei Fehler<\/td><td>nein (manuell)<\/td><td>automatisch<\/td><\/tr><tr><td>Lebenszyklus<\/td><td>endlos (manuelles shutdown)<\/td><td>try-with-resources<\/td><\/tr><tr><td>Thread-Eltern-Beziehung<\/td><td>entkoppelt<\/td><td>erhalten<\/td><\/tr><tr><td>Stacktraces<\/td><td>fragmentiert<\/td><td>zusammenh\u00e4ngend<\/td><\/tr><tr><td>Virtual-Thread-kompatibel<\/td><td>ja<\/td><td>optimiert<\/td><\/tr><tr><td>Fehlerbehandlung<\/td><td>manuell per Future.get()<\/td><td>deklarativ per ShutdownOnFailure<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Fazit<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Structured Concurrency bringt eine l\u00e4ngst \u00fcberf\u00e4llige Ordnung in die nebenl\u00e4ufige Java-Programmierung. Tasks werden an definierte Scopes gebunden, automatisch beendet und Fehler werden konsistent propagiert. Mit Virtual Threads im R\u00fccken und Scoped Values f\u00fcr die Kontext-Propagation entsteht ein Programmiermodell, das die Komplexit\u00e4t klassischer Threads und Futures radikal reduziert. Wer heute neue Microservices in Java 21+ schreibt, sollte&nbsp;<code>StructuredTaskScope<\/code>&nbsp;dem&nbsp;<code>ExecutorService<\/code>&nbsp;vorziehen \u2013 der Code wird k\u00fcrzer, lesbarer und korrekter. Allerdings sollte beachtet werden, dass die API noch im Preview-Status ist und sich bis zur Finalisierung noch \u00e4ndern kann.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Nebenl\u00e4ufigkeit in Java war lange von zwei Mustern gepr\u00e4gt:&nbsp;ExecutorService&nbsp;mit&nbsp;Future&nbsp;und&nbsp;CompletableFuture&nbsp;mit asynchronen Callbacks. Beide haben ihre Nachteile: Futures erfordern manuelle Synchronisation, und asynchrone Pipelines werden schnell un\u00fcbersichtlich. Mit Java 21 (JEP 453) wurde&nbsp;Structured Concurrency&nbsp;als Preview-Feature eingef\u00fchrt \u2013 ein Paradigma, das die Lebenszyklen nebenl\u00e4ufiger Tasks an den umgebenden Code kn\u00fcpft und Task-Abbruch sowie Fehlerbehandlung fundamental vereinfacht. Hinweis:&nbsp;Structured Concurrency [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4],"tags":[],"class_list":["post-664","post","type-post","status-publish","format-standard","hentry","category-plain_java"],"_links":{"self":[{"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=\/wp\/v2\/posts\/664","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=664"}],"version-history":[{"count":1,"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=\/wp\/v2\/posts\/664\/revisions"}],"predecessor-version":[{"id":665,"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=\/wp\/v2\/posts\/664\/revisions\/665"}],"wp:attachment":[{"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=664"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=664"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=664"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}