From caaf6d65389f3afb2c7a59e22a59a4414941d29b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Apr 2026 13:40:41 +0000 Subject: [PATCH 1/7] Initial plan From 4ae420040c15e1d38ce28fb27d8570b1e65132f6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Apr 2026 14:01:14 +0000 Subject: [PATCH 2/7] Add virtual thread support on Java 21+ via Multi-Release JAR Agent-Logs-Url: https://github.com/github/copilot-sdk-java/sessions/e74714e4-bb84-4af7-b1ef-44f576c5b37c Co-authored-by: brunoborges <129743+brunoborges@users.noreply.github.com> --- README.md | 10 ++- jbang-example.java | 1 + pom.xml | 30 ++++++++ .../github/copilot/sdk/CliServerManager.java | 3 +- .../com/github/copilot/sdk/CopilotClient.java | 7 ++ .../github/copilot/sdk/CopilotSession.java | 5 ++ .../com/github/copilot/sdk/JsonRpcClient.java | 7 +- .../copilot/sdk/ThreadFactoryProvider.java | 71 +++++++++++++++++++ .../copilot/sdk/ThreadFactoryProvider.java | 60 ++++++++++++++++ src/site/markdown/advanced.md | 32 +++++++++ src/site/markdown/getting-started.md | 2 +- src/site/markdown/index.md | 2 +- .../sdk/ThreadFactoryProviderTest.java | 62 ++++++++++++++++ 13 files changed, 281 insertions(+), 11 deletions(-) create mode 100644 src/main/java/com/github/copilot/sdk/ThreadFactoryProvider.java create mode 100644 src/main/java21/com/github/copilot/sdk/ThreadFactoryProvider.java create mode 100644 src/test/java/com/github/copilot/sdk/ThreadFactoryProviderTest.java diff --git a/README.md b/README.md index 677389c6e..63edf0001 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Java SDK for programmatic control of GitHub Copilot CLI, enabling you to build A ### Requirements -- Java 17 or later. **JDK 25 recommended**. Selecting JDK 25 enables the use of virtual threads, as shown in the [Quick Start](#quick-start). +- Java 17 or later. **JDK 21+ recommended** for automatic virtual thread support (see [Virtual Threads](#virtual-threads) below). Selecting JDK 25 additionally enables the use of virtual threads for the custom executor, as shown in the [Quick Start](#quick-start). - GitHub Copilot CLI 1.0.17 or later installed and in `PATH` (or provide custom `cliPath`) ### Maven @@ -143,6 +143,14 @@ jbang https://github.com/github/copilot-sdk-java/blob/latest/jbang-example.java - [MCP Servers Integration](https://github.github.io/copilot-sdk-java/latest/mcp.html) - [Cookbook](src/site/markdown/cookbook/) — Practical recipes for common use cases +## Virtual Threads + +When running on **Java 21+**, the SDK automatically uses [virtual threads](https://openjdk.org/jeps/444) for its internal I/O threads (JSON-RPC reader loop, CLI stderr forwarding). This is implemented via a [Multi-Release JAR (JEP 238)](https://openjdk.org/jeps/238) — no configuration or code changes are required on your part. + +On Java 17–20, the SDK falls back to standard platform (daemon) threads. + +> **Note:** The `ScheduledExecutorService` used for `sendAndWait` timeouts always uses platform threads, because the JDK does not provide a virtual-thread-based scheduled executor. + ## Projects Using This SDK | Project | Description | diff --git a/jbang-example.java b/jbang-example.java index 3d02653c1..3d696caa3 100644 --- a/jbang-example.java +++ b/jbang-example.java @@ -12,6 +12,7 @@ class CopilotSDK { public static void main(String[] args) throws Exception { // Create and start client + // On Java 21+, the SDK automatically uses virtual threads for internal I/O. try (var client = new CopilotClient()) { client.start().get(); diff --git a/pom.xml b/pom.xml index 43e36ec4d..57c2a33e8 100644 --- a/pom.xml +++ b/pom.xml @@ -133,6 +133,7 @@ com.github.copilot.sdk.java + true @@ -261,6 +262,11 @@ 2.44.5 + + src/main/java/**/*.java + src/main/java21/**/*.java + src/test/java/**/*.java + 4.33 @@ -556,6 +562,30 @@ -XX:+EnableDynamicAgentLoading + + + + org.apache.maven.plugins + maven-compiler-plugin + + + compile-java21 + compile + + compile + + + 21 + + ${project.basedir}/src/main/java21 + + true + + + + + + diff --git a/src/main/java/com/github/copilot/sdk/CliServerManager.java b/src/main/java/com/github/copilot/sdk/CliServerManager.java index 217699986..42172e60d 100644 --- a/src/main/java/com/github/copilot/sdk/CliServerManager.java +++ b/src/main/java/com/github/copilot/sdk/CliServerManager.java @@ -172,7 +172,7 @@ JsonRpcClient connectToServer(Process process, String tcpHost, Integer tcpPort) } private void startStderrReader(Process process) { - var stderrThread = new Thread(() -> { + var stderrThread = ThreadFactoryProvider.newThread(() -> { try (BufferedReader reader = new BufferedReader( new InputStreamReader(process.getErrorStream(), StandardCharsets.UTF_8))) { String line; @@ -186,7 +186,6 @@ private void startStderrReader(Process process) { LOG.log(Level.FINE, "Error reading stderr", e); } }, "cli-stderr-reader"); - stderrThread.setDaemon(true); stderrThread.start(); } diff --git a/src/main/java/com/github/copilot/sdk/CopilotClient.java b/src/main/java/com/github/copilot/sdk/CopilotClient.java index f00e2fd11..4dc7fd6c2 100644 --- a/src/main/java/com/github/copilot/sdk/CopilotClient.java +++ b/src/main/java/com/github/copilot/sdk/CopilotClient.java @@ -44,6 +44,13 @@ * provides methods to create and manage conversation sessions. It can either * spawn a CLI server process or connect to an existing server. *

