Java Wait for thread to finish
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.
Introduction
If you need to wait for a Java thread to finish, the most direct tool is Thread.join(). The broader answer is that waiting strategy depends on how the work was started: raw threads use join, executor tasks use Future.get, and coordinated groups often use synchronization utilities such as CountDownLatch.
Use join() for a Plain Thread
join() blocks the current thread until the target thread has completed.
That is the standard answer when you created the thread yourself.
Use a Timed Join if Waiting Forever Is Unsafe
Sometimes an unbounded wait is not acceptable.
This gives you a fallback path instead of letting the caller block forever.
Use Future.get() for Executor Work
If you submitted the task to an executor, waiting on the returned Future is usually the correct abstraction.
This is better than trying to reach inside the executor and work with raw thread objects.
Use CountDownLatch for Multiple Workers
If several workers must finish before one thread proceeds, CountDownLatch can express that clearly.
This is often clearer than calling join() on many thread references manually.
Handle Interruptions Properly
Both join() and await() can throw InterruptedException. Do not swallow that exception casually. Either propagate it or restore the interrupted status.
That matters because interruption is part of Java’s thread coordination model, not just an inconvenient checked exception.
Waiting Is Coordination, Not Just Blocking
The best waiting primitive is the one that matches the ownership model of the work. If the code uses raw threads, join() is right. If it uses an executor, futures are right. Choosing the correct abstraction keeps concurrency code easier to understand later.
Avoid Waiting in the Wrong Thread
In UI or request-handling code, blocking the current thread can be just as problematic as forgetting to wait at all. Make sure the thread doing the waiting is the one that can safely block, otherwise the program stays correct but becomes unresponsive.
Make Completion Ownership Explicit
Good concurrent code makes it obvious which thread or component owns the responsibility to wait. Hidden waits buried inside utility methods often make timing and shutdown behavior harder to reason about later.
Common Pitfalls
The biggest mistake is using Thread.sleep() in the main thread as a fake way to “wait” for work to finish. Sleep guesses. join() and other coordination APIs actually synchronize on completion.
Another issue is calling join() on executor-managed work when a Future is the real control surface.
A third problem is ignoring interrupts, which makes shutdown and cancellation logic harder to reason about.
Summary
- Use
Thread.join()to wait for a raw thread you started directly. - Use timed join when an indefinite block is risky.
- Use
Future.get()for executor-submitted work. - Use
CountDownLatchwhen several workers must complete before continuing. - Handle
InterruptedExceptiondeliberately rather than hiding it.
Related reading
- java.lang.OutOfMemoryError unable to create new native Thread
- java.lang.RuntimeException Can't create handler inside thread that has not called Looper.prepare;
- Java's Fork/Join vs ExecutorService - when to use which?
- JavaScript - sync wait for async operation sleep
- javac option to compile all java files under a given directory recursively
- Javadoc link to method in other class
- JavaScript and Threads
- JavaScript async await doesn't work inside forEach loop

OOD Fundamentals
Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.