VOOZH about

URL: https://dev.to/realnamehidden1_61/how-to-detect-and-fix-thread-leaks-in-java-applications-2542

⇱ How to Detect and Fix Thread Leaks in Java Applications - DEV Community


Learn how to detect and fix thread leaks in Java applications using Java 21, thread dumps, ExecutorService, monitoring tools, and Spring Boot examples.

Introduction

Imagine running a restaurant where employees keep showing up for work but never leave.

At first, everything seems fine. But after a few hours, the kitchen becomes crowded, employees bump into each other, and service slows down. Eventually, the restaurant stops functioning.

That’s exactly what happens during a thread leak in Java applications.

Threads are essential workers inside a Java application. They process web requests, background jobs, database operations, and scheduled tasks. But when threads are created and never properly terminated, they continue consuming memory and CPU resources forever.

Over time, this causes:

  • High CPU usage
  • OutOfMemoryError issues
  • Slow response times
  • Application crashes
  • Server instability

Understanding How to Detect and Fix Thread Leaks in Java Applications is one of the most important skills in modern Java programming.

In this guide, you’ll learn:

  • What thread leaks are
  • Why they happen
  • How to detect them
  • How to fix them properly
  • Best practices for preventing them in production

If you want to learn Java concurrency and build stable applications, mastering thread leak detection is essential.

What Is a Thread Leak?

A thread leak happens when threads are continuously created but never cleaned up or terminated.

Instead of disappearing after completing work, leaked threads remain alive indefinitely.

Simple Real-World Analogy

Real World Java Application
Employees Threads
Restaurant tasks Application tasks
Employees never leaving Threads never terminating
Crowded kitchen Resource exhaustion
Restaurant slowdown Application performance issues

How Thread Leaks Happen

Common causes include:

  • Forgetting to shut down thread pools
  • Infinite loops inside threads
  • Blocking operations
  • Improper async handling
  • Creating threads manually for every request
  • Scheduled tasks running forever

Symptoms of Thread Leaks

1. Increasing Thread Count

Your application starts with 50 threads.

After several hours:

50 → 200 → 1000 → 5000 threads

That’s a warning sign.

2. High Memory Usage

Every thread consumes stack memory.

More threads = more memory consumption.

3. Slow Performance

Excessive threads increase:

  • CPU context switching
  • Garbage collection pressure
  • Scheduler overhead

4. Application Crash

Eventually you may see:

java.lang.OutOfMemoryError: unable to create native thread

Core Concepts of Thread Leak Detection

1. Thread Lifecycle

A Java thread normally goes through:

NEW → RUNNABLE → WAITING → TERMINATED

Leaked threads never reach the TERMINATED state.

2. Thread Dumps

A thread dump is a snapshot of all running threads.

It helps identify:

  • Stuck threads
  • Deadlocks
  • Infinite loops
  • Excessive thread creation

3. ExecutorService

Instead of manually creating threads, Java recommends using ExecutorService.

Benefits:

  • Reuses threads
  • Controls thread limits
  • Prevents uncontrolled thread growth

Common Causes of Thread Leaks

Problem Result
Missing shutdown() Thread pool never stops
Infinite loops Threads run forever
Blocking network calls Threads get stuck
Unbounded thread creation Memory exhaustion
Forgotten scheduled tasks Zombie threads

Detecting Thread Leaks Using JDK Tools

Java provides built-in tools for thread analysis.

Useful Commands

View Running Java Processes

jps

Generate Thread Dump

jstack <PID>

Monitor Threads

jcmd <PID> Thread.print

Code Example 1 — Bad Example Causing a Thread Leak

This example demonstrates a common mistake: creating threads repeatedly without cleanup.

Project Setup

Maven pom.xml

<dependencies>

 <!-- Spring Boot Web -->
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-web</artifactId>
 </dependency>

</dependencies>

Leaky Thread Service

package com.example.threadleak.service;

import org.springframework.stereotype.Service;

@Service
public class LeakyThreadService {

 public void startLeakyTask() {

 // BAD PRACTICE:
 // Creating a new thread for every request
 Thread thread = new Thread(() -> {

 while (true) {

 try {
 // Simulate background work
 Thread.sleep(1000);

 System.out.println(
 "Running thread: "
 + Thread.currentThread().getName());

 } catch (InterruptedException e) {

 Thread.currentThread().interrupt();
 }
 }
 });

 thread.start();
 }
}

REST Controller

package com.example.threadleak.controller;

import com.example.threadleak.service.LeakyThreadService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class LeakController {

 private final LeakyThreadService service;

 public LeakController(LeakyThreadService service) {
 this.service = service;
 }

 @GetMapping("/leak")
 public String createLeak() {

 service.startLeakyTask();

 return "Leaky thread started";
 }
}

Run Application

mvn spring-boot:run

Trigger Leak Using curl

curl -X GET http://localhost:8080/leak

