Both submit() and execute() schedule a task on an ExecutorService's thread pool, but execute() only accepts Runnable and returns nothing, while submit() accepts Runnable or Callable and returns a Future for retrieving results or exceptions.
Key Points: • execute(Runnable) comes from the base Executor interface and is a fire-and-forget call — no way to know when it finishes or whether it threw an exception. • submit() comes from ExecutorService and always returns a Future, even for a Runnable (in which case Future<?> resolves to null on success). • Exceptions thrown inside a task submitted via submit() are captured in the Future and only surface when you call future.get(), which wraps them in an ExecutionException. • Exceptions thrown inside a task run via execute() go to the thread's uncaught exception handler instead, which is easy to lose track of if not configured. • submit() is generally preferred whenever you need to know a task completed, need its result, or need to reliably observe exceptions.
Example: Fire-and-forget logging can use executor.execute(() -> log(message)) since no result is needed, while a task computing a report should use Future<Report> future = executor.submit(() -> buildReport()) so the exception path and result are both properly observable via future.get().
Code Example:
executor.execute(() -> System.out.println("fire and forget"));
Future<Integer> future = executor.submit(() -> {
return computeValue();
});
Integer value = future.get(); // exceptions surface hereInterview Tip: A concise interview answer is:
"execute() only takes a Runnable and gives you no way to observe completion or exceptions — it's fire-and-forget. submit() takes Runnable or Callable and always returns a Future, so you can retrieve a result and, importantly, exceptions thrown inside the task get captured and rethrown when you call future.get() instead of silently disappearing."