Bug Report Checklist
Description
With library=vertx and useVertx5=true, the generated client throws a NullPointerException on the first API call when the ApiClient is built with the two-argument constructor — the documented default:
Vertx vertx = Vertx.vertx();
ApiClient client = new ApiClient(vertx, new JsonObject()); // 2-arg ctor
new DefaultApiImpl(client).someOperation(request) // → NPE
java.lang.NullPointerException: Cannot invoke "java.util.concurrent.TimeUnit.toMillis(long)" because "sourceUnit" is null
at java.base/java.util.concurrent.TimeUnit.convert(TimeUnit.java:189)
at io.vertx.core.http.impl.HttpClientImpl.<init>(HttpClientImpl.java:72)
at io.vertx.core.http.impl.HttpClientBuilderInternal.build(HttpClientBuilderInternal.java:111)
at io.vertx.core.Vertx.createHttpClient(Vertx.java:274)
at io.vertx.ext.web.client.WebClient.create(WebClient.java:86)
at com.zyte.api.client.ApiClient.buildWebClient(ApiClient.java:644)
at com.zyte.api.client.ApiClient.getWebClient(ApiClient.java:134)
at com.zyte.api.client.ApiClient.invokeAPI(ApiClient.java:472)
at com.zyte.api.client.api.DefaultApiImpl.extract(DefaultApiImpl.java:94)
at com.zyte.api.client.api.DefaultApi.extract(DefaultApi.java:32)
(com.zyte.api.client is a custom invokerPackage; the default org.openapitools.client produces the same trace. The NPE surfaces on the first call rather than at construction because buildWebClient is invoked lazily from getWebClient().)
Root cause — two pieces interact:
-
The template's 2-arg constructor delegates with an empty pool config, and buildWebClient unconditionally turns it into a PoolOptions
(ApiClient.mustache lines 76–78 and 728–735 on current master):
public ApiClient(Vertx vertx, JsonObject config) {
this(vertx, config, new JsonObject());
}
...
protected WebClient buildWebClient(Vertx vertx, JsonObject config, JsonObject poolConfig) {
...
return WebClient.create(vertx, new WebClientOptions(config), new PoolOptions(poolConfig));
}
-
Vert.x 5's PoolOptions(JsonObject) constructor does not initialize defaults, unlike its no-arg constructor, and unlike sibling options classes such as HttpClientOptions(JsonObject), which calls init() before applying the JSON.
public PoolOptions() {
http1MaxSize = DEFAULT_MAX_POOL_SIZE; // 5
http2MaxSize = DEFAULT_HTTP2_MAX_POOL_SIZE; // 1
maxLifetime = DEFAULT_MAXIMUM_LIFETIME; // 0
maxLifetimeUnit = DEFAULT_MAXIMUM_LIFETIME_TIME_UNIT; // SECONDS
cleanerPeriod = DEFAULT_POOL_CLEANER_PERIOD; // 1000
eventLoopSize = DEFAULT_POOL_EVENT_LOOP_SIZE; // 0
maxWaitQueueSize = DEFAULT_MAX_WAIT_QUEUE_SIZE; // -1
}
public PoolOptions(JsonObject json) {
PoolOptionsConverter.fromJson(json, this); // no defaults applied first
}
So new PoolOptions(new JsonObject()) yields maxLifetimeUnit = null (plus http1MaxSize = 0, http2MaxSize = 0, cleanerPeriod = 0, …), and HttpClientImpl's constructor NPEs on
(HttpClientImpl.java line 72):
this.maxLifetime = MILLISECONDS.convert(poolOptions.getMaxLifetime(), poolOptions.getMaxLifetimeUnit());
Note the NPE is only the loudest symptom. A partially populated poolConfig (e.g. just {"maxLifetimeUnit": "SECONDS"}) avoids the NPE but silently produces http1MaxSize = 0 / http2MaxSize = 0 instead of the documented defaults (5 / 1), because every field not present in the JSON keeps its uninitialized value. Any fix should restore Vert.x defaults for all absent keys, not just dodge the NPE.
The minimal demonstration needs no generated code at all:
// NPEs immediately on any Vert.x 5.0.x:
WebClient.create(Vertx.vertx(), new WebClientOptions(), new PoolOptions(new JsonObject()));
openapi-generator version
7.23.0 (first affected — the poolConfig parameter was introduced by #23829, milestone 7.23.0). Still present on current master. Not a regression from 7.22.0 in the sense that 7.22.0's useVertx5 did not pass PoolOptions at all.
Vert.x: reproduced with 5.0.11
OpenAPI declaration file content or url
Any spec reproduces it — the bug is in the supporting ApiClient, not in path/model generation. Minimal example:
openapi: 3.0.3
info:
title: repro
version: 1.0.0
paths:
/ping:
get:
operationId: ping
responses:
'200':
description: ok
content:
application/json:
schema:
type: object
Generation Details
openapi-generator-cli generate \
-g java \
--library vertx \
--additional-properties useVertx5=true \
-i repro.yaml \
-o out
(supportVertxFuture is not required to reproduce.)
Steps to reproduce
-
Generate a client with the command above.
-
Run:
Vertx vertx = Vertx.vertx();
ApiClient client = new ApiClient(vertx, new JsonObject());
new DefaultApiImpl(client).ping().onComplete(System.out::println);
-
First call fails with the NullPointerException above (thrown from buildWebClient → WebClient.create).
Related issues/PRs
Suggest a fix
In Java/libraries/vertx/ApiClient.mustache, build the PoolOptions on top of Vert.x defaults so absent keys keep their documented values (PoolOptionsConverter's fromJson/toJson methods are package-private — @JsonGen(publicConverter = false) — so the overlay has to go through PoolOptions.toJson()):
protected WebClient buildWebClient(Vertx vertx, JsonObject config, JsonObject poolConfig) {
if (!config.containsKey("userAgent")) {
config.put("userAgent", "...");
}
// PoolOptions(JsonObject) does not initialize defaults (unlike its no-arg
// constructor); overlay the user config on serialized defaults so absent
// keys keep their documented values instead of 0/null.
PoolOptions poolOptions = new PoolOptions(new PoolOptions().toJson().mergeIn(poolConfig));
return WebClient.create(vertx, new WebClientOptions(config), poolOptions);
}
A minimal alternative — poolConfig.isEmpty() ? WebClient.create(vertx, options) : ... — fixes the default-constructor NPE but still mis-handles partially populated pool configs, so the overlay variant is preferable.
Workaround for affected users (no fork needed, buildWebClient is protected):
ApiClient client = new ApiClient(vertx, config) {
@Override
protected WebClient buildWebClient(Vertx vertx, JsonObject config, JsonObject poolConfig) {
return WebClient.create(vertx, new WebClientOptions(config)); // let WebClient apply pool defaults
}
};
Bug Report Checklist
Description
With
library=vertxanduseVertx5=true, the generated client throws aNullPointerExceptionon the first API call when theApiClientis built with the two-argument constructor — the documented default:(
com.zyte.api.clientis a custominvokerPackage; the defaultorg.openapitools.clientproduces the same trace. The NPE surfaces on the first call rather than at construction becausebuildWebClientis invoked lazily fromgetWebClient().)Root cause — two pieces interact:
The template's 2-arg constructor delegates with an empty pool config, and
buildWebClientunconditionally turns it into aPoolOptions(
ApiClient.mustachelines 76–78 and 728–735 on current master):Vert.x 5's
PoolOptions(JsonObject)constructor does not initialize defaults, unlike its no-arg constructor, and unlike sibling options classes such asHttpClientOptions(JsonObject), which callsinit()before applying the JSON.So
new PoolOptions(new JsonObject())yieldsmaxLifetimeUnit = null(plushttp1MaxSize = 0,http2MaxSize = 0,cleanerPeriod = 0, …), andHttpClientImpl's constructor NPEs on(
HttpClientImpl.javaline 72):Note the NPE is only the loudest symptom. A partially populated
poolConfig(e.g. just{"maxLifetimeUnit": "SECONDS"}) avoids the NPE but silently produceshttp1MaxSize = 0/http2MaxSize = 0instead of the documented defaults (5 / 1), because every field not present in the JSON keeps its uninitialized value. Any fix should restore Vert.x defaults for all absent keys, not just dodge the NPE.The minimal demonstration needs no generated code at all:
openapi-generator version
7.23.0 (first affected — the
poolConfigparameter was introduced by #23829, milestone 7.23.0). Still present on current master. Not a regression from 7.22.0 in the sense that 7.22.0'suseVertx5did not passPoolOptionsat all.Vert.x: reproduced with 5.0.11
OpenAPI declaration file content or url
Any spec reproduces it — the bug is in the supporting
ApiClient, not in path/model generation. Minimal example:Generation Details
(
supportVertxFutureis not required to reproduce.)Steps to reproduce
Generate a client with the command above.
Run:
First call fails with the
NullPointerExceptionabove (thrown frombuildWebClient→WebClient.create).Related issues/PRs
[BUG] [Java] [vertx] Cannot configure PoolOptions in vertx 5(the feature request this code came from)[Java] [vertx] Allow PoolOptions configuration when vertx 5(merged 2026-05-22, milestone 7.23.0; introduced the affected code path)PoolOptions(JsonObject)deviates from theHttpClientOptions(JsonObject)convention of initializing defaults before applying JSON. But the template needs to work with all released 5.0.x versions regardless, so a template-side fix is needed either way.Suggest a fix
In
Java/libraries/vertx/ApiClient.mustache, build thePoolOptionson top of Vert.x defaults so absent keys keep their documented values (PoolOptionsConverter'sfromJson/toJsonmethods are package-private —@JsonGen(publicConverter = false)— so the overlay has to go throughPoolOptions.toJson()):A minimal alternative —
poolConfig.isEmpty() ? WebClient.create(vertx, options) : ...— fixes the default-constructor NPE but still mis-handles partially populated pool configs, so the overlay variant is preferable.Workaround for affected users (no fork needed,
buildWebClientisprotected):