Runnable Interface in Java: Implementation, Steps & Errors
By Rohan Vats
Updated on Jul 02, 2025 | 12 min read | 25.44K+ views
Share:
For working professionals
For fresh graduates
More
By Rohan Vats
Updated on Jul 02, 2025 | 12 min read | 25.44K+ views
Share:
Did you know? Apache Tomcat, one of the most widely used open-source web servers, relies heavily on the Runnable interface in Java to handle thousands of concurrent client requests. This lets Tomcat handle countless web sessions at once, keeping major websites and enterprise apps running smoothly! |
The Runnable interface in Java allows a class to define a task that can be executed concurrently by a separate thread. For example, consider an online shopping application where various tasks, such as checking inventory, processing payments, and updating the UI, can run simultaneously.
However, you might struggle to understand how to manage multiple threads and ensure smooth coordination between them.
This blog will walk you through the core aspects of the Runnable interface in Java, including how to implement and use it effectively.
Ready to turn your Java multithreading practice into a powerful development skillset? Explore upGrad’s Online Software Development Courses to gain hands-on experience, build real-life projects, and learn from top industry experts. Start today!
The Runnable interface in Java is a functional interface that represents a task or unit of work that can be executed concurrently by a thread. It contains a single method, run(), which defines the task to be executed by the thread.
The Runnable interface is a key component in Java's multithreading model. It allows you to define the task that will be run by a thread without directly managing thread creation. By implementing the Runnable interface, you can decouple the task definition from thread management.
Working with the Runnable interface in Java isn’t just about understanding theory. You need the right hands-on experience to apply your skills, manage concurrency effectively. Here are three programs that can help you:
Importance of Creating Concurrent Tasks:
Runnable allows multiple tasks to be shared between threads, optimizing thread usage. Instead of directly extending the Thread class, you can implement Runnable and pass it to a Thread or ExecutorService, giving you more control and flexibility over thread management.
Having trouble getting a solid foundation in Java? Enroll in upGrad’s free Core Java Basics course and start building strong coding skills today. Learn variables, data types, loops, and OOP principles from the ground up!
The Runnable interface is a key part of Java's concurrency model. It allows you to define tasks that can be executed concurrently in separate threads. Here's how it fits into Java's threading system:
This setup enables efficient multitasking in Java, where tasks are defined separately from thread management, resulting in cleaner and easier-to-maintain code.
Now, let’s take a closer look at the structure of the Runnable interface in Java to understand how it facilitates this streamlined approach.
If you're finding it tough to break down complex problems efficiently, check out upGrad’s Data Structures & Algorithms free course to strengthen your foundation and start solving challenges with ease. Start today!
Now that we know how the interface defines tasks and how threads manage them, let’s see how you can implement it in your own Java applications to run tasks concurrently.
Before you start implementing the Runnable interface in Java, there are a few prerequisites you'll need to be familiar with:
Also Read: What is a Virtual Method in Java? Exploring Interfaces and Polymorphism
Once you're familiar with these concepts, implementing the Runnable interface becomes a straightforward process.
Imagine an online shopping system where the following tasks need to be performed concurrently:
Step 1: Define the Tasks (Runnable)
Each task (inventory check, payment processing, UI update) will be modeled as a separate Runnable.
InventoryCheck Task
This task will simulate checking the inventory to see if a product is available.
class InventoryCheck implements Runnable {
@Override
public void run() {
try {
System.out.println("Checking inventory...");
Thread.sleep(2000); // Simulating inventory check time
System.out.println("Inventory check completed.");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
PaymentProcessing Task
This task simulates processing the payment after the inventory check.
class PaymentProcessing implements Runnable {
@Override
public void run() {
try {
System.out.println("Processing payment...");
Thread.sleep(3000); // Simulating payment processing time
System.out.println("Payment processed successfully.");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
UIUpdate Task
This task simulates updating the UI to inform the user about the status of their order.
class UIUpdate implements Runnable {
@Override
public void run() {
try {
System.out.println("Updating UI...");
Thread.sleep(1000); // Simulating UI update time
System.out.println("UI updated with order status.");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
Step 2: Create and Start Threads for Each Task
Now that we have the tasks defined as Runnable implementations, we will create individual Thread objects for each task and start them concurrently.
public class OnlineShoppingSimulation {
public static void main(String[] args) {
// Create instances of Runnable tasks
Runnable inventoryCheck = new InventoryCheck();
Runnable paymentProcessing = new PaymentProcessing();
Runnable uiUpdate = new UIUpdate();
// Create threads for each task
Thread inventoryThread = new Thread(inventoryCheck);
Thread paymentThread = new Thread(paymentProcessing);
Thread uiThread = new Thread(uiUpdate);
// Start threads concurrently
inventoryThread.start();
paymentThread.start();
uiThread.start();
// Wait for all threads to finish (optional for demo purposes)
try {
inventoryThread.join();
paymentThread.join();
uiThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("All tasks completed.");
}
}
Explanation of the Code:
Step 3: Output of the Program
When you run the program, you should see the tasks executed concurrently, like this:
Checking inventory...
Processing payment...
Updating UI...
Inventory check completed.
Payment processed successfully.
UI updated with order status.
All tasks completed.
Although developers often turn to the runnable interface when implementing multi-threading in Java. However, certain errors can arise during its usage.
upGrad’s Exclusive Software Development Webinar for you –
SAAS Business – What is So Different?
Also Read: Top 135+ Java Interview Questions You Should Know in 2025
When implementing the Runnable interface in Java, there are several common errors and edge cases that developers may encounter. These can lead to unexpected behavior or issues with thread execution.
Below are some of the most frequent mistakes, along with solutions and best practices for avoiding them:
1. Missing run() Method Implementation
Problem: A class implementing Runnable must override the run() method. Failing to do so will result in a compile-time error.
Solution: Always ensure that the run() method is properly overridden in your Runnable implementation.
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Task is running!");
}
}
2. Calling run() Directly Instead of start()
Problem: A common mistake is calling the run() method directly, which does not start a new thread. Instead, it executes the run() method in the current thread.
Solution: Always call the start() method on the Thread object to ensure that the task is executed in a new thread.
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start(); // Correct way to start the thread
Incorrect Approach:
myRunnable.run(); // This runs in the current thread, not a new thread
3. Ignoring InterruptedException
Problem: When your thread is performing long-running tasks (like I/O operations or sleeping), it might be interrupted. Failing to handle InterruptedException properly can cause unexpected behavior.
Solution: Always handle InterruptedException in your run() method, either by catching it or propagating it.
public class MyRunnable implements Runnable {
@Override
public void run() {
try {
Thread.sleep(2000); // Simulating long task
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // Handle interruption
}
}
}
4. Concurrency Issues (Race Conditions)
Problem: When multiple threads access and modify shared resources without synchronization, race conditions can occur, leading to inconsistent or incorrect results.
Solution: Use synchronization mechanisms (like synchronized blocks or ReentrantLock) to ensure thread-safe access to shared resources.
public class Counter implements Runnable {
private static int count = 0;
@Override
public void run() {
synchronized (Counter.class) { // Ensure only one thread modifies count at a time
count++;
System.out.println(Thread.currentThread().getName() + " count: " + count);
}
}
}
5. Deadlocks and Resource Locking
Problem: When threads wait indefinitely for each other to release resources, a deadlock occurs. This can happen when multiple threads are involved in accessing shared resources in a cyclic manner.
Solution: To avoid deadlocks, acquire locks in a consistent order and use timeout mechanisms where possible.
// Example with ReentrantLock to avoid deadlocks
ReentrantLock lock1 = new ReentrantLock();
ReentrantLock lock2 = new ReentrantLock();
public void task1() {
lock1.lock();
lock2.lock();
// Perform work
lock2.unlock();
lock1.unlock();
}
public void task2() {
lock2.lock();
lock1.lock();
// Perform work
lock1.unlock();
lock2.unlock();
}
6. Using ExecutorService for Thread Pooling
Problem: Manually creating a new thread for each task can lead to performance issues, especially if there are a large number of tasks. This approach can also overwhelm the system with too many threads.
Solution: Use an ExecutorService to manage a thread pool efficiently. This reduces the overhead of creating new threads and improves the scalability of your application.
ExecutorService executorService = Executors.newFixedThreadPool(3);
executorService.submit(new MyRunnable());
executorService.submit(new MyRunnable());
executorService.shutdown();
7. Handling Exceptions within the run() Method
Problem: Exceptions thrown within the run() method of a Runnable are not propagated back to the caller, leading to silent failures. This makes debugging difficult.
Solution: Use a try-catch block inside the run() method to handle exceptions properly and log them if necessary.
public class MyRunnable implements Runnable {
@Override
public void run() {
try {
// Code that may throw an exception
int result = 10 / 0; // This will throw an ArithmeticException
System.out.println("Task completed with result: " + result);
} catch (Exception e) {
System.err.println("Error occurred: " + e.getMessage());
}
}
}
Next. you can explore advanced concurrency utilities in the java.util.concurrent package and experiment with the Fork/Join Framework for parallel processing. These topics will help you optimize your Java applications for better performance and scalability.
The Runnable interface in Java is key to multithreading, enabling tasks to run concurrently and enhancing application performance. It offers flexibility in defining tasks that execute independently, making your code more efficient.
Understanding multithreading gets tricky in complex apps, especially with multiple threads and shared resources. Focus on synchronization, thread pooling with ExecutorService, and advanced concurrency tools to handle it well.
To grow further in Java, upGrad’s courses offer hands-on projects and mentorship to help you tackle advanced development challenges. In addition to the courses mentioned above, here are some more free courses that can help you elevate your skills:
Curious which courses can help you advance in Java development? upGrad’s personalized career guidance can help you explore the right learning path based on your goals. You can also visit your nearest upGrad center and start hands-on training today!
Boost your career with our popular Software Engineering courses, offering hands-on training and expert guidance to turn you into a skilled software developer.
Master in-demand Software Development skills like coding, system design, DevOps, and agile methodologies to excel in today’s competitive tech industry.
Stay informed with our widely-read Software Development articles, covering everything from coding techniques to the latest advancements in software engineering.
Reference:
https://tomcat.apache.org/tomcat-9.0-doc/
408 articles published
Software Engineering Manager @ upGrad. Passionate about building large scale web apps with delightful experiences. In pursuit of transforming engineers into leaders.
Get Free Consultation
By submitting, I accept the T&C and
Privacy Policy
India’s #1 Tech University
Executive PG Certification in AI-Powered Full Stack Development
77%
seats filled
Top Resources