Skip to content
teach

Learning: Java

Become the engineer trusted to own a Java service on a team: able to model a domain in modern Java rather than in the inheritance hierarchies the language used to demand, reason about concurrency from the memory model instead of from experiment, read a profile and a garbage-collection log to a decision, and review someone's Java and name concretely what an abstraction is costing them.

Start here: 0001. References Are Values
Latest lesson: 0052. Reflection and Annotations

Success looks like

  • Predict what reference equality, a broken hashCode, and a shared mutable field do, before running the code.
  • Model a domain with records, sealed interfaces and enums, and say why an inheritance hierarchy would have been worse.
  • State what synchronized, volatile and a concurrent collection each guarantee, in the vocabulary of the Java memory model.
  • Use virtual threads where they pay, and name what still blocks a carrier thread.
  • Read a garbage-collection log and a profile, then size a heap from that evidence rather than from a default.
  • Ship a build someone else can run: declared dependencies, a test suite, a runnable artifact.
  • Review a pull request and say precisely why a class hierarchy, a checked exception, or a stream chain is the wrong tool there.
  • Settle a language argument from the specification and the JEP that introduced the feature.

Constraints

  • Assumes no prior Java. Experience in another language shortens the early stages but is not required, and it brings habits Java punishes quietly: a getter for every field, an interface with one implementation, a class where a record would say it better.
  • The baseline is the current long-term-support release. Every language feature is introduced with the release it arrived in, because Java code is read across many versions and "modern Java" ages.
  • Needs only a JDK and a terminal on any supported OS. An IDE helps and is never required, and nothing in the arc requires paid tooling or a cloud account.
  • Most reps are small programs that fit one sitting. Stage 6 is the exception: a profile needs a program that runs long enough to profile.
  • Version-sensitive claims are checked against the specification, the API documentation and the JEP index rather than against a book, because the six-month release cadence outpaces every book listed.

Out of scope

  • Frameworks as subjects in their own right: Spring, Jakarta EE, Quarkus, Hibernate. Judging whether a framework earns its place is a review skill and belongs in stage 7.
  • Other JVM languages: Kotlin, Scala, Clojure, Groovy.
  • Android, and mobile targets generally.
  • Build-tool comparison. The arc teaches the tasks a build has to perform and uses one tool to do them, rather than arguing about which.
  • JVM internals past the point where they stop predicting program behaviour: JIT implementation, bytecode engineering, writing a garbage collector.
  • Kubernetes and infrastructure beyond what one service needs to run.

The arc

Seven stages, zero to senior. Not a lesson list: a stage takes several lessons, and the boundaries are soft.

Stage Lessons Covers Done when
1. Foundations 0001 to 0006 Primitives and references, null, strings and their pool, arrays, the collections framework, equals and hashCode, Comparable Can predict identity versus equality and aliasing without running the code
2. Modelling 0007 to 0014 Classes and interfaces, records, sealed types, enums, generics and erasure, pattern matching, immutability as a default Models a domain without reaching for inheritance first
3. Idiom and the library 0015 to 0021 Exceptions and what to do with checked ones, Optional, streams and collectors, iteration, files and IO, the time API, text blocks Writes Java a reviewer would not describe as unidiomatic
4. Concurrency 0022 to 0028 Threads, the memory model, synchronized and volatile, java.util.concurrent, executors, virtual threads, structured concurrency, the traps Can name the guarantee a broken concurrent program violated
5. Testing and build 0029 to 0035 JUnit 6, parameterised tests, test doubles and when not to use one, dependency declaration, a reproducible runnable artifact Someone else can clone, build, test and run it
6. The runtime 0036 to 0042 Memory areas, object layout and escape analysis, garbage collectors and their trade-offs, reading a GC log, JMH, profiling, allocation reduction Optimises from a profile and proves the win with a benchmark that is trustworthy
7. Judgment 0043 to 0049 API design and backwards compatibility, deprecation, review, reading the specification and the JEPs for answers Trusted to make the call and to explain it to someone else; stage 8 completes it with the mechanism, reflection and annotations, every judged framework was actually built from
8. Modules and reflection 0050 to 0052 The module system, module-info.java, requires and exports, strong encapsulation, jlink and jpackage runtime images, reflection and annotations Declares a module boundary deliberately, ships a runtime image built from it, and reads what a framework's reflection actually touches before judging whether it earns its place

Lessons

Work through these in order.

