apache/iceberg · warning
HTTP client reference count went negative key={}, refCount={
Error message
HTTP client reference count went negative key={}, refCount={} What it means
A warning logged by HttpClientCache.release() when the reference count drops below zero, meaning more releases were issued than acquires for this client key. The cache defensively resets refCount to 0 to keep the entry usable and returns false instead of throwing.
Source
Thrown at aws/src/main/java/org/apache/iceberg/aws/HttpClientCache.java:147
/**
* Release a reference to the HTTP client, decrementing the reference count. If the count
* reaches zero, the client is closed.
*
* @return true if the client was closed, false otherwise
*/
synchronized boolean release() {
if (closed) {
LOG.warn("Attempted to release already closed HTTP client: key={}", clientKey);
return false;
}
refCount--;
LOG.debug("Released HTTP client: key={}, refCount={}", clientKey, refCount);
if (refCount == 0) {
return closeHttpClient();
} else if (refCount < 0) {
LOG.warn(
"HTTP client reference count went negative key={}, refCount={}", clientKey, refCount);
refCount = 0;
}
return false;
}
@VisibleForTesting
SdkHttpClient httpClient() {
return httpClient;
}
/**
* Close the HTTP client if not already closed.
*
* @return true if the client was closed by this call, false if already closed
*/
private boolean closeHttpClient() {
if (!closed) {View on GitHub (pinned to 86d9c8fc54)
Solutions
- Match every release() 1:1 with an acquire(); never release a client you didn't acquire for the same key
- Check whether an exception path releases both where it failed and in finally, and remove the duplicate
- If you see this repeatedly, log the calling stack at your call site to find the unbalanced release
- Rely on the cache's self-heal (refCount reset to 0) only as a safety net, not as expected behavior
Example fix
// before
if (condition) {
cache.releaseClient(key); // released without ever acquiring
}
// after
S3Client client = cache.acquire(key);
try {
use(client);
} finally {
cache.releaseClient(key);
} Defensive patterns
Strategy: validation
Validate before calling
// ensure acquire happened before release
if (!acquiredKeys.contains(key)) {
throw new IllegalStateException("release without acquire for " + key);
} Prevention
- Pair every acquire with exactly one release
- Centralize client borrowing in a wrapper that owns the count
- Audit refactors that remove acquire calls but leave releases
- Watch for this warning — it means your counting drifted
When it happens
Trigger: An unbalanced release with no matching acquire, or a double release racing past the refCount==0 close check. Because release() is synchronized, the negative value is only observable when the closed-flag guard didn't fire first (e.g. entry recreated between calls).
Common situations: Utility wrappers that release a client they never acquired; refactoring removed an acquire but left the release; concurrent callers sharing a key with hand-rolled counting that drifts.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- Attempted to release already closed HTTP client: key={}
- Cannot acquire closed HTTP client: ${clientKey}
- Unrecognized HTTP client type ${httpClientType}
- Unrecognized HTTP client type ${httpClientType}
- Cannot create %s to generate and configure the http client b
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/ea0f984f5d871f72.
Report an issue: GitHub.