github/copilot-sdk · error · IllegalArgumentException
handler must not be null for tool ' + toolName + '
Error message
handler must not be null for tool ' + toolName + '
What it means
A ToolDefinition must have an actual handler to execute when the tool is invoked; a null handler would produce a tool that cannot run. requireNonNullHandler throws this IllegalArgumentException when the handler argument to a from* factory is null, including the tool name in the message to help locate the offending registration.
Solutions
- Pass a non-null handler (lambda, method reference, or Function/BiFunction) for each tool registration.
- Null-check the handler (or assert bean presence) before calling the from* method and fail fast with context.
- Fix DI/initialization ordering so the handler object exists before ToolDefinition registration runs.
Example fix
// before
Function<String, String> h = handlers.get("search"); // null if missing
ToolDefinition.from("search", "Search docs", h);
// after
Function<String, String> h = Objects.requireNonNull(handlers.get("search"), "missing search handler");
ToolDefinition.from("search", "Search docs", h); Defensive patterns
Strategy: type-guard
Validate before calling
Objects.requireNonNull(handler, "handler must not be null");
Type guard
static boolean hasHandler(Object handler) { return handler != null; } Try / catch
try { return ToolDefinition.from(name, desc, handler); } catch (IllegalArgumentException e) {
log.error("registration for tool '{}' failed: {}", name, e.getMessage()); throw e;
} Prevention
- Wire handlers via DI and verify bean presence at startup.
- Avoid nullable map lookups for handlers — use getOrDefault or explicit checks.
- Register tools in an initialization phase where missing handlers fail fast, not lazily.
When it happens
Trigger: Calling ToolDefinition.from/fromAsync/fromWithToolInvocation/fromAsyncWithToolInvocation with a null handler — typically a method reference or lambda expression that resolved to null, or a nullable field/config-supplied handler.
Common situations: Spring/DI wiring where the handler bean is null at registration time; conditionally-initialized handlers (e.g. behind a feature flag) that were never created; passing a static method reference from a class that failed to load; storing handlers in a map and looking up a missing key.
Related errors
- Tool name must not be null or blank
- Tool description must not be null or blank
- CliUrl is mutually exclusive with CliPath
- TcpConnectionToken must be a non-empty string
- Invalid value ' '. Expected 'inprocess', 'stdio', or unset.
AI-assisted analysis of github/copilot-sdk@cd8cf15dc3 (2026-09-09).
Data as JSON: /api/errors/7123bdf680ced644.
Report an issue: GitHub.
Appendix: source
Thrown at java/sdk/src/main/java/com/github/copilot/rpc/ToolDefinition.java:920
// ------------------------------------------------------------------
// Validation helpers
// ------------------------------------------------------------------
private static void requireNonBlankToolName(String name) {
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("Tool name must not be null or blank");
}
}
private static void requireNonBlankDescription(String description) {
if (description == null || description.isBlank()) {
throw new IllegalArgumentException("Tool description must not be null or blank");
}
}
private static void requireNonNullHandler(Object handler, String toolName) {
if (handler == null) {
throw new IllegalArgumentException("handler must not be null for tool '" + toolName + "'");
}
}
@SuppressWarnings("unchecked")
private static List<ToolDefinition> loadDefinitions(Class<?> clazz, Object instance) {
String metaClassName = clazz.getName() + "$$CopilotToolMeta";
try {
Class<?> metaClass = Class.forName(metaClassName, true, clazz.getClassLoader());
var provider = (com.github.copilot.tool.CopilotToolMetadataProvider<Object>) metaClass
.getDeclaredConstructor().newInstance();
return provider.definitions(instance, getConfiguredMapper());
} catch (ClassNotFoundException e) {
throw new IllegalStateException("Generated class " + metaClassName + " not found. "
+ "Ensure the CopilotToolProcessor annotation processor ran during compilation. "
+ "Add the copilot-sdk-java dependency to your annotation processor path.", e);
} catch (ReflectiveOperationException e) {
throw new IllegalStateException("Failed to invoke " + metaClassName + ".definitions()", e);
}View on GitHub (pinned to cd8cf15dc3)