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 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
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 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
Runnable
task. - Cannot return a result.
submit()
- Overloaded to support both
Runnable
andCallable
. 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 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 |