Wednesday, July 23, 2025

Java Platform Threads Explained: Are They Too Expensive to Scale? (Hands-On Guide)

Wondering why Java applications struggle to scale with thousands of concurrent tasks?

The answer lies in the cost of using traditional Java threads, known as platform threads. In this blog post, we’ll explore how platform threads behave under load, the limitations they pose, and why developers are shifting to Java’s new virtual threads for scalability.


Platform threads in Java are heavyweight OS threads that consume a significant amount of memory (typically 1MB stack each), making them expensive to create and manage at scale. Beyond a certain threshold, the JVM throws errors like OutOfMemoryError: unable to create new native thread.

 

๐Ÿงต What Are Platform Threads in Java?

A platform thread is Java’s traditional thread backed by a native operating system thread. Each thread:

  • Is created using the new Thread() API
  • Requires dedicated stack memory (~1MB by default)
  • Is limited by OS-level thread creation limits

๐Ÿ’ก Key Characteristics of Platform Threads:

 | Feature             | Platform Thread                       |
 | ------------------- | ------------------------------------- |
 | Backed By           | OS native thread (e.g., POSIX thread) |
 | Stack Memory        | \~1MB (adjustable with `-Xss`)        |
 | Creation Cost       | High                                  |
 | Scalability         | Limited                               |
 | Blocking Operations | Blocks the OS thread                  |
 

๐ŸŽฏ Why Understanding Thread Creation Cost Matters

In microservice architectures, Java apps make frequent network calls and I/O-bound operations. These operations often block threads, making them idle while waiting for responses.

To handle high concurrency, developers often try to create more threads — but platform threads come at a cost:

  • High memory usage per thread
  • OS-imposed limits on number of threads
  • Performance bottlenecks under load

Let’s explore this with a practical example.


๐Ÿ› ️ Java Thread Creation Demo – Hands-On


We'll simulate a typical I/O-heavy workload using Thread.sleep() to mimic latency, and then create thousands of threads to see what happens.

✅ Project Setup

  • Java Version: 21 (for Duration.ofSeconds)
  • Tools: IntelliJ IDEA or any Java IDE


๐Ÿงฑ Simulate I/O With Thread.sleep()


 public class Task {
     private static final Logger logger = LogManager.getLogger(Task.class);

     public static void ioIntensive(int i) {
         logger.info("Starting IO Task: " + i + " - " + Thread.currentThread().getName());
         try {
             Thread.sleep(Duration.ofSeconds(10));
         } catch (InterruptedException e) {
             Thread.currentThread().interrupt();
         }
         logger.info("Ending IO Task: " + i + " - " + Thread.currentThread().getName());
     }
 }
 

๐Ÿ” Why Thread.sleep()?

Though it’s not a real network call, it effectively simulates thread blocking, allowing us to observe memory and OS behavior without external dependencies.


๐Ÿ” Creating Threads in a Loop


 public class InboundOutboundTaskDemo {
     private static final int MAX_PLATFORM_THREADS = 50_000;

     public static void main(String[] args) {
         for (int i = 0; i < MAX_PLATFORM_THREADS; i++) {
             final int taskNumber = i;
             Thread thread = new Thread(() -> Task.ioIntensive(taskNumber));
             thread.start();
         }
     }
 }
 

Run the code with MAX_PLATFORM_THREADS = 10 — it works fine.

Increase it to 50,000, and you’ll likely see:


Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread


๐Ÿ“‰ Why Does Thread Creation Fail?

Let’s break it down.

๐Ÿ” Behind the Scenes

  • When you create a thread:
  • JVM uses native OS call pthread_create() (on Linux/macOS)
  • Each thread gets a default stack of ~1MB
  • OS imposes a per-process limit on thread count

Even if you have available heap memory, thread creation can still fail due to:

  • Native memory exhaustion
  • Reaching user-level thread limits

๐Ÿ“Š Java Platform Thread Limits: A Quick Comparison


| Resource                | Platform Thread | Virtual Thread (Preview) |
| ----------------------- | --------------- | ------------------------ |
| Memory per Thread       | \~1MB           | Few KB                   |
| Max Threads in Practice | \~8,000–15,000  | 1,000,000+               |
| Blocking Cost           | High            | Minimal (non-blocking)   |
| Backed by OS Thread?    | Yes             | No (uses carrier thread) |
| Thread Creation Speed   | Slow            | Fast                     |


