> For clean Markdown content of this page, append .md to this URL. For the complete documentation index, see https://buildwithfern.com/learn/llms.txt.

# Changelog

## September 26, 2026

## 4.22.3

**`(fix):`** Honor `package-prefix` and `package-layout` for `local-file-system` output when the CLI
requests a full project (e.g. `--generate-tests`, `--package`, or self-hosted SDKs).
Previously these were dropped when converting the local-file-system config, so the root
package fell back to `com.<org>.<api>` instead of the configured prefix. Full-project local
output now also writes sources at their full package path (`src/main/java/com/acme/...`)
instead of stripping the prefix from the directory.

## September 25, 2026

## 4.23.0

**`(feat):`** XML-encoded (TwiML) types now carry inline Javadoc on their user-facing API: the class itself gets the
type description, every `Builder` setter overload gets the property description, and child appenders
(`say.break_(...)`, `response.say(...)`) document the child element plus `@param`/`@return`.

## 4.22.2

**`(fix):`** `RetryInterceptor` now applies the client `timeout` to each attempt individually instead of to the
whole retry loop. Previously a `Retry-After` wait as long as the timeout (e.g. `Retry-After: 60`
with the default 60s timeout) cancelled the call during the backoff sleep and surfaced
`IOException: Canceled` instead of retrying or returning the received 429. The call timeout is
paused while waiting for the backoff and restarted for each retry attempt, so a call that retries
may now take up to `timeout * (maxRetries + 1)` plus the backoff waits.

## 4.22.1

**`(fix):`** Closing the root client (or `ClientOptions.close()`) now disconnects any WebSocket clients that are still
connected before the SDK-owned OkHttp dispatcher is shut down, so they stop reconnecting cleanly instead of
failing asynchronously with `InterruptedIOException: executor rejected`. `ClientOptions` exposes `isClosed()`;
WebSocket connect/reconnect attempts made after close, as well as `OkHttpWebSocketFactory.create(...)` when
constructed with a closed-check, fail fast with `IllegalStateException("root client has been closed")`.

## September 24, 2026

## 4.22.0

**`(feat):`** Generate the shared `WebhooksHelper` from the API-wide `api.settings.webhook-signature`
setting in `generators.yml` (IR `sdkConfig.webhookSignatureVerification`). The helper is
emitted even when the API definition models no webhooks; webhook-specific signature
overrides still produce their own named helpers.

**`(fix):`** Fix the generated `ReconnectingWebSocketListener` connection-timeout message, which was missing the
closing parenthesis after the retry attempt number (e.g. `... milliseconds (retry attempt #2`).

## 4.21.2

**`(fix):`** `RetryInterceptor` no longer discards an API response it has already received (e.g. a 429)
when a later retry attempt fails, such as when the call timeout expires mid-retry. The
previous response is buffered and returned instead of surfacing a generic `IOException`.

## September 22, 2026

## 4.21.1

**`(chore):`** Bump bundled @fern-api/generator-cli to 0.10.1. Auto-versioned MAJOR/MINOR bumps no longer
produce a version-only `changelog.md` block when the AI analysis returns an empty changelog
entry; the PR description, version bump reason, or commit message body is used instead.

## 4.21.0

**`(feat):`** Generated root clients (sync and async) now implement `AutoCloseable`. `close()` shuts down
the underlying `OkHttpClient`'s dispatcher executor and evicts its connection pool, but only
when the SDK created that `OkHttpClient` itself — a client supplied via `Builder.httpClient(...)`
is left running, since the caller owns its lifecycle. This lets short-lived applications (e.g.
CLIs, or code that finishes after using WebSockets) release the client's background threads
instead of relying on JVM shutdown.

## September 21, 2026

## 4.20.0