Run the command multiple times.

Sample Response

Leaky thread started

What Goes Wrong?

Each API request creates:

  • A brand-new thread
  • Infinite loop
  • No shutdown mechanism

Result:

  • Thread count continuously increases
  • Memory usage spikes
  • Application eventually crashes

This is a classic thread leak in Java applications.

Detect the Leak

Generate Thread Dump

jstack <PID>

Example Thread Dump Output

"Thread-25" #45 prio=5 os_prio=0 cpu=120ms elapsed=400s tid=0x000001 waiting
"Thread-26" #46 prio=5 os_prio=0 cpu=150ms elapsed=390s tid=0x000002 waiting
"Thread-27" #47 prio=5 os_prio=0 cpu=110ms elapsed=380s tid=0x000003 waiting

Notice how thread count keeps increasing.

Code Example 2 — Proper Fix Using ExecutorService

Now let’s solve the problem correctly using Java 21 best practices.

Fixed Service Using Thread Pool

package com.example.threadleak.service;

import jakarta.annotation.PreDestroy;
import org.springframework.stereotype.Service;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Service
public class SafeThreadService {

 // Fixed-size thread pool
 private final ExecutorService executorService =
 Executors.newFixedThreadPool(5);

 public void processTask() {

 executorService.submit(() -> {

 try {

 System.out.println(
 "Processing task using: "
 + Thread.currentThread().getName());

 // Simulate work
 Thread.sleep(3000);

 System.out.println("Task completed");

 } catch (InterruptedException e) {

 Thread.currentThread().interrupt();
 }
 });
 }

 // Proper cleanup during shutdown
 @PreDestroy
 public void shutdown() {

 System.out.println("Shutting down thread pool");

 executorService.shutdown();
 }
}

REST Controller

package com.example.threadleak.controller;

import com.example.threadleak.service.SafeThreadService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class SafeController {

 private final SafeThreadService service;

 public SafeController(SafeThreadService service) {
 this.service = service;
 }

 @GetMapping("/safe-task")
 public String processTask() {

 service.processTask();

 return "Task submitted successfully";
 }
}

Run Application

mvn spring-boot:run

Test Endpoint

curl -X GET http://localhost:8080/safe-task

Sample Response

Task submitted successfully

Console Output

Processing task using: pool-1-thread-1
Task completed

Notice:

  • Threads are reused
  • Thread count remains stable
  • No resource leak occurs

Modern Java 21 Alternative — Virtual Threads

Java 21 introduces Virtual Threads.

They are lightweight threads managed by the JVM.

Example

ExecutorService executor =
 Executors.newVirtualThreadPerTaskExecutor();

Benefits:

  • Millions of lightweight threads
  • Reduced memory usage
  • Better scalability
  • Simpler concurrency model

Spring Boot Virtual Thread Support

Enable virtual threads:

spring.threads.virtual.enabled=true

This is one of the best modern solutions for preventing thread exhaustion.

Tools for Detecting Thread Leaks

Tool Purpose
jstack Generate thread dumps
jcmd JVM diagnostics
VisualVM Monitor thread activity
JConsole JVM monitoring
Spring Boot Actuator Application metrics

Best Practices for Preventing Thread Leaks

1. Always Shut Down ExecutorService

Bad:

Executors.newFixedThreadPool(10);

Good:

executorService.shutdown();

2. Avoid Manual Thread Creation

Prefer:

  • ExecutorService
  • Spring @Async
  • Virtual threads

instead of:

new Thread()

3. Monitor Thread Counts Regularly

Use:

  • Grafana
  • Prometheus
  • Spring Boot Actuator

to monitor production systems.

4. Use Timeouts for Blocking Operations

Never allow threads to wait forever.

Bad:

socket.read();

Good:

socket.setSoTimeout(5000);

5. Prefer Virtual Threads in Java 21

Virtual threads dramatically reduce the risk of thread exhaustion.

Common Mistakes Beginners Make

Mistake Problem
Creating threads per request Resource exhaustion
Forgetting shutdown Zombie threads
Infinite loops CPU spikes
Blocking threads indefinitely Application freeze
Ignoring monitoring Hidden leaks

Learn More

Conclusion

Understanding How to Detect and Fix Thread Leaks in Java Applications is critical for building reliable backend systems.

Here’s what you learned:

  • What thread leaks are
  • Why they happen
  • How to detect them using thread dumps
  • How to fix them using ExecutorService
  • Why Java 21 virtual threads are important

Thread leaks are dangerous because they slowly destroy application performance over time.

But with proper monitoring, thread pools, and modern Java concurrency tools, you can build scalable and stable systems confidently.

If you’re serious about Java programming and want to learn Java concurrency deeply, thread management is a must-have skill.

Call to Action

Have you ever faced thread leaks in production systems?

Share your experience, ask questions, or discuss Java concurrency challenges in the comments.