multithreading
java concurrency
thread return value
programming
code examples

Returning value from Thread

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

A Thread in Java does not directly return a value the way a normal method does. If you want a background computation to produce a result, the usual solution is to use Callable and Future, or a higher-level concurrency API such as CompletableFuture, instead of trying to force a return value out of Thread itself.

Why Thread Does Not Return A Result

The Thread API is built around Runnable, and Runnable.run() returns void.

java
Runnable task = () -> {
    System.out.println("work");
};

That is why code like "start a thread and return its result" is not part of the raw Thread abstraction.

Use Callable And Future

Callable is the result-producing counterpart to Runnable.

java
1import java.util.concurrent.Callable;
2import java.util.concurrent.ExecutorService;
3import java.util.concurrent.Executors;
4import java.util.concurrent.Future;
5
6ExecutorService pool = Executors.newSingleThreadExecutor();
7
8Callable<Integer> task = () -> 21 * 2;
9Future<Integer> future = pool.submit(task);
10
11Integer result = future.get();
12System.out.println(result);
13
14pool.shutdown();

This is the standard Java answer because it gives you:

  • a return value
  • exception propagation
  • cancellation support
  • coordination without manual shared-state hacks

Avoid Shared Mutable Variables As A Return Mechanism

People often try something like this:

java
1final int[] box = new int[1];
2Thread t = new Thread(() -> box[0] = 42);
3t.start();
4t.join();
5System.out.println(box[0]);

This works in a narrow sense, but it is a poor pattern compared with Future because the value transport is hidden inside shared mutable state.

Use shared variables only when you truly need shared-state concurrency, not because you wanted a return value.

CompletableFuture For Modern Code

For newer Java code, CompletableFuture is often more expressive.

java
1import java.util.concurrent.CompletableFuture;
2
3CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 42);
4System.out.println(future.join());

This is especially useful when background work needs to be chained, transformed, or combined with other asynchronous results.

join() And get() Are Different

With a raw Thread, join() only waits for completion. It does not retrieve a result.

With Future, get() both waits and returns the result.

That difference is the reason higher-level concurrency APIs are the right abstraction for value-returning tasks.

Exceptions Matter Too

Another benefit of Callable and Future is that task exceptions are surfaced through the future.

java
Callable<Integer> badTask = () -> {
    throw new IllegalStateException("boom");
};

If you submit this to an executor, future.get() throws an ExecutionException wrapping the original cause.

That is far better than losing the failure in a background thread and wondering why the value never appeared.

Use Thread Only When You Need Thread Control

If the actual requirement is simply "do work concurrently and get a value back," then Thread is usually too low-level. ExecutorService, Future, and CompletableFuture are the real tools for that job.

Use raw Thread only when you specifically need to manage thread lifecycle directly.

Common Pitfalls

The biggest mistake is trying to make Thread.run() itself return a value. Another is using shared mutable containers as an improvised result channel when Future already solves the problem explicitly. Developers also often forget that join() only waits and does not transport a result. Finally, ignoring exceptions in worker threads can make a missing result look like a logic bug when it was really a failed computation.

Summary

  • A raw Java Thread does not directly return a value.
  • Use Callable plus Future when you want a result from background work.
  • 'CompletableFuture is often the cleaner modern choice for asynchronous result pipelines.'
  • Shared mutable variables are a weak substitute for proper result-handling APIs.
  • Prefer higher-level concurrency abstractions unless you specifically need raw thread control.

Course illustration
Course illustration

All Rights Reserved.