Notes guide
Java, Spring, Kubernetes
A map of how I actually run backend software: Java on the JVM, Spring as the application layer, Kubernetes as the place it lives. Start here, then open the note that matches the problem in front of you.
This is not a tutorial index. It is the order I would walk a teammate through after an incident.
If you are new here
- Spring
@Transactionaldoes not mean what you think — where transactions actually start, and why a method calling another method onthissilently does nothing. - Liveness is not health — the Kubernetes outage that looks like a crash loop but started as a slow dependency.
- Reliability lessons from SQLite — why a tool that solves more problems than it creates stays in production.
The Java course is the longer path if you want to write this stack with me, not only read about it.
Java / JVM
You are reading a stack trace in production. Learn to read it from the bottom: your code, then the framework, then the container. The first line is rarely the cause.
You are arguing about threads. Default Spring MVC is one request, one thread. ExecutorService is for work you can name and bound. Unbounded async is how a latency spike becomes a thread-pool outage.
You are tuning the JVM before measuring. Heap size, GC logs, and allocation rate beat a ritual -Xmx. I do not change GC algorithm until I have a graph of pause time under real load.
Spring
You wrapped a method in @Transactional and the database still disagrees. Read the transactional note. Self-invocation, REQUIRED vs REQUIRES_NEW, and rollback rules are the three bugs I still see in review.
You put business rules in the controller. Controllers parse HTTP. Services own the transaction boundary. If the service method cannot be called from a test without MockMvc, the boundary is wrong.
You have four profiles and none of them match production. local, test, and prod are enough. Feature flags belong in config, not in a fifth YAML file nobody deploys.
Kubernetes
Pods restart and the app team blames the cluster. Read the probes note. If liveness hits the database, Kubernetes will kill healthy processes because a dependency was slow.
You added CPU limits because “that is what we do.” Limits throttle. Requests are what the scheduler believes. I set requests from measured usage and I am slow to set CPU limits on latency-sensitive JVMs.
You cannot tell if a pod is ready to take traffic. That is what readiness is for. Startup probes exist so a slow JVM boot does not get murdered by liveness on the first minute.
What I believed → what I do now
I used to treat Spring and Kubernetes as separate skills: one for writing services, one for “ops.” I now treat them as one runtime. A transaction boundary that is wrong in Spring becomes a retry storm that Kubernetes happily amplifies.
Takeaways
- Start from the failure in front of you, not from the framework’s getting-started page.
@Transactionalis a proxy. If you do not know where the proxy sits, you do not have a transaction.- Liveness must not depend on the same things the pod needs to do its job.
- Java, Spring, and Kubernetes fail as a system. Debug them as a system.