Executor Framework
The Executor framework — thread pools, ExecutorService types, and how to configure them for production workloads.
Last reviewed: January 2025
🔍 What Is It?
Executor hierarchy:
Executor
└── ExecutorService
├── AbstractExecutorService
│ └── ThreadPoolExecutor (the main implementation)
│ └── ScheduledThreadPoolExecutor
└── ForkJoinPool (work-stealing, for parallel streams)Factory methods (Executors class):
- →
newFixedThreadPool(n)— fixed n threads, unbounded queue - →
newCachedThreadPool()— grows as needed, idle threads expire after 60s - →
newSingleThreadExecutor()— one thread, tasks run sequentially - →
newScheduledThreadPool(n)— for scheduled/periodic tasks - →
newVirtualThreadPerTaskExecutor()— virtual thread per task (Java 21) - →
newWorkStealingPool()— ForkJoinPool with parallelism = CPU count
ThreadPoolExecutor parameters:
- →corePoolSize — minimum threads kept alive
- →maximumPoolSize — maximum threads allowed
- →keepAliveTime — how long idle threads above core survive
- →workQueue — queue for pending tasks (LinkedBlockingQueue, SynchronousQueue, ArrayBlockingQueue)
- →threadFactory — how threads are created (set names here!)
- →rejectedExecutionHandler — what to do when queue is full
🎯 Why Do We Need It?
Without a thread pool:
- →Creating a new thread for each task is expensive (1MB stack allocation, OS thread creation)
- →Unlimited thread creation exhausts memory and OS resources
- →No control over concurrency level
With a thread pool:
- →Threads are reused across tasks
- →Concurrency is bounded (no resource exhaustion)
- →Queue provides backpressure
- →Monitoring and management are centralized
🏗️ What Happens in Real Projects?
In Spring Boot, configure thread pools as beans:
@Configuration
public class AsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(50);
executor.setQueueCapacity(500);
executor.setThreadNamePrefix("async-");
executor.initialize();
return executor;
}
}⚙️ How It Works
Task Submission Flow:
- Task submitted to ExecutorService
- If corePoolSize not reached: create new thread
- If corePoolSize reached: add to queue
- If queue full: create new thread (up to maximumPoolSize)
- If maximumPoolSize reached and queue full: apply rejection policy
Rejection Policies:
- →AbortPolicy (default) — throw RejectedExecutionException
- →CallerRunsPolicy — run in the calling thread (backpressure)
- →DiscardPolicy — silently discard
- →DiscardOldestPolicy — discard oldest queued task
Sizing thread pools:
- →CPU-bound: threads = CPU cores (no benefit from more)
- →I/O-bound: threads = CPU cores * (1 + wait_time/compute_time) — more threads to cover I/O wait
💻 Example
Thread Pool Configuration for Production — How to configure and use thread pools in enterprise Java.
🤔 Common Confusions
❓ What is the problem with Executors.newFixedThreadPool()?
newFixedThreadPool() uses an unbounded LinkedBlockingQueue. If tasks are submitted faster than they are processed, the queue grows without limit, eventually causing OutOfMemoryError. For production, use ThreadPoolExecutor with a bounded queue and a rejection policy (CallerRunsPolicy for backpressure).
❓ How many threads should I put in my pool?
For CPU-bound tasks: number of CPU cores (Runtime.getRuntime().availableProcessors()). For I/O-bound tasks (DB calls, HTTP): more threads — typically 10-50 for typical web services. The right number depends on your I/O wait time. Monitor thread pool utilization and tune based on actual metrics. With Java 21 virtual threads, I/O-bound sizing concerns largely disappear.
🚨 Common Production Issues
Unbounded queue causing OOM — newFixedThreadPool() has an unbounded queue. Use bounded ThreadPoolExecutor in production.
Not shutting down executor — ExecutorService threads are non-daemon by default. Forgetting to call shutdown() prevents JVM exit.
Wrong rejection policy — AbortPolicy (default) throws exceptions that may be swallowed. CallerRunsPolicy provides natural backpressure for most services.
✅ Best Practices
Use bounded queues and explicit rejection policies in production.
Name threads meaningfully for debugging.
Set UncaughtExceptionHandler on thread pools.
Monitor pool metrics (active, queued, completed) in production.
Always shutdown executors gracefully on application stop.
Use virtual threads (Java 21) for I/O-bound tasks to simplify sizing.
🏛️ Architect's Note
In Spring Boot, prefer Spring's ThreadPoolTaskExecutor over raw ThreadPoolExecutor — it integrates with Spring's lifecycle (graceful shutdown on context close) and task decoration (MDC propagation, security context propagation). For Java 21+, consider virtual threads for I/O-bound services: spring.threads.virtual.enabled=true replaces Tomcat's thread pool with virtual threads.
💬 Frequently Asked Questions
🎯 Key Takeaways
🔗 Related Topics
🎯 Practice this topic in interviews
Core Java Interview Prep
Practice interview questions that connect directly to this technology track.
↗ Where this topic fits
Core Java Technology Guide
Return to the Core Java learning hub.
Backend Engineer Career Path
A structured journey through Core Java, Spring Boot, production backend engineering, and interview preparation.
Full Stack Java Developer Career Path
A guided route through frontend foundations, Java backend engineering, delivery workflows, and interview readiness.
Sources
Sources / References
Use these Core Java references to verify the concept in official docs and continue into the most relevant SplashRide pages.
Official documentation
Related SplashRide pages
SplashRide Core Java learning path
Browse the full Core Java topic collection and learning order on SplashRide.
Core Java Interview Prep
Practice Core Java interview questions and scenario-based answers.
Backend Engineer Career Path
A structured journey through Core Java, Spring Boot, production backend engineering, and interview preparation.
Full Stack Java Developer Career Path
A guided route through frontend foundations, Java backend engineering, delivery workflows, and interview readiness.
Threads
Java threads — creating and managing threads, thread lifecycle, and the concurrency problems every enterprise developer must understand.
Runnable
The Runnable interface — the standard way to define a task for concurrent execution in Java.