Checkpoint: Practical Java
This checkpoint ties together the practical-Java track — collectors, virtual threads, the HTTP client, equals/hashCode, immutability, the builder pattern, dependency injection, and serialization — into one combined build challenge.
Learn Checkpoint: Practical Java in our free Java course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick…
Part of the free Java course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
A recap of eight lessons, a multi-step build challenge (an immutable record assembled by a builder, grouped with collectors, with a correct equals/hashCode), a written checkpoint quiz, and a 10-question timed quiz. If anything here feels shaky, revisit that lesson before moving to the final project.
💡 Analogy: Up to now you've been collecting individual tools — a saw here, a clamp there. A checkpoint is where you build something using several tools at once . The challenge below makes one small program that needs a record (the material), a builder (the jig that assembles it), equals/hashCode (so duplicates don't sneak in), and collectors (to measure and sort the finished pieces). That's how real code feels — patterns combine.
Complete the starter below: finish the record's validation, the nested Builder , and the four stream pipelines. Then compare against the full compiled solution. The expected output is shown in the comments.
A record built by a builder, deduplicated by its generated equals / hashCode , and grouped four ways with Collectors:
Real output (compiled and run with javac 21 ):
Notice how every concept appears: the record gives free equals/hashCode (so the duplicate Apple collapses, leaving 5 distinct), the builder assembles each product with defaults and validation, and collectors compute totals, counts, and the dearest item per category — all in deterministic TreeMap order.
Answer: the record auto-generates equals / hashCode from all components, so two Apples with the same name, category and price are equal and hash to the same bucket — the HashSet treats them as one.
Answer: sorted, deterministic key order. Without it you'd get a HashMap whose printed order is undefined.
Answer: it runs for every way the record is created (including via the builder's build() ), so an invalid Product can never exist.
Answer: submit each HTTP send to Executors.newVirtualThreadPerTaskExecutor() — virtual threads let thousands of blocking calls run concurrently without exhausting OS threads.
Answer: defensively copy it in the compact constructor with tags = List.copyOf(tags) , so neither the caller's list nor the accessor can mutate the record.
Answer: maxBy reduces each group to an Optional<Product> ; the finishing function opt.map(Product::name).orElse("none") turns it into just the name.
You combined records, builders, equals/hashCode and collectors into one working program, and recapped the whole practical track — collectors, virtual threads, the HTTP client, immutability, dependency injection, and serialization. These are the patterns you'll reach for constantly in real Java.
Next up: the Final Project — bring everything together into one substantial application.
Practice quiz
Which collector groups elements and totals a numeric field per group?
- toMap(k, f)
- partitioningBy(f)
- groupingBy(k, summingInt(f))
- joining(f)
Answer: groupingBy(k, summingInt(f)). groupingBy with a summingInt downstream produces a Map of key to the integer total per group.
Why do records pair so well with hash-based collections?
- They auto-generate consistent equals and hashCode
- They are static
- They are faster to allocate
- They skip validation
Answer: They auto-generate consistent equals and hashCode. A record's generated equals/hashCode satisfy the contract, so records work correctly as Set elements and Map keys.
Where should a Builder validate the object it constructs?
- In each setter
- In toString
- In main
- In build() (or the product's constructor)
Answer: In build() (or the product's constructor). build() is the single point where the whole object exists; for a record the compact constructor also validates.
What scales virtual threads for I/O-bound work?
- They use more CPU
- Blocked threads unmount and free their carrier
- They never block
- They avoid the heap
Answer: Blocked threads unmount and free their carrier. A blocked virtual thread is unmounted so its carrier serves others — millions of mostly-waiting tasks become feasible.
What must you check after HttpClient.send for a 404?
- response.statusCode()
- Catch an exception
- response.error()
- Nothing — it throws
Answer: response.statusCode(). HTTP error statuses are returned, not thrown; inspect statusCode() yourself.
Defensive copying in an immutable class prevents...
- faster code
- compilation
- callers mutating your internal collection
- serialization
Answer: callers mutating your internal collection. Copying mutable inputs/outputs (e.g. List.copyOf) stops outside code from changing your state.
Constructor injection improves testability because...
- it removes interfaces
- you can inject fakes instead of real collaborators
- it runs faster
- it avoids constructors
Answer: you can inject fakes instead of real collaborators. Depending on an injected interface lets tests pass a fake and assert on interactions with no real I/O.
Which field keyword excludes a field from serialization?
- volatile
- static
- final
- transient
Answer: transient. transient fields are skipped and return to defaults after deserialization.
What does Collectors.collectingAndThen do?
- Sorts the stream
- Collects then applies a finishing function
- Counts elements
- Splits the stream
Answer: Collects then applies a finishing function. It runs a collector and post-processes the result — e.g. mapping a maxBy Optional to a name, or making a list unmodifiable.
If a.equals(b) is true, the contract requires...
- a == b
- a.toString() == b.toString()
- a.hashCode() == b.hashCode()
- nothing
Answer: a.hashCode() == b.hashCode(). Equal objects must have equal hash codes, or hash collections break.