Thread.start() vs Thread.run() — Why Calling run() Directly Is a Classic Bug
Fast recall
Quick revision
Core answer
run(). `run()` is just a regular instance method — calling it directly runs the code on the current thread, with no concurrency at all. start() can only be called once per Thread instance — the second call throws IllegalThreadStateException. Under the hood, start() registers the thread with the JVM, allocates the OS thread stack, invokes pthread_create/CreateThread, and the new native thread's entry point eventually calls your run(). This is the #1 "why doesn't my threaded code work" bug for beginners — new Thread(task).run() silently runs on the caller's thread instead of spawning.Whether the candidate knows that `run()` is a normal method and `start()` is what actually creates a thread; the classic beginner bug that silently runs everything serially
Saying `start()` just 'calls run()' — it does much more: allocates stack, registers with JVM thread group, invokes OS primitives. Also forgetting `start()` can only be called once
Walk through what `start()` does internally (native `start0()`, OS thread creation, JVM bookkeeping), and note that `IllegalThreadStateException` on restart is because thread state machines don't allow TERMINATED → NEW
Interview-ready guide
Interview answer
start() and run() look similar but are completely different operations.
`run()` — a plain instance method
- Inherited from
Runnable, overridden inThread - Calling
thread.run()is just a method call; it executes on the current thread - No concurrency, no new thread created
`start()` — a native method that creates a new OS thread
- Asks the JVM to allocate a thread stack, register bookkeeping, and call
pthread_create(Linux/Mac) orCreateThread(Windows) - That new OS thread then invokes your
run() - The original thread continues immediately — you now have two threads running in parallel
- Can only be called once per Thread instance; second call throws
IllegalThreadStateException
The classic beginner bug
new Thread(task).run()— no error, no warning, runs sequentially on the caller- Developer thinks "threading doesn't work"
new Thread(task).start()— the correct version; actually spawns
What `start()` does under the hood
- Checks that the thread is in
NEWstate (that's why it'ssynchronized) - Registers with the parent
ThreadGroup - Calls native
start0()which invokes the OS thread creation primitive - Allocates ~1 MB of stack, sets up thread-local JVM storage
- Returns immediately while the new thread begins executing
run() - Cost: tens of microseconds plus the memory — this is why pools exist
Why you can't restart a terminated thread
- Once
run()returns, the thread transitions toTERMINATED - The state machine doesn't allow
TERMINATED→NEW start()reads the state; throwsIllegalThreadStateExceptionif notNEW- Need to execute the task again? Create a new Thread instance, or use
ExecutorServicewhich reuses underlying threads across many tasks
Recommendation: Don't call start() or new Thread(...) directly in production code. Submit tasks to ExecutorService (or Executors.newVirtualThreadPerTaskExecutor() on Java 21+) and let the framework manage thread lifecycle. When you do see raw Thread in a code review, check immediately that the caller uses start(), not run() — it's a silent bug that often ships to production because tests pass (sequentially) and the performance issue only shows up under load.
`Thread.start()` vs `Thread.run()` — Why Calling `run()` Directly Is a Classic Bug
Direct Answer
start() is a native method that asks the OS to create a new thread and have that thread execute run(). run() is just a regular instance method — calling it directly runs the code on the current thread, with no concurrency at all. start() can only be called once per Thread instance — the second call throws IllegalThreadStateException. Under the hood, start() registers the thread with the JVM, allocates the OS thread stack, invokes pthread_create/CreateThread, and the new native thread's entry point eventually calls your run(). This is the #1 "why doesn't my threaded code work" bug for beginners — new Thread(task).run() silently runs on the caller's thread instead of spawning.
Common Mistakes & Pitfalls
Saying start() just 'calls run()' — it does much more: allocates stack, registers with JVM thread group, invokes OS primitives. Also forgetting start() can only be called once
Check your understanding
Answer these without looking back at the article.
- Why can't a terminated thread be restarted?
- What does native `start0()` actually do?
- How does `ExecutorService` avoid the 'one start() per Thread' limitation?
- What's the cost of creating a platform thread vs a virtual thread?
- Why is `Thread.start()` declared `synchronized`?
Related interview prep categories
2Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.