# Lesson Teaches
0001 References Are Values A variable holds a primitive or a reference, and every assignment and argument copies that
0002 Identity and Equality == compares references, so it answers a different question for strings and boxed numbers
0003 The equals and hashCode Contract Break the contract and a hash collection loses your object without raising anything
0004 null and Where It Comes From null is a reference that refers to nothing, and the fix is to reject it at the boundary
0005 Arrays and Collections Arrays are covariant and fixed, and three list factories differ in ways their names hide
0006 Comparable and Comparator A sorted collection uses ordering rather than equals, so an inconsistent comparator loses data
0007 Classes and Objects A class is a template for state and behaviour, and every field starts at a default you did not write
0008 Records A transparent carrier for immutable data, with the constructor, accessors, equals, hashCode and toString derived from the header
0009 Interfaces A contract with no state, plus the default methods that let one grow without breaking every implementor
0010 Inheritance and Composition What extends actually gives you, and why the answer is usually a field instead of a superclass
0011 Sealed Types and Pattern Matching A closed set of alternatives, and a compiler that checks you handled all of them
0012 Enums A fixed set of instances the language guarantees, with room for state and behaviour on each one
0013 Generics and Erasure Type parameters the compiler checks and the runtime forgets, and the wildcards that make them usable
0014 Immutability as a Default Make the object impossible to change and most of the hard questions stop being asked
0015 Exceptions Which failures belong in a signature, which belong in a stack trace, and cleanup that survives both
0016 Optional A return type that makes absence part of the contract, and the four places it does not belong
0017 Streams A pipeline that describes work, does none of it until asked, and can only be asked once
0018 Collectors Turning a pipeline back into a container, and the three collectors that throw where you do not expect it
0019 Files and Paths Paths that are not strings, streams that must be closed, and a charset that is finally a default worth having
0020 Dates and Times Which of the six time types you actually meant, and the three that are not a point in time
0021 Strings and Text Text blocks, the formatting you should use, and why length is not the number of characters
0022 Threads A thread is a scheduled call stack, and almost every mistake in this stage starts with treating one as free
0023 The Memory Model Why a value written by one thread may never be seen by another, and the exact rule that fixes it
0024 Mutual Exclusion One thread at a time, the two ways to say it, and the deadlock you can find before it happens
0025 Concurrent Collections and Atomics The collections that survive concurrent access, and the check-then-act that defeats every one of them
0026 Executors and Futures Submit work instead of creating threads, and find the exception that submit swallowed
0027 Virtual Threads Threads cheap enough to block, and the pooling habit you now have to unlearn
0028 Choosing a Model One question picks the model, and the failure you are looking at names the guarantee it broke
0029 Your First Test Where tests live, what the runner actually does, and the version scheme every write-up gets wrong
0030 Assertions That Name the Failure A failing test is a bug report, and the assertion you chose decides how good a report it is
0031 Parameterised Tests One test method, many cases, and the line where a loop inside a test stops being good enough
0032 Test Doubles Five kinds of stand-in, when a real object beats all of them, and what a mock quietly stops testing
0033 Declaring Dependencies Coordinates, scopes and the transitive graph, plus the version that arrives without being asked for
0034 The Build Lifecycle Phases and goals instead of memorised commands, and the wrapper that makes the build the same everywhere
0035 A Runnable Artifact Packaging what you built so that someone with nothing but a JDK can run it
0036 Where Memory Goes The five places the JVM puts memory, and which OutOfMemoryError names which one
0037 The Shape of an Object A small object is mostly header, and the flag that shrinks it is off by default
0038 The Allocation That Never Happened Escape analysis can delete an allocation entirely, and one field store puts it back
0039 Collectors and the Trade You Are Making Five collectors, one three-way trade, and why the default is usually the right answer
0040 Reading a Garbage Collection Log One line of log says how much was collected, how long it took, and whether to care
0041 A Benchmark You Can Trust Why the obvious timing loop lies, and the harness setup that silently measures nothing
0042 From Profile to Proof Record the run, read where the time went, change one thing, and prove the win
0043 What Counts as Breaking Three kinds of compatibility, and the change that is safe to compile against and fatal to run against
0044 Designing a Signature The parameter and return types decide what callers can do, and most of the damage is done at the boundary
0045 Evolving a Type Without Breaking It How to add to an interface, a record, an enum and a sealed hierarchy after people depend on them
0046 Deprecation That Works Marking something deprecated changes nothing unless you say what happens next
0047 Settling It From the Source Where to look when the argument is about what Java does, and which document answers which question
0048 Reviewing Java Naming what an abstraction costs, instead of saying it feels wrong
0049 Does This Framework Earn Its Place The last judgment in stage 7, made from the service's constraints rather than the framework's promises
0050 The Module System module-info.java replaces the JAR as the boundary the compiler and runtime both recognise, requires and exports decide what crosses it, and strong encapsulation means a public class in an unexported package is invisible past its own module
0051 Runtime Images jlink assembles a runtime image from nothing but the transitive closure of the modules it is told to start with, but only from explicit modules, an application depending on even one automatic module cannot be linked at all
0052 Reflection and Annotations This is stage 8's capstone, reflection reads and calls a class's own shape at run time instead of at compile time, an annotation is inert data until something reads it reflectively, and every out-of-scope framework in this arc is built from exactly these two mechanisms

Reference

  • Glossary: canonical terms for this topic
  • Resources: trusted sources, each annotated with what it covers
  • Equality, hashing and ordering: the three contracts, and which comparison to write
  • Modelling: which construct to reach for, what each one commits you to, and the rules that are easy to misremember
  • Idiom and the library: the library decisions a reviewer notices, and the traps that throw where nobody looks
  • Concurrency: which guarantee each construct gives, which model fits the workload, and what the symptom in front of you means
  • Testing and build: the build task each command actually performs, the double to reach for, and the failure that means the artifact is wrong
  • The runtime: where the memory goes, which collector answers which requirement, and the measurement that supports a claim
  • Judgment: which changes break what, how to retire an API, and how to argue the call from the source
  • The Module System: the stage 8 sheet, with the three conditions for cross-module access, what each directive does, and where a jar's code ends up

How this works

Each lesson is short and self-contained. Answer keys are collapsed: recall first, then open them. The real-world reps matter more than the reading, and spacing them out is the point. Anything still unclear at the end of a lesson is worth chasing to its primary source before moving on.

Table of contents