Capstone — Building a Small Project

Course 2 · Ch 8 — Capstone
Building a Small Project
A small inventory/order system combining nearly every chapter across both Java courses

Sixteen chapters, two courses — this capstone builds one small, real inventory and ordering system that touches almost all of it: records and sealed types for the data model, a custom checked exception, a generic repository, interfaces, and a streams-based reporting pipeline.

Designing the Data Model

public record Product(String sku, String name, double price, int stock) implements Priced {} // java2-7's record — free constructor/equals/hashCode/toString public sealed interface OrderStatus permits Pending, Shipped, Cancelled {} // java2-7's sealed interface public record Pending() implements OrderStatus {} public record Shipped(String trackingNumber) implements OrderStatus {} public record Cancelled(String reason) implements OrderStatus {}

Product is a java2-7 record — immutable, with equality and formatting generated for free. OrderStatus is a java2-7 sealed interface with exactly three permitted shapes, each carrying its own relevant data — data-carrying variants, genuinely comparable to rust1-6's own enum design covered right back in the Rust track.

A Custom Checked Exception

public class InsufficientStockException extends Exception { // java1-7 — checked, not RuntimeException public InsufficientStockException(String sku, int requested, int available) { super("Cannot fulfill " + requested + " of " + sku + ", only " + available + " in stock"); } }

Extending Exception rather than RuntimeException makes this a java1-7 checked exception — every caller is compiler-forced to either catch it or declare it, exactly right for a genuinely expected, recoverable business condition like running out of stock.

A Generic Repository

public class Repository<T> { // java2-1's generics private final Map<String, T> items = new HashMap<>(); // java1-8 / java2-2's Map public void save(String key, T item) { items.put(key, item); } public Optional<T> find(String key) { return Optional.ofNullable(items.get(key)); } public List<T> all() { return List.copyOf(items.values()); } }

One Repository<T> class, reused for both Repository<Product> and Repository<Order>java2-1's type erasure means this is genuinely one shared implementation underneath both, not two separately compiled copies.

Interfaces & Polymorphism in the Model

public interface Priced { // java1-6's interface double price(); default String priceLabel() { return "$" + price(); } // java1-6's default method }

Product implements Priced — its record-generated price() accessor satisfies the interface automatically, and priceLabel() comes free from the interface's own default method, no override required.

Streams for Reporting

double totalValue = products.stream() // java2-4's Stream pipeline .mapToDouble(p -> p.price() * p.stock()) .sum(); List<Product> lowStock = products.stream() .filter(p -> p.stock() < 5) .sorted(Comparator.comparing(Product::stock)) // java2-2's Comparator + java2-3's method reference .collect(Collectors.toList());

Both reports read as a description of the result, not a manual loop — the exact style java2-4 introduced, built directly on java2-2's Comparator and java2-3's method references.

Putting It Together — A Small Run

public static void ship(Product p, int quantity) throws InsufficientStockException { if (p.stock() < quantity) { throw new InsufficientStockException(p.sku(), quantity, p.stock()); } // ship it... } try { ship(widget, 10); } catch (InsufficientStockException e) { System.out.println(e.getMessage()); } OrderStatus status = new Shipped("TRK123"); String summary = switch (status) { // java2-7's exhaustive switch — no default needed case Pending p -> "Awaiting shipment"; case Shipped s -> "Shipped: " + s.trackingNumber(); case Cancelled c -> "Cancelled: " + c.reason(); };

Chapter Attribution

Capstone pieceChapter
Product / OrderStatus records & sealed interfacejava2-7
InsufficientStockException (checked)java1-7
Repository<T> genericsjava2-1
Map/List storagejava1-8, java2-2
Priced interface & default methodjava1-6
Streams reportingjava2-4
Exhaustive switch over OrderStatusjava1-3, java2-7

What's Still Out of Scope

Honestly: no concurrency or thread-safety anywhere in this capstone, despite java2-5 covering it in full — a single-threaded demo has no genuine need for it. No persistence to a real file or database — Repository<T> is entirely in-memory. No automated tests. No build tooling. This capstone proves the pieces fit together, not that the result is production-ready.

A capstone's value is in the seams, not the size
This project is deliberately small — the point was never scale, it was confirming that records, generics, streams, custom exceptions, and interfaces genuinely compose cleanly together in real code, not just in isolated chapter examples.
This is a teaching capstone, not a template for a real system
A real inventory system would need persistence, concurrency control around stock levels (directly relevant to java2-5's own race-condition material), input validation, and tests — treat this as proof the language features fit together, not as production-ready code to copy.

Coding Challenges

Challenge 1

Add a fourth OrderStatus variant, Returned(String reason), update the permits clause, and update the exhaustive switch. Show what happens if you forget to add a case for it.

📄 View solution
Challenge 2

Write a stream pipeline over a List<Product> that groups products by whether their stock is above or below 10, using Collectors.partitioningBy(), and print both groups.

📄 View solution
Challenge 3

Write a short paragraph (as a comment) explaining specifically what would need to change in this capstone's Repository<T> class to make it safe for concurrent access from multiple threads, referencing java2-5's own material directly.

📄 View solution

Chapter 8 Quick Reference — Java Track Complete

  • Records + sealed interfaces model the domain with compiler-enforced closed sets (java2-7)
  • A checked custom exception forces callers to handle a genuinely expected failure (java1-7)
  • One generic Repository<T> serves every entity type, thanks to type erasure (java2-1)
  • Interfaces and default methods keep behavior decoupled from any one class (java1-6)
  • Streams describe reports declaratively rather than via manual loops (java2-2, java2-3, java2-4)
  • Concurrency, persistence, and testing are honestly named as still out of scope
  • Both Java courses are now complete — 16 chapters total, framed against Kotlin, C++, and Rust throughout.