Short answer
I'd pool only genuinely expensive-to-create resources — DB connections, threads, large buffers — where the creation cost dominates, and accept the real costs: borrow/return discipline, leak risk, validation of stale entries, sizing, and contention.
Explanation
For ordinary short-lived objects like DTOs, pooling is usually a net loss on modern JVMs because TLAB allocation is fast and generational GC handles young garbage cheaply, so a pool just adds complexity and contention. Concretely I'd use HikariCP for connections and an ExecutorService for threads rather than building my own pool.
Common mistakes
- Pooling cheap objects.
- Ignoring that modern GC makes short-lived allocation cheap.