Part 3 – Kubernetes Workloads and Infrastructure Sizing for Java Microservices

“The first time I deployed a Spring Boot application to Kubernetes, the platform team didn’t ask for my JAR file. Instead, they asked me a dozen questions that I had never considered before.”


Introduction

If you’ve spent most of your career building Java applications, your deployment experience probably looked something like this.

  • Build the JAR
  • Copy it to a server
  • Restart Tomcat (or run the JAR)
  • Verify the application starts
  • Go home

The infrastructure team took care of everything else.

Kubernetes changes this completely.

The platform is no longer simply hosting your application—it is continuously making decisions about where, when, and how your application should run.

Before your application can even be deployed, you’ll likely receive a questionnaire similar to this.

---------------------------------------------------------
Application Name :
Environment :
Business Criticality :
---------------------------------------------------------

Replicas :
CPU Request :
CPU Limit :
Memory Request :
Memory Limit :

Peak TPS :
Average TPS :
Concurrent Users :

Startup Time :

Liveness Endpoint :
Readiness Endpoint :

Persistent Storage :

Database Connections :

Kafka Topics :

External APIs :

Expected Availability :
---------------------------------------------------------

The first time I saw this questionnaire, I understood barely half of it.

If you’ve ever wondered what 500m CPU, 2Gi Memory, Replicas, or Requests vs Limits actually mean, this article is for you.

By the end of this chapter, you’ll be able to have a meaningful conversation with your platform engineering team.


Before We Deploy Anything…

Let’s understand how Kubernetes sees your application.

A Java developer sees:

PaymentService.jar

Kubernetes sees:

Deployment

↓

ReplicaSet

↓

Pods

↓

Containers

↓

Java Process

Your Spring Boot application is simply another Linux process running inside a container.

Everything Kubernetes manages is built around that simple fact.


The Basic Deployment Unit

Many developers believe a Pod is the unit they deploy.

It isn’t.

The real deployment hierarchy looks like this.

Deployment

      │

      ▼

ReplicaSet

      │

      ▼

Pod

      │

      ▼

Container

      │

      ▼

Spring Boot JVM

Let’s understand each layer.


Deployment

A Deployment describes the desired state.

For example:

“I always want three instances of Payment Service running.”

Payment Deployment

Desired Replicas = 3

The Deployment itself never runs your application.

Instead, it delegates that responsibility.


ReplicaSet

The ReplicaSet continuously compares:

Desired Pods

vs

Running Pods

Suppose one pod crashes.

Desired

Pod-1

Pod-2

Pod-3


Actual

Pod-1

Pod-3

ReplicaSet immediately notices.

Missing Pod

↓

Create New Pod

This is the first level of Kubernetes self-healing.


Pod

A Pod is the smallest deployable unit.

Think of it as a wrapper around one or more containers.

+----------------------------------+

Pod

------------------------------------

Spring Boot Container

(Optional Sidecar)

Volumes

Networking

------------------------------------

In future articles we’ll see why Service Mesh introduces another container inside every pod.


Container

Finally…

Inside the container…

runs our familiar Spring Boot application.

Container

↓

java -jar payment-service.jar

Nothing magical.

Just another Java process.


The Questions Every Platform Team Will Ask

Let’s go through them one by one.


Question 1

How Much CPU Does Your Application Need?

This confuses almost every Java developer.

Unlike virtual machines…

Kubernetes measures CPU using millicores.

1000m = 1 CPU Core

500m = Half Core

250m = Quarter Core

2000m = 2 CPU Cores

So if someone asks

“Can your service run with 500m CPU?”

they’re really asking

“Can your application run with half a CPU core guaranteed?”


CPU Request

CPU Request is the minimum CPU Kubernetes guarantees.

Example

resources:

  requests:

    cpu: 500m

Meaning

Guaranteed CPU

0.5 Core

Kubernetes will not place your pod on a node unless it can guarantee this much CPU.


CPU Limit

CPU Limit defines the maximum CPU your application may consume.

resources:

  limits:

    cpu: "2"

Meaning

Maximum

2 CPU Cores

Request vs Limit

Think of it like driving on a highway.

Request

Reserved Lane

Limit

Maximum Speed

Your application may accelerate…

But never beyond the configured limit.


What Happens When CPU Limit is Reached?

Imagine your application suddenly receives heavy traffic.

Current Usage

2.4 CPU

Limit

2 CPU

Kubernetes does not kill the container.

Instead

CPU Throttling

The JVM becomes slower.

Response times increase.

Requests begin queueing.

This is why proper CPU sizing is critical.


Question 2

How Much Memory Does Your Application Need?

Memory works differently.

Unlike CPU…

