Skip to content

docs(specs): Java SDK RFC for working group review#4145

Open
bokelley wants to merge 3 commits intomainfrom
bokelley/java-sdk-rfc
Open

docs(specs): Java SDK RFC for working group review#4145
bokelley wants to merge 3 commits intomainfrom
bokelley/java-sdk-rfc

Conversation

@bokelley
Copy link
Copy Markdown
Contributor

@bokelley bokelley commented May 5, 2026

Summary

Adds specs/java-sdk-rfc.md proposing a first-class Java SDK to reach L0–L3 parity with @adcp/sdk (TS, GA) and adcp (Python, beta). Multiple JVM adopters have asked for one; this is the design surface for the Builders Working Group to vote on before someone starts coding.

What's in the RFC

  • Why now: JVM dominates publisher / SSP / ad-server / broadcaster middleware. The unique JVM win Python sidecars can't deliver is shared transaction context with the existing decisioning engine.
  • Architecture: Java 17 baseline, sync-shaped public API with virtual-threads scaling on 21+, JDK HttpClient, Jackson 2.15+, JSpecify nullability, framework-neutral core + Spring Boot starter.
  • Surface: 5 Maven artifacts at GA mapped against verified @adcp/sdk 6.x exports — adcp, adcp-server, adcp-testing, adcp-spring-boot-starter, adcp-cli. Reactor + Mutiny adapters at GA, not fast-follow. Kotlin co-released at v1.0.
  • L3 specifics: sealed-type idempotency conflicts (no payload echo, byte-identical replay), per-adcp_use KMS keys, lazy KMS init, mock-server forwarding contract for storyboards, (action, from, to) transition validators so NOT_CANCELLABLE precedence works.
  • Spec gotchas section: 8 things the TS and Python builds bled time on, codified up front so the Java team doesn't relearn them.
  • Open questions: 7 JVM-specific decisions the WG should weigh in on (MCP SDK choice, A2A fallback, Spring Boot 2.7 floor, etc.).
  • Decisions wanted: funding model + design-partner gating up top — without 2–3 letters of intent and a contributed engineer at 50%+ for ~12 months, this is build-it-and-they-will-come.

Process notes

  • Drafted, then run by DX, JS protocol, ad-tech protocol, and agentic product-architect reviewers.
  • Factual fixes from review: removed fabricated "lifecycle YAMLs the TS SDK uses" claim (TS doesn't validate transitions in v6.0; this is now framed as "lead path 2 with WG buy-in"); softened "fully custom codegen" claim (TS uses json-schema-to-typescript + post-processors); fixed versioning rule (SDK majors are independent of spec majors — TS 6.x for AdCP 3.x is a coincidence, not policy).
  • Strategic fixes from review: collapsed 10 artifacts to 5; dropped GraalVM and Bouncy Castle from v1; narrowed *Async mirror to L0 caller methods only; moved storyboard CI gate from v0.4 to v0.1; pushed realistic GA from M+9 to M+12.

Test plan

  • WG reviews and weighs in on the 6 decisions in Decisions wanted
  • Funding / staffing answer: contributed engineer + named maintainer + 2–3 design partners, or revisit at next major
  • Coordination with @adcp/sdk and adcp (Python) maintainers on shared lifecycle YAML path
  • Maintainer named with merge rights post-GA

🤖 Generated with Claude Code

Captures JVM-specific architecture decisions to reach L0-L3 parity with
@adcp/sdk (TS) and adcp (Python): Java 17 baseline, sync + virtual threads,
framework-neutral core + Spring Boot starter, 5-artifact Maven layout,
codified spec gotchas, design-partner gating, funding ask.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread specs/java-sdk-rfc.md
Comment thread specs/java-sdk-rfc.md Outdated
adopter feedback.
6. **Compatibility with Spring Boot 2.7.** End of OSS support is Nov
2025; do we cover it for the long tail or set 3.x as the floor?
Floor 3.x is cleaner; long tail at large enterprises is real.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also need to account for namespace split with Spring Boot 2.7 (javax) and 3.x (jakarta)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right — the namespace split is the load-bearing problem, not the version EOL. Updated open question 6 in 9307e4d to spell out three shapes (floor at 3.x, dual starters mirroring springdoc-openapi, community port) with a v0.3-alpha decision deadline since the starter package layout depends on the answer.

- Jackson StreamReadConstraints / StreamWriteConstraints: explicit
  AdCP-shaped defaults + adopter overrides; cross-language conformance
  bugs surface here first since TS/Python have no equivalent limits.
- Spring Boot 2.x compatibility: javax → jakarta namespace split is the
  hard part, not just version EOL. Three shapes laid out (floor at 3.x,
  dual starters, community port) with a v0.3-alpha decision deadline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread specs/java-sdk-rfc.md Outdated

`TaskStore` and `WebhookEmitter` SPIs. Same shape as `IdempotencyStore` —
in-memory + JDBC + Redis reference impls. Webhook delivery on a
configurable `ScheduledExecutorService` (virtual threads on 21+).
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ScheduledExecutorService and newVirtualThreadPerTaskExecutor() aren't interchangeable — one schedules, the other runs blocking work — so "VTs on 21+" can't apply to the scheduler itself.

Right pattern is a small platform-thread scheduler that dispatches each delivery onto a separate VT executor, configured independently.

Avoids mistake of an adopter single-threading the scheduler assuming VTs scale it, and silently wedge their retry pipeline behind one slow receiver.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right — fixed in 0c43c96. Split the webhook delivery into two executors on WebhookEmitter.builder(): a small platform-thread scheduler (size 1–2) for due-time dispatch, and a separate dispatcher that runs the HTTP delivery — defaulting to Executors.newVirtualThreadPerTaskExecutor() on 21+ and a bounded platform-thread pool on 17–20. Both injectable so adopters can wire a Spring TaskExecutor, Mutiny scheduler, or shared pool. Called out the wedge-on-one-slow-receiver failure mode explicitly.

Scheduler and dispatcher aren't interchangeable: a ScheduledExecutorService
schedules; it doesn't run blocking work. Conflating them invites adopters to
single-thread the scheduler thinking VTs scale it, wedging their retry
pipeline behind one slow receiver. Two-executor pattern: small platform-
thread scheduler + separate dispatcher (VT executor on 21+, bounded
platform pool on 17-20), both independently configurable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants