Unable to create new native thread error - but very few threads are in use
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
java.lang.OutOfMemoryError: unable to create new native thread does not usually mean the JVM heap is full. It means the JVM asked the operating system for another native thread and the OS refused, often because of process limits, memory reserved for thread stacks, container limits, or leaked thread creation.
Why It Happens Even With "Few" Visible Threads
A Java thread maps to an operating-system thread in normal JVM deployments. The limit is therefore not just "how many threads your code started intentionally." It is constrained by several things at once:
- per-process or per-user thread limits
- available virtual memory for thread stacks
- container PID limits
- native memory fragmentation
- hidden threads created by frameworks, pools, drivers, or the JVM itself
So you can see the error even when your own application code appears to use only a modest number of threads.
Thread Stack Size Matters
Each thread needs stack memory. If the stack size is large, the process can hit native-memory limits with fewer threads than expected.
You can inspect or reduce the stack size with -Xss.
This does not solve every case, but it often explains why one environment fails at a much smaller thread count than another.
Check OS and Container Limits
On Linux, start by checking user and kernel limits.
If the application runs in Docker or Kubernetes, inspect the container's PID and memory constraints too. A tight cgroup PID limit can trigger the error long before the host machine looks busy.
This is why "very few threads are in use" can be misleading. The host may have capacity, but the container or service account does not.
Inspect What the JVM Already Started
Do not guess. Count threads and inspect their names.
or:
Look for patterns such as:
- unbounded executor pools
- a new thread per request
- scheduler threads created repeatedly
- stuck threads that never terminate
- third-party libraries creating their own pools silently
A small bug in thread lifecycle can produce a slow leak that only fails hours later.
A Typical Bad Pattern
This style of code creates unbounded native threads and eventually crashes under load.
The fix is almost never "allow infinite threads." The fix is controlled concurrency.
Use a Bounded Executor Instead
A thread pool keeps thread creation under control.
This does not just improve performance. It prevents native-thread exhaustion from careless thread creation.
Heap Memory Is Not the Whole Story
Many developers focus on heap metrics and miss native memory. A process with free heap can still fail to create threads if native memory is exhausted by thread stacks, direct buffers, JNI code, or other off-heap usage.
That is why this error is an OutOfMemoryError even when the heap graph looks fine.
Common Pitfalls
The biggest mistake is assuming the error means "I need more heap." Usually it does not.
Another mistake is counting only business threads and ignoring framework pools, GC threads, HTTP clients, database drivers, and scheduler threads.
A third issue is running inside containers without checking PID and native-memory limits.
Finally, lowering -Xss blindly can hide the problem without fixing the real leak. If code creates unbounded threads, the application still needs a concurrency redesign.
Summary
- This error means the OS refused another native thread, not necessarily that the Java heap is full.
- Thread limits depend on stack size, native memory, OS limits, and container settings.
- Inspect actual JVM threads with
jcmdorjstackinstead of guessing. - Unbounded thread creation is a common root cause.
- Prefer bounded executors and controlled concurrency.
- Native-thread failures often require OS-level and JVM-level diagnosis together.