Memory cannot be throttled safely.

Example

requests:

 memory: 1Gi

limits:

 memory: 2Gi

Meaning

Guaranteed

1 GB


Maximum

2 GB

What Happens if Memory Crosses the Limit?

This is one of the most common production issues.

Heap Growing

↓

Memory Limit

↓

Container Exceeds Limit

↓

OOMKilled

↓

Pod Restart

Unlike CPU…

Memory violations usually terminate your container.


JVM Heap vs Container Memory

One of the biggest mistakes Java developers make.

Container Memory

2GB

Java Configuration

-Xmx2G

Looks correct?

Actually…

No.

The JVM requires memory for much more than the Java heap.

Container Memory

--------------------------------

Java Heap

Metaspace

Thread Stacks

Native Memory

Direct Buffers

GC

JNI

Operating System

--------------------------------

If you allocate the entire container to the heap…

The operating system has nowhere else to allocate memory.

Result

OOMKilled

A good rule of thumb is to leave approximately 20–30% of the container memory for non-heap usage, then validate the setting under realistic load. The exact percentage depends on thread counts, libraries, and native memory usage.


Question 3

How Many Pods?

This is another common question.

Replicas

3

Does this mean the application is three times faster?

Not necessarily.

It simply means

Three Independent Copies

Running Simultaneously

How Traffic Flows

                Service

                   │

     ----------------------------

     │            │            │

   Pod-1        Pod-2       Pod-3

The Service distributes traffic across available pods.

If Pod-2 crashes…

Service

      │

   Pod-1

   Pod-3

↓

Traffic Continues

↓

New Pod Created

Users may never notice the failure.


Question 4

Expected TPS

Platform engineers often ask

What is your expected TPS?

TPS means

Transactions Per Second

Example

Average

120 TPS

Peak

850 TPS

This information helps determine

  • Number of Pods
  • CPU
  • Memory
  • Autoscaling Rules

TPS vs Concurrent Users

These are NOT the same.

Example

1000 Users

may generate

50 TPS

Another application with

50 Users

might generate

800 TPS

Always clarify which metric is being discussed.


Question 5

Startup Time

Spring Boot applications don’t start instantly.

Suppose

Startup Time

45 Seconds

But Kubernetes expects

5 Seconds

Result

Application Still Starting

↓

Health Probe Fails

↓

Container Restarted

↓

CrashLoopBackOff

This is why startup probes become extremely important.

We’ll dedicate an entire article to this topic later.


Question 6

Database Connections

Consider this configuration.

10 Pods

↓

50 Connections Per Pod

↓

500 Oracle Connections

Can your Oracle database support 500 simultaneous sessions?

Sometimes the bottleneck isn’t Kubernetes.

It’s the database.

Infrastructure sizing must consider the entire system.


Question 7

Storage Requirements

The platform team may ask:

Do you require persistent storage?

Not every application does.

Typical options include:

Temporary

↓

emptyDir


Persistent

↓

Persistent Volume


External

↓

Oracle

Redis

Amazon S3

Object Storage

For most stateless Spring Boot microservices, business data should reside in external systems rather than inside the container.


Question 8

External Dependencies

Your platform team also needs to understand what your application depends on.

Example

Oracle Database

Redis

Kafka

Solace

SMTP

REST APIs

LDAP

Vault

Every dependency affects startup, health checks, resilience, and deployment planning.


Horizontal Scaling

The cloud-native approach is to scale out.

1 Pod

↓

2 Pods

↓

5 Pods

↓

10 Pods

Instead of building one extremely large server.


Vertical Scaling

Traditional applications usually scale up.

2 CPU

↓

4 CPU

↓

8 CPU

Both strategies have their place.

Modern microservices generally prefer horizontal scaling because it improves availability and fault tolerance.


Sample Deployment Configuration

A typical deployment request might look like this.

Application : payment-service

Environment : SIT

Replicas : 3

CPU Request : 500m
CPU Limit : 2

Memory Request : 1Gi
Memory Limit : 2Gi

Peak TPS : 1200

Average TPS : 250

Concurrent Users : 1500

Startup Time : 40 Seconds

Database : Oracle

Redis : Yes

Kafka : Yes

Persistent Storage : No

Liveness :
/actuator/health/liveness

Readiness :
/actuator/health/readiness

Startup :
/actuator/health/startup

This isn’t just paperwork.

These values directly influence how Kubernetes schedules, scales, and protects your application.


A Practical Sizing Guide

The following table provides reasonable starting points. These values should always be validated through performance and load testing.

