Question
Java Threads: implements Runnable vs extends Thread
Question
In Java, I have seen two common ways to create work that runs on a separate thread.
One approach is to implement Runnable:
public class MyRunnable implements Runnable {
@Override
public void run() {
// Code
}
}
This is started with:
new Thread(new MyRunnable()).start();
Another approach is to extend Thread:
public class MyThread extends Thread {
public MyThread() {
super("MyThread");
}
@Override
public void run() {
// Code
}
}
This is started with:
new MyThread().start();
Is there any significant difference between these two approaches, and when should one be preferred over the other?
Short Answer
By the end of this page, you will understand the difference between implements Runnable and extends Thread in Java, why they are not exactly the same design choice, and why Runnable is usually preferred in real applications. You will also see how this relates to separation of concerns, code reuse, thread management, and modern Java concurrency practices.
Concept
In Java, both implements Runnable and extends Thread can be used to execute code on a new thread. In both cases, the actual work is placed inside a run() method, and the code begins running on a new thread when start() is called.
However, the two approaches represent different design ideas.
Runnable means “this class defines a task”
A class that implements Runnable describes work to be done.
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Task is running");
}
}
This class is not itself a thread. It is just a task. You pass that task to a Thread object:
Thread thread = new Thread(new ());
thread.start();
Mental Model
Think of Runnable as a job description and Thread as a worker.
- A
Runnablesays: “Here is the work that needs to be done.” - A
Threadsays: “I am the worker that will perform work.”
Using implements Runnable is like writing a task on a piece of paper and handing it to any available worker.
Using extends Thread is like creating a brand-new worker and permanently attaching the task to that worker.
In real systems, it is usually more flexible to keep the task separate from the worker. That way, many different workers can execute the same kind of task, and task objects can also be reused with thread pools.
Syntax and Examples
Core syntax
Using Runnable
class PrintTask implements Runnable {
@Override
public void run() {
System.out.println("Running task in: " + Thread.currentThread().getName());
}
}
public class Main {
public static void main(String[] args) {
Thread thread = new Thread(new PrintTask());
thread.start();
}
}
Using Thread
class PrintThread extends Thread {
@Override
public void run() {
System.out.println( + getName());
}
}
{
{
();
thread.start();
}
}
Step by Step Execution
Consider this example:
class HelloTask implements Runnable {
@Override
public void run() {
System.out.println("Hello from " + Thread.currentThread().getName());
}
}
public class Main {
public static void main(String[] args) {
System.out.println("Main starts");
Thread thread = new Thread(new HelloTask());
thread.start();
System.out.println("Main ends");
}
}
What happens step by step
1. main() begins running
The JVM starts the main thread and executes:
System.out.println("Main starts");
Output so far:
Main starts
Real World Use Cases
Where this concept is used
Background processing
Applications often need to do work without blocking the main flow:
- sending emails
- generating reports
- resizing images
- syncing data
These are naturally modeled as Runnable tasks.
Server-side request handling
In web servers and backend systems, units of work are often executed by managed worker threads. Even when you do not create Thread objects manually, the underlying idea is still task execution on threads.
Scheduled jobs
Periodic work such as:
- clearing cache
- polling an API
- refreshing tokens
- archiving logs
can be represented as Runnable tasks and run by schedulers.
Desktop applications
In GUI applications, long-running work should not freeze the interface. A background task can be defined separately and executed on another thread.
Data processing pipelines
If multiple independent items need processing, each processing step can be represented as a task and submitted to executors.
Real Codebase Usage
In real Java codebases, developers usually avoid manually subclassing Thread unless they have a strong reason.
Common patterns
1. Task classes implement Runnable
class CleanupTask implements Runnable {
@Override
public void run() {
// cleanup logic
}
}
This makes the task easy to test and reuse.
2. Use ExecutorService instead of new Thread(...)
ExecutorService executor = Executors.newFixedThreadPool(4);
executor.submit(new CleanupTask());
executor.shutdown();
This is more scalable than creating raw threads everywhere.
3. Guard clauses inside tasks
Developers often validate state early:
class {
String email;
{
.email = email;
}
{
(email == || email.isBlank()) {
;
}
System.out.println( + email);
}
}
Common Mistakes
1. Calling run() instead of start()
Broken code:
Thread thread = new Thread(new MyRunnable());
thread.run();
Why it is wrong:
run()is just a normal method call- no new thread is created
Correct code:
thread.start();
2. Thinking Runnable creates a thread by itself
Broken assumption:
MyRunnable task = new MyRunnable();
// task is not running yet
A Runnable only defines work. It must be passed to a Thread or executor.
3. Extending Thread when inheritance is needed elsewhere
Comparisons
implements Runnable vs extends Thread
| Aspect | implements Runnable | extends Thread |
|---|---|---|
| What it represents | A task | A thread object |
| Inheritance flexibility | Can still extend another class | Cannot extend another class |
| Separation of concerns | Better | Weaker |
| Reuse with executors | Natural fit | Less natural |
| Beginner simplicity | Slightly more setup | Slightly shorter syntax |
| Recommended in real apps | Usually yes | Usually no |
run() vs
Cheat Sheet
Quick rules
- Use
implements Runnableto define a task. - Use
new Thread(task).start()to run that task on a new thread. extends Threadalso works, but is usually less flexible.- Prefer
Runnablein most real Java code. - Never call
run()when you mean to start a new thread. - Use
ExecutorServicefor production-style concurrency.
Basic syntax
class MyTask implements Runnable {
@Override
public void run() {
// work here
}
}
Thread t = new Thread(new MyTask());
t.start();
Alternative syntax
class MyThread extends Thread {
{
}
}
().start();
FAQ
Is implements Runnable better than extends Thread in Java?
Usually, yes. Runnable is more flexible, separates the task from the thread, and works better with executors and thread pools.
Do both approaches create a new thread?
Yes, if you call start(). In both cases, a new thread is created and run() executes on that thread.
What happens if I call run() directly?
It runs on the current thread like a normal method call. No new thread is created.
Why is extending Thread considered less flexible?
Because Java supports only single inheritance. If your class extends Thread, it cannot extend any other class.
Can I use lambda expressions with Runnable?
Yes. Runnable is a functional interface, so lambdas work well for short tasks.
Is extends Thread ever useful?
Yes, but mostly in special cases where you need a custom thread type. For most application logic, Runnable is preferred.
What is the modern Java way to run background tasks?
Mini Project
Description
Build a small Java program that runs multiple background tasks to simulate processing files. This project demonstrates why Runnable is useful: each task describes a unit of work, while separate Thread objects execute that work. It mirrors real applications where jobs such as uploads, report generation, or cleanup run independently.
Goal
Create a program that starts several file-processing tasks concurrently using Runnable and prints which thread handles each task.
Requirements
- Create a class that implements
Runnable. - Pass task-specific data such as a file name into the task.
- Start at least three separate
Threadobjects using different task instances. - Print the current thread name and the file being processed.
- Wait for all threads to finish before the program exits.
Keep learning
Related questions
Avoiding Java Code in JSP with JSP 2: EL and JSTL Explained
Learn how to avoid Java scriptlets in JSP 2 using Expression Language and JSTL, with examples, best practices, and common mistakes.
Choosing a @NotNull Annotation in Java: Validation vs Static Analysis
Learn how Java @NotNull annotations differ, when to use each one, and how to choose between validation, IDE hints, and static analysis tools.
Convert a Java Stack Trace to a String
Learn how to convert a Java exception stack trace to a string using StringWriter and PrintWriter, with examples and common mistakes.