Summary
ApiClient's OAuth token methods build a brand-new JAX-RS Client via buildHttpClient(...) on every invocation but never close it — their finally blocks close only the Response. Each call therefore leaks a Jersey Client (and its underlying connection pool + executor threads + sockets). Long-running services that refresh tokens periodically (e.g. JWT grant per user, on cache expiry) accumulate these until the JVM runs out of heap / threads / file descriptors.
Because the SDK is JAX-RS/Jersey-based, this leak is invisible to tooling that watches OkHttp and only shows up as a steady climb in live org.glassfish.jersey.client.JerseyClient instance counts.
Affected version
Reproduced on 6.2.0 and confirmed still present on master (6.6.0) — so there is currently no released version without the issue.
Affected methods
At least the following in src/main/java/com/docusign/esign/client/ApiClient.java:
requestJWTUserToken(...)
requestJWTApplicationToken(...) (delegates to requestJWTUserToken)
generateAccessToken(...)
- the deprecated
configureJWTAuthorizationFlow(...)
All share this shape:
Client client = buildHttpClient(debugging);
WebTarget target = client.target("https://" + getOAuthBasePath() + "/oauth/token");
...
Response response = null;
try {
response = invocationBuilder.post(entity);
...
} finally {
try {
if (response != null) {
response.close(); // <-- Response is closed
}
} catch (Exception e) {
// it's not critical, ...
}
// <-- client is NEVER closed
}
jakarta.ws.rs.client.Client is AutoCloseable and holds a connection pool and a (default async) executor; per the JAX-RS spec it must be closed to release those resources. Since a fresh Client is created per token call and discarded, every token request leaks one.
Expected behavior
Each token method should close the Client it creates (or reuse a single shared instance). E.g.:
Client client = buildHttpClient(debugging);
try {
...
} finally {
if (response != null) {
try { response.close(); } catch (Exception ignored) { }
}
client.close(); // release the Client built for this call
}
(Since these files appear to be Swagger-Codegen-generated, the durable fix probably belongs in the generator template.)
Steps to reproduce
- In a long-running JVM, call
apiClient.requestJWTUserToken(...) in a loop (e.g. once per simulated token expiry).
- Take
jmap -histo:live <pid> snapshots over time.
- Observe
org.glassfish.jersey.client.JerseyClient (and ClientRuntime / ThreadPoolExecutor) instance counts rising monotonically with the number of token calls, never reclaimed even after full GC.
Impact
OutOfMemoryError / thread / file-descriptor exhaustion after long runtime in any service that uses JWT (or auth-code) tokens and refreshes them periodically. Consumers can only work around it by subclassing ApiClient and overriding protected buildHttpClient(boolean) to memoize a single shared Client, since the leaked instance is local to the SDK method and otherwise unreachable.
Environment
- docusign-esign-java 6.2.0 (also reproduced reading
master @ 6.6.0)
- Jersey / JAX-RS (jakarta.ws.rs) client
- Java 17
Summary
ApiClient's OAuth token methods build a brand-new JAX-RSClientviabuildHttpClient(...)on every invocation but never close it — theirfinallyblocks close only theResponse. Each call therefore leaks a JerseyClient(and its underlying connection pool + executor threads + sockets). Long-running services that refresh tokens periodically (e.g. JWT grant per user, on cache expiry) accumulate these until the JVM runs out of heap / threads / file descriptors.Because the SDK is JAX-RS/Jersey-based, this leak is invisible to tooling that watches OkHttp and only shows up as a steady climb in live
org.glassfish.jersey.client.JerseyClientinstance counts.Affected version
Reproduced on 6.2.0 and confirmed still present on
master(6.6.0) — so there is currently no released version without the issue.Affected methods
At least the following in
src/main/java/com/docusign/esign/client/ApiClient.java:requestJWTUserToken(...)requestJWTApplicationToken(...)(delegates torequestJWTUserToken)generateAccessToken(...)configureJWTAuthorizationFlow(...)All share this shape:
jakarta.ws.rs.client.ClientisAutoCloseableand holds a connection pool and a (default async) executor; per the JAX-RS spec it must be closed to release those resources. Since a freshClientis created per token call and discarded, every token request leaks one.Expected behavior
Each token method should close the
Clientit creates (or reuse a single shared instance). E.g.:(Since these files appear to be Swagger-Codegen-generated, the durable fix probably belongs in the generator template.)
Steps to reproduce
apiClient.requestJWTUserToken(...)in a loop (e.g. once per simulated token expiry).jmap -histo:live <pid>snapshots over time.org.glassfish.jersey.client.JerseyClient(andClientRuntime/ThreadPoolExecutor) instance counts rising monotonically with the number of token calls, never reclaimed even after full GC.Impact
OutOfMemoryError/ thread / file-descriptor exhaustion after long runtime in any service that uses JWT (or auth-code) tokens and refreshes them periodically. Consumers can only work around it by subclassingApiClientand overridingprotected buildHttpClient(boolean)to memoize a single sharedClient, since the leaked instance is local to the SDK method and otherwise unreachable.Environment
master@ 6.6.0)