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