{"record":{"id":"54b935945b467b51","repo":"github/copilot-sdk","slug":"failed-to-serialize-tool-result-to-json","errorCode":null,"errorMessage":"Failed to serialize tool result to JSON","messagePattern":"Failed to serialize tool result to JSON","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"java/sdk/src/main/java/com/github/copilot/rpc/ToolDefinition.java","lineNumber":898,"sourceCode":"     * <li>{@code null} — mapped to {@code \"Success\"} (covers handlers that return\n     * null to indicate a successful no-value result)</li>\n     * <li>any other value — JSON-serialized via {@link ObjectMapper}</li>\n     * </ul>\n     */\n    private static Object formatResult(Object result, ObjectMapper mapper) {\n        if (result == null) {\n            return \"Success\";\n        }\n        if (result instanceof String) {\n            return result;\n        }\n        if (result instanceof ToolResultObject) {\n            return result;\n        }\n        try {\n            return mapper.writeValueAsString(result);\n        } catch (com.fasterxml.jackson.core.JsonProcessingException ex) {\n            throw new IllegalStateException(\"Failed to serialize tool result to JSON\", ex);\n        }\n    }\n\n    // ------------------------------------------------------------------\n    // Validation helpers\n    // ------------------------------------------------------------------\n\n    private static void requireNonBlankToolName(String name) {\n        if (name == null || name.isBlank()) {\n            throw new IllegalArgumentException(\"Tool name must not be null or blank\");\n        }\n    }\n\n    private static void requireNonBlankDescription(String description) {\n        if (description == null || description.isBlank()) {\n            throw new IllegalArgumentException(\"Tool description must not be null or blank\");\n        }\n    }","sourceCodeStart":880,"sourceCodeEnd":916,"githubUrl":"https://github.com/github/copilot-sdk/blob/cd8cf15dc3f9e762615790aaed0a771a0f392755/java/sdk/src/main/java/com/github/copilot/rpc/ToolDefinition.java#L880-L916","documentation":"When a tool handler returns a result, ToolDefinition.formatResult serializes it to JSON via Jackson (unless it is already a ToolResultObject). If Jackson cannot serialize the returned object (no accessible getters, unsupported types, self-referencing structures, failing custom serializers), an IllegalStateException wrapping the JsonProcessingException is thrown with this message.","triggerScenarios":"A tool handler registered via from/fromAsync/fromWithToolInvocation/fromAsyncWithToolInvocation returns an object Jackson cannot write: a class with no getters, an infinite recursion (parent/child cyclic reference), or a type with a broken custom JsonSerializer.","commonSituations":"Returning domain entities with bidirectional relationships causing infinite recursion (JsonMappingException); returning objects not designed for serialization (InputStream, lambdas); Jackson version/config mismatches after upgrading; returning records/POJOs with incompatible module (e.g. missing jackson-datatype-jsr310 for LocalDate).","solutions":["Read the wrapped cause (ex.getCause()) to identify the failing property or type.","Return a ToolResultObject (or a serializable DTO/String/Map) from the handler instead of the raw domain object.","Fix the returned type: add getters, break cyclic references with @JsonManagedReference/@JsonBackReference or @JsonIgnore.","Register needed Jackson modules on the SDK-configured ObjectMapper (or return pre-serialized data) so types like LocalDate serialize."],"exampleFix":"// before\npublic Object getReport(String id) { return reportRepo.find(id); } // cyclic entity\n\n// after\npublic Object getReport(String id) {\n    Report r = reportRepo.find(id);\n    return Map.of(\"id\", r.getId(), \"title\", r.getTitle()); // serializable DTO\n}","handlingStrategy":"validation","validationCode":"new ObjectMapper().writeValueAsString(result); // dry-run serialize in tests","typeGuard":"boolean serializable(Object o) {\n  try { mapper.writeValueAsString(o); return true; } catch (JsonProcessingException e) { return false; }\n}","tryCatchPattern":"try { return tool.call(invocation); } catch (IllegalStateException e) {\n  log.error(\"tool result serialization failed\", e.getCause());\n  return Map.of(\"error\", \"result could not be serialized\");\n}","preventionTips":["Return simple DTOs/Maps/Strings from tool handlers, not domain entities.","Annotate cyclic relationships with @JsonIgnore or DTO-mapping.","Unit-test every handler by serializing its output in CI."],"tags":["java","jackson","json-serialization","tool-handler"],"backgroundTag":"json-serialization-failed","analyzedSha":"cd8cf15dc3f9e762615790aaed0a771a0f392755","analyzedAt":"2026-09-09T18:32:31.973Z","contentChangedAt":"2026-09-09T18:32:31.973Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}