Mastering Apache Commons Pool2: Build Efficient Object Pools in Java
This article explains the concepts, core interfaces, object states, borrowing and returning processes, configuration options, and step‑by‑step usage of Apache Commons Pool2, including a complete example of a simple string pool with code snippets and best‑practice tips.
Introduction
Object pool is a pool that stores objects, similar to thread pools, database connection pools, or HTTP connection pools. It centralizes management, reduces frequent creation and destruction, improves reuse, saves resources, and lessens JVM garbage‑collection pressure.
Apache Commons Pool2 provides a generic object‑pool implementation; the popular Redis client Jedis uses it internally.
Core Interfaces
Key internal classes of Apache Commons Pool2:
ObjectPool : the pool interface used to borrow and return objects. borrowObject, returnObject, addObject, invalidateObject, getNumActive, getNumIdle PooledObject : wrapper that holds the actual object plus metadata such as creation time and last‑used time.
PooledObjectFactory : factory that manages the lifecycle of pooled objects (create, destroy, validate, activate, passivate).
BaseObjectPoolConfig (subclass GenericObjectPoolConfig): configuration base class for pool properties.
KeyedObjectPool<K,V> and KeyedPooledObjectFactory<K,V> : keyed pool variants (rarely used).
Pool Object States
The enum PooledObjectState defines possible states such as IDLE, ALLOCATED, EVICTION, VALIDATION, INVALID, ABANDONED, RETURNING, etc.
public enum PooledObjectState {
IDLE,
ALLOCATED,
EVICTION,
EVICTION_RETURN_TO_HEAD,
VALIDATION,
VALIDATION_PREALLOCATED,
VALIDATION_RETURN_TO_HEAD,
INVALID,
ABANDONED,
RETURNING
}Example: an object becomes ABANDONED when it stays allocated longer than getRemoveAbandonedTimeout.
private void removeAbandoned(final AbandonedConfig abandonedConfig) {
final long now = System.currentTimeMillis();
final long timeout = now - (abandonedConfig.getRemoveAbandonedTimeout() * 1000L);
// iterate over allObjects and mark abandoned ones
}Borrowing Process
Objects are stored in a ConcurrentHashMap called allObjects.
When borrowing, the pool may run an abandoned‑object cleanup based on AbandonedConfig.
It tries to take an object from the idle queue; if none and blockWhenExhausted is true, it waits according to borrowMaxWaitMillis.
If an object is obtained, the factory’s activateObject is called.
If testOnBorrow is true, validateObject is executed; failure leads to destruction.
Statistics are updated via updateStatsBorrow.
public T borrowObject(final long borrowMaxWaitMillis) throws Exception {
// core logic omitted for brevity
if (blockWhenExhausted) {
if (borrowMaxWaitMillis < 0) {
p = idleObjects.takeFirst();
} else {
p = idleObjects.pollFirst(borrowMaxWaitMillis, TimeUnit.MILLISECONDS);
}
}
// activation, validation, stats update...
return p.getObject();
}Returning Process
When returning, passivateObject is invoked; if validation fails, the object is destroyed; otherwise it is placed back into the idle queue.
public void returnObject(final T obj) {
final PooledObject<T> p = allObjects.get(new IdentityWrapper<>(obj));
try {
factory.passivateObject(p);
} catch (Exception e) {
destroy(p, DestroyMode.NORMAL);
// ensure idle queue size, update stats, etc.
}
}Configuration Options
maxTotal: maximum total objects (default 8). maxIdle, minIdle: bounds for idle objects. lifo: use LIFO order for idle objects (default true). blockWhenExhausted: block when pool is exhausted (default true). fairness: fair lock when blocking (default false). maxWaitMillis: maximum wait time for borrowing (default -1 = indefinite). testOnCreate, testOnBorrow, testOnReturn: validation flags. timeBetweenEvictionRunsMillis: eviction interval. testWhileIdle: validate idle objects during eviction.
Usage Steps
Create a factory by extending BaseGenericObjectPool or implementing PooledObjectFactory and override lifecycle methods.
Create the pool using GenericObjectPool (preferred) or ObjectPool.
(Optional) Define a custom configuration by extending GenericObjectPoolConfig or BaseObjectPoolConfig.
(Optional) Create a wrapper class for additional metadata.
Example: Simple String Pool
String Factory
package com.anqi.demo.demopool2.pool.fac;
import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
public class StringPoolFac extends BasePooledObjectFactory<String> {
@Override
public String create() throws Exception {
return "str-val-";
}
@Override
public PooledObject<String> wrap(String s) {
return new DefaultPooledObject<>(s);
}
// destroyObject, validateObject, activateObject, passivateObject omitted
}String Pool
package com.anqi.demo.demopool2.pool;
import org.apache.commons.pool2.PooledObjectFactory;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
public class StringPool extends GenericObjectPool<String> {
public StringPool(PooledObjectFactory<String> factory) {
super(factory);
}
public StringPool(PooledObjectFactory<String> factory, GenericObjectPoolConfig<String> config) {
super(factory, config);
}
}Test Main
import com.anqi.demo.demopool2.pool.fac.StringPoolFac;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class StringPoolTest {
private static final Logger LOG = LoggerFactory.getLogger(StringPoolTest.class);
public static void main(String[] args) {
StringPoolFac fac = new StringPoolFac();
GenericObjectPoolConfig<String> config = new GenericObjectPoolConfig<>();
config.setMaxTotal(2);
config.setMinIdle(1);
config.setMaxWaitMillis(3000);
StringPool pool = new StringPool(fac, config);
for (int i = 0; i < 3; i++) {
String s = "";
try {
s = pool.borrowObject();
LOG.info("str:{}", s);
} catch (Exception e) {
e.printStackTrace();
} finally {
// pool.returnObject(s); // uncomment to release
}
}
}
}Running the test without returning objects shows a NoSuchElementException after the third attempt because the pool is exhausted. Uncommenting the return statement releases the objects and the program completes successfully.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Interview Crash Guide
Dedicated to sharing Java interview Q&A; follow and reply "java" to receive a free premium Java interview guide.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
