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.

Java Interview Crash Guide
Java Interview Crash Guide
Java Interview Crash Guide
Mastering Apache Commons Pool2: Build Efficient Object Pools in Java

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaResource Managementobject poolApache Commons Pool2
Java Interview Crash Guide
Written by

Java Interview Crash Guide

Dedicated to sharing Java interview Q&A; follow and reply "java" to receive a free premium Java interview guide.

0 followers
Reader feedback

How this landed with the community

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.