[JAVA] Add generic okhttp client - #24734
Conversation
…ON-B Adds a new "okhttp" library to the Java client generator, forked from "okhttp-gson" but decoupled from Gson: one template set emits clients for Gson (default), Jackson 2, Jackson 3 and JSON-B, on OkHttp 5.4.0, and honours the existing useJspecify option. Templates (Java/libraries/okhttp/): - JSON.mustache carries three parallel implementations - Gson (gson-fire + TypeAdapterFactory), Jackson (ObjectMapper, jacksonPackage-parameterised for 2.x/3.x) and JSON-B (Yasson + JsonbAdapters, with a GenericType<T> helper standing in for TypeToken). - ApiClient holds JSON as an instance so serializer config is per-client, passes request payloads to auth as byte[] rather than String, and signs AWS4 last so it sees the final query string. - auth/RetryingOAuth embeds its own TokenRequestBuilder and talks to OkHttp directly, dropping the Apache Oltu dependency that okhttp-gson needs. - api/pojo/oneof/anyof switch between TypeToken, TypeReference and JSON.GenericType, and use the shared nullableArgument partials plus the jSpecifyDatatype lambda already provided by AbstractJavaCodegen. Templates that only differed cosmetically from their root-level counterparts (Pair, StringUtil, ServerVariable, ServerConfiguration, maven.yml, travis, git_push.sh) are deliberately not forked - mustache resolution falls back to Java/ and the generated output is verified to include them. JavaClientCodegen: - registers the library, adds it to JSPECIFY_SUPPORTED_LIBRARIES and to the useJackson3 allowlist, and reuses the okhttp-gson supporting-file branch without forcing the serialization library. - resolves isGson/isJackson/isJsonb after the serialization switch, where getSerializationLibrary() is authoritative, instead of guessing earlier. - forces openApiNullable off for JSON-B, since jackson-databind-nullable is Jackson-only and the pom omits it there. - applies the x-enum-as-string discriminator rewrite for every serialization library (a narrowed discriminator otherwise generates a getter that cannot override the parent's String getter), while keeping the JsonNullable and JsonIgnore imports Jackson-only. - extends the useSingleRequestParameter and oneOf/anyOf ModelNull handling to the new library, both of which its templates support.
…library bin/configs/java-okhttp-user-defined-templates.yaml generated the okhttp-gson sample despite the neutral name; every other okhttp-gson config on master is already named java-okhttp-gson-*. Renaming it keeps the naming consistent and frees the java-okhttp-* prefix for the new okhttp library, without touching the generated sample.
27 configs covering the new library: the four serialization variants (Gson, Jackson 2, Jackson 3, JSON-B), JSpecify for each of Jackson 2/3 and JSON-B, oneOf/anyOf, nullable-required, AWS4 signing, dynamic operations, grouped parameters, parcelable models, streaming, swagger 1/2 annotations, OpenAPI 3.1, the echo API and user-defined templates. Fixes carried over from the prototype branch: - java-okhttp-echo-api.yaml had "library" commented out, so it silently generated with the default okhttp-gson library instead of okhttp. - java-okhttp-jackson-nullable-required.yaml never set serializationLibrary: jackson, making it a duplicate of the gson config. - java-okhttp.yaml had "useReflectionEqualsHashCode::" with a double colon, so the property never applied. - java-okhttp-jackson3-oneOf.yaml wrote to others/java/oneOf-okhttp-jackson3, breaking the okhttp-<serializer>-<feature> convention of its siblings, and java-okhttp-jackson3-jspecify.yaml reused the artifactId of the non-jspecify jackson3 config. - java-okhttp-jsonb-jspecify.yaml set useJackson3 alongside JSON-B. Every config names its serialization library explicitly, including the Gson ones: JavaClientCodegen infers its internal "jackson" flag from the absence of the property, so relying on the default would opt Gson output into Jackson-specific nullable handling. All 27 configs generate, and every generated sample compiles and passes its tests under Maven.
- pom.xml: java-client-okhttp profile builds the four serialization variants, java-client-okhttp-parcelable mirrors the okhttp-gson parcelable profile. - Workflows: each okhttp-gson entry gains its okhttp counterpart, in both the path triggers and the sample matrices. The petstore-server workflow gets the three samples that mirror okhttp-gson there; the plain client workflow gets the serialization, jspecify, oneOf, nullable-required, swagger, streaming, AWS4, grouped-parameter and parcelable samples; echo-api, gradle and sbt get their okhttp equivalents. - docs/generators/java.md: regenerated - the library table gains okhttp and useJspecify now lists it as supported.
Generated output for the 27 new bin/configs. Every sample compiles and passes its tests under Maven, across all four serialization variants.
There was a problem hiding this comment.
10 issues found across 3000 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="samples/client/others/java/okhttp-jackson-oneOf/settings.gradle">
<violation number="1" location="samples/client/others/java/okhttp-jackson-oneOf/settings.gradle:1">
P3: The new settings.gradle is missing a trailing newline at end of file, unlike every other sample settings.gradle in this directory. Add a final newline to match conventions and avoid a spurious diff marker.</violation>
</file>
<file name="samples/client/others/java/okhttp-jackson-oneOf/src/main/java/org/openapitools/client/RFC3339InstantDeserializer.java">
<violation number="1" location="samples/client/others/java/okhttp-jackson-oneOf/src/main/java/org/openapitools/client/RFC3339InstantDeserializer.java:32">
P2: This deserializer is never installed in the generated mapper, so the `_fromString` override that normalizes space-separated RFC3339 values never runs. Register `RFC3339JavaTimeModule` in `JSON` alongside `JavaTimeModule`, or omit this supporting class.</violation>
</file>
<file name="samples/client/others/java/okhttp-jsonb-oneOf/src/main/java/org/openapitools/client/ApiResponse.java">
<violation number="1" location="samples/client/others/java/okhttp-jsonb-oneOf/src/main/java/org/openapitools/client/ApiResponse.java:23">
P3: Fields declare `final private` where the conventional order is `private final`. This is non-idiomatic modifier ordering throughout this new file.</violation>
<violation number="2" location="samples/client/others/java/okhttp-jsonb-oneOf/src/main/java/org/openapitools/client/ApiResponse.java:42">
P3: The javadoc for the `data` parameter says "deserialized from response bod" — a typo for "body". Worth fixing while this file is new.</violation>
</file>
<file name="samples/client/echo_api/java/okhttp-user-defined-templates/src/main/java/org/openapitools/client/auth/HttpBearerAuth.java">
<violation number="1" location="samples/client/echo_api/java/okhttp-user-defined-templates/src/main/java/org/openapitools/client/auth/HttpBearerAuth.java:40">
P2: When callers read the bearer token before configuring one, `getBearerToken()` throws a `NullPointerException` even though `applyToParams()` treats an unset token as valid unauthenticated state. Return null when `tokenSupplier` is unset, matching the other auth getters.</violation>
</file>
<file name="samples/client/echo_api/java/okhttp-user-defined-templates/src/main/java/org/openapitools/client/Configuration.java">
<violation number="1" location="samples/client/echo_api/java/okhttp-user-defined-templates/src/main/java/org/openapitools/client/Configuration.java:39">
P2: During concurrent first access, `AtomicReference.updateAndGet` may retry this updater after a CAS failure, so `apiClientFactory.get()` can construct and discard extra clients. Initialize the singleton under a non-retryable critical section, or keep factory invocation outside a retryable updater.</violation>
</file>
<file name="modules/openapi-generator/src/main/resources/Java/libraries/okhttp/additional_properties.mustache">
<violation number="1" location="modules/openapi-generator/src/main/resources/Java/libraries/okhttp/additional_properties.mustache:39">
P1: When `serializationLibrary=jsonb` is used with `additionalProperties: true`, JSON-B cannot round-trip undeclared fields. Unknown response keys are ignored, while values added through `putAdditionalProperty` are not emitted under their original names; add a JSON-B serializer/deserializer that captures and flattens undeclared fields and excludes the backing bean property.</violation>
</file>
<file name="samples/client/echo_api/java/okhttp-jackson-user-defined-templates/modelCopy/TestQueryStyleDeepObjectExplodeTrueObjectAllOfQueryObjectParameter.java">
<violation number="1" location="samples/client/echo_api/java/okhttp-jackson-user-defined-templates/modelCopy/TestQueryStyleDeepObjectExplodeTrueObjectAllOfQueryObjectParameter.java:23">
P2: The `@JsonTypeName` is emitted as `"java"` instead of the model name, because `{{name}}` in pojo.mustache resolves to the language name when the model is rendered through this `files`/modelCopy configuration. This corrupts the Jackson discriminator value that serialization depends on; the same models in `src/main/java/.../model/` emit the correct names. Fix the variable used in the modelCopy generation (e.g. render the model's name/discriminator value) so it matches the src output.</violation>
</file>
<file name="samples/client/others/java/okhttp-jackson-oneOf/git_push.sh">
<violation number="1" location="samples/client/others/java/okhttp-jackson-oneOf/git_push.sh:47">
P2: When the directory already has a non-`origin` remote, this check skips adding `origin`, but the script later pulls and pushes from `origin`. Check specifically for `origin` before deciding whether to add it.</violation>
<violation number="2" location="samples/client/others/java/okhttp-jackson-oneOf/git_push.sh:54">
P1: When `GIT_TOKEN` is set, this command stores the credential in plaintext in `.git/config` and exposes it through Git remote inspection. Use Git's credential helper or `GIT_ASKPASS` instead of embedding the token in the remote URL.</violation>
</file>
Note: This PR contains a large number of files. cubic only reviews up to 400 files per PR, so some files may not have been reviewed. cubic prioritizes the most important files to review.
Re-trigger cubic
| {{#isJackson}} | ||
| @JsonAnyGetter | ||
| {{/isJackson}} | ||
| public Map<String, Object> getAdditionalProperties() { |
There was a problem hiding this comment.
P1: When serializationLibrary=jsonb is used with additionalProperties: true, JSON-B cannot round-trip undeclared fields. Unknown response keys are ignored, while values added through putAdditionalProperty are not emitted under their original names; add a JSON-B serializer/deserializer that captures and flattens undeclared fields and excludes the backing bean property.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/resources/Java/libraries/okhttp/additional_properties.mustache, line 39:
<comment>When `serializationLibrary=jsonb` is used with `additionalProperties: true`, JSON-B cannot round-trip undeclared fields. Unknown response keys are ignored, while values added through `putAdditionalProperty` are not emitted under their original names; add a JSON-B serializer/deserializer that captures and flattens undeclared fields and excludes the backing bean property.</comment>
<file context>
@@ -0,0 +1,55 @@
+ {{#isJackson}}
+ @JsonAnyGetter
+ {{/isJackson}}
+ public Map<String, Object> getAdditionalProperties() {
+ return additionalProperties;
+ }
</file context>
| echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment." | ||
| git remote add origin https://${git_host}/${git_user_id}/${git_repo_id}.git | ||
| else | ||
| git remote add origin https://${git_user_id}:"${GIT_TOKEN}"@${git_host}/${git_user_id}/${git_repo_id}.git |
There was a problem hiding this comment.
P1: When GIT_TOKEN is set, this command stores the credential in plaintext in .git/config and exposes it through Git remote inspection. Use Git's credential helper or GIT_ASKPASS instead of embedding the token in the remote URL.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/others/java/okhttp-jackson-oneOf/git_push.sh, line 54:
<comment>When `GIT_TOKEN` is set, this command stores the credential in plaintext in `.git/config` and exposes it through Git remote inspection. Use Git's credential helper or `GIT_ASKPASS` instead of embedding the token in the remote URL.</comment>
<file context>
@@ -0,0 +1,63 @@
+ echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment."
+ git remote add origin https://${git_host}/${git_user_id}/${git_repo_id}.git
+ else
+ git remote add origin https://${git_user_id}:"${GIT_TOKEN}"@${git_host}/${git_user_id}/${git_repo_id}.git
+ fi
+
</file context>
| * @return The bearer token | ||
| */ | ||
| public String getBearerToken() { | ||
| return tokenSupplier.get(); |
There was a problem hiding this comment.
P2: When callers read the bearer token before configuring one, getBearerToken() throws a NullPointerException even though applyToParams() treats an unset token as valid unauthenticated state. Return null when tokenSupplier is unset, matching the other auth getters.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/echo_api/java/okhttp-user-defined-templates/src/main/java/org/openapitools/client/auth/HttpBearerAuth.java, line 40:
<comment>When callers read the bearer token before configuring one, `getBearerToken()` throws a `NullPointerException` even though `applyToParams()` treats an unset token as valid unauthenticated state. Return null when `tokenSupplier` is unset, matching the other auth getters.</comment>
<file context>
@@ -0,0 +1,74 @@
+ * @return The bearer token
+ */
+ public String getBearerToken() {
+ return tokenSupplier.get();
+ }
+
</file context>
| return tokenSupplier.get(); | |
| return Optional.ofNullable(tokenSupplier).map(Supplier::get).orElse(null); |
The previously committed samples were generated with a jar that still contained seven okhttp templates from an earlier prototype build: Pair, StringUtil, ServerVariable, ServerConfiguration, maven.yml, travis and git_push.sh. Those templates are intentionally not part of this library - mustache resolution is supposed to fall back to the shared Java/ and _common/ templates - but stale target/classes output survived an incremental build and was shaded into the CLI jar, where library-local templates take precedence. Rebuilt with `mvn clean install` and regenerated. Only git_push.sh actually differed in the output; it now matches _common/git_push.sh.mustache and is byte-identical to the okhttp-gson sample, as are Pair, StringUtil, ServerVariable, ServerConfiguration, maven.yml and travis.
Correctness fixes to the forked templates: - GzipRequestInterceptor: restore the forceContentLength() wrapper dropped from okhttp-gson. Without it the gzip body reports contentLength() == -1, OkHttp falls back to Transfer-Encoding: chunked, and every compressed request fails against servers that reject chunked request bodies. The unused okio.Buffer import was the tell that the removal was accidental. - ProgressResponseBody: restore the memoized bufferedSource field. OkHttp calls source() again on close(), so without caching each call built a fresh ForwardingSource over a partially consumed stream, resetting the progress counter and risking double reads. - ProgressRequestBody: CountingSink no longer reports done, so a known-length body gets exactly one terminal callback instead of two (writeTo still emits the terminal event, which is what unknown-length bodies rely on). Dropped the unreachable private countingSink(Sink) helper. - AWS4Auth: sign x-amz-* headers. They were never added to the signable request, so SigV4 omitted them from SignedHeaders and AWS rejected the signature. Content-Type is deliberately left unsigned because OkHttp's BridgeInterceptor appends the multipart boundary after signing. This also wires up the two-pass auth ordering in ApiClient, which until now had no effect. - JSON: register RFC3339JavaTimeModule for Jackson 2. The codegen already emitted RFC3339InstantDeserializer and RFC3339JavaTimeModule, but nothing installed them, so the space-separated RFC3339 normalization never ran. Every other Jackson library registers it. Jackson 3 does not emit the classes and is left untouched. - additional_properties: replace Java `transient` with @JsonbTransient. The transient keyword was added by this fork and made Java serialization drop all undeclared properties when serializableModel=true; @JsonbTransient keeps JSON-B from emitting the backing map without that side effect. - ApiResponse: lowercase header keys in the caseInsensitiveResponseHeaders branch, matching okhttp-gson, native, jersey2 and jersey3. - pojo: stop emitting five Jackson imports that JavaClientCodegen already contributes via model.imports, which produced duplicate import lines. JsonTypeName is only contributed for Jackson 2, so it is still emitted from the template under useJackson3. Build files: - build.gradle: add the com.google.android:android compileOnly dependency under parcelableModel, mirroring the pom. Generated Parcelable models now compile with a plain Gradle build. - build.sbt: add the swagger-parser-v3 dependency under dynamicOperations and the jakarta.validation-api dependency under useBeanValidation, both of which the pom and Gradle builds already declared; gate jackson-annotations on Jackson 2 so Jackson 3 clients take the version aligned by jackson-databind. All 27 samples regenerated; each compiles and passes mvn test, and JavaClientCodegenTest still passes 288 tests. okhttp-gson output is unchanged.
…imports The previous fix emitted the JsonTypeName import from pojo.mustache whenever useJackson3 was set. AbstractJavaCodegen already adds that import for any model whose classname was sanitized, regardless of Jackson version, so models such as Apple, Banana and EnumTest ended up importing it twice. Contribute it from JavaClientCodegen.postProcessModelProperty instead. Because model.imports is a Set, the import is emitted exactly once whichever path asks for it, and the template no longer hardcodes it. Jackson-annotation duplicate imports across the okhttp samples drop from 19 to 1. The remaining one is JsonIgnore on a model that hits both the additionalProperties template path and the shared nullable addImports path, which appends to a List without de-duplicating - the same pre-existing mechanism that leaves five duplicate imports in okhttp-gson-nullable-required on master.
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
There was a problem hiding this comment.
1 issue found across 1 file (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="samples/client/others/java/okhttp-jackson-oneOf/git_push.sh">
<violation number="1" location="samples/client/others/java/okhttp-jackson-oneOf/git_push.sh:32">
P2: `git_branch` is never defined in this script (it only sets git_user_id, git_repo_id, release_note, git_host), so `-b "$git_branch"` expands to an empty string and `git init -b ""` fails with `fatal: invalid initial branch name: ''` (exit 128). The generating template modules/openapi-generator/src/main/resources/_common/git_push.sh.mustache still uses plain `git init` and defines no git_branch, so this sample is also inconsistent with its source. Either drop `-b "$git_branch"` (matching the template) or set git_branch to a default like `master` before calling git init.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| fi | ||
|
|
||
| # Initialize the local directory as a Git repository | ||
| git init -b "$git_branch" |
There was a problem hiding this comment.
P2: git_branch is never defined in this script (it only sets git_user_id, git_repo_id, release_note, git_host), so -b "$git_branch" expands to an empty string and git init -b "" fails with fatal: invalid initial branch name: '' (exit 128). The generating template modules/openapi-generator/src/main/resources/_common/git_push.sh.mustache still uses plain git init and defines no git_branch, so this sample is also inconsistent with its source. Either drop -b "$git_branch" (matching the template) or set git_branch to a default like master before calling git init.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/others/java/okhttp-jackson-oneOf/git_push.sh, line 32:
<comment>`git_branch` is never defined in this script (it only sets git_user_id, git_repo_id, release_note, git_host), so `-b "$git_branch"` expands to an empty string and `git init -b ""` fails with `fatal: invalid initial branch name: ''` (exit 128). The generating template modules/openapi-generator/src/main/resources/_common/git_push.sh.mustache still uses plain `git init` and defines no git_branch, so this sample is also inconsistent with its source. Either drop `-b "$git_branch"` (matching the template) or set git_branch to a default like `master` before calling git init.</comment>
<file context>
@@ -29,7 +29,7 @@ if [ -z "${release_note}" ]; then
# Initialize the local directory as a Git repository
-git init
+git init -b "$git_branch"
# Adds the files in the local repository and stages them for commit.
</file context>
| git init -b "$git_branch" | |
| git init |
Adds a new
okhttplibrary to the Java client generator, marked[BETA]. It is derived fromokhttp-gson, but decoupled from Gson: a single template set emits working clients for Gson(default), Jackson 2, Jackson 3 and JSON-B, on OkHttp 5.4.0, and it honours the existing
useJspecifyoption.okhttp-gsonis untouched and remains the default library — its generated output is byte-identicalto master. This is purely additive for existing users; the new library is opt-in via
--library okhttp.JavaClientCodegenJSPECIFY_SUPPORTED_LIBRARIESand to theuseJackson3allowlist, and reuses the
okhttp-gsonsupporting-file branch without forcing a serializationlibrary.
isGson/isJackson/isJsonbafter the serialization switch, wheregetSerializationLibrary()is authoritative. No new early property reads, so no behavioural riskto other libraries.
openApiNullableoff for JSON-B:jackson-databind-nullableis Jackson-only and the pomomits it there, so leaving the flag on emitted
JsonNullablereferences that could not resolve.x-enum-as-stringdiscriminator rewrite for every serialization library, not justJackson. This is not a Jackson nicety — a child schema that narrows an inherited discriminator to
a single-value enum otherwise generates a getter that cannot override the parent's
Stringgetter.
JsonNullable/JsonIgnoreimports stay Jackson-only.useSingleRequestParameterand theoneOf/anyOfModelNullhandling to the newlibrary; both were gated on allowlists that excluded it, even though its templates support them.
Samples — 27 configs covering the four serialization variants, JSpecify for each of Jackson 2/3
and JSON-B,
oneOf/anyOf, nullable-required, AWS4 signing, dynamic operations, groupedparameters, parcelable models, streaming, Swagger 1/2 annotations, OpenAPI 3.1, the echo API and
user-defined templates.
One rename —
bin/configs/java-okhttp-user-defined-templates.yaml→java-okhttp-gson-user-defined-templates.yaml. It generated theokhttp-gsonsample despite theneutral name, and every other
okhttp-gsonconfig is already namedjava-okhttp-gson-*. Contentunchanged, generated sample untouched.
PR checklist
Commit all changed files.
This is important, as CI jobs will verify all generator outputs of your HEAD commit as it would merge with master.
These must match the expectations made by your contribution.
You may regenerate an individual generator by passing the relevant config(s) as an argument to the script, for example
./bin/generate-samples.sh bin/configs/java*.IMPORTANT: Do NOT purge/delete any folders/files (e.g. tests) when regenerating the samples as manually written tests may be removed.
@bbdouglas @sreeshas @jfiala @lukoyanov @cbornet @jeff9finger @karismann @Zomzog @lwlee2608 @martin-mfg @KannaKim
Summary by cubic
Adds a new
okhttpJava client library that generates OkHttp 5.4.0 clients for Gson (default), Jackson 2/3, and JSON‑B.okhttp-gsonremains the default and byte‑identical; the new library is opt‑in via--library okhttp. Also standardizes discriminator handling and fixes correctness issues in the new templates.okhttpinJavaClientCodegen, resolves serializer flags afterserializationLibrary, addsokhttptoJSPECIFY_SUPPORTED_LIBRARIES, extendsuseSingleRequestParameterandoneOf/anyOfnull handling, forcesopenApiNullable=falsefor JSON‑B, and applies thex-enum-as-stringdiscriminator rewrite across all serializers.Java/libraries/okhttp/*with per‑client JSON config, payload bytes passed to auth, and AWS4 signed last;RetryingOAuthuses OkHttp (drops Apache Oltu). Correctness fixes include gzip content length restoration, progress body/source memoization, signingx‑amz-*headers, Jackson 2 RFC3339 date module registration, and@JsonbTransientfor additionalProperties.okhttpsample configs (Gson/Jackson 2/3/JSON‑B, JSpecify,oneOf/anyOf, streaming, AWS4, OpenAPI 3.1, grouped params, dynamic operations, parcelable), wires them into GitHub Actions and new Maven profiles, and updates generator docs. User‑defined template configs include a rename for theokhttp-gsonsample and newokhttp/okhttp-jacksonvariants.Usage and migration
okhttp-gsonusers.openApiNullableis disabled;jackson-databind-nullabletypes are not generated.Written for commit d7145f5. Summary will update on new commits.