One question comes up almost every time developers start deploying Java applications on Kubernetes.
“If my application uses
CompletableFutureto upload five files in parallel, do I need five CPU cores?”
Or another variation:
“If my container has a CPU limit of 500m, does that mean it can only create 500 threads?”
The short answer is No.
Threads, CPU cores and Kubernetes CPU limits are related, but they are not the same thing.
Let’s understand why.
A Simple Analogy
Imagine a bank with customers waiting in line.
Customers = Tasks
Cashiers = CPU Cores
Queue = Threads
Suppose the bank has only one cashier.
Bank
+------------+
| Cashier 1 |
+------------+
Waiting Queue
Customer 1
Customer 2
Customer 3
Customer 4
Customer 5
Even though five customers are waiting, only one customer is being served at any given moment.
The same thing happens inside your computer.
CPU Cores Execute Work
A CPU core is the hardware that actually performs computations.
A thread is simply a unit of work waiting to be executed.
Think of it this way:
CPU Core
↓
Executes
↓
One Runnable Thread
↓
For a Very Small Time Slice
After a few milliseconds, the operating system may pause that thread and run another one.
This process is called context switching.
Because these switches happen thousands of times per second, it appears as though many threads are running simultaneously—even on a single CPU core.
Threads Are Not CPU Cores
This is one of the biggest misconceptions.
Consider the following configuration.
ExecutorService executor =
Executors.newFixedThreadPool(20);
Does this mean your application now needs twenty CPU cores?
Absolutely not.
It simply means Java can schedule up to twenty worker threads.
Those twenty threads will compete for the available CPU time.
For example:
20 Java Threads
↓
Linux Scheduler
↓
2 CPU Cores
The operating system continuously decides which thread gets CPU time next.
What Does 500m CPU Actually Mean?
Another common misunderstanding is that Kubernetes somehow limits the number of threads.
It doesn’t.
Suppose your Deployment specifies:
resources:
requests:
cpu: 500m
limits:
cpu: "1"
This configuration does not mean:
- 500 threads
- 500 requests
- 500 concurrent users
Instead it means:
Guaranteed CPU Time
↓
Half of One CPU Core
Maximum CPU Time
↓
One CPU Core
Your application can still create hundreds of threads if it wants to.
Kubernetes only controls how much processor time those threads receive.
Let’s Apply This to File Uploads
Imagine your application receives five files.
File 1 : 50 MB
File 2 : 50 MB
File 3 : 50 MB
File 4 : 50 MB
File 5 : 50 MB
You decide to upload them in parallel.
CompletableFuture.allOf(
upload(file1),
upload(file2),
upload(file3),
upload(file4),
upload(file5)
);
Internally, five worker threads begin processing.
Upload Thread 1
Upload Thread 2
Upload Thread 3
Upload Thread 4
Upload Thread 5
Does this mean you need five CPU cores?
Not necessarily.
The answer depends on what those threads are doing.
Scenario 1 – Uploading Files to Object Storage
Suppose each thread simply streams data to Amazon S3.
Read File
↓
Send Network Packet
↓
Wait
↓
Send Next Packet
↓
Wait
↓
Complete Upload
Notice something interesting.
Most of the time, the thread isn’t performing calculations.
It’s waiting for:
- Network latency
- Disk I/O
- Storage acknowledgement
This type of workload is called I/O-bound.
Even with five concurrent uploads, CPU utilization may remain surprisingly low because the processor spends very little time performing actual computation.
Scenario 2 – Processing Every File
Now consider a different application.
Each uploaded image is:
- Virus scanned
- Watermarked
- Resized
- Compressed
- OCR processed
- Encrypted
The flow now becomes:
Upload
↓
Virus Scan
↓
OCR
↓
Image Resize
↓
Compression
↓
Encryption
↓
Store File
This workload is very different.
Here, every thread continuously performs CPU-intensive calculations.
This is called a CPU-bound workload.
In this case, adding more CPU cores often improves throughput because multiple calculations can execute simultaneously.
I/O-Bound vs CPU-Bound
Understanding this distinction is one of the most important skills in performance tuning.
| I/O-Bound Workloads | CPU-Bound Workloads |
|---|---|
| REST API Calls | Image Processing |
| Oracle Queries | OCR |
| Redis Access | PDF Generation |
| Kafka/Solace Publish | Encryption |
| S3 Upload | Compression |
| File Download | Video Transcoding |
I/O-bound applications typically benefit from additional threads because many of them spend their time waiting.
CPU-bound applications benefit from additional CPU cores because the processor is continuously busy.
So How Many Threads Should You Create?
There is no universal answer.
For CPU-intensive workloads, creating hundreds of threads rarely improves performance because only a limited number of CPU cores can execute simultaneously.
For I/O-intensive workloads such as REST APIs, database access, or cloud storage uploads, using more threads often improves throughput because most threads spend their time waiting rather than computing.
The optimal thread pool size depends on:
- CPU availability
- Network latency
- Database response time
- Storage performance
- External service latency
- Amount of work performed per request
The correct value should always be determined through performance testing rather than guesswork.
Applying This to Our File Upload Service
Let’s revisit the document upload service used throughout this series.
Client
↓
Spring Boot
↓
Upload to Object Storage
↓
Save Metadata in Oracle
↓
Publish Solace Event
↓
Return Response
Most of the processing time is spent waiting for external systems:
- Object Storage
- Oracle Database
- Messaging Broker
This means the service is primarily I/O-bound.
A modest CPU allocation—combined with an appropriately sized thread pool—can often support many concurrent uploads.
If the service later adds OCR, image processing, encryption, or virus scanning, CPU demand will increase significantly, and infrastructure sizing should be revisited.
Key Takeaways
✔ Threads do not map one-to-one with CPU cores.
✔ Kubernetes CPU limits control processor time, not the number of Java threads.
✔ CompletableFuture creates concurrent work but does not automatically require additional CPU cores.
✔ File upload services are usually I/O-bound unless they perform heavy processing such as OCR, image manipulation, compression, or encryption.
✔ The best infrastructure sizing comes from performance testing under realistic production workloads—not from simply counting threads or files.