Accepted.
The Market Data API speaks JSON over HTTP. The SDK must:
- Parse responses into typed POJOs/records (requirements §11.1, §11.2)
- Decode the API's "compressed, array-keyed JSON" wire format into named-field models (§11.1) — this requires custom deserialization logic, not just default reflection
- Serialize request bodies (where applicable) and query parameters
- Avoid imposing classpath conflicts on consumers
The JDK has no built-in high-level JSON binding library. JEP 198
proposed java.util.json years ago and never landed; as of JDK 25
there is still no standard library answer. We must pick a third-party
dependency.
The wire-format-decoding requirement is non-trivial: the API's primary
response shape is a parallel-arrays format like
{"s":"ok","symbol":["AAPL","MSFT"],"price":[150.0,400.0]} rather than
the array-of-objects form [{"symbol":"AAPL","price":150.0}, …]. The
SDK has to expand this into typed models. Whichever library we pick
must support custom deserializers cleanly.
The de facto JSON library for Java.
Pros
- Industry-standard. The vast majority of Java consumers already have Jackson on their classpath.
- Records support since Jackson 2.12 (released 2020) — works out of the box for record-based response models.
- Modular:
jackson-core,jackson-databind,jackson-annotationsare separable; we only depend on what we use. - Excellent custom serializer/deserializer support — well-suited to the wire-format decoding requirement.
- Mature streaming API (
JsonParser/JsonGenerator) for cases where we want to avoid full tree allocation. - Active development; security patches land quickly.
Cons
- Classpath collision risk. Consumers running an older Jackson version may break when our SDK pulls in a newer one (or vice versa). This is the recurring pain point with Jackson on shared classpaths.
- Mitigations exist (shading Jackson into our JAR; documenting a minimum Jackson version) but each has costs.
- Fairly large surface area —
jackson-databindalone is ~1.5 MB. That's not a lot in absolute terms but isn't negligible. - Historically the source of a meaningful number of CVEs (the polymorphic-deserialization vulnerabilities of 2017–2019). We don't use polymorphic deserialization, so we're not exposed, but the CVE-tracking overhead is non-zero.
Google's JSON library.
Pros
- Simpler API than Jackson.
new Gson().fromJson(...)is hard to beat for ergonomics. - Smaller surface area (~280 KB).
- No annotation requirements for basic cases.
- Single dependency — no
core/databind/annotationssplit. - Lower historical CVE burden.
Cons
- Records support is workable but less polished than Jackson's. Need
a custom
TypeAdapterFactoryfor full ergonomics, or rely on Gson's reflection-based path which is slower. - Custom deserialization for the parallel-arrays wire format requires more boilerplate than Jackson's equivalent.
- Active development has slowed. Releases are infrequent compared to Jackson (or Moshi). For an SDK that needs long-term security maintenance this is a real concern.
- No streaming Java records integration; would need to mix Gson's
JsonReaderwith manual record construction.
Square's JSON library, born out of OkHttp's lineage.
Pros
- Modern design; clean API.
- Strong codegen support via
moshi-kotlin-codegenfor build-time reflection avoidance — fastest of the three at runtime. - Smaller than Jackson (~150 KB).
- Lower historical CVE burden.
- Records support via
moshi-java-records-reflectadd-on.
Cons
- Sweet spot is Kotlin/Android. Pure-Java usage works but is less documented and the codegen story is Kotlin-first.
- Records support is not in the core artifact — adds another module to the dep list.
- Smaller community than Jackson; less Stack Overflow surface area for unusual deserialization cases.
- Less obvious win for our use case: the wire-format decoder is the hard part, and Moshi's codegen advantage doesn't apply to custom adapters.
The standardized Jakarta EE JSON binding API with the reference implementation.
Pros
- Standard API — in principle the implementation could be swapped.
- Annotation model is similar to Jackson's; familiar to Jakarta EE developers.
Cons
- Requires both an API artifact (
jakarta.json.bind-api) and an implementation (yasson— itself ~600 KB and pulls inparsson). - Limited third-party SDK adoption. Smaller community.
- Custom deserialization (needed for wire-format decoding) is workable but less ergonomic than Jackson's.
- The "swappable implementation" benefit is theoretical — almost no one swaps JSON-B implementations in practice.
Mentioned for completeness only.
Pros
- Zero dependencies.
Cons
- Unequivocally a bad idea for an SDK that must handle real-world edge cases (Unicode escapes, numeric precision, trailing commas emitted by misbehaving servers, very large responses, etc.).
- Maintenance burden grows over time as the API surface grows.
- Listed and rejected; not a serious option.
Option A (Jackson).
The deciding factors:
- Records support is mature and frictionless. Given that ADR-002 is trending toward JDK 17 (which makes records the natural response model), Jackson's record support is the cleanest of the three.
- The wire-format decoding requirement (§11.1) is the load-bearing
technical challenge here, and Jackson's
@JsonDeserialize+ customJsonDeserializermodel is the most ergonomic and best-documented solution among the options. - Industry default. New Java engineers onboarding to this SDK will already know Jackson.
- Active development pace and rapid CVE response.
The classpath-collision concern is real but manageable. Two reasonable mitigations, in order of preference:
- Document a minimum Jackson version (likely 2.17+) and list Jackson as a required dependency in our published POM. Consumers with a strictly older Jackson version will see a clear failure; upgrade paths exist.
- Shade Jackson into the SDK JAR if collisions become a real customer pain. Adds build complexity (Gradle Shadow plugin or Maven Shade plugin) and roughly doubles JAR size. Defer until there's evidence it's needed.
The strongest counter-recommendation is Option B (Gson) if the team values simplicity and a smaller dep over Jackson's ecosystem. Gson is genuinely fine; it's the second-best fit.
Option C (Moshi) would be the right answer if this were a Kotlin-first SDK — but ADR-001 is trending toward Java-only, and Moshi's advantages don't translate.
Option D (JSON-B) has no clear upside over Jackson here.
Option A — Jackson. The SDK uses Jackson (jackson-databind) as
its JSON library.
The team's reasoning: Jackson is the de facto Java JSON standard. New Java engineers onboarding will already know it; records support is the cleanest of the candidates; and the wire-format decoding work (parallel-arrays JSON → typed records, per generic SDK requirements doc §11.1) is best supported by Jackson's custom-deserializer model.
The classpath-collision risk that comes with Jackson on shared classpaths is acknowledged. We document a minimum Jackson version in the published POM. Shading the dependency into the SDK JAR is held in reserve as a mitigation if collisions become a real customer pain post-launch.
Options B (Gson), C (Moshi), D (JSON-B), and E (hand-rolled) were considered but offered no meaningful advantage over Jackson for this SDK's needs.
Follow-on work implied by each option. The chosen option is marked.
- A (chosen): Add
jackson-databind(and transitivelycore+annotations) as a runtime dependency. Document the minimum Jackson version. Custom deserializers for wire-format decoding live ininternal/wire/or similar. Decide later whether to shade. - B (Gson): Add
com.google.code.gson:gsonas a runtime dependency. Hand-write aTypeAdapterfamily for response models; customJsonDeserializerfor wire-format decoding. - C (Moshi): Add
com.squareup.moshi:moshiplusmoshi-java-records-reflect. Codegen path likely not used. More boilerplate for the wire-format adapter than Jackson would need. - D (JSON-B): Add
jakarta.json.bind-apiandyassonas runtime dependencies. Custom serializers via the JSON-B serializer SPI.