Learnitweb

What’s the difference between submit() and execute() in ExecutorService?

In Java, the ExecutorService interface provides two commonly used methods to run tasks asynchronously: submit() and execute(). Although both are used to schedule and run tasks in a thread pool, they differ in return type, error handling, and use cases.

Below is a detailed explanation of the differences between submit() and execute() in ExecutorService.

1. Return Type

execute(Runnable command)

  • This method returns void.
  • You cannot track the result of the task or know whether it completed successfully or failed.

submit(Runnable task)

  • This method returns a Future<?>.
  • Even though the task does not return a result, you can use the Future to:
    • Check if the task is done (isDone()).
    • Cancel the task (cancel()).
    • Detect exceptions that occurred during execution (get() will throw ExecutionException).

submit(Callable<T> task)

  • This version returns a Future<T>, which contains the actual result of the task once it completes.
  • It is used when the task produces a return value.

2. Exception Handling

execute()

  • If an exception occurs during execution, and it is not caught within the task, it is handled by the thread’s UncaughtExceptionHandler or logged by the thread pool.
  • You cannot programmatically retrieve or detect the exception from outside the task.

submit()

  • If an exception occurs, it is stored inside the returned Future.
  • When you call future.get(), the exception is wrapped inside an ExecutionException, and you can retrieve it using getCause().

This makes submit() more robust and suitable for applications where you want to monitor task failures or retry on error.

3. Task Type Support

execute()

  • Accepts only a Runnable task.
  • Cannot return a result.

submit()

  • Overloaded to support both Runnable and Callable.
  • Callable is useful when you want to return a result or throw checked exceptions.

4. When to Use Which?

Use execute() when:

  • You don’t care about the result.
  • You don’t need to handle exceptions programmatically.
  • You want to fire and forget a task.

Use submit() when:

  • You want to return a result from the task.
  • You want to handle exceptions properly using Future.
  • You want to cancel, monitor, or manage the task execution lifecycle.

Code Example

ExecutorService executor = Executors.newFixedThreadPool(2);

// Using execute()
executor.execute(() -> {
    System.out.println("Task with execute()");
    if (true) throw new RuntimeException("Oops in execute!");
});

// Using submit()
Future<?> future = executor.submit(() -> {
    System.out.println("Task with submit()");
    if (true) throw new RuntimeException("Oops in submit!");
});

try {
    future.get(); // Throws ExecutionException
} catch (ExecutionException e) {
    System.out.println("Caught exception from submit: " + e.getCause());
}

executor.shutdown();

Output:

  • The exception in execute() is not visible to the main thread.
  • The exception in submit() is caught and handled using Future.

Summary Table

Featureexecute()submit()
Return TypevoidFuture<?> or Future<T>
Supports CallableNoYes
Exception VisibilityNot visible outside the taskAvailable via Future.get()
Result TrackingNot possiblePossible using Future
Cancellation SupportNoYes, via future.cancel()
When to UseFire-and-forget tasksWhen result or exception handling is needed