Posts

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...

Date, Time & Money: java.time and BigDecimal Done Right

Two of the most common sources of production bugs in Java applications are date-time handling and money arithmetic. The old java.util.Date and Calendar classes were mutable (so one method could silently change a date it didn't own), had zero-based months (January is 0 , which has confused every Java developer at least once), and mixed up "a moment in time" with "a wall-clock reading" in ways that made timezone bugs nearly inevitable. We mention them once so you can read legacy code — from here on, everything is java.time (Java 8+, current best practice in Java 25) and BigDecimal . The java.time Toolbox: Pick the Right Type java.time gives you several distinct types because dates and times are genuinely different concepts. Picking the right one is a decision rule, not a vocabulary test: LocalDate , LocalTime , LocalDateTime — a calendar date and/or a wall-clock time with no timezone attached . Use them for things that are inherently local: a store's...

Records, Sealed Classes & Pattern Matching (Java 17+)

The sixty-line Point Open any older Java codebase and you will find classes like this: two fields, a constructor, two getters, and then equals , hashCode , and toString — written by hand or by an IDE — for a total of fifty or sixty lines whose only job is to carry two integers. public final class Point { private final int x; private final int y; public Point(int x, int y) { this.x = x; this.y = y; } public int getX() { return x; } public int getY() { return y; } // ... then equals(): 20 lines comparing x and y ... // ... then hashCode(): consistent with equals ... // ... then toString(): "Point{x=3, y=4}" ... } Every line is correct and every line is noise. None of it says anything about your problem; it says "this is a value with two parts," repeated in the syntax Java used to demand. Records delete that ceremony. Sealed classes solve the mirror problem — hierarchies that are supposed to be closed but aren't. And patte...

Streams, Collectors & Optional

Streams, Collectors & Optional Post 14 gave you lambdas — small functions you can pass around. This post shows where lambdas earn their keep: streams for bulk data processing, and Optional for saying "this value might be missing" without null landmines. The stream pipeline mental model Forget the individual method list for a moment. A stream pipeline always has exactly three parts: Source Intermediate ops (lazy) Terminal (eager) Hold onto one rule that explains almost everything in this post: Intermediate operations are lazy — they describe the work. One terminal operation is eager — it does the work, in a single fused pass. List<String> names = List.of("anita", "bob", "farah", "bo", "kunal"); long count = names.stream() // source .filter(n -> n.length() >= 4) // intermediate (lazy) .map(String::toUpperCase) // intermediate (lazy)...