Posts

JUnit 5 & Mockito: Testing That Catches Bugs

It's the Monday after Black Friday. Your team's checkout service has been live for three weeks, and every test you have is a person clicking through the demo. At 11:40 a support ticket lands: a customer bought a $219.00 espresso machine and was charged $3.17 . Then another ticket. Then thirty. By noon someone finds it: the new coupon feature applies the percentage discount once per line item and once more to the order total. Stack a 20% coupon with a gift card, and the discount multiplies. Nobody tested that combination, because "testing" meant a senior dev squinting at the code and saying "looks right." The fix took eleven minutes. The refunds took two weeks. And the uncomfortable lesson is the ratio every experienced engineer eventually internalizes: a bug caught by a unit test costs minutes; the same bug caught by a customer costs days. Unit tests are the cheapest bug-catcher you will ever buy — and in Java, JUnit 5 plus Mockito is the standard-issue k...

Maven vs Gradle: Builds Demystified

You cloned a Java repo. The README says "just run the build," and the repo root contains a file you didn't write: pom.xml or build.gradle.kts . Your instinct from the Java Setup 2026 post is to reach for javac — but this project has twelve libraries, a test suite, and a src/main/java directory three levels deep. Hand-assembling the classpath already looks like this: javac -cp lib/gson-2.10.jar:lib/slf4j-api-2.0.9.jar:lib/commons-lang3-3.14.0.jar \ -d out $(find src -name '*.java') java -cp out:lib/gson-2.10.jar:lib/slf4j-api-2.0.9.jar:lib/commons-lang3-3.14.0.jar com.acme.Main That works exactly once. The moment someone adds a thirteenth library, updates a version, or asks "but does it pass the tests before it ships?", the hand-rolled commands rot. A build tool replaces all of this with one declared file and one command. It owns the whole pipeline — compile the sources, download the dependencies, run the tests, and package the result — and it ...

Production Diagnosis: Thread Dumps, Heap Dumps, JFR & async-profiler

It's 2 AM. The API's p99 went from 150ms to 3 seconds, CPU is pinned at 95%, and it works fine on your laptop. Restarting "fixed" it last time — for four hours. This post is the toolkit for the time restarting doesn't fix it: four tools, each answering one question, mapped to the symptom in front of you. 1. The scenario we'll diagnose Two failure shapes cover most production mysteries. Learn to tell them apart first, because each one reaches for a different tool: High CPU, slow responses — threads are doing something expensive (hot loop, lock contention, pathological regex). The threads are guilty; find what they're executing. Growing memory, eventual OOM or long GC pauses — objects are accumulating . The heap is guilty; find what's being retained. Requests hang forever, CPU idle — threads are waiting on each other. Nobody's guilty yet; find the deadlock. Here's a lab specimen containing all three sins. Run it, then diagnose it li...

JVM Memory & Garbage Collection: What Java Developers Need to Know

Every Java interview eventually lands here: "heap vs stack?", "how does GC work?" Most answers are memorized definitions. This post gives you the mental model that makes the definitions obvious — where objects live, what the collector actually does, and the one rule that matters most: diagnose before you tune . 1. Heap vs stack vs metaspace: three neighborhoods The JVM splits memory by lifetime and purpose , not by type: Stack — one per thread. Holds local variables and call frames. Memory is freed the instant a method returns. Small, fast, and never garbage collected — a StackOverflowError means runaway recursion, not a leak. Heap — one shared region. Every new object lands here. This is the only region the garbage collector manages. A OutOfMemoryError: Java heap space means the heap filled with objects that are still reachable. Metaspace — class metadata (loaded classes, method bytecode). Grows as classes load; a leak here ( OutOfMemoryError: Metaspa...

final vs finally vs finalize() in Java — and Why finalize() Is Dead

Three keywords, one sound, three completely unrelated jobs. Interviewers love this question because it catches people who memorized definitions without understanding mechanics. Let's fix that — with code you can run. 1. final: a promise the compiler enforces final means "this cannot be reassigned / overridden / extended" — and what it applies to changes the meaning: final variable — assigned exactly once. A final reference can't point at a different object, but the object itself can still mutate (unless it's immutable too). final method — cannot be overridden in subclasses. final class — cannot be extended at all ( String is final ; that's part of why it's safe). public class FinalDemo { public static void main(String[] args) { final int maxRetries = 3; // maxRetries = 4; // COMPILE ERROR: cannot assign a value to final variable final StringBuilder sb = new StringBuilder("a"); sb.append("...

Files, I/O & NIO: Reading and Writing Data

Almost every program you've written so far has worked with data that vanishes when the program ends. Real programs keep data around: they read configuration files, load CSV exports, write logs, generate reports. Java has two file APIs — the 1996 original ( java.io ) and the modern java.nio.file package, introduced in Java 7 and known as NIO.2 . In 2026, NIO.2 is the default: it is shorter, safer, and harder to misuse. You'll still meet java.io in legacy code and libraries, so this post teaches NIO.2 first and gives you just enough java.io to read old code without fear. This is the final post of the Core Java track — it leans on exceptions and try-with-resources (post 10), collections (post 11), and streams (post 15). The next track, Tooling & Testing, assumes you can read and write files comfortably. The decision rule: which API do I reach for? Writing new code? Use java.nio.file ( Path + Files ). Reading old code or a library's streams? Learn the java.io sh...