Backend Development 7 min read

Using Apache Commons Pool2 GenericKeyedObjectPool for gRPC Connection Management

This article explains how to apply Apache Commons Pool2's GenericKeyedObjectPool to pool gRPC ManagedChannel objects, discusses thread‑safety and performance considerations, and provides concrete Java code for a keyed object pool factory, pool configuration, and typical acquire/return usage patterns.

FunTester
FunTester
FunTester
Using Apache Commons Pool2 GenericKeyedObjectPool for gRPC Connection Management

In a previous post the author introduced the commons-pool2 library and its two main pool implementations: GenericObjectPool for single‑type objects and GenericKeyedObjectPool for a map‑like pool keyed by a custom identifier. The current article shows why the keyed pool is useful when a gRPC client needs to select different backend nodes based on a shard key.

The GenericKeyedObjectPool essentially stores objects in a Map<K, ObjectPool<V>> where the key is user‑defined and the value implements org.apache.commons.pool2.ObjectPool . The underlying map implementation is a thread‑safe ConcurrentHashMap . Two important notes from the source code are:

The internal map uses ConcurrentHashMap , guaranteeing thread safety.

Frequent borrow/return operations can cause performance overhead, so a dedicated benchmark is planned.

In the author's gRPC scenario, each io.grpc.ManagedChannel can serve dozens of concurrent threads, so the pool is not expected to become a bottleneck.

Poolable Class

The objects that will be pooled are the same as in the previous article (e.g., a wrapper implementing IPooled ).

Pool Factory Class

The factory must define the key and value types and extend BaseKeyedPooledObjectFactory . The following code shows a minimal abstract factory:

package com.funtester.funpool;

import com.funtester.base.interfaces.IPooled;
import org.apache.commons.pool2.BaseKeyedPooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
/**
 * 可池化工厂类
 */
abstract class KeyPoolFactory
extends BaseKeyedPooledObjectFactory
{

    abstract IPooled init();

    @Override
    IPooled create(F k) throws Exception {
        return init();
    }

    @Override
    PooledObject
wrap(IPooled obj) {
        return obj.reInit();
    }

    @Override
    void destroyObject(F key, PooledObject
p) throws Exception {
        p.getObject().destory();
        super.destroyObject(key, p);
    }
}

If the pooled objects do not hold external resources, overriding destroyObject is optional.

Object Pool

The pool itself is created with a GenericObjectPoolConfig that defines limits, idle policies, and blocking behavior. Example configuration:

package com.funtester.funpool;

import com.funtester.base.interfaces.IPooled;
import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;

class KeyPool {

    KeyPool(KeyPoolFactory factory) {
        this.factory = factory;
        this.pool = init();
    }

    private GenericKeyedObjectPool
pool = init();
    private KeyPoolFactory
factory;

    private GenericKeyedObjectPool
init() {
        // pool configuration
        GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
        poolConfig.setMaxTotal(8);          // maximum total objects
        poolConfig.setMinIdle(0);            // minimum idle objects
        poolConfig.setMaxIdle(8);            // maximum idle objects
        poolConfig.setMaxWaitMillis(-1);     // wait indefinitely when exhausted
        poolConfig.setLifo(true);            // LIFO ordering
        poolConfig.setMinEvictableIdleTimeMillis(1000L * 60L * 30L); // 30 min
        poolConfig.setBlockWhenExhausted(true);
        return new GenericKeyedObjectPool
(factory, poolConfig);
    }

    /**
     * Acquire an object from the pool
     */
    IPooled get(String key) {
        try {
            return pool.borrowObject("FunTester");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return factory.create("FunTester");
    }

    /**
     * Return an object to the pool
     */
    void back(String key, IPooled iPooled) {
        pool.returnObject("FunTester", iPooled);
    }

    /**
     * Execute a closure with a pooled client and ensure return
     */
    def execute(String key, Closure closure) {
        IPooled client = get(key);
        try {
            closure(client);
        } finally {
            back(key, client);
        }
    }
}

The author notes that the generic approach may be abandoned later due to its complexity, and plans to benchmark the two pool implementations.

Conclusion

The article demonstrates a practical way to integrate GenericKeyedObjectPool with gRPC channels, highlights thread‑safety via ConcurrentHashMap , and warns about potential performance impacts when borrowing and returning objects too frequently.

Javabackend developmentConcurrencygRPCObject PoolingApache Commons Pool2
FunTester
Written by

FunTester

10k followers, 1k articles | completely useless

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.