**`(feat):`** Object types with xml encoding metadata (e.g. TwiML defined via OpenAPI `xml` objects) now
implement `XmlSerializable` with `toXml()` and a static `fromXml(String)` parser, backed by new
core `XmlWriter`/`XmlReader` utilities. Builders gain per-child-element methods
(e.g. `Response.builder().say(...)`) and undiscriminated child unions dispatch `fromXml` by
element name. `Builder.fromXml(String)` returns a populated builder, unknown child elements are
preserved as `XmlElement`s (readable via `getAdditionalChildren()`, addable via `addChild(...)`)
and round-tripped by `toXml()`, and very large objects that use a builder-based constructor also
get `fromXml`. Types without xml metadata are unchanged.

## September 15, 2026

## 4.19.3

**`(fix):`** Generated `ReconnectingWebSocketListener` now acknowledges peer-initiated close frames by
overriding `onClosing`, so OkHttp completes the close handshake and `onClosed` (and the
public disconnect lifecycle) fires for server-initiated closes. A no-status close (local
sentinel 1005) is acknowledged with 1000, since 1005 is not a wire-valid close code.
The reconnect decision is exposed as a `protected boolean shouldReconnectAfterClose(WebSocket, int, String)`
hook (default unchanged: `code != 1000`) so resource-specific subclasses can treat a
protocol-established terminal close as final. Adds `send(String, Consumer<WebSocket>)` /
`sendBinary(ByteString, Consumer<WebSocket>)` overloads that report which socket directly
accepted a message (not invoked for queued or dropped messages).

## August 26, 2026

## 4.19.2

**`(fix):`** Respect custom OAuth token headers and prefixes for client credentials authentication.

## August 20, 2026

## 4.19.1

**`(fix):`** Honor an endpoint's `retries: {disabled: true}` configuration (the `x-fern-retries` OpenAPI
extension). A call to such an endpoint tags its request with a max-retries override of zero, so it
bypasses automatic retries regardless of the client-wide `ClientOptions.maxRetries` or a
per-request `RequestOptions.maxRetries`. Endpoints without the configuration are unchanged.

## August 13, 2026

## 4.19.0

**`(fix):`** With `respect-optional-request-body` enabled, a call that leaves out an optional request body now
passes a null body to OkHttp instead of an empty one, so the request carries no body at all. The
methods OkHttp requires a body for (POST, PUT, PATCH) still send an empty body, which is what
endpoints with no request body at all already send.

**`(feat):`** Add a `respect-optional-request-body` option. When enabled, an endpoint whose request body the API
does not require also gets an overload without the body parameter, and calling that overload sends
no body. The body parameter keeps its own type, so `alpha(String id, Body body)` gains a sibling
`alpha(String id)` rather than becoming `alpha(String id, Optional<Body> body)`. Examples that
supply no body render as `client.alpha(id)`. That call also leaves out the `Content-Type` header,
which now only goes out when a body does. The option defaults to false, so existing signatures
and snippets are unchanged.

## 4.18.2

**`(fix):`** Build an empty request body in snippets for an endpoint example that omits a body the wrapped
request requires, instead of leaving the builder's required `body` stage unset.

## 4.18.1

**`(fix):`** Fix the generated OAuth token supplier when the get-token request has required properties
other than the client credentials. The builder calls are now ordered by the request's
required properties, so the staged builder's stages are satisfied in order, and hardcoded
values (such as `grant_type`) are written using the property's type, which means an
enumerated `grant_type` gets the generated enum constant instead of a string literal.

## August 11, 2026

## 4.18.0

**`(feat):`** Add support for the `FERN_JAVA_SKIP_FORMATTING` environment variable. When set to a truthy
value (`1`, `true`, `yes`, or `on`), the generator skips the `./gradlew :spotlessApply` pass
that normally runs after code generation, so generation makes no Gradle invocation at all.
This unblocks restricted networks that cannot reach the Gradle distribution or plugin
repositories, and shortens generation time.

