Skip to content

feat: expose immutable Headers map view - #900

Closed
sylvesterkaczmarek wants to merge 2 commits into
openai:mainfrom
sylvesterkaczmarek:feature/headers-map-view
Closed

sylvesterkaczmarek wants to merge 2 commits into
openai:mainfrom
sylvesterkaczmarek:feature/headers-map-view

Conversation

@sylvesterkaczmarek

Copy link
Copy Markdown
Contributor

Summary

Expose the SDK's existing immutable header representation as Headers.asMap() and document the value guarantees of Headers.values(...).

Addresses the header-conversion portion of #664.

Problem

Custom HttpClient implementations commonly need to adapt SDK headers into another HTTP library. Today that requires a two-step traversal:

headers.names().forEach(name -> {
    List<String> values = headers.values(name);
    // adapt name + values
});

Internally, Headers already stores exactly the desired Map<String, List<String>>, with case-insensitive name lookup and immutable value lists. The public API does not expose that representation directly.

The issue discussion also notes an undocumented guarantee: names returned by names() always have at least one value, while values(name) returns an empty list only for an absent header.

Change

Add:

Map<String, List<String>> headers = sourceHeaders.asMap();

asMap() returns the same immutable, case-insensitive representation held by the Headers instance. It does not copy or flatten multi-valued headers.

Also document the values(name) absent/present behaviour.

Regression coverage

Added focused tests verifying that the returned map:

  • preserves case-insensitive header lookup;
  • preserves multiple values in insertion order;
  • cannot be structurally mutated;
  • exposes immutable value lists.

Scope

Validation

The branch is based on current upstream main at 81134d727946fb84cc48db98e6c8c53c8a8d22ab and is 0 commits behind upstream.

Risk

Low. This is an additive API over the immutable data structure already stored by Headers; existing behaviour is unchanged.

@sylvesterkaczmarek
sylvesterkaczmarek requested a review from a team as a code owner August 18, 2026 23:02
@sylvesterkaczmarek

Copy link
Copy Markdown
Contributor Author

Following up after refreshing this branch to current main. It is now 0 behind, mergeable, and has no failing checks or unresolved review threads. Could a maintainer review it when convenient?

@dpiet-oai

Copy link
Copy Markdown
Contributor

Thank you, Sylvester, for the proposal and tests. We built on this work in #1245, which has now merged and adds Headers.asMap(). Closing this PR as superseded by that implementation.

@dpiet-oai dpiet-oai closed this Oct 20, 2026
jaredosgood pushed a commit to jaredosgood/openai-java that referenced this pull request Oct 22, 2026
Custom `HttpClient` adapters currently rebuild header maps and bridge
`HttpRequestBody.writeTo(OutputStream)` into an input stream. Add the
two accessors discussed in
[openai-java#664](openai#664):

```java
Map<String, List<String>> headers = request.headers().asMap();
try (InputStream content = body.content()) {
    // Give content to the transport and wait for consumption to finish.
}
// Close body after the final attempt, including failure or cancellation.
```

`Headers.asMap()` exposes an immutable, case-insensitive view with
immutable value lists. `HttpRequestBody.content()` has a default
implementation for existing Java implementations; that fallback buffers
`writeTo()` output. Built-in JSON bodies reuse their encoded bytes,
while multipart bodies stream raw uploads lazily. Repeatability,
close-once upload cleanup, and explicitly enabled DEBUG logging are
preserved. `HttpResponse.body()` already returns an `InputStream`.

Includes coverage for header immutability, Java source/binary
compatibility, multipart byte equivalence, shared uploads, early close
and cleanup failures, cancellation, retries, and stream-based request
logging.

Builds on Sylvester Kaczmarek's [headers proposal
openai#900](openai#900) and
[request-stream proposal
openai#923](openai#923).

Validation: 296 selected tests passed; no failures or skips. Kotlin lint
passed.

```sh
SKIP_MOCK_TESTS=true ./gradlew :openai-java-core:test \
  --tests '*HttpRequestBodyContentTest' --tests '*HttpRequestBodyJavaTest' \
  --tests '*HeadersMapViewTest' --tests '*LoggingHttpClientTest' \
  --tests '*HeadersTest' --tests '*HttpRequestBodiesTest' \
  --tests '*MultipartBodyCleanupTest' --tests '*RetryingHttpClientTest' \
  --tests '*UnionUploadStreaming*' :openai-java-core:lintKotlin
```

Co-authored-by: dpiet-oai <326640584+dpiet-oai@users.noreply.github.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