Java · How It Runs in Production
How Java Programming Works in Real-World Companies
Quick summary — how Java works in real companies
Nobody writes Java from scratch in production. Companies use a framework (mostly Spring Boot), a database (mostly Postgres or MySQL), a build tool (Maven or Gradle), and a deployment pipeline (GitHub Actions → Kubernetes or ECS). Your job is fitting into that system, not inventing it.
In this guide you will learn:
- Architecture — monolith vs microservices, and what companies actually run.
- The stack — Spring Boot, databases, messaging, deployment.
- Code review — how real Java code gets merged.
- Deployment & monitoring — how it reaches production safely.
- Templates — a service checklist and review checklist.
SECTION 01Architecture — monolith, microservices and what companies run
Most tutorials teach microservices. Most companies still run monoliths. Here's the honest picture:
| Architecture | What it looks like | Companies using it | Pros / cons |
|---|---|---|---|
| Modular monolith | One deployable, several internal modules | Most mid-size companies | Simple ops / harder to scale a team |
| Microservices | Many small services, own databases | Large product teams, fintechs | Independent scaling / heavy ops cost |
| Batch & data jobs | Scheduled jobs, ETL, reports | Banks, insurance, retail | Predictable load / slow feedback loop |
| Serverless / functions | Event-triggered Java functions | Some startups, some AWS-native teams | No servers / cold starts |
What you'll actually see in your first Java job:
90% of Java companies:
- 1 to 3 big applications
- Spring Boot (or a legacy Spring)
- Postgres / MySQL / Oracle
- Redis for caching
- Kafka or RabbitMQ for events
- Kubernetes or ECS for deployment
- Jenkins / GitHub Actions for CI
If it's a monolith:
- One repo, several packages (modules)
- One database
- One deployment
- Startup time: 30-90 seconds
- Deploy frequency: weekly to monthly
If it's microservices:
- Many repos, or a monorepo
- Service per domain
- Each service has its own DB
- Startup time: 3-15 seconds
- Deploy frequency: daily
You will not be asked to architect a new system
on day one. You will be asked to fit into the
existing one — which is why understanding the
architecture matters more than memorising tools.
Why companies switch to microservices:
Real reasons:
✓ Teams grew — cannot deploy independently
✓ One part needs different scaling
✓ Different data consistency requirements
✓ Acquisition — different tech stacks merged
✓ Regulatory isolation (payments vs rest)
Weak reasons (that backfire):
✗ "It's modern"
✗ "Startups do it"
✗ "It's faster"
✗ "Monoliths are bad"
The real cost of microservices:
- 3-5x more operational complexity
- Distributed tracing becomes mandatory
- Any bug is now a debugging expedition
- New hires take 2-3x longer to contribute
- Local development is harder
Rule most companies learn the hard way:
Start with a modular monolith. Split when a real
team boundary forces it. Not before.
If you're interviewing, ask:
"Is this a monolith or microservices, and why?"
An honest answer tells you a lot about the team.
SECTION 02The real Java stack inside a company
Here's the stack you'll be working with, and what each tool actually does in production:
| Layer | Tools | What you actually do | Common pitfalls |
|---|---|---|---|
| Framework | Spring Boot, Quarkus | Write controllers, services, repositories | Overusing annotations, magic config |
| Persistence | JPA/Hibernate, jOOQ, JDBC | Entity design, queries, transactions | N+1 queries, lazy loading in loops |
| Database | Postgres, MySQL, Oracle | Schema, indexes, migrations | Schema drift, missing indexes |
| Messaging | Kafka, RabbitMQ | Publish/subscribe, retry, idempotency | Duplicate messages, out-of-order events |
| Deploy | Docker, Kubernetes, ECS | Containerise, deploy, observe | Fat containers, missing health checks |
| Observability | Prometheus, Grafana, ELK, Datadog | Add metrics, structured logs, traces | Verbose logs, unlabeled metrics |
// Realistic Spring Boot service — what a company expects
@RestController
@RequestMapping("/api/v1/orders")
public class OrderController {
private final OrderService service;
public OrderController(OrderService service) {
this.service = service;
}
@GetMapping("/{id}")
public ResponseEntity<OrderDto> get(@PathVariable Long id) {
return service.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<OrderDto> create(
@RequestBody @Valid CreateOrderRequest req) {
OrderDto created = service.create(req);
URI location = URI.create("/api/v1/orders/" + created.id());
return ResponseEntity.created(location).body(created);
}
}
@Service
public class OrderService {
private final OrderRepository repo;
public OrderService(OrderRepository repo) {
this.repo = repo;
}
@Transactional
public OrderDto create(CreateOrderRequest req) {
Order order = Order.create(req.customerId(),
req.items(),
req.total());
Order saved = repo.save(order);
return OrderDto.from(saved);
}
}
// Points worth noting in real companies:
// - constructor injection, not @Autowired fields
// - DTOs, not entities in controllers
// - @Valid on request bodies
// - @Transactional on the service, not the controller
// - Location header on 201 Created
// A common production bug: the N+1 query
// The naive JPA version
List<Order> orders = orderRepo.findAll();
for (Order o : orders) {
// Each access loads customer lazily — one query per order
System.out.println(o.getCustomer().getName());
}
// 500 orders = 501 queries.
// Works in dev, collapses under load in prod.
// The fix — fetch join or entity graph
@Query("""
select o from Order o
join fetch o.customer
where o.createdAt >= :since
""")
List<Order> findRecentWithCustomer(@Param("since") Instant since);
// Or a batch size hint
@BatchSize(size = 50)
@OneToOne(fetch = FetchType.LAZY)
private Customer customer;
// Or load explicitly when you need it
List<Order> orders = orderRepo.findAll();
Set<Long> ids = orders.stream().map(Order::getCustomerId)
.collect(Collectors.toSet());
Map<Long, Customer> byId = customerRepo.findAllById(ids)
.stream().collect(Collectors.toMap(Customer::getId, c -> c));
// This is the kind of bug interviewers love:
// it's invisible in a small dataset, brutal in production,
// and it tests whether you understand the ORM.
SECTION 03Code review and merge realities
This is where most self-taught Java developers struggle — not the code, but the process:
| Step | What happens | What reviewers check | How long it takes |
|---|---|---|---|
| Pull request | You open a PR from a feature branch | Description, scope, tests | 15-30 min to open well |
| CI checks | Build, tests, lint, security scan | All green before review | 5-20 min |
| Review | 1-2 senior devs read your diff | Logic, naming, tests, error handling | 1-3 days |
| Revisions | You push fixes; CI re-runs | Addressed comments, no regressions | Hours to a day |
| Merge | Approved PR merges to main | Branch protected, squash or merge | Minutes |
# PR template most teams use:
## What
Adds a /api/v1/orders/{id} endpoint that returns
a single order by ID.
## Why
The mobile app needs a single-order fetch; today
it fetches the entire list. Page load time on
mobile drops from 1.2s to 200ms in staging.
## How
- Added OrderController.get(...)
- Service method returns Optional<OrderDto>
- Added integration test covering 404 and 200
- Reused existing OrderDto and mapping
## Testing
- [x] Unit tests pass
- [x] Integration tests pass
- [x] Manual smoke on staging
- [x] No new dependencies
## Risk
Low — new endpoint, no existing endpoint modified.
Rollback: revert the commit; no data changes.
## Screenshots
(before/after response times)
## Checklist
- [x] Tests added/updated
- [x] Logs and metrics added
- [x] No secrets committed
- [x] Error paths handled
What reviewers actually check (in order):
1. CORRECTNESS
Does the code do what the PR claims?
Are edge cases handled?
Are errors returned, not swallowed?
2. TESTS
Is there a test?
Does it test the behaviour, not the implementation?
Would it catch a regression?
3. READABILITY
Are names clear?
Is the method doing one thing?
Would a new hire understand this in 6 months?
4. ERROR HANDLING
Are exceptions logged with context?
Are client errors 400, server errors 500?
Is there a timeout on external calls?
5. SECURITY
Input validated?
SQL parameterised?
No secrets, no logs of PII?
6. PERFORMANCE
Any N+1 query?
Any loop calling the DB?
Any unbounded list operation?
Common reasons PRs get rejected:
✗ No tests
✗ God-class methods (200+ lines)
✗ Mixed concerns in one PR
✗ Unclear commit messages
✗ Silent exception swallowing
✗ Hardcoded values that should be config
✗ Breaking change without a migration
SECTION 04Deployment, monitoring & rollback
Here's how Java services reach production safely:
| Stage | Tool | What you actually do | What can break |
|---|---|---|---|
| Build | Maven / Gradle | Package into a JAR | Missing dependency, JDK mismatch |
| Containerise | Docker (multi-stage) | Copy JAR into a small image | Fat image, no health check |
| Deploy | Kubernetes / ECS | Rolling update, readiness probe | Slow start, missing probes |
| Observe | Grafana / Datadog | Latency, error rate, memory, GC | No alerts, wrong thresholds |
| Rollback | Revert commit or previous image | Redeploy prior version | DB migrations can't roll back |
# Multi-stage Dockerfile for a Spring Boot app
# Stage 1: build
FROM eclipse-temurin:21-jdk AS build
WORKDIR /app
COPY pom.xml .
RUN mvn -q -B dependency:go-offline
COPY src ./src
RUN mvn -q -B package -DskipTests
# Stage 2: runtime (small image)
FROM eclipse-temurin:21-jre
WORKDIR /app
# Non-root user — standard production practice
RUN useradd -r -u 1001 appuser
USER appuser
COPY --from=build /app/target/app.jar /app/app.jar
EXPOSE 8080
ENTRYPOINT ["java","-XX:MaxRAMPercentage=75","-jar","/app/app.jar"]
# Why this pattern matters:
# - The build image is ~800MB, the runtime image is ~180MB
# - No source code or build tools in prod
# - -XX:MaxRAMPercentage respects container memory limits
# - Running as non-root is checked in most security reviews
Pre-deploy checklist (companies usually automate this):
☐ Build passes on CI (not just locally)
☐ All tests pass, including integration tests
☐ Container image built and pushed
☐ Image uses JDK 17+ (or whatever the org standard is)
☐ Image runs as non-root user
☐ Health check endpoint exists (/healthz or /actuator/health)
☐ Readiness probe configured (not just liveness)
☐ Memory limits set to match the JVM's heap
☐ Secrets injected via env vars, not baked into image
☐ Migrations reviewed and applied before deploy
☐ Rollback plan documented
☐ Monitoring dashboard exists with alerts
☐ On-call knows the deploy is happening
Rollback reality:
- App rollback is easy: redeploy previous image
- DB rollback is hard: migrations can be forward-only
- If a migration changes column types,
rolling back the app breaks it in prod
Rule: forward-compatible migrations.
Add columns, backfill, then use them.
Never rename or drop in the same release.
SECTION 05Service & review templates
These are the two artifacts every Java developer should be able to produce from memory:
New Java service checklist (what companies expect):
PROJECT
☐ Maven or Gradle, single build
☐ Java 17+ (or org standard)
☐ Spring Boot (or Quarkus if org uses it)
☐ Layered packages: controller / service / repo
CONFIG
☐ application.yml per environment
☐ Secrets via env vars, not code
☐ Feature flags for risky features
☐ Timeouts on all outbound calls
DATA
☐ Schema created via migrations (Flyway/Liquibase)
☐ Indexes on frequently filtered columns
☐ Connection pool sized (HikariCP)
☐ No raw SQL string concatenation
API
☐ Consistent error format
☐ Versioned paths (/api/v1/...)
☐ Pagination on list endpoints
☐ Request validation via @Valid
TESTS
☐ Unit tests for services
☐ Integration tests for controllers
☐ At least one end-to-end test
DEPLOY
☐ Multi-stage Dockerfile
☐ Health endpoint
☐ Readiness and liveness probes
☐ Non-root user
☐ Memory and CPU limits
OBSERVABILITY
☐ Structured JSON logs
☐ Metrics for key endpoints
☐ Trace IDs propagated
☐ Alerts on error rate and latency
Observability checklist (Spring Boot):
METRICS (via Micrometer → Prometheus)
☐ http_server_requests_seconds (latency)
☐ http_server_requests_total by status
☐ jvm_memory_used_bytes
☐ jvm_gc_pause_seconds
☐ hikaricp_connections_active
☐ custom business metrics (orders_created_total)
LOGS
☐ Structured JSON, not free text
☐ Request ID in every log line
☐ No PII in logs (emails, phones, card numbers)
☐ Errors logged with stack trace and context
☐ Log level configurable per environment
TRACES
☐ Trace ID from inbound requests
☐ Propagated to outbound calls
☐ DB spans, if the tracer supports it
ALERTS (the four that matter)
☐ Error rate > 1% for 5 minutes
☐ P95 latency > SLO for 5 minutes
☐ Pod restarts > 1 in 10 minutes
☐ DB connection pool > 80% for 5 minutes
Everything else is nice-to-have.
These four catch 90% of real incidents.
On-call basics every Java developer should know:
1. FIRST 5 MINUTES OF AN INCIDENT
☐ Check the dashboard (error rate, latency, memory)
☐ Check recent deploys (git log --since="2 hours ago")
☐ Check logs for exceptions in the last 15 min
☐ Confirm the blast radius — who is affected?
2. TRIAGE
☐ Is it a code bug, a dependency, or infra?
☐ Can you roll back?
☐ Is it a data issue (bad rows)?
☐ Is it a traffic spike?
☐ Is it a DB issue (slow queries, locks)?
3. ROLLBACK vs FORWARD FIX
Roll back first if:
- The change is recent
- Rollback is faster than fixing
- Users are impacted right now
Fix forward if:
- Rollback breaks something else
- The issue is caused by data, not code
4. COMMUNICATION
☐ Post in the incident channel immediately
☐ State: impact, users affected, ETA
☐ Update every 15 minutes, even if unchanged
5. AFTER
☐ Write a blameless post-mortem
☐ Add a test that would have caught it
☐ Add an alert that would have noticed it sooner
The single most useful skill in on-call:
reading a stack trace and reading a deploy log.
Both take five minutes to learn, and five years to master.
SECTION 06Interview Q&A — Java in real companies
Q1Do companies really use microservices everywhere?
No. Most mid-size companies run a modular monolith. Microservices are used where team independence or scaling genuinely requires them. Ask the interviewer which the team runs and why — it's a good signal.
Q2How much of my job is writing new code?
Realistically 30-50%. The rest is reading existing code, fixing bugs, writing tests, reviewing PRs, and debugging production. The "greenfield" projects are a small fraction of the work.
Q3Should I learn Kubernetes as a Java developer?
Learn to deploy to it, not to administer it. You need to know Docker, basic kubectl, and how probes and limits work. The deep cluster work is usually a platform team's job.
Q4What's the most common production bug in Java?
The N+1 query. Small datasets hide it, production exposes it. The second most common is a missing timeout on an external call, which ties up threads until the service falls over.
Q5Do I need to write tests for everything?
No — but every bug fix needs a test that would have caught it, and every service method with branching logic needs one. Tests for getters are noise; tests for business rules are mandatory.
SECTION 07Test yourself — Java in production quiz
Five questions. No sign-up.
0 / 5Pick an answer to see why it is right or wrong.
SECTION 08Frequently asked questions
What's the biggest gap between tutorials and real companies?
Process. Tutorials teach you to write a class; companies expect you to open a PR, pass CI, respond to review comments, and deploy safely. The code is often simpler than the tutorial; the process is what takes time.
Do I need to know Kubernetes to get a Java job?
You need to know how to deploy to it — Docker, basic kubectl, and how readiness and liveness probes work. You don't need to run a cluster unless you're applying to a platform role.
Which database should I learn first?
PostgreSQL. It's the most common modern choice and its SQL is close enough to MySQL and Oracle that switching is a short step. Learn joins, indexes and transactions properly.
How much of my job is Spring Boot vs Core Java?
Roughly 70% Spring Boot and 30% Core Java — but the Core Java matters most when you're debugging a production issue. Streams, generics, collections and concurrency show up everywhere.
What should I build to prepare for a real Java role?
One service with: Spring Boot, Postgres, real migrations, tests, a Docker container, health checks and one alert. That covers everything a junior interview actually probes.
SECTION 09Related reads
Classroom & online · Noida
Learn Java the way companies actually use it
Our Java Full Stack Training Course covers Spring Boot, databases, testing, Docker and deployment — with projects that match real production shape.
₹18,500 · full programme- Core Java to Spring Boot
- Docker deployment & CI/CD
- Mock interviews & code reviews
- Weekday & weekend batches