Skip to main content
  1. Java Concurrency (java.util.concurrent)/

ExecutorService Interface

1 min

While Executor is a simple “fire and forget” interface, ExecutorService expands it into a full-featured management framework. It adds two critical capabilities: Lifecycle Management and Task Tracking.

Source Code #

View Source on GitHub

Canonical Usage #

When to use: Use ExecutorService when you need to track the progress of a task, handle a return value (Future), or manage the orderly shutdown of your thread pool.

Common Patterns:

  • Result-Bearing Tasks: Use submit(Callable) to execute tasks that return a result or can throw checked exceptions.
  • Bulk Execution: Use invokeAll to run multiple tasks and wait for all of them to complete (e.g., parallelizing a data processing job).
  • Lifecycle Management: Always ensure an ExecutorService is shut down properly, often using a “two-phase” shutdown pattern (first shutdown, then shutdownNow if needed).
public <T> T runAndHandle(Callable<T> task) throws ExecutionException, InterruptedException {
    Future<T> future = executorService.submit(task);
    try {
        return future.get(5, TimeUnit.SECONDS);
    } catch (TimeoutException e) {
        future.cancel(true); // Cancel if not done in time
        throw e;
    }
}

// Two-phase shutdown pattern
public void stop(ExecutorService pool) {
    pool.shutdown();
    try {
        if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
            pool.shutdownNow();
        }
    } catch (InterruptedException e) {
        pool.shutdownNow();
        Thread.currentThread().interrupt();
    }
}