Generated code is emitted unformatted; the Spotless plugin is still wired into the generated
`build.gradle`, so `./gradlew spotlessApply` can be run separately. The variable is unset by
default, leaving existing output byte-for-byte unchanged. The Fern CLI forwards it from the
host into the generator container automatically.

## August 8, 2026

## 4.17.1

**`(fix):`** Fixed wire tests generated for APIs with `auth: any` combining Basic and OAuth schemes.
Tests no longer assert both a Bearer and a Basic `Authorization` header on the same
request; only the OAuth Bearer assertion is emitted since the client sends the OAuth
token when OAuth credentials are configured.

## August 3, 2026

## 4.17.0

**`(feat):`** Add opt-in `allowUserAgentAppInfo` config. When enabled, the generated client's builder exposes
an `appInfo(name, version, comment)` option whose product token is appended to the `User-Agent`
header the SDK would otherwise send (`{sdk}/{version} ... {name}/{version} ({comment})`),
following RFC 9110. Disabled by default, so existing generated output is unchanged, and it
composes with `includePlatformHeaders`, the `runtime-version` jar-manifest path, the configured
`user-agent` template value, and the default `{coordinate}/{version}`. Caller-supplied values are
trimmed and sanitized (name/version percent-encoded to RFC 7230 tchars; comment delimiters,
control characters and any non-ASCII characters percent-encoded as UTF-8 bytes so OkHttp's header
validation never rejects them), so untrusted values cannot inject header content. Still overridable
by an explicit `User-Agent` and suppressed by `omit-fern-headers`.

## July 28, 2026

## 4.16.0

**`(feat):`** Add an opt-in `runtime-version` config flag. When enabled, the SDK version reported
in the telemetry headers (`X-Fern-SDK-Version` and the version segment of
`User-Agent`) is resolved at runtime from the jar manifest's `Implementation-Version`
attribute instead of being baked in as a literal at generation time. The generated
build.gradle records the project version in the jar manifest, so an external tool such
as release-please only has to rewrite a single `version` line for the reported version
to track the actually-published artifact — rather than a version the SDK may never
publish. Falls back to the generation-time version when the manifest attribute is
absent (e.g. running from unpackaged classes). Disabled by default, so existing
generated output is unchanged, and subject to `omitFernHeaders`.

## 4.15.5

**`(fix):`** Resolve the `X-Fern-SDK-Name` header from the configured Maven coordinate in
GitHub output mode. Previously the header was read from the (Fiddle-synthesized)
publish config, so it emitted `com.<org>.fern:<workspace>-sdk` instead of the
coordinate the user configured under `output.coordinate`, leaving it inconsistent
with the `User-Agent` product token. The header now reads
`output.mode.github.publishInfo.maven.coordinate`, which is populated for both
remote and `--local` generation. Also emits `X-Fern-SDK-Version` in cases where a
coordinate resolves without a version previously being set.

## July 24, 2026

## 4.15.4

**`(fix):`** Global headers now honor their `env` and `client-default` values. The
builder initializes the header from the environment variable when set,
otherwise the client default, and an explicitly provided value still takes
precedence. Previously a global header was only sent when the caller set it
explicitly.

## 4.15.3

**`(fix):`** Fix a `NullPointerException` thrown at runtime on every OAuth (or inferred-auth) authenticated
request under `auth: endpoint-security`. To fetch a token, the OAuth auth provider builds an
internal client for the (unauthenticated) token endpoint whose `ClientOptions` has no auth
provider; `ClientOptions.getAuthHeaders(...)` then dereferenced the null provider and threw. It
now returns no auth headers when no auth provider is configured, so token fetching (and therefore
all OAuth-authenticated calls) works.

