{"slug": "using-the-github-copilot-sdk-for-java", "title": "Using the GitHub Copilot SDK for Java", "summary": "Microsoft and GitHub released the GitHub Copilot SDK for Java, version 1.0.7-preview.1, a framework-agnostic client library that lets enterprise Java developers drive AI from idiomatic Java code using annotations, virtual threads, and CompletableFuture. The SDK supports any direct model provider, including OpenAI, Azure, Anthropic, or OpenAI-compatible endpoints, via a provider/ProviderConfig with a custom baseUrl and apiKey, and requires no Copilot subscription. A sample Jakarta EE 11 application demonstrates a real-estate lead-management agent pipeline using Open Liberty 26.0.0.5, PrimeFaces 15.0.16, and H2 in-memory database with 10 seed property listings.", "body_md": "###\n[Edward Burns](https://github.blog/author/edburns/)\n\nEd Burns is a Principal Software Engineer working to bring Java idiomatic experiences to Microsoft and GitHub technologies. Ed's been working with Java since 1997 in all aspects from client to server to cloud and AI.\n\nEnterprise Java developers have a new superpower—drive GitHub Copilot from idiomatic Java code with annotations, virtual threads, and more.\n\nJava developers no longer have to rely on Java framework-specific approaches to drive AI from their enterprise apps.\n\nWhile it is true that Langchain4j empowered developers by disintermediating specific AI vendors, you still had a dependency on Langchain4j. And with Spring AI, well, of course you had a dependency on design choices made by Spring, if not on Spring itself.\n\nNow, GitHub Copilot SDK for Java is the first truly framework agnostic way to drive AI from Java. And with its BYOK support, GitHub Copilot SDK for Java is also AI vendor neutral.\n\n💡 Even though it’s called GitHub Copilot SDK, you can use it with any direct model provider, such as OpenAI, Azure, Anthropic, or OpenAI-compatible endpoints, by passing a `provider` /`ProviderConfig` with your own `baseUrl` + `apiKey` (or bearer token). No Copilot subscription required. |\n\nThe GitHub Copilot SDK for Java is a client library that empowers your server-side Java code to create Copilot agent sessions, register tools, send prompts, and receive structured responses—all programmatically. It works in server environments, including Jakarta EE and Spring. If you’ve been building enterprise Java for any length of time, this SDK will feel like home: `CompletableFuture`\n\n, annotations, lambdas, virtual threads, it’s all here.\n\nThis post shows you how to use the SDK, walks through a complete Jakarta EE 11 sample application, and leaves you with concrete next steps to try it yourself. I chose Jakarta EE 11 for my demo because I was the lead release coordinator for that release. I believe in open standards as the best way to empower developers. For more on Jakarta EE 11 see [this InfoQ article](https://www.infoq.com/news/2025/07/jakarta-ee-11-updates/).\n\nThis sample app is an agent harness using Jakarta EE 11. But, of course, developers can build their own agent harness using the well-known Java frameworks and libraries of their choice.\n\n[Clone the sample app and try it yourself >](https://github.com/microsoft/Build26-BRK206-your-agent-anywhere-multiclient-multidevice-with-github-copilot-sdk)\n\nThe SDK is available as a Maven dependency:\n\n```\n<dependency>\n    <groupId>com.github</groupId>\n    <artifactId>copilot-sdk-java</artifactId>\n    <version>1.0.7-preview.1</version>\n</dependency>\n```\n\n**Prerequisites:**\n\nThe best way to see the SDK in action is to run [this sample application](https://github.com/microsoft/Build26-BRK206-your-agent-anywhere-multiclient-multidevice-with-github-copilot-sdk).\n\n```\ngit clone https://github.com/microsoft/Build26-BRK206-your-agent-anywhere-multiclient-multidevice-with-github-copilot-sdk.git\ncd Build26-BRK206-your-agent-anywhere-multiclient-multidevice-with-github-copilot-sdk/src/java-agent-orchestrator\nmvn clean package liberty:run\n# Open http://localhost:9080/index.xhtml\n```\n\nThe Java demo is built on:\n\n| Concern | Technology |\n|---|---|\n| Runtime | Open Liberty 26.0.0.5 |\n| Platform | Jakarta EE 11 (Faces 4.1, CDI 4.1, WebSocket 2.2, Data 1.0, Persistence 3.2) |\n| UI | PrimeFaces 15.0.16 |\n| AI orchestration | Copilot SDK for Java 1.0.7-preview.1 |\n| Database | H2 in-memory (10 seed property listings) |\n\nThe application is a real-estate lead-management agent pipeline. A customer submits an enquiry (“I’m looking for a 3-bedroom house in London under £800,000”), and the system spins up an isolated Copilot Agent on a virtual thread to process it through a pipeline:\n\nThe architecture uses Jakarta WebSocket to push real-time status updates from the server to the browser, so you can watch agents progress through phases as the model calls tools:\n\nSubmit multiple inquiries simultaneously to see concurrent virtual-thread agents in action. Each one processes independently with its own Copilot session.\n\nLet’s walk through the key SDK features as they appear in the sample code.\n\n`@CopilotTool`\n\nThis is the headline API. If you’ve ever written a `@GET`\n\nendpoint in JAX-RS or an `@MessageDriven`\n\nbean, this will feel instantly familiar:\n\n```\n@CopilotTool(value = \"Sets the current phase of the agent. Use this to report progress.\",\n             name = \"set_current_phase\")\npublic String setCurrentPhase(\n        @CopilotToolParam(\"The phase to transition to (VALIDATING, SEARCHING, \"\n                + \"WRITING_REPORT, REJECTED_GARBAGE, REJECTED_NO_MATCHES, or DONE)\")\n        String phaseName) {\n    phase = Phase.valueOf(phaseName.trim().toUpperCase(Locale.ROOT));\n    notifyUi();\n    return \"Phase set to \" + phase.getLabel();\n}\n```\n\nThe `@CopilotTool`\n\nannotation declares the method as a tool the model can call. The `@CopilotToolParam`\n\nannotation describes each parameter so the model knows what to pass. The SDK handles all the JSON Schema generation, argument parsing, and dispatch. You just write a normal Java method.\n\n**Two build prerequisites for @CopilotTool.** The annotation-based tool API is currently an experimental feature of the SDK, so you need to configure two things in your Maven build:\n\n`-Acopilot.experimental.allowed=true`\n\nto the compiler. Without this flag, the annotation processor will refuse to generate the tool metadata. For more details on the experimental APIs see `annotationProcessorPath`\n\nso the compiler can find the `@CopilotTool`\n\nprocessor and generate the `$$CopilotToolMeta`\n\nclasses at compile time.Both are configured in the `maven-compiler-plugin`\n\n:\n\n```\n<plugin>\n    <groupId>org.apache.maven.plugins</groupId>\n    <artifactId>maven-compiler-plugin</artifactId>\n    <version>3.15.0</version>\n    <configuration>\n        <compilerArgs>\n            <arg>-Acopilot.experimental.allowed=true</arg>\n        </compilerArgs>\n        <annotationProcessorPaths>\n            <path>\n                <groupId>com.github</groupId>\n                <artifactId>copilot-sdk-java</artifactId>\n                <version>1.0.7-preview.1</version>\n            </path>\n        </annotationProcessorPaths>\n    </configuration>\n</plugin>\n```\n\nTo register all annotated tools from an object:\n\n```\nList<ToolDefinition> annotatedTools = ToolDefinition.fromObject(this);\n```\n\n`ToolDefinition.from(...)`\n\nWhen you want a tool defined at the call site without a dedicated method, use the lambda style:\n\n```\nToolDefinition reportIntentTool = ToolDefinition\n        .from(\"report_intent\",\n              \"Reports the current intent of the agent\",\n              Param.of(String.class, \"intent\", \"Intent in max 4 words\"),\n              (String intent) -> {\n                  currentIntent = intent;\n                  addEvent(Instant.now(), \"intent\", \"Intent updated\", intent);\n                  notifyUi();\n                  return \"ok\";\n              })\n        .overridesBuiltInTool(true);\n```\n\nNotice `.overridesBuiltInTool(true)`\n\n. This tells the SDK that our `report_intent`\n\ntool deliberately replaces a built-in tool of the same name. This is useful when you need custom behaviour for a tool the model already knows about.\n\nTools don’t have to live in the same class as your agent logic. Here’s `searchProperties`\n\ndefined in a separate CDI bean:\n\n```\n@ApplicationScoped\npublic class PropertyDatabase {\n\n    @CopilotTool(value = \"Searches the real estate listings database. \"\n                       + \"Returns up to 10 matching properties.\",\n                 name = \"search_properties\")\n    public List<Property> searchProperties(\n            @CopilotToolParam(\"Property type substring (e.g. 'flat', 'house')\") String type,\n            @CopilotToolParam(\"City substring (e.g. 'London', 'Bristol')\") String city,\n            @CopilotToolParam(\"Minimum number of bedrooms (0 for no minimum)\") int minBedrooms,\n            @CopilotToolParam(\"Maximum price in GBP (0 for no maximum)\") double maxPriceGbp) {\n        // ... filter and return matching properties ...\n    }\n}\n```\n\nYou would normally register these with `ToolDefinition.fromObject(propertyDatabase)`\n\n. In the sample app, we use a lambda wrapper instead, because CDI client proxies can obscure the annotation metadata.\n\nThe SDK gives you fine-grained control over the system message. Use `SystemMessageMode.CUSTOMIZE`\n\nto replace specific sections while preserving the rest:\n\n```\nSystemMessageConfig systemMessage = new SystemMessageConfig()\n        .setMode(SystemMessageMode.CUSTOMIZE)\n        .setSections(Map.of(SystemMessageSections.IDENTITY,\n            new SectionOverride()\n                .setAction(SectionOverrideAction.REPLACE)\n                .setContent(\"\"\"\n                    You are part of a real estate recommendation system.\n                    You will receive enquiries from customers, and you must\n                    carry out the following workflow...\n                    \"\"\")));\n```\n\nThe text block (`\"\"\"...\"\"\"`\n\n) makes multi-line prompts readable without string concatenation. The `IDENTITY`\n\nsection override replaces only the model’s self-description while leaving safety guardrails intact. If you prefer a simpler approach, `SystemMessageMode.APPEND`\n\nadds your content after the default system message without replacing anything.\n\n`sendAndWait(...)`\n\nOne line kicks off the full agentic loop:\n\n```\nsession = client.createSession(sessionConfig).get();\n// ...\nAssistantMessageEvent result = session.sendAndWait(escapedEnquiry).get();\n```\n\nBehind `.get()`\n\n, the model reasons, calls your tools (potentially multiple times), and returns its final response. On a virtual thread, `.get()`\n\nis cheap. No platform thread is consumed while waiting. The SDK dispatches tool calls to your registered handlers automatically and feeds results back to the model until it’s done.\n\n`session.on(...)`\n\nSubscribe to session events to build responsive UIs:\n\n``` php\nsessionSubscription = session.on(event -> {\n    captureSessionEvent(event);\n    uiUpdateSocket.pushDetailUpdate(id);\n});\n```\n\nEvery tool call, every result, every assistant message fires an event. The sample app captures these events and pushes them to the browser via Jakarta WebSocket, so the pipeline dashboard updates in real time. You can use pattern matching to handle specific event types:\n\n```\nif (event instanceof AssistantMessageEvent msg) {\n    finalReport = msg.getData().content();\n} else if (event instanceof ToolExecutionStartEvent start) {\n    // Tool is being invoked...\n}\n```\n\nThe client is configured for server-side operation:\n\n```\ncopilotClient = new CopilotClient(\n        new CopilotClientOptions()\n                .setMode(CopilotClientMode.EMPTY)\n                .setCopilotHome(copilotHome)\n                .setExecutor(contextualVirtualThreadExecutor));\n```\n\n`CopilotClientMode.EMPTY`\n\nmeans no IDE integration — the client talks directly to the Copilot CLI. The custom `Executor`\n\n(discussed below) ensures tool callbacks run with container context.\n\nFor permission handling, the sample uses:\n\n```\nsessionConfig.setOnPermissionRequest(PermissionHandler.APPROVE_ALL);\n```\n\n`APPROVE_ALL`\n\nis appropriate for demos and development. In production, implement a real permission policy that validates which tools the model is allowed to invoke.\n\nThe SDK is not a framework island. It composes naturally with Jakarta EE — and of course also with proprietary frameworks such as Spring.\n\n**The Executor parameter is the key integration point.** Jakarta Concurrency (§5.2 in the 3.1 spec) requires that application-created threads be obtained from a\n\n`ManagedThreadFactory`\n\nso the container can:`@PreDestroy`\n\n/ server stop)`contextualRunnable`\n\n)Open Liberty 26.x supports virtual-thread `ManagedThreadFactory`\n\nvia the `virtual`\n\nattribute in `server.xml`\n\n.\n\n```\n<managedThreadFactory jndiName=\"concurrent/virtualThreadFactory\" virtual=\"true\" />\n```\n\nThen, in `AppState.java`\n\nwe inject the factory:\n\n```\n@Resource(lookup = \"concurrent/virtualThreadFactory\")\nprivate ManagedThreadFactory virtualThreadFactory;\n```\n\nAnd use it to create the `Executor`\n\nwe pass to the Copilot SDK.\n\n```\n// The ManagedThreadFactory (virtual=true) creates container-managed virtual\n// threads that automatically propagate CDI, JNDI, and transaction context.\nExecutor managedVirtualExecutor = runnable ->\n    virtualThreadFactory.newThread(runnable).start()\n\nString copilotHome = Path.of(System.getProperty(\"user.home\"), \".copilot\").toString();\nCopilotClientOptions copilotClientOptions = new CopilotClientOptions()\n        .setMode(CopilotClientMode.EMPTY)\n        .setCopilotHome(copilotHome)\n        .setExecutor(managedVirtualExecutor);\ncopilotClient = new CopilotClient(copilotClientOptions);\n```\n\nThis creates virtual threads that carry the container’s context. When the SDK dispatches a tool call to `searchProperties()`\n\n, that method can `@Inject`\n\na JPA repository and query the database, because the container context is present on the callback thread.\n\nOther integration patterns in the sample:\n\n`@ApplicationScoped`\n\n`CopilotClient`\n\n(one client per application lifecycle).`f:websocket`\n\npush`PushContext`\n\n.`@Repository`\n\n**Fine-grained tool access control with ToolSet.** The\n\n`SessionConfig`\n\nlets you specify exactly which tools each session can access:\n\n```\nsessionConfig.setAvailableTools(new ToolSet()\n        .addCustom(\"*\")           // all registered custom tools\n        .addBuiltIn(\"web_fetch\")); // only the web_fetch built-in\n```\n\nThis is an important production concern. Rather than exposing every built-in tool (file system access, shell execution, etc.), you explicitly opt in to only what the agent needs. In the sample app, we allow all custom tools plus `web_fetch`\n\nso the agent can look up real-time property information during the Search phase.\n\nHere’s what we covered:\n\n`CompletableFuture`\n\n, annotations, lambdas, and virtual threads make the SDK feel like idiomatic Java, not a ported-from-another-language afterthought.`sendAndWait(...)`\n\nhandles the full tool-calling loop automatically.`session.on(...)`\n\nenables responsive UIs and observability.`Executor`\n\nintegration point.`provider`\n\n/`ProviderConfig`\n\nwith your own `baseUrl`\n\n+ `apiKey`\n\n(or bearer token). No Copilot subscription required.`session.setModel(...)`\n\nto experiment with different Copilot models.`@CopilotTool`\n\nmethod (a mortgage calculator, a school-district lookup) and watch the agent discover and use it.The Copilot SDK for Java puts the full power of GitHub Copilot behind your Java code with no IDE required and no framework lock-in.\n\nGo beyond chat in the GitHub Copilot app with these slash commands. They’ll help you plan, collaborate, automate, and customize your dev workflow.\n\nLearn how to build tools to simplify how you work—without writing a single line of code.\n\nInstead of one huge, un-reviewable pull request, teach coding agents to decompose work into a clean, ordered stack with GitHub stacked pull requests.", "url": "https://wpnews.pro/news/using-the-github-copilot-sdk-for-java", "canonical_source": "https://github.blog/engineering/using-the-github-copilot-sdk-for-java/", "published_at": "2026-08-10 19:30:00+00:00", "updated_at": "2026-08-10 19:37:55.288572+00:00", "lang": "en", "topics": ["developer-tools", "ai-tools", "ai-agents", "artificial-intelligence"], "entities": ["GitHub", "Microsoft", "Edward Burns", "GitHub Copilot SDK for Java", "Langchain4j", "Spring AI", "OpenAI", "Azure"], "alternates": {"html": "https://wpnews.pro/news/using-the-github-copilot-sdk-for-java", "markdown": "https://wpnews.pro/news/using-the-github-copilot-sdk-for-java.md", "text": "https://wpnews.pro/news/using-the-github-copilot-sdk-for-java.txt", "jsonld": "https://wpnews.pro/news/using-the-github-copilot-sdk-for-java.jsonld"}}