Skip to content

Conversation

@newtork
Copy link
Contributor

@newtork newtork commented Feb 10, 2026

Context

https://github.com/SAP/cloud-sdk-java-backlog/issues/501

Tasks

  • Initial implementation
  • Measurement
  • Feedback from CAP
    • Decision on / whether closing connection manager

This PR introduces a new opt-in API for caching HTTP connection pool managers in the Apache HttpClient 5 module. Connection pool managers can consume significant memory (~100KB each), and this feature allows users to reduce memory consumption by sharing connection managers based on configurable caching strategies.

Some users have reported high memory consumption due to the creation of many connection pool managers. By default, a new connection manager is created for each HTTP client, which provides maximum isolation but can lead to excessive memory usage in multi-tenant applications with many destinations.

This PR provides an opt-in mechanism to cache and reuse connection managers based on various strategies, including a smart byOnBehalfOf() strategy that automatically determines tenant isolation requirements based on the destination's header providers.

Usage Example

ApacheHttpClient5Factory factory = new ApacheHttpClient5FactoryBuilder()
    .connectionPoolManagerProvider(ConnectionPoolManagerProviders.cached().byOnBehalfOf()) // new API
    .build();

ApacheHttpClient5Accessor.setHttpClientFactory(factory);

New API

ConnectionPoolManagerProviders.noCache()

A singleton reference to indicate no caching / no re-use.
This is the default value, the behavior we've had before the change.

ConnectionPoolManagerProviders.cached()

A fluent builder API for creating cached connection pool manager providers:

// Use SINGLE connection pool manager by current tenant
ConnectionPoolManagerProviders.cached().byCurrentTenant()

// Cache SINGLE connection pool manager destination name
ConnectionPoolManagerProviders.cached().byDestinationName()

// Smart caching based on OnBehalfOf indication (recommended)
ConnectionPoolManagerProviders.cached().byOnBehalfOf()

// Custom cache key
ConnectionPoolManagerProviders.cached().by(dest -> dest.getUri().getHost())

Custom Cache Support

Users can supply their own cache implementation (e.g., Caffeine with expiration):

// Using Caffeine cache with expiration
Cache<Object, HttpClientConnectionManager> cache = Caffeine.newBuilder()
    .expireAfterAccess(Duration.ofMinutes(30))
    .maximumSize(100)
    .build();

ConnectionPoolManagerProviders.cached(cache::get).byOnBehalfOf()

byOnBehalfOf() Strategy

This strategy intelligently determines whether tenant isolation is required by inspecting the destination's header providers for IsOnBehalfOf implementations:

  • If a header provider indicates NAMED_USER_CURRENT_TENANT or TECHNICAL_USER_CURRENT_TENANT, the connection manager is cached per tenant
  • If a header provider indicates TECHNICAL_USER_PROVIDER or no custom header provider exist on the destination, the connection manager is shared across tenants

Breaking Changes

None. This is a purely additive change:

  • ✅ All existing public APIs remain unchanged
  • ✅ Default behavior is unchanged (noCache() is still the default)
  • ✅ Users must explicitly opt-in to use the new caching features
  • ✅ All new APIs are marked as @Beta

Definition of Done

  • Functionality scope stated & covered
  • Tests cover the scope above
  • Error handling created / updated & covered by the tests above
  • Documentation updated
  • Release notes updated

@newtork newtork added the please review Request to review a pull request label Feb 10, 2026
@newtork newtork marked this pull request as draft February 10, 2026 18:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

please review Request to review a pull request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant