Skip to content

feat: opt-in API version auto-negotiation#2630

Open
postalservice14 wants to merge 2 commits into
docker-java:mainfrom
postalservice14:fix/2547-api-version-auto-negotiation
Open

feat: opt-in API version auto-negotiation#2630
postalservice14 wants to merge 2 commits into
docker-java:mainfrom
postalservice14:fix/2547-api-version-auto-negotiation

Conversation

@postalservice14
Copy link
Copy Markdown

@postalservice14 postalservice14 commented May 20, 2026

Summary

Adds an opt-in API version auto-negotiation flag, similar to moby/client.NegotiateAPIVersion. When enabled, the client queries GET /version once at construction and pins itself to min(daemon ApiVersion, latest version supported by docker-java). If that result is below the daemon's MinAPIVersion, the daemon's minimum is used and a WARN is logged.

Fixes #2547.

Behaviour

  • Default off. Existing callers see no behaviour change.
  • Opt-in surface: DefaultDockerClientConfig.Builder.withApiVersionAutoNegotiation(boolean), env var DOCKER_API_VERSION_AUTO_NEGOTIATION, property api.version.auto.negotiation (truthy values: true case-insensitive, 1).
  • Precedence: explicit withApiVersion(...) always wins. If both are set, the explicit version is used and negotiation is skipped.
  • Failure mode: a non-2xx /version response or transport exception throws DockerClientException — the caller opted in, so loud failure is the right call.

Implementation

  • DockerClientConfig gets a new default boolean isApiVersionAutoNegotiationEnabled() { return false; }. The project's japicmp config already tolerates METHOD_NEW_DEFAULT, so this is binary-compatible against the 3.3.4 baseline.
  • ApiVersionNegotiator is a new public utility class that calls GET /version directly via DockerHttpClient and applies the moby negotiation formula. It deliberately does not reuse VersionCmdExec to avoid a circular dependency with the cmd-exec factory that's still being constructed.
  • NegotiatedDockerClientConfig is a package-private decorator that overrides only getApiVersion(), so DefaultDockerClientConfig stays immutable.
  • The negotiation hook runs inside DockerClientImpl.getInstance(DockerClientConfig, DockerHttpClient), before the cmd-exec factory is built, so every subsequent command picks up the negotiated version.
  • The client's "latest supported version" is discovered by reflection over RemoteApiVersion's VERSION_1_X constants — adding new constants in future bumps just works without touching the negotiator.

Test plan

  • ./mvnw -pl docker-java -am test -Dtest='ApiVersionNegotiatorTest,DefaultDockerClientConfigTest' — 30 tests, all green
  • ./mvnw clean install -DskipITs — full reactor build, japicmp clean on docker-java-api + every transport adapter, Checkstyle clean across all modules
  • Negotiator covers: daemon newer than client (pins to client max), daemon older than client (pins to daemon), daemon min above client max (pins to daemon min, warns), non-2xx and missing-ApiVersion failure paths
  • Config covers: builder default-off, builder boolean setter, string parser parity with withDockerTlsVerify, env-var path, system-property path

Note: OkHttpClientTests#testHijacking is flaky on my dev machine — failing identically on this branch and on main — but it's untouched by this PR.

Out of scope

  • Flipping the default to on (a future PR can do that once this one has shipped).
  • Re-running negotiation after a daemon restart / reconnect. The current behaviour is one-shot at construction, matching moby's NegotiateAPIVersion.

This change is Reviewable

Adds DefaultDockerClientConfig.Builder.withApiVersionAutoNegotiation(boolean)
(also exposed via DOCKER_API_VERSION_AUTO_NEGOTIATION env var and the
api.version.auto.negotiation property). When enabled and no explicit
api version is set, DockerClientImpl.getInstance(config, httpClient) queries
the daemon's /version endpoint and pins the client to
min(daemon ApiVersion, latest version known to docker-java). If that result
is below the daemon's MinAPIVersion, the daemon's minimum is used and a
WARN is logged - matching the behaviour of moby/client.NegotiateAPIVersion.

Default is off; existing callers see no change. The new
isApiVersionAutoNegotiationEnabled() method on DockerClientConfig is a
default-returning-false interface method, which japicmp already tolerates
via the project's METHOD_NEW_DEFAULT override.

Fixes docker-java#2547
@postalservice14 postalservice14 requested a review from a team as a code owner May 20, 2026 18:48
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.

Automatically negotiate API version to use

1 participant