+ * Threading: On Java 21+, the SDK automatically uses virtual threads for + * its internal I/O threads (JSON-RPC reader, CLI stderr forwarding). On Java + * 17–20, standard platform threads are used. The + * {@link java.util.concurrent.ScheduledExecutorService} used for + * {@code sendAndWait} timeouts always uses platform threads because the JDK + * does not provide a virtual-thread-based scheduled executor. + *

* Example usage: * *

{@code
diff --git a/src/main/java/com/github/copilot/sdk/CopilotSession.java b/src/main/java/com/github/copilot/sdk/CopilotSession.java
index 23b1b5368..f35353e4c 100644
--- a/src/main/java/com/github/copilot/sdk/CopilotSession.java
+++ b/src/main/java/com/github/copilot/sdk/CopilotSession.java
@@ -89,6 +89,11 @@
  * session data on disk — the conversation can be resumed later via
  * {@link CopilotClient#resumeSession}. To permanently delete session data, use
  * {@link CopilotClient#deleteSession}.
+ * 

+ * Threading: The {@link java.util.concurrent.ScheduledExecutorService} + * used for {@code sendAndWait} timeouts always uses platform threads regardless + * of the Java version, because the JDK does not provide a virtual-thread-based + * scheduled executor. * *

Example Usage

* diff --git a/src/main/java/com/github/copilot/sdk/JsonRpcClient.java b/src/main/java/com/github/copilot/sdk/JsonRpcClient.java index 66ab1726d..4d16b02d3 100644 --- a/src/main/java/com/github/copilot/sdk/JsonRpcClient.java +++ b/src/main/java/com/github/copilot/sdk/JsonRpcClient.java @@ -15,7 +15,6 @@ import java.util.concurrent.CompletionException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicLong; import java.util.function.BiConsumer; import java.util.logging.Level; @@ -57,11 +56,7 @@ private JsonRpcClient(InputStream inputStream, OutputStream outputStream, Socket this.outputStream = outputStream; this.socket = socket; this.process = process; - this.readerExecutor = Executors.newSingleThreadExecutor(r -> { - Thread t = new Thread(r, "jsonrpc-reader"); - t.setDaemon(true); - return t; - }); + this.readerExecutor = ThreadFactoryProvider.newSingleThreadExecutor("jsonrpc-reader"); startReader(); } diff --git a/src/main/java/com/github/copilot/sdk/ThreadFactoryProvider.java b/src/main/java/com/github/copilot/sdk/ThreadFactoryProvider.java new file mode 100644 index 000000000..3f8ead92b --- /dev/null +++ b/src/main/java/com/github/copilot/sdk/ThreadFactoryProvider.java @@ -0,0 +1,71 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.sdk; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.logging.Logger; + +/** + * Provides thread factories for the SDK's internal thread creation. + *

+ * On Java 17, this class returns standard platform-thread factories. On Java + * 21+, the multi-release JAR overlay replaces this class with one that returns + * virtual-thread factories, giving the SDK lightweight threads for its + * I/O-bound JSON-RPC communication without any user configuration. + *

+ * The {@link java.util.concurrent.ScheduledExecutorService} used for + * {@code sendAndWait} timeouts in {@link CopilotSession} is not + * affected, because the JDK offers no virtual-thread-based scheduled executor. + * + * @since 0.2.2-java.1 + */ +final class ThreadFactoryProvider { + + private static final Logger LOG = Logger.getLogger(ThreadFactoryProvider.class.getName()); + + private ThreadFactoryProvider() { + } + + /** + * Creates a new daemon thread with the given name and runnable. + * + * @param runnable + * the task to run + * @param name + * the thread name for debuggability + * @return the new (unstarted) thread + */ + static Thread newThread(Runnable runnable, String name) { + Thread t = new Thread(runnable, name); + t.setDaemon(true); + return t; + } + + /** + * Creates a single-thread executor suitable for the JSON-RPC reader loop. + * + * @param name + * the thread name for debuggability + * @return a single-thread {@link ExecutorService} + */ + static ExecutorService newSingleThreadExecutor(String name) { + return Executors.newSingleThreadExecutor(r -> { + Thread t = new Thread(r, name); + t.setDaemon(true); + return t; + }); + } + + /** + * Returns {@code true} when this class uses virtual threads (Java 21+ + * multi-release overlay), {@code false} for platform threads. + * + * @return whether virtual threads are in use + */ + static boolean isVirtualThreads() { + return false; + } +} diff --git a/src/main/java21/com/github/copilot/sdk/ThreadFactoryProvider.java b/src/main/java21/com/github/copilot/sdk/ThreadFactoryProvider.java new file mode 100644 index 000000000..0b6720b2d --- /dev/null +++ b/src/main/java21/com/github/copilot/sdk/ThreadFactoryProvider.java @@ -0,0 +1,60 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.sdk; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.logging.Logger; + +/** + * Java 21+ override that uses virtual threads for the SDK's internal thread + * creation. + *

+ * This class is placed under {@code META-INF/versions/21/} in the multi-release + * JAR and replaces the baseline {@code ThreadFactoryProvider} when running on + * Java 21 or later. + * + * @since 0.2.2-java.1 + */ +final class ThreadFactoryProvider { + + private static final Logger LOG = Logger.getLogger(ThreadFactoryProvider.class.getName()); + + private ThreadFactoryProvider() { + } + + /** + * Creates a new virtual thread with the given name and runnable. + * + * @param runnable + * the task to run + * @param name + * the thread name for debuggability + * @return the new (unstarted) virtual thread + */ + static Thread newThread(Runnable runnable, String name) { + return Thread.ofVirtual().name(name).unstarted(runnable); + } + + /** + * Creates a virtual-thread-per-task executor for the JSON-RPC reader loop. + * + * @param name + * the thread name prefix for debuggability + * @return a virtual-thread {@link ExecutorService} + */ + static ExecutorService newSingleThreadExecutor(String name) { + return Executors.newThreadPerTaskExecutor(Thread.ofVirtual().name(name).factory()); + } + + /** + * Returns {@code true} — this is the virtual-thread overlay. + * + * @return {@code true} + */ + static boolean isVirtualThreads() { + return true; + } +} diff --git a/src/site/markdown/advanced.md b/src/site/markdown/advanced.md index 5ae5c8f94..19a3d9ae8 100644 --- a/src/site/markdown/advanced.md +++ b/src/site/markdown/advanced.md @@ -54,6 +54,7 @@ This guide covers advanced scenarios for extending and customizing your Copilot - [Session Capabilities](#Session_Capabilities) - [Outgoing Elicitation via session.getUi()](#Outgoing_Elicitation_via_session.getUi) - [Getting Session Metadata by ID](#Getting_Session_Metadata_by_ID) +- [Virtual Threads (Java 21+)](#Virtual_Threads_Java_21) --- @@ -1237,6 +1238,37 @@ This is more efficient than `listSessions()` when you already know the session I --- +## Virtual Threads (Java 21+) + +When running on **Java 21 or later**, the SDK automatically uses [virtual threads (JEP 444)](https://openjdk.org/jeps/444) for its internal I/O threads. This is implemented via a [Multi-Release JAR (JEP 238)](https://openjdk.org/jeps/238) — no configuration or code changes are required. + +### What Uses Virtual Threads + +| Component | Thread name | Java 17–20 | Java 21+ | +|-----------|------------|-------------|----------| +| JSON-RPC reader loop | `jsonrpc-reader` | Platform (daemon) | Virtual | +| CLI stderr forwarding | `cli-stderr-reader` | Platform (daemon) | Virtual | +| `sendAndWait` timeouts | `sendAndWait-timeout` | Platform (daemon) | Platform (daemon) | + +The `sendAndWait` timeout scheduler always uses platform threads because the JDK does not provide a virtual-thread-based `ScheduledExecutorService`. + +### Performance Implications + +Virtual threads are lightweight and scheduled by the JVM on a shared `ForkJoinPool` carrier pool. For the I/O-bound JSON-RPC communication this SDK performs, virtual threads reduce memory footprint and improve scalability when many concurrent sessions are active. + +### How It Works + +The SDK JAR includes `Multi-Release: true` in its manifest. On Java 21+, the JVM loads the `ThreadFactoryProvider` class from `META-INF/versions/21/`, which uses `Thread.ofVirtual()`. On earlier JVMs, the baseline class under the main class path is loaded, which creates standard platform threads. + +### Verifying Virtual Thread Usage + +You can check at runtime whether the SDK is using virtual threads: + +```java +// Thread names are preserved for debuggability regardless of thread type. +// On Java 21+, the jsonrpc-reader and cli-stderr-reader threads will be virtual. +``` + ## Next Steps - 📖 **[Documentation](documentation.html)** - Core concepts, events, streaming, models, tool filtering, reasoning effort diff --git a/src/site/markdown/getting-started.md b/src/site/markdown/getting-started.md index 39bf43844..913ae7ad2 100644 --- a/src/site/markdown/getting-started.md +++ b/src/site/markdown/getting-started.md @@ -18,7 +18,7 @@ Copilot: In Tokyo it's 75°F and sunny. Great day to be outside! Before you begin, make sure you have: -- **Java 17+** installed +- **Java 17+** installed (**Java 21+ recommended** for automatic [virtual thread](https://openjdk.org/jeps/444) support) - **GitHub Copilot CLI** installed and authenticated ([Installation guide](https://docs.github.com/en/copilot/how-tos/set-up/install-copilot-cli)) Verify the CLI is working: diff --git a/src/site/markdown/index.md b/src/site/markdown/index.md index b599484d9..1abd0efc1 100644 --- a/src/site/markdown/index.md +++ b/src/site/markdown/index.md @@ -8,7 +8,7 @@ Welcome to the documentation for the **GitHub Copilot SDK for Java** — a Java ### Requirements -- Java 17 or later +- Java 17 or later (**Java 21+ recommended** for automatic [virtual thread](https://openjdk.org/jeps/444) support) - GitHub Copilot CLI 1.0.17 or later installed and in PATH (or provide custom `cliPath`) ### Installation diff --git a/src/test/java/com/github/copilot/sdk/ThreadFactoryProviderTest.java b/src/test/java/com/github/copilot/sdk/ThreadFactoryProviderTest.java new file mode 100644 index 000000000..a4dcd74f2 --- /dev/null +++ b/src/test/java/com/github/copilot/sdk/ThreadFactoryProviderTest.java @@ -0,0 +1,62 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.sdk; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.jupiter.api.Test; + +/** + * Tests for {@link ThreadFactoryProvider}, verifying that the factory methods + * produce working threads and executors regardless of the Java version. + */ +class ThreadFactoryProviderTest { + + @Test + void newThreadCreatesNamedThread() { + var ran = new AtomicReference(); + Thread t = ThreadFactoryProvider.newThread(() -> ran.set(Thread.currentThread().getName()), "test-thread"); + assertNotNull(t); + assertEquals("test-thread", t.getName()); + t.start(); + try { + t.join(5000); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + assertEquals("test-thread", ran.get()); + } + + @Test + void newSingleThreadExecutorRunsTask() throws Exception { + ExecutorService executor = ThreadFactoryProvider.newSingleThreadExecutor("test-executor"); + try { + var ran = new AtomicReference(false); + executor.submit(() -> ran.set(true)).get(5, TimeUnit.SECONDS); + assertTrue(ran.get()); + } finally { + executor.shutdownNow(); + } + } + + @Test + void isVirtualThreadsReturnsBoolean() { + // On Java 17 this returns false; on Java 21+ it returns true. + // We just verify it doesn't throw. + boolean result = ThreadFactoryProvider.isVirtualThreads(); + int javaVersion = Runtime.version().feature(); + if (javaVersion >= 21) { + assertTrue(result, "Expected virtual threads on Java 21+"); + } else { + assertTrue(!result, "Expected platform threads on Java < 21"); + } + } +} From ce4fd3d7b0983cce5a6a5af1b85c899703ffa081 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Apr 2026 14:04:27 +0000 Subject: [PATCH 3/7] Address code review feedback: use assertFalse, simplify README Agent-Logs-Url: https://github.com/github/copilot-sdk-java/sessions/e74714e4-bb84-4af7-b1ef-44f576c5b37c Co-authored-by: brunoborges <129743+brunoborges@users.noreply.github.com> --- README.md | 12 +++--------- .../copilot/sdk/ThreadFactoryProviderTest.java | 3 ++- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 63edf0001..dc1de5d28 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Java SDK for programmatic control of GitHub Copilot CLI, enabling you to build A ### Requirements -- Java 17 or later. **JDK 21+ recommended** for automatic virtual thread support (see [Virtual Threads](#virtual-threads) below). Selecting JDK 25 additionally enables the use of virtual threads for the custom executor, as shown in the [Quick Start](#quick-start). +- Java 17 or later. **JDK 21+ recommended** — the SDK automatically uses virtual threads for its internal I/O on Java 21+ (see [Virtual Threads](#virtual-threads)). - GitHub Copilot CLI 1.0.17 or later installed and in `PATH` (or provide custom `cliPath`) ### Maven @@ -69,23 +69,17 @@ implementation 'com.github:copilot-sdk-java:0.2.1-java.1' import com.github.copilot.sdk.CopilotClient; import com.github.copilot.sdk.events.AssistantMessageEvent; import com.github.copilot.sdk.events.SessionUsageInfoEvent; -import com.github.copilot.sdk.json.CopilotClientOptions; import com.github.copilot.sdk.json.MessageOptions; import com.github.copilot.sdk.json.PermissionHandler; import com.github.copilot.sdk.json.SessionConfig; -import java.util.concurrent.Executors; - public class CopilotSDK { public static void main(String[] args) throws Exception { var lastMessage = new String[]{null}; // Create and start client - try (var client = new CopilotClient()) { // JDK 25+: comment out this line - // JDK 25+: uncomment the following 3 lines for virtual thread support - // var options = new CopilotClientOptions() - // .setExecutor(Executors.newVirtualThreadPerTaskExecutor()); - // try (var client = new CopilotClient(options)) { + // On Java 21+, the SDK automatically uses virtual threads for internal I/O. + try (var client = new CopilotClient()) { client.start().get(); // Create a session diff --git a/src/test/java/com/github/copilot/sdk/ThreadFactoryProviderTest.java b/src/test/java/com/github/copilot/sdk/ThreadFactoryProviderTest.java index a4dcd74f2..a87782b4e 100644 --- a/src/test/java/com/github/copilot/sdk/ThreadFactoryProviderTest.java +++ b/src/test/java/com/github/copilot/sdk/ThreadFactoryProviderTest.java @@ -5,6 +5,7 @@ package com.github.copilot.sdk; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -56,7 +57,7 @@ void isVirtualThreadsReturnsBoolean() { if (javaVersion >= 21) { assertTrue(result, "Expected virtual threads on Java 21+"); } else { - assertTrue(!result, "Expected platform threads on Java < 21"); + assertFalse(result, "Expected platform threads on Java < 21"); } } } From af2540dedc1af111f2538e137414cd28169f3416 Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Tue, 7 Apr 2026 10:07:58 -0400 Subject: [PATCH 4/7] Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dc1de5d28..65e1bf6c3 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Java SDK for programmatic control of GitHub Copilot CLI, enabling you to build A ### Requirements -- Java 17 or later. **JDK 21+ recommended** — the SDK automatically uses virtual threads for its internal I/O on Java 21+ (see [Virtual Threads](#virtual-threads)). +- Java 17 or later. **JDK 21+ recommended** — the SDK automatically uses virtual threads for its internal I/O on Java 21+, and any virtual-thread-based custom executor examples using `Executors.newVirtualThreadPerTaskExecutor()` also require Java 21+ (see [Virtual Threads](#virtual-threads)). - GitHub Copilot CLI 1.0.17 or later installed and in `PATH` (or provide custom `cliPath`) ### Maven From 25829a48aad37ede0a7aa4c19a279f2d08a195cf Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Tue, 7 Apr 2026 10:08:45 -0400 Subject: [PATCH 5/7] Update src/test/java/com/github/copilot/sdk/ThreadFactoryProviderTest.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../copilot/sdk/ThreadFactoryProviderTest.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/test/java/com/github/copilot/sdk/ThreadFactoryProviderTest.java b/src/test/java/com/github/copilot/sdk/ThreadFactoryProviderTest.java index a87782b4e..3ea57ce96 100644 --- a/src/test/java/com/github/copilot/sdk/ThreadFactoryProviderTest.java +++ b/src/test/java/com/github/copilot/sdk/ThreadFactoryProviderTest.java @@ -50,14 +50,16 @@ void newSingleThreadExecutorRunsTask() throws Exception { @Test void isVirtualThreadsReturnsBoolean() { - // On Java 17 this returns false; on Java 21+ it returns true. - // We just verify it doesn't throw. + // Unit tests run against exploded classes rather than the packaged + // multi-release JAR, so Java 21+ may still load the base implementation + // and report false here. Verify only behavior that does not depend on + // multi-release class selection. boolean result = ThreadFactoryProvider.isVirtualThreads(); int javaVersion = Runtime.version().feature(); - if (javaVersion >= 21) { - assertTrue(result, "Expected virtual threads on Java 21+"); - } else { + if (javaVersion < 21) { assertFalse(result, "Expected platform threads on Java < 21"); + } else if (result) { + assertTrue(javaVersion >= 21, "Virtual threads are only supported on Java 21+"); } } } From fef1833066f230c874ba235fb785d6f50829df13 Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Tue, 7 Apr 2026 10:09:34 -0400 Subject: [PATCH 6/7] s Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../com/github/copilot/sdk/ThreadFactoryProvider.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java21/com/github/copilot/sdk/ThreadFactoryProvider.java b/src/main/java21/com/github/copilot/sdk/ThreadFactoryProvider.java index 0b6720b2d..72970df70 100644 --- a/src/main/java21/com/github/copilot/sdk/ThreadFactoryProvider.java +++ b/src/main/java21/com/github/copilot/sdk/ThreadFactoryProvider.java @@ -39,14 +39,15 @@ static Thread newThread(Runnable runnable, String name) { } /** - * Creates a virtual-thread-per-task executor for the JSON-RPC reader loop. + * Creates a single-thread executor backed by a virtual-thread factory for the + * JSON-RPC reader loop. * * @param name - * the thread name prefix for debuggability - * @return a virtual-thread {@link ExecutorService} + * the thread name for debuggability + * @return a single-thread virtual-thread-backed {@link ExecutorService} */ static ExecutorService newSingleThreadExecutor(String name) { - return Executors.newThreadPerTaskExecutor(Thread.ofVirtual().name(name).factory()); + return Executors.newSingleThreadExecutor(Thread.ofVirtual().name(name).factory()); } /** From 9d456f6c69bd719b56ab17818bdd3f603ea0c9a1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Apr 2026 14:11:59 +0000 Subject: [PATCH 7/7] Remove unused LOG fields, use newSingleThreadExecutor for Java 21 overlay, fix empty docs section Agent-Logs-Url: https://github.com/github/copilot-sdk-java/sessions/46706dd3-402c-41c9-9150-3cfae7db8c3f Co-authored-by: brunoborges <129743+brunoborges@users.noreply.github.com> --- .../java/com/github/copilot/sdk/ThreadFactoryProvider.java | 3 --- .../com/github/copilot/sdk/ThreadFactoryProvider.java | 3 --- src/site/markdown/advanced.md | 7 +------ 3 files changed, 1 insertion(+), 12 deletions(-) diff --git a/src/main/java/com/github/copilot/sdk/ThreadFactoryProvider.java b/src/main/java/com/github/copilot/sdk/ThreadFactoryProvider.java index 3f8ead92b..2f864d921 100644 --- a/src/main/java/com/github/copilot/sdk/ThreadFactoryProvider.java +++ b/src/main/java/com/github/copilot/sdk/ThreadFactoryProvider.java @@ -6,7 +6,6 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.logging.Logger; /** * Provides thread factories for the SDK's internal thread creation. @@ -24,8 +23,6 @@ */ final class ThreadFactoryProvider { - private static final Logger LOG = Logger.getLogger(ThreadFactoryProvider.class.getName()); - private ThreadFactoryProvider() { } diff --git a/src/main/java21/com/github/copilot/sdk/ThreadFactoryProvider.java b/src/main/java21/com/github/copilot/sdk/ThreadFactoryProvider.java index 72970df70..6fef02aca 100644 --- a/src/main/java21/com/github/copilot/sdk/ThreadFactoryProvider.java +++ b/src/main/java21/com/github/copilot/sdk/ThreadFactoryProvider.java @@ -6,7 +6,6 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.logging.Logger; /** * Java 21+ override that uses virtual threads for the SDK's internal thread @@ -20,8 +19,6 @@ */ final class ThreadFactoryProvider { - private static final Logger LOG = Logger.getLogger(ThreadFactoryProvider.class.getName()); - private ThreadFactoryProvider() { } diff --git a/src/site/markdown/advanced.md b/src/site/markdown/advanced.md index 19a3d9ae8..9b1484750 100644 --- a/src/site/markdown/advanced.md +++ b/src/site/markdown/advanced.md @@ -1262,12 +1262,7 @@ The SDK JAR includes `Multi-Release: true` in its manifest. On Java 21+, the JVM ### Verifying Virtual Thread Usage -You can check at runtime whether the SDK is using virtual threads: - -```java -// Thread names are preserved for debuggability regardless of thread type. -// On Java 21+, the jsonrpc-reader and cli-stderr-reader threads will be virtual. -``` +Thread names are preserved for debuggability regardless of thread type. On Java 21+, a thread dump will show `jsonrpc-reader` and `cli-stderr-reader` as virtual threads rather than platform threads. You can verify this via `jcmd Thread.dump_to_file -format=json ` or your IDE's thread inspector. ## Next Steps