**`(fix):`** Fix generated code failing to compile under `auth: endpoint-security` when the OAuth token
endpoint declares custom request properties (scopes, custom body properties, or headers). The
generated `OAuthTokenSupplier` constructor takes an extra parameter for each such property, but
the `OAuthAuthProvider` always called it with a fixed three arguments
(`clientId, clientSecret, authClient`), producing a "constructor cannot be applied to given
types" error. `OAuthAuthProvider` now passes a matching argument for every extra property
(`Optional.empty()` for optional properties), sharing the property computation with the token
supplier generator so the two cannot drift.

**`(fix):`** Fix generated OAuth code snippets and README failing to compile under `auth: endpoint-security`.
The snippet/README examples instantiate the client with `Client.withCredentials(clientId,
clientSecret)`, but under endpoint-security the client builder is not staged, so that factory was
never generated — producing "cannot find symbol: method withCredentials" errors across the
generated example files. The client now generates a `withCredentials(clientId, clientSecret)`
convenience factory under endpoint-security that returns the standard builder pre-configured with
the OAuth credentials.

**`(fix):`** Fix generated wire tests asserting OAuth authentication on endpoints that do not require it under
`auth: endpoint-security`. The wire-test generator applied the OAuth token flow (enqueue a token
response and assert `Authorization: Bearer ...`) to every test method based on an API-level check,
so a test for an `auth: []` (no-auth) endpoint enqueued a token and asserted a bearer header the
SDK correctly never sends — failing at runtime. Under endpoint-security the OAuth flow is now
applied per endpoint (only to endpoints that require auth); global-auth behavior is unchanged.

## 4.15.2

**`(fix):`** Fix a `NullPointerException` when generating an SDK with `auth: endpoint-security` whose
OAuth (or inferred-auth) token endpoint is grouped under a subpackage not named `auth`.
The `RoutingAuthProvider` setup previously located the auth client by scanning for a
subpackage literally named `auth`, yielding `null` (and an NPE) when the token endpoint
lived in a differently-named group (e.g. `token`). It now resolves the client from the
token endpoint's actual subpackage, falling back to the core `AuthClient` when the token
endpoint sits at the API root.

## July 22, 2026

## 4.15.1

**`(fix):`** Fix `ConsoleLogger` dropping DEBUG-level log messages. The `ConsoleHandler` used by the
default logger keeps java.util.logging's default `INFO` handler level, so `FINE` records
(SDK debug logs, including HTTP request/response logging) were silently discarded even
when `LogConfig` was configured with `LogLevel.DEBUG` and `silent(false)`. The handler
level is now set to `ALL` so the configured `LogLevel` controls filtering.

## 4.15.0

**`(feat):`** Add webhook body-hash binding support. When a webhook's HMAC signature verification
declares a `bodyHashBinding` in the IR, the generated `verifySignature` helper now
recomputes the encoded hash of the raw request body using the binding's own
algorithm/encoding, extracts the configured query parameter from the notification URL,
and timing-safe-compares them (failing closed) before performing the existing HMAC
verification over the verbatim notification URL. A stdlib-only `WebhookBodyHash` core
utility (unkeyed digest plus read-only query-parameter extraction) is emitted alongside
the existing signature utility. Output is unchanged when no binding is present.

**`(feat):`** Add webhook signature verification support. When a webhook declares HMAC signature
verification in the IR, the generated SDK now emits a `WebhooksHelper` with a static
`verifySignature` method and a stdlib-only core utility for HMAC computation and
constant-time comparison.

**`(feat):`** Extend the generated `verifySignature` webhook helper with Twilio-compatible
verification, mirroring the TypeScript reference:

* Multi-value form params: when `payloadFormat.bodySort` is set, a `Map<String, ?>`
  overload accepts POST body parameters whose values are a `String` or a
  `Collection<String>`; keys are sorted, each key's values are deduped and sorted, and
  `key + value` pairs are concatenated with no separator before signing.
* Runtime body-hash branch: when a `bodyHashBinding` is configured, the configured
  query parameter is read from the notification URL at runtime. When present (JSON) the
  raw body is hashed and constant-time compared and the signature is checked over the
  URL only; when absent (classic form) the signature is checked over the URL + params
  with no body-hash check.
