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
Futureto:- Check if the task is done (
isDone()). - Cancel the task (
cancel()). - Detect exceptions that occurred during execution (
get()will throwExecutionException).
- Check if the task is done (
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
UncaughtExceptionHandleror 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 anExecutionException, and you can retrieve it usinggetCause().
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
Runnabletask. - Cannot return a result.
submit()
- Overloaded to support both
RunnableandCallable. Callableis 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 usingFuture.
Summary Table
| Feature | execute() | submit() |
|---|---|---|
| Return Type | void | Future<?> or Future<T> |
| Supports Callable | No | Yes |
| Exception Visibility | Not visible outside the task | Available via Future.get() |
| Result Tracking | Not possible | Possible using Future |
| Cancellation Support | No | Yes, via future.cancel() |
| When to Use | Fire-and-forget tasks | When result or exception handling is needed |
