There are two approaches to create threads in Java applications.
- Extending Thread Class
- Implementing Runnable Interface

It is important to note that the Thread class itself implements the Runnable interface. The Thread class provides the ability to create and manage a thread, along with several utility methods such as start(), sleep(), and join().
When we create a thread—regardless of whether we extend Thread or implement Runnable—the actual execution is always handled by a Thread object.
By extending Thread class

Step 1: Create a Thread subclass
- Declare a user defined class and extend
java.lang.Threadclass - Override Thread class
run()method in user defined thread class with the implementation representing a particular task which we want the thread to perform.
public class MyThread extends Thread {
public void run() {
for(int i = 0; i < 10; i++) {
System.out.println("Hello Thread: " + i);
}
}
}
Step 2: Initiate and start the thread
- In main class, in
main()method, create object for user defined class. - Access Thread class provided
start()method on user defined thread class object reference variable.
public class Main {
public static void main(String []args) {
MyThread mt = new MyThread();
mt.start();
for(int i = 0; i < 10; i++) {
System.out.println("Main Thread!");
}
}
}
Execution always starts from
main()method and hence Main Thread is formed first and then goes through to generate other child Threads.
Extending the Thread class will lead to occupying the extends keyword and we will not be able to extend any other class, as Java does not allow multiple inheritance due to ambiguity in method names.
By implementing Runnable interface

Step 1: Create a Runnable object
- Declare a user defined class and implement
java.lang.Runnableinterface. - Provide implementation for
run()method which the thread will execute
class MyRunnable implements Runnable {
public void run() {
System.out.println("code executed by thread: " + Thread.currentThread().getName());
}
}
Step 2: Start the thread
- Create an instance of your user defined class
- Pass the runnable object to Thread class constructor
- Start the thread
public class Main() {
public static void main(String[] args) {
System.out.println("Going inside main method: " + Thread.currentThread().getName());
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
System.out.println("Finished main method: " + Thread.currentThread().getName());
}
}
To perform the 2nd step above, we can have the following cases:
Case-1: Just creating class object and starting it
MyRunnable mt = new MyRunnable();
mt.start();
Status: Compilation Error
Reason: start() method was not declared in MyRunnable class or in its superclass java.lang.Object, because start() method exists in java.lang.Thread class and Thread class object is needed to call that method.
Case-2: Directly calling the run method on object instance
MyRunnable mt = new MyRunnable();
mt.run();
Status: No Compilation Error
Main thread will access MyRunnable class run() method like a normal Java method, no multi-threading environment. Normal method call.
Case-3: Calling the thread class start() method without using the custom thread implementation
MyRunnable mt = new MyRunnable();
Thread t = new Thread();
t.start();
Status: No Compilation Error
start() method creates new thread and it access Thread class run() method, not MyRunnable class run() method. Because we did not pass the Runnable into the Thread constructor so how will it know to call the Runnable run() method.
Case-4: Correct
MyRunnable mt = new MyRunnable();
Thread t = new Thread(mt);
t.start();
Status: No Compilation Error
start() method creates new thread and it will bypass calling the Thread class run method and move on to call MyRunnable class run() method.
Explanation:

- JVM kicks off the Main thread and it calls the
main()method. Inside which we make an object of Thread class and pass it an implementation of Runnable interface. This is updated inside the Thread class. - Now when we call the
start()method of the Thread class it will then check if there exist a Runnable or not. Since there is a Runnable updated so it will call the Runnable implementationrun()method and not the Thread classrun()method.
Why do we have 2 ways to create threads?
Extending the Thread class was introduced early in Java (JDK 1.0) as a simple, direct way to create a thread.
What problem it solves
- Very easy to understand
- Combines task + thread into one object
- Good for quick demos, learning, or simple use cases
Limitations (why this is not ideal)
- Java allows only single inheritance
- Your class is now forced to be a thread
- Poor separation of responsibility
👉 This approach exists mainly for backward compatibility and simplicity
Implementing the Runnable interface exists because Java designers realized that: “A task is not the same thing as a thread.”
What problem it solves
- Separates what to run from how it runs
- Allows your class to extend another class
- Better object-oriented design
- Enables thread reuse (important for thread pools)
👉 This approach supports composition over inheritance
Why Java didn’t remove Thread inheritance:
- Millions of legacy programs depend on it
- Removing it would break backward compatibility
- Simpler API for beginners
Java provides two ways to create threads because it separates thread execution (Thread) from task definition (Runnable). Extending Thread is a simple, legacy approach, while implementing Runnable follows better object-oriented design and is preferred in real-world applications.