* No-throw: the verifier is a pure boolean. Every throw on the verification path (null
  or empty inputs, a missing/malformed timestamp) now returns `false`.
* URL any-match normalization: when `notificationUrlNormalization` is configured, the
  signature is verified against several normalized notification-URL candidates
  (standard-port/no-port variants and legacy query re-encoding), accepting on the first
  constant-time match. A stdlib-only `notificationUrlCandidates` method is added to the
  `WebhookSignature` core utility.
  Output is unchanged for webhooks that do not configure these fields, other than the
  no-throw behavior which applies to all generated helpers.

## July 21, 2026

## 4.14.6

**`(fix):`** Fixed an issue where the client builder rendered the first environment's URL
template instead of the selected environment's template when both a named
environment and server URL variables (e.g. `region`) were provided. The
builder now resolves the template(s) from the selected environment, falls
back to the first templated environment when no environment is selected, and
no longer overrides a custom environment or explicitly provided base URL.

## July 20, 2026

## 4.14.5

**`(fix):`** Honor `includePlatformHeaders` when generating to local file system output.
Previously the flag was only read for GitHub/publish output, so locally
generated SDKs always emitted the static `{package}/{version}` User-Agent
instead of the structured `{package}/{version} (os; arch) Java/{version}` form.

## July 15, 2026

## 4.14.4

**`(fix):`** Make the smart-casing digit/word boundary behavior opt-in via the new
`smart-casing-digit-word-boundary` option in `generators.yml`. By default, snake\_case
names keep a digit run fused to the following word (`conversations_v2configuration`),
restoring pre-4.13.4 output. When enabled, the word boundary after the digit run is
preserved (`conversations_v2_configuration`).

## 4.14.3

**`(fix):`** Endpoints without a request wrapper (no request object, or a request that is
just a body) now send service- and endpoint-level headers whose types are
literals (e.g. `Accept-Encoding: literal<"gzip">`). Previously these headers
were silently dropped because there was no request object to carry them.

## 4.14.2

**`(fix):`** Fix a `NullPointerException` when constructing an OAuth-only client under multi-scheme
(`any`) auth combining OAuth client-credentials with an API-key header. Building via
`withCredentials(...)` (no `apiKey(...)`) previously baked a `null` API-key header into
`ClientOptions`, which crashed okhttp `Headers.of` on the first HTTP call (the token
fetch). The generated `setAuthentication` now guards the API-key header add on a non-null
value, and `ClientOptions.Builder.addHeader(String, String)` defensively skips null values.
Supplying both credentials and an API key still sends both the `Authorization: Bearer`
and the API-key header, and an API-key-only client still validates the key at build time.

## 4.14.1

**`(fix):`** OAuth client credentials token requests now send `grant_type: "client_credentials"`
when the token endpoint declares a non-literal `grant_type` request property. The
property is synthesized in the token request and is no longer surfaced as a
builder option.

## July 14, 2026

## 4.14.0

**`(feat):`** Idempotency-key auto-generation is now driven by the IR (`SdkConfig.idempotencyKeyGeneration`)
instead of a per-generator config flag. When enabled, the SDK adds the configured idempotency
header on the configured HTTP methods unless the caller already supplied one. The header name
and eligible methods come from the IR, and generated output is unchanged when the feature is off.

## 4.13.4

**`(fix):`** Fix smart-casing snake\_case names dropping the word boundary after a number.
Names like `ConversationsV2Configuration` now produce the snake\_case
`conversations_v2_configuration` instead of `conversations_v2configuration`,
matching the camelCase and pascalCase variants.

## July 13, 2026

## 4.13.3

**`(chore):`** Upgrade the Java SDK generator to consume IR v67. The generated SDK output is unchanged.

_Showing the 20 most recent of 270 entries. Append `/llms.txt` to the changelog URL for the complete index._