Files
awesome-copilot/cookbook/copilot-sdk/java/error-handling.md
Bruno Borges e03ccb978a fix: Java cookbook recipes to compile with copilot-sdk-java 0.2.1-java.1
Fix compilation errors and documentation inaccuracies in Java cookbook
recipes against the actual SDK API:

- MultipleSessions: Replace non-existent destroy() with close()
- AccessibilityReport: Replace non-existent McpServerConfig class with
  Map<String, Object> (the actual type accepted by setMcpServers)
- error-handling.md: Replace non-existent session.addTool(),
  ToolDefinition.builder(), and ToolResultObject with actual SDK APIs
  (ToolDefinition.create(), SessionConfig.setTools(),
  CompletableFuture<Object> return type)

All 7 recipes now compile successfully with jbang build.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-06 15:20:20 -04:00

6.1 KiB

Error Handling Patterns

Handle errors gracefully in your Copilot SDK applications.

Runnable example: recipe/ErrorHandling.java

jbang recipe/ErrorHandling.java

Example scenario

You need to handle various error conditions like connection failures, timeouts, and invalid responses.

Basic try-with-resources

Java's try-with-resources ensures the client is always cleaned up, even when exceptions occur.

//DEPS com.github:copilot-sdk-java:0.2.1-java.1

import com.github.copilot.sdk.*;
import com.github.copilot.sdk.json.*;

public class BasicErrorHandling {
    public static void main(String[] args) {
        try (var client = new CopilotClient()) {
            client.start().get();
            var session = client.createSession(
                new SessionConfig()
                    .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
                    .setModel("gpt-5")).get();

            var response = session.sendAndWait(
                new MessageOptions().setPrompt("Hello!")).get();
            System.out.println(response.getData().content());

            session.close();
        } catch (Exception ex) {
            System.err.println("Error: " + ex.getMessage());
        }
    }
}

Handling specific error types

Every CompletableFuture.get() call wraps failures in ExecutionException. Unwrap the cause to inspect the real error.

import java.io.IOException;
import java.util.concurrent.ExecutionException;

try (var client = new CopilotClient()) {
    client.start().get();
} catch (ExecutionException ex) {
    var cause = ex.getCause();
    if (cause instanceof IOException) {
        System.err.println("Copilot CLI not found or could not connect: " + cause.getMessage());
    } else {
        System.err.println("Unexpected error: " + cause.getMessage());
    }
} catch (InterruptedException ex) {
    Thread.currentThread().interrupt();
    System.err.println("Interrupted while starting client.");
}

Timeout handling

Use the overloaded get(timeout, unit) on CompletableFuture to enforce time limits.

import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

var session = client.createSession(
    new SessionConfig()
        .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
        .setModel("gpt-5")).get();

try {
    var response = session.sendAndWait(
        new MessageOptions().setPrompt("Complex question..."))
        .get(30, TimeUnit.SECONDS);

    System.out.println(response.getData().content());
} catch (TimeoutException ex) {
    System.err.println("Request timed out after 30 seconds.");
    session.abort().get();
}

Aborting a request

Cancel an in-flight request by calling session.abort().

var session = client.createSession(
    new SessionConfig()
        .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
        .setModel("gpt-5")).get();

// Start a request without waiting
session.send(new MessageOptions().setPrompt("Write a very long story..."));

// Abort after some condition
Thread.sleep(5000);
session.abort().get();
System.out.println("Request aborted.");

Graceful shutdown

Use a JVM shutdown hook to clean up when the process is interrupted.

var client = new CopilotClient();
client.start().get();

Runtime.getRuntime().addShutdownHook(new Thread(() -> {
    System.out.println("Shutting down...");
    try {
        client.close();
    } catch (Exception ex) {
        System.err.println("Cleanup error: " + ex.getMessage());
    }
}));

Try-with-resources (nested)

When working with multiple sessions, nest try-with-resources blocks to guarantee each resource is closed.

try (var client = new CopilotClient()) {
    client.start().get();

    try (var session = client.createSession(
            new SessionConfig()
                .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
                .setModel("gpt-5")).get()) {

        session.sendAndWait(
            new MessageOptions().setPrompt("Hello!")).get();
    } // session is closed here

} // client is closed here

Handling tool errors

When defining tools, return an error string to signal a failure back to the model instead of throwing.

import com.github.copilot.sdk.json.ToolDefinition;
import java.util.concurrent.CompletableFuture;

var readFileTool = ToolDefinition.create(
    "read_file",
    "Read a file from disk",
    Map.of(
        "type", "object",
        "properties", Map.of(
            "path", Map.of("type", "string", "description", "File path")
        ),
        "required", List.of("path")
    ),
    invocation -> {
        try {
            var path = (String) invocation.getArguments().get("path");
            var content = java.nio.file.Files.readString(
                java.nio.file.Path.of(path));
            return CompletableFuture.completedFuture(content);
        } catch (java.io.IOException ex) {
            return CompletableFuture.completedFuture(
                "Error: Failed to read file: " + ex.getMessage());
        }
    }
);

// Register tools when creating the session
var session = client.createSession(
    new SessionConfig()
        .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
        .setModel("gpt-5")
        .setTools(List.of(readFileTool))
).get();

Best practices

  1. Use try-with-resources: Always wrap CopilotClient (and sessions, if AutoCloseable) in try-with-resources to guarantee cleanup.
  2. Unwrap ExecutionException: Call getCause() to inspect the real error — the outer ExecutionException is just a CompletableFuture wrapper.
  3. Restore interrupt flag: When catching InterruptedException, call Thread.currentThread().interrupt() to preserve the interrupted status.
  4. Set timeouts: Use get(timeout, TimeUnit) instead of bare get() for any call that could block indefinitely.
  5. Return tool errors, don't throw: Return an error string from the CompletableFuture so the model can recover gracefully.
  6. Log errors: Capture error details for debugging — consider a logging framework like SLF4J for production applications.