๐Ÿ“Œ Tuning Tips: Can We Create More Platform Threads?


Yes, but with caveats.

✅ Use JVM Flags


 java -Xss256k InboundOutboundTaskDemo
 

This reduces thread stack size to 256KB (vs 1MB default), allowing more threads.


✅ Use Thread Pools

Instead of raw new Thread(), prefer:


 ExecutorService executor = Executors.newFixedThreadPool(200);
 

But even with thread pools, you’re limited by blocked threads during I/O calls.


๐Ÿง  Alternative: Enter Virtual Threads (Project Loom)

Java 21 introduced virtual threads as a preview feature. These threads are:

  • Lightweight
  • Not backed by OS threads
  • Scalable to millions of concurrent tasks

Stay tuned — in the next post, we’ll rewrite this same example using virtual threads and compare memory usage, performance, and thread behavior.


✅ Pros and Cons of Java Platform Threads

๐Ÿ‘ Pros:

Mature and battle-tested

Great for CPU-bound tasks

Full integration with debuggers, profilers, and logging


๐Ÿ‘Ž Cons:

High memory usage per thread

Not suitable for high-concurrency I/O apps

Blocking operations are expensive


๐Ÿ”„ Summary: Should You Still Use Platform Threads?

Use platform threads when:

  • You’re doing CPU-bound tasks
  • The number of concurrent threads is low to moderate
  • You need deep integration with existing thread tools


Avoid platform threads when:

  • You're building a high-throughput microservice
  • The app makes lots of blocking I/O calls
  • You need to handle hundreds of thousands of concurrent users


Helpful Links


Java Virtual Threads Tutorial (coming soon)


๐Ÿ“Œ Final Thoughts


Platform threads are reliable — but expensive. They don't scale well in modern, I/O-heavy applications.

Understanding their limitations is the first step toward embracing virtual threads, the future of Java concurrency.

➡️ Ready to explore virtual threads? Read Part 2: Java Virtual Threads vs Platform Threads – Full Benchmark and Code

Two ways to Unstage a File in Git git rm --cached and git reset Head)

Git Unstage file

Common question on git unstage :

This post covers the following questions with in-depth answers along with step-by-step example.
git reset - Why are there 2 ways to unstage a file in git? - Stack Overflow
version control - How to undo 'git add' before commit? - Stack Overflow
git - How can I unstage my files again after making a local commit.

More article on Git Series


Unstage a File in Git:

In Git, unstaging a file can be done in two ways.

1) git rm --cached <file-name>
2) git reset Head <file-name>

These commands are very useful when we add the files to git. But later or before commit, we realize that mistakenly added the files to git. we should remove the files from git. The process of removing a file from  staging area is called "Unstaging file" from git.

We will be discussing indepth in this tutorial.


Unstage a File in Git

Way 1) git rm --cached <file-name>:

This can be used in two ways.

1) On the brand new file which is not on github.
2) On existing file which exists on github.

We will see how this command behaves on above 2 scenarios.

Case 1: rm --cached on new file which is not committed.

rm --cached <brand-new-file-name>is useful when we want to remove only the file(s) from staging area where this file is not available on github ever. After executing this command, the file remains in the local machine, it just unstaged from staging area.

Example:

The below example is on the new file.

Venki@Venki-PC MINGW64 /d/Site/adeepdrive/git/practice/gitdemo (master)
$ ls 
fileone.txt

Venki@Venki-PC MINGW64 /d/Site/adeepdrive/git/practice/gitdemo (master)
$ echo "this is second file" >> filetwo.txt
Venki@Venki-PC MINGW64 /d/Site/adeepdrive/git/practice/gitdemo (master)
$ ls
fileone.txt  filetwo.txt

Venki@Venki-PC MINGW64 /d/Site/adeepdrive/git/practice/gitdemo (master)
$ git add filetwo.txt

Venki@Venki-PC MINGW64 /d/Site/adeepdrive/git/practice/gitdemo (master)
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)
        new file:   filetwo.txt

Venki@Venki-PC MINGW64 /d/Site/adeepdrive/git/practice/gitdemo (master)

$ git rm --cached filetwo.txt
rm 'filetwo.txt'Venki@Venki-PC MINGW64 /d/Site/adeepdrive/git/practice/gitdemo (master)
$ git status
On branch master
Untracked files:
  (use "git add <file>..." to include in what will be committed)
        filetwo.txt
nothing added to commit but untracked files present (use "git add" to track)