Service TypeCPU RequestCPU LimitMemory RequestMemory LimitReplicas
Lightweight REST API250m1 Core512Mi1Gi2–3
CRUD Service500m2 Cores1Gi2Gi2–4
Payment Processing1 Core4 Cores2Gi4Gi3–6
Event Consumer500m2 Cores1Gi2GiBased on partition count
Batch Worker2 Cores4 Cores4Gi8Gi1–2

Remember that these are reference values, not universal standards.


Production Tips

✔ Always define CPU and memory requests.

✔ Never set -Xmx equal to the container memory limit.

✔ Load test before deciding replica counts.

✔ Size database connection pools based on total pod count.

✔ Keep pods stateless.

✔ Define startup, liveness, and readiness endpoints before deployment.

✔ Monitor CPU throttling and OOMKilled events after go-live.


Common Mistakes

❌ Assuming one CPU equals one Java thread.

❌ Ignoring non-heap memory.

❌ Using local disk for business data.

❌ Guessing replica counts without load testing.

❌ Configuring unlimited CPU and memory.

❌ Forgetting that every additional pod increases database connections.

❌ Treating CPU requests and CPU limits as the same thing.


Infrastructure Checklist Before Deployment

Before handing your application to the platform team, make sure you can answer the following questions:

  • What is the expected average and peak TPS?
  • How many concurrent users are expected?
  • How much CPU does the application require under peak load?
  • What is the minimum and maximum memory requirement?
  • How long does the application take to start?
  • Does the application support horizontal scaling?
  • What health endpoints are available?
  • How many database connections will each pod open?
  • Does the application write to local storage?
  • What external systems must be available for startup?

If you cannot answer these questions, you’re not quite ready for production deployment.

Special Considerations for File Upload and Download Services

Most examples in Kubernetes documentation revolve around lightweight REST APIs that process JSON payloads. However, enterprise applications frequently expose services for uploading documents, downloading reports, processing images, generating PDFs, or exchanging files with object storage.

These workloads have very different infrastructure requirements.

If your application handles file uploads or downloads, your platform team will ask additional questions beyond CPU and memory.

Let’s understand why.


A Typical Enterprise File Upload Flow

Consider a banking application where customers upload KYC documents.

A request might follow this path:

Customer

      │

      ▼

API Gateway

      │

      ▼

Spring Boot Upload Service

      │

      ├────────► Virus Scan

      │

      ├────────► Object Storage (S3 / MinIO)

      │

      ├────────► Oracle Metadata

      │

      └────────► Event (Kafka / Solace)

                    │

                    ▼

          Downstream Processing

Unlike a simple REST API, the application now performs several expensive operations within a single request.

  • Receives a multipart request
  • Validates the file
  • Streams data
  • Stores metadata
  • Uploads to object storage
  • Publishes an event
  • Returns a response

Each step consumes CPU, memory, disk, and network bandwidth.


Questions the Platform Team Will Ask

For file handling applications, expect additional infrastructure questions such as:

Maximum Upload Size?

Average File Size?

Peak File Size?

Uploads Per Minute?

Downloads Per Minute?

Supported File Types?

Expected Concurrent Uploads?

Object Storage?

Temporary Storage Required?

Virus Scanning?

Encryption Required?

Compression Required?

These answers directly affect infrastructure sizing.


File Size Matters

Uploading a 20 KB JSON payload is very different from uploading a 500 MB video.

Consider the following examples.

WorkloadAverage Payload
Customer Profile API5 KB
Payment API2 KB
PDF Upload8 MB
Passport Scan15 MB
High Resolution Image40 MB
Video Upload500 MB

The infrastructure requirements increase significantly as file size grows.


Streaming vs Loading into Memory

One of the biggest design mistakes is reading the entire file into memory.

Avoid this approach:

byte[] bytes = multipartFile.getBytes();

For large files, every concurrent request allocates another large byte array on the JVM heap.

Instead, stream the file directly to storage whenever possible.

Client

↓

Input Stream

↓

Object Storage

↓

Close Stream

Streaming keeps memory usage predictable regardless of file size.


Memory Planning

Suppose your application allows:

  • Maximum file size: 100 MB
  • Concurrent uploads: 20

If every upload is fully loaded into memory, the application could theoretically require:

100 MB × 20

=

2 GB

And that’s before accounting for:

  • JVM Heap
  • Thread Stacks
  • HTTP Buffers
  • Spring Objects
  • Metadata
  • Native Memory

This is why file upload services generally require larger memory limits than standard REST APIs.


CPU Considerations

Uploading a file is not always CPU intensive.

However, CPU usage increases significantly if the application performs:

  • Image resizing
  • Thumbnail generation
  • PDF generation
  • OCR
  • Compression
  • Encryption
  • Digital signing
  • Checksum calculation
  • Virus scanning

