Skip to content

feat(sdk)!: the Java client reads the error contract it is answered with - #130

Merged
olavgg merged 1 commit into
mainfrom
sdk/problem-contract
Sep 18, 2026
Merged

olavgg merged 1 commit into
mainfrom
sdk/problem-contract

Conversation

@JosteinGj

@JosteinGj JosteinGj commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

The API converged on one RFC 9457 shape (errors/115). The Java SDK had not caught up: it matched error types by substring over the raw body and classified retries by status code alone.

What changed

Problem (new, datahub-api-model, Apache-2.0) — the wire contract read. Typed type/title/status/detail/instance/retry/requestId/fields; everything else in extensions(), since RFC 9457 §3.2 requires unknown members to be tolerated. Problem.of(status, body) never throws and never returns null, so an error path needs no special case for a proxy's HTML or an empty 502 (slug() is simply null there). Mirrors the console's DataHubProblem, which reads the same shape in the browser.

DatahubApiException.problem() — never null. The message now carries title, detail, rejected fields and requestId instead of just HTTP 409 for POST /x.

ApiHttp — asks for application/problem+json as well as application/json, and carries Retry-After.

Classification — three substring matches are gone: tenant-limit-reached, unknown-timeseries, external-id-mismatch. The last two also fired on a detail sentence merely naming a series.

Behaviour changes worth review

  1. Ingest retry follows retry. A 409 optimistic-lock is now retried; a 500 internal (needs-operator) is not. Correct per the contract, but note a momentary DB-pool exhaustion inside the API is also a 500 internal. If that should be retryable the fix belongs in Problems.retryFor, not here.
  2. Retry-After over 30s fails the batch immediately rather than sleeping. A spent daily quota returns at midnight UTC, so the spool should hold it, not a parked thread.
  3. Buffering deliberately does not follow retry. A 401 is change-request, yet spooling it while a credential is renewed is the whole point of the spool. isRetryable and isBufferable answer different questions; both AGENTS.md and the javadoc say so.

Also

TimeseriesBinaryIngestTest fixtures were still the pre-errors/9 shape (type: datapoint-block-rejected), which is why matching on type turned them red. Updated to what DatapointBlockRejectedException sends today.

BatchError gains a fifth component; the 3- and 4-arg constructors are kept, so it stays source-compatible.

Tests

16 new (ProblemTest, ProblemContractTest, IngestRetryContractTest, plus three wire-level cases in TimeseriesIngestTest). Full repo compiles; :datahub-java-sdk:test and :datahub-api-model:test green.

Docs

datahub-sdk-docs: IntelliStream-DataHub/datahub-sdk-docs#95

Follow-up, not in this PR: the API's doc-only ApiProblem/FieldProblem could now point at api-model's Problem, finishing what c0deda8 started.

🤖 Generated with Claude Code

Refusals were matched by substring over the raw body, and retries went by
status alone. DatahubApiException.problem() now exposes the RFC 9457
document (Problem, new in api-model, never null) and ingest follows its
retry member: a 409 optimistic-lock is retried, a 500 internal is not,
and Retry-After is the backoff floor.

Buffering stays status-based on purpose. A 401 is change-request, yet
spooling it while a credential is renewed is what the spool is for.

The binary stale-series retry reads type and reason instead of searching
the whole body, which also fired on a detail sentence naming a series.
Its test fixtures were still the pre-errors/9 shape.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: jgjesdal <jostein@intellistream.ai>
@olavgg
olavgg merged commit 460c8f5 into main Sep 18, 2026
8 of 9 checks passed
@olavgg
olavgg deleted the sdk/problem-contract branch September 18, 2026 14:03
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