Case 2: rm --cached on existing file.

If 'rm --cached <existing-file-name>.'command is used on the existing file on git then this file will be marked for delete and remains as untracked on machine. If we do commit after this command then the file on the github will be deleted permanently. We should be very careful while using this command. Hence, This is not recommend for upntaging a file.

Below example will demonstrate on existing file.

Example:

Venki@Venki-PC MINGW64 /d/Site/adeepdrive/git/practice/gitdemo (master)
$ git status
On branch master
Untracked files:
  (use "git add <file>..." to include in what will be committed)
        filetwo.txt
nothing added to commit but untracked files present (use "git add" to track)

Venki@Venki-PC MINGW64 /d/Site/adeepdrive/git/practice/gitdemo (master)

$ git add filetwo.txt
Venki@Venki-PC MINGW64 /d/Site/adeepdrive/git/practice/gitdemo (master)
$ git commit -m "second file commit"
[master 2efa8d9] second file commit
 1 file changed, 1 insertion(+)
 create mode 100644 filetwo.txt
Venki@Venki-PC MINGW64 /d/Site/adeepdrive/git/practice/gitdemo (master)
$ echo "file 1 is modified" >> fileone.txt
Venki@Venki-PC MINGW64 /d/Site/adeepdrive/git/practice/gitdemo (master)
$ echo "file 2 is modified" >> filetwo.txt
Venki@Venki-PC MINGW64 /d/Site/adeepdrive/git/practice/gitdemo (master)
$ git add fileone.txt filetwo.txt
Venki@Venki-PC MINGW64 /d/Site/adeepdrive/git/practice/gitdemo (master)
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)
        modified:   fileone.txt
        modified:   filetwo.txt
Venki@Venki-PC MINGW64 /d/Site/adeepdrive/git/practice/gitdemo (master)
$ git commit -m "second commit"
[master d81f0ef] second commit
 2 files changed, 2 insertions(+)

Venki@Venki-PC MINGW64 /d/Site/adeepdrive/git/practice/gitdemo (master)
$ ls
fileone.txt  filetwo.txt

$ git rm --cached filetwo.txt
rm 'filetwo.txt'

Venki@Venki-PC MINGW64 /d/Site/adeepdrive/git/practice/gitdemo (master)
$ ls
fileone.txt  filetwo.txt

Venki@Venki-PC MINGW64 /d/Site/adeepdrive/git/practice/gitdemo (master)
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)
        deleted:    filetwo.txt
Untracked files:
  (use "git add <file>..." to include in what will be committed)
        filetwo.txt

Here we see that filetwo.txt is marked as deleted but original file is present on local machine. After git commit, file is completely not tracked by git.

$ git commit -m "delete from git commit"
[master bc72f1b] delete from git commit
 1 file changed, 2 deletions(-)
 delete mode 100644 filetwo.txt

Venki@Venki-PC MINGW64 /d/Site/adeepdrive/git/practice/gitdemo (master)
$ ls

fileone.txt  
filetwo.txt

Venki@Venki-PC MINGW64 /d/Site/adeepdrive/git/practice/gitdemo (master)
$ git status
On branch master
Untracked files:
  (use "git add <file>..." to include in what will be committed)

        filetwo.txt

nothing added to commit but untracked files present (use "git add" to track)

Way 2) git reset Head <file name>:

There is a scenario, where the file is already exists on the github. We made some changes to the file and added the file to git staging area but not committed. After some time, this change is no longer required. We just want to remove from git index or unstage the file and our new changes should be available in the file. If we use git reset head <file-name> command then changes will not be lost.

After unstage, this modified will be in the modified files list when we do git status command..

Example:

Venki@Venki-PC MINGW64 /d/Site/adeepdrive/git/practice/gitdemo (master)
$ ls
fileone.txt  filetwo.txt

Venki@Venki-PC MINGW64 /d/Site/adeepdrive/git/practice/gitdemo (master)
$ git status
On branch master
nothing to commit, working tree clean

Venki@Venki-PC MINGW64 /d/Site/adeepdrive/git/practice/gitdemo (master)
$ echo "added new line" >> filetwo.txt

Venki@Venki-PC MINGW64 /d/Site/adeepdrive/git/practice/gitdemo (master)
$ git add filetwo.txt

Venki@Venki-PC MINGW64 /d/Site/adeepdrive/git/practice/gitdemo (master)
$ cat filetwo.txt
this is second file
file 2 is modified
added new line

