[Java][vertx] Apply Vert.x pool defaults in buildWebClient for useVertx5 (#24015)

Merged
SE
5mo ago
+29-3

Fixes #24015

Problem

With `library=vertx` and `useVertx5=true`, the generated client throws a `NullPointerException` on the first API call when the `ApiClient` is built with the documented two-arg constructor:

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 io.vertx.core.http.impl.HttpClientImpl.<init>(HttpClientImpl.java:72)
	...
	at ...ApiClient.buildWebClient(ApiClient.java:644)

Root cause

The two-arg constructor delegates with an empty pool config, and buildWebClient turned it into new PoolOptions(poolConfig). Unlike its no-arg constructor (and unlike WebClientOptions(JsonObject), which calls init() first), Vert.x 5's PoolOptions(JsonObject) constructor does not initialize defaults before applying the JSON. So new PoolOptions(new JsonObject()) yields maxLifetimeUnit = null (plus pool sizes 0), and HttpClientImpl NPEs on the null time unit.

This path was introduced by #23829 (merged for 7.23.0).

Fix

In Java/libraries/vertx/ApiClient.mustache, overlay poolConfig on the serialized no-arg defaults so absent keys keep their documented values while explicit pool settings still apply:

PoolOptions poolOptions = new PoolOptions(new PoolOptions().toJson().mergeIn(poolConfig));
return WebClient.create(vertx, new WebClientOptions(config), poolOptions);

PoolOptionsConverter is package-private (@JsonGen(publicConverter = false)), so the overlay goes through the public PoolOptions.toJson(). This also handles partially-populated pool configs correctly (unlike a simple poolConfig.isEmpty() guard).

Test evidence

Standalone runtime check against io.vertx:vertx-core:5.0.11:

BEFORE  maxLifetimeUnit=null     http1MaxSize=0   (-> NPE in WebClient.create)
AFTER   maxLifetimeUnit=SECONDS  http1MaxSize=5
PARTIAL http1MaxSize=20 (user override applied)   maxLifetimeUnit=SECONDS (default kept)

Regenerated the vertx5 and vertx5-supportVertxFuture samples (bin/generate-samples.sh); both compile (mvn compile, JDK 21). Regenerating the non-vertx5 vertx samples produces no diff (the {{^useVertx5}} branch is unchanged).

Verification done

  1. No in-flight PR — gh pr list --search "vertx PoolOptions" only shows the merged #23829 that introduced the code; 2. no self-claim comments (0 comments); 3. code-focused (.mustache template + regenerated .java); 4. grepped master — new PoolOptions(poolConfig) still present in template line 734; 5. not a closed sub-issue.

Summary by cubic

Fixes #24015: Prevents a NullPointerException on the first API call in Vert.x 5 clients by applying default PoolOptions values in buildWebClient when the two-arg ApiClient constructor is used. Defaults are merged with poolConfig so missing fields are initialized and user overrides still apply.

  • Bug Fixes
    • For library=vertx with useVertx5=true, avoid NPE caused by new PoolOptions(poolConfig) when the JSON is empty/partial (Vert.x 5 doesn’t init defaults in PoolOptions(JsonObject)).
    • Create PoolOptions via new PoolOptions().toJson().mergeIn(poolConfig) and pass to WebClient.create(...).
    • No change for non-useVertx5; regenerated vertx5 samples compile.