Implement Object Pool in Java with bounded capacity, explicit reset rules, and leak-safe borrow and release mechanics.
Object pool: A bounded collection of reusable resource objects that clients borrow, use briefly, reset, and return instead of constructing or tearing them down repeatedly.
In Java, Object Pool is only worth implementing when the pooled thing is genuinely expensive, scarce, or externally limited. Database connections, native buffers, parser instances with costly setup, and a few specialized infrastructure objects can fit. Ordinary domain objects usually do not.
Before writing a pool, define three things:
That contract matters more than the queue implementation.
1import java.time.Duration;
2import java.util.concurrent.ArrayBlockingQueue;
3import java.util.concurrent.BlockingQueue;
4import java.util.concurrent.TimeUnit;
5
6public final class ParserPool {
7 private final BlockingQueue<JsonParser> available;
8
9 public ParserPool(int size) {
10 this.available = new ArrayBlockingQueue<>(size);
11 for (int i = 0; i < size; i++) {
12 available.add(new JsonParser());
13 }
14 }
15
16 public JsonParser borrow(Duration timeout) throws InterruptedException {
17 JsonParser parser = available.poll(timeout.toMillis(), TimeUnit.MILLISECONDS);
18 if (parser == null) {
19 throw new IllegalStateException("No parser available");
20 }
21 return parser;
22 }
23
24 public void release(JsonParser parser) {
25 parser.reset();
26 if (!available.offer(parser)) {
27 throw new IllegalStateException("Pool overflow");
28 }
29 }
30}
The code is simple on purpose. A Java pool does not need cleverness first. It needs a correct lease boundary.
The flow below is the real lifecycle:
flowchart LR
Client["Client"] --> Borrow["borrow()"]
Borrow --> Pool["Bounded pool"]
Pool --> Use["Use resource"]
Use --> Reset["reset()"]
Reset --> Release["release()"]
Release --> Pool
The main failure mode in object pools is not queue choice. It is state contamination between borrowers.
Reset logic should clear:
If you cannot define a reliable reset routine, you probably should not pool the object.
When the pool is empty, the code should not improvise. Choose one of these behaviors deliberately:
The right choice depends on latency goals, resource limits, and failure tolerance.
Lease-based code should make return unavoidable:
1JsonParser parser = parserPool.borrow(Duration.ofMillis(200));
2try {
3 return parser.parse(payload);
4} finally {
5 parserPool.release(parser);
6}
That finally block is not optional. Without it, the pool quietly turns into a leak.
Many Java teams should not build a custom pool at all. For database access, HTTP connection reuse, thread execution, and common infrastructure concerns, mature libraries already solve capacity, monitoring, timeouts, and fault handling more safely than a homegrown pool.
Custom pools make sense when:
When reviewing a Java object pool, ask:
Object Pool is valid when it manages a real constrained resource. It is a smell when it exists only because “object creation is expensive” was copied from an old tutorial.