Venki@Venki-PC MINGW64 /d/Site/adeepdrive/git/practice/gitdemo (master)
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)
        modified:   filetwo.txt

Venki@Venki-PC MINGW64 /d/Site/adeepdrive/git/practice/gitdemo (master)
$ git reset head filetwo.txt
Unstaged changes after reset:
M       filetwo.txt

Venki@Venki-PC MINGW64 /d/Site/adeepdrive/git/practice/gitdemo (master)
$ cat filetwo.txt
this is second file
file 2 is modified
added new line

Java 21 Virtual Threads Basic and Foundation

Introduction

Hey everyone ๐Ÿ‘‹,

Welcome to the first post in our deep dive series on Java Virtual Threads! Before we jump into the nuts and bolts of virtual threads, it’s important to lay the foundation. Yes, the first couple of sections may feel basic—but they are essential to understand how things work under the hood, especially when we eventually zoom in on virtual threads.

So bear with me. This foundational knowledge will help you fully grasp the power and potential of virtual threads.

๐Ÿš€ Starting Simple: Running a Java Program

Let’s imagine you’ve developed a simple Java application and packaged it into a JAR file.

 java -jar myapp.jar


When you run this command, your program is loaded into memory and a process is created.

Sunday, July 20, 2025

Java 8 - How to convert Calendar to LocalDateTime?

1. Overview

In this article, You'll learn how to convert Calendar to LocalDateTime object in java 8 DateTime api

LocalDateTime api can be used completely replacement to the Date class as all features of Date functions are done in a simple and precise way such as adding minutes to the current date.

Java 8 - How to convert Calendar to LocalDateTime?

Ultimate Guide to Java Virtual Threads: Build Scalable Applications with Ease

Are Java Virtual Threads the Future of Concurrency? Absolutely. Here's Why You Should Care.

If you're a Java developer, you already know: every line of code runs on a thread. Threads are the fundamental unit of scheduling and concurrency in any Java application.

Traditionally, developers have relied on multiple OS-level threads to handle concurrent tasks. For example, Spring Boot with embedded Tomcat comes configured with 200 threads by default, allowing it to process up to 200 concurrent requests. Sounds powerful, right?

But here’s the catch...

๐Ÿง  Why Traditional Threads Limit Scalability

Each request in a Java web application is processed by a dedicated OS thread. If each request takes 1 second, a 200-thread system can handle 200 requests per second — that’s your application throughput.

So, why not just add more threads?

Because OS threads are heavyweight:

Each thread consumes significant memory.

The OS has limits on how many threads can be created.

Managing thousands of threads leads to context switching overhead, degrading performance.

That’s where Java Virtual Threads come into play — a game-changing concurrency model introduced in Project Loom.

Saturday, July 19, 2025

Mastering @JsonFormat in Jackson: A Complete Guide with Examples

If you're working with Java and JSON, the Jackson library is likely part of your tech stack. One of the most powerful yet underused annotations it provides is @JsonFormat.

In this ultimate guide, we’ll break down what @JsonFormat does, when to use it, and how to handle advanced scenarios with fully working examples. This post is optimized for SEO to help developers find quick and accurate answers.


✅ What is @JsonFormat in Jackson?

@JsonFormat is a Jackson annotation that helps control how Java fields are serialized (Java to JSON) or deserialized (JSON to Java). This is especially useful for dates, enums, and number formats.

Import Statement:


import com.fasterxml.jackson.annotation.JsonFormat;

Dependencies

Make sure you include the correct Jackson modules for java.time:


 <dependency>
   <groupId>com.fasterxml.jackson.datatype</groupId>
   <artifactId>jackson-datatype-jsr310</artifactId>
   <version>2.15.3</version>
 </dependency>
 

And register it:


 ObjectMapper mapper = new ObjectMapper();
 mapper.registerModule(new JavaTimeModule());
 

Thursday, September 5, 2024

Java Program To Reverse A String Without Using String Inbuilt Function reverse()

1. Introduction


In this tutorial, You'll learn how to write a java program to reverse a string without using string inbuilt function reverse().
This is a very common interview question that can be asked in many forms as below.

A) java program to reverse a string without using string inbuilt function
B) java program to reverse a string using recursion
C) java program to reverse a string without using the reverse method
C) Try using other classes of Java API (Except String).



The interviewer's main intention is not to use the String class reverse() method.