For these workloads, CPU sizing should be based on actual processing time rather than request count.


Network Bandwidth

Traditional REST services exchange small JSON payloads.

File services primarily consume network bandwidth.

For example:

100 Users

↓

Uploading

50 MB Files

↓

5 GB Network Traffic

Platform teams may therefore ask about:

  • Average upload bandwidth
  • Peak upload bandwidth
  • Average download bandwidth
  • Network throughput
  • Internal storage traffic

This information is essential when multiple services share the same Kubernetes cluster.


Temporary Storage

Many upload services require temporary working space.

Examples include:

  • Virus scanning
  • ZIP extraction
  • Image conversion
  • Video transcoding
  • PDF processing

In Kubernetes, temporary files are typically stored in an emptyDir volume.

Container

↓

emptyDir

↓

Temporary Files

↓

Deleted When Pod Terminates

Business data should never rely on this storage because it disappears when the pod is recreated.


Persistent Storage

Ask yourself one important question.

Should the application store the file locally?

In most cloud-native architectures, the answer is No.

A better approach is:

Client

↓

Spring Boot

↓

Object Storage

↓

Save Metadata

↓

Publish Event

The application stores only metadata in the database while the actual file resides in object storage such as Amazon S3, Azure Blob Storage, Google Cloud Storage, MinIO, or an enterprise object storage platform.


Large File Downloads

Download services have their own challenges.

Avoid loading the complete file into memory before sending it to the client.

Instead, stream the response.

Object Storage

↓

Input Stream

↓

Spring Boot

↓

HTTP Response Stream

↓

Client

Streaming reduces memory usage and improves scalability.


Timeouts

Large uploads take longer.

Infrastructure teams need to know:

  • Expected upload duration
  • Client timeout
  • API Gateway timeout
  • Load Balancer timeout
  • Reverse Proxy timeout

If any component times out before the upload completes, the request fails even if the application is functioning correctly.


Infrastructure Sizing Example

Suppose you’re deploying a document management service.

A sizing request might look like this:

Application : document-service

Replicas : 4

CPU Request : 1 Core
CPU Limit : 4 Cores

Memory Request : 2Gi
Memory Limit : 4Gi

Maximum Upload Size : 100 MB

Average File Size : 8 MB

Concurrent Uploads : 50

Concurrent Downloads : 100

Temporary Storage : 5Gi emptyDir

Object Storage : Amazon S3

Virus Scan : Yes

Compression : No

Image Processing : Yes

Peak Upload Rate : 500 Files/Minute

Peak Download Rate : 1200 Files/Minute

Ingress Timeout : 10 Minutes

Average Response Time : < 3 Seconds

Health Endpoints :

/actuator/health/liveness

/actuator/health/readiness

/actuator/health/startup

This level of information enables the platform team to size worker nodes, configure ingress timeouts, allocate storage, and provision networking correctly.


Production Recommendations

For enterprise file services, consider the following best practices.

✔ Stream files instead of loading them into memory.

✔ Store files in object storage rather than inside containers.

✔ Keep only metadata in the database.

✔ Define maximum upload limits.

✔ Configure ingress and gateway timeouts for large uploads.

✔ Use asynchronous processing for OCR, thumbnail generation, virus scanning, and other long-running operations.

✔ Publish events after successful uploads to decouple downstream processing.

✔ Monitor upload latency, download throughput, network utilization, and object storage performance.


Final Thoughts

A file upload service is fundamentally different from a typical REST microservice.

Its success depends not only on CPU and memory but also on storage architecture, network throughput, streaming design, object storage integration, and asynchronous processing.

When discussing deployment with your platform team, be prepared to answer questions about file sizes, upload rates, storage strategy, bandwidth, and timeout configuration—not just replicas and CPU limits.

Understanding these characteristics will help you build scalable, resilient, and cloud-native file processing applications that perform reliably in production.


Key Takeaways

Kubernetes doesn’t just run applications—it manages resources.

As Java developers, we need to move beyond thinking only about code and begin thinking about capacity, scheduling, scalability, and resilience.

Understanding CPU requests, memory limits, replica sizing, startup characteristics, and infrastructure planning is just as important as writing clean business logic.

The better you understand these concepts, the more effectively you’ll collaborate with platform engineers and the more reliable your applications will become.


What’s Next?

So far we’ve learned how Kubernetes schedules and sizes workloads.

In the next article, we’ll look at the objects that make this possible.

We’ll explore Pods, ReplicaSets, Deployments, Namespaces, Labels, Selectors, and Services in detail, and see how they work together to create a highly available platform for Spring Boot microservices.

Leave a Reply

Your email address will not be published. Required fields are marked *