Skip to main content

acp-core has no databind

Since 0.18.0, acp-core depends on jackson-annotations only, not on jackson-databind or jackson-core. The JSON implementation is a separate SPI (AcpJsonMapper, chosen at runtime through AcpJsonMapperSupplier, a ServiceLoader provider), so applications that need a specific Jackson version, or a different library entirely, aren’t forced into whatever acp-core happened to pin.
If you depend on acp-core alone with no JSON module, AcpJsonMapper.createDefault() fails with ā€œNo AcpJsonMapperSupplier found on the classpathā€. acp-agent-support, acp-test, acp-streamable-http-jetty, and the acp-autoconfig starter all already bring acp-json-jackson2 transitively, so most applications never need to think about this; a library consuming acp-core directly does.

Two modules, one wire format

Both write identical bytes on the wire; the choice is purely about which Jackson major version your application already carries. With both modules on the classpath, Jackson 3 wins (the higher priority); an application’s own AcpJsonMapperSupplier wins over either, and the system property acp.json.mapper.supplier names one explicitly when more than one choice needs to be pinned.
JacksonAcpJsonMapper.defaultObjectMapper() / Jackson3AcpJsonMapper.defaultJsonMapper() return the module’s own lenient configuration; building a JacksonAcpJsonMapper from a bare new ObjectMapper() instead is strict about unknown fields, which can reject forward-compatible messages a newer peer sends. Start from the module’s default and customize it, rather than a bare mapper, unless strictness is specifically what you want.

A supported Jackson floor, checked at startup

Frameworks manage their own Jackson version, and earlier SDK versions stated no floor: an older Jackson than the SDK’s tests actually exercise (Vert.x 4.5’s jackson-core 2.16.1 under jackson-databind 2.22.3, for example) could fail much later with a confusing NoSuchMethodError while reading a message, long after the mismatch was introduced. Each mapper supplier and mapper constructor now checks the jackson-core and jackson-databind actually on the classpath and fails fast with an IllegalStateException naming the version found and the version required: Quarkus 3.40 (Jackson 2.21.7) and Spring Boot 4.1 (Jackson 3.1.7) are both comfortably within these floors. Migration is needed only for an application pinning something older; align the Jackson artifacts on one version at or above the floor, for example by importing jackson-bom.

Open values and unions still deserialize cleanly

The JSON layer is also what makes forward compatibility actually work: an unrecognized union variant reads as its Unknown* record rather than failing the whole message, and an unrecognized value of an open type (StopReason, ToolCallStatus, and others) reads as that same record type holding a string its constants don’t list. Neither module treats a response missing an optional field as an error either: AcpSchema’s DefaultOnNull marker interface lets a response read as empty when the peer answers "result": null (legal JSON-RPC, and what the Python SDK sends for a handler returning None) or omits the result outright.

TypeRef

Generic type information Java erases at runtime, for reading or converting into a parameterized type:
Used throughout the extension-method API (see Extension Methods) and anywhere else the SDK needs a caller-supplied type that isn’t just SomeClass.class.

Forward Compatibility

What a newer peer’s message looks like once it’s deserialized

Installation

Which modules to add, and which transports already bring one