Language & library reference
Concept clusters →Every named result in this course, grouped by chapter. Each one links back to where it's derived.
Language fundamentals
Rule
Widening is implicit, narrowing needs a cast
also known as: widening and narrowing primitive conversions, casts
Formal. Java converts a primitive value to another primitive type
automatically only along the widening chain byte โ short โ int โ
long โ float โ double (and char โ int onward), where every
value of the source type fits the target's range. A conversion the other
way is narrowing and needs a cast, (int) x. A cast from double to an
integer type drops the fraction, truncating toward zero; a cast to a
smaller integer type keeps only the low bits, so the value wraps.
boolean converts to nothing.
In plain English. Pouring a small cup into a big jug is always safe,
so Java does it for you. Pouring a big jug into a small cup might spill,
so Java makes you say "I know, do it anyway" by writing a cast, and what
spills is simply lost: (int) 9.99 is 9, not 10.
Example. long total = 42; and double ratio = count; compile.
int whole = 9.99; fails with possible lossy conversion from double to int; int whole = (int) 9.99; compiles and stores 9. (byte) 300 is 44.
Why this rule exists. Silent data loss is one of the oldest sources of bugs. Making every lossy conversion visible in the source keeps it deliberate.
Rule
Precedence first, then left to right
also known as: operator precedence and associativity
Formal. In a Java expression, *, / and % bind tighter than
binary + and -, which bind tighter than the comparisons, which bind
tighter than &&, which binds tighter than ||. Operators of the same
precedence group left to right, except assignment, which groups right to
left. Operands are evaluated left to right, and && and || evaluate
their right operand only when the left one doesn't already decide the
result.
In plain English. Do multiplication, division and remainder before adding and subtracting, then work from the left, just as in school arithmetic. Brackets override everything, so when in doubt, add them.
Example. 2 + 3 * 4 is 14, 20 - 5 - 3 is 12, and 24 / 4 / 2 is
3. "Total: " + 2 + 3 is "Total: 23" because the left + meets a
String first, while 2 + 3 + " items" is "5 items".
Why this rule exists. A fixed, familiar order lets everyone read the same expression the same way, without brackets around every step.
Strings, arrays, and basic data processing
Rule
Strings are immutable
also known as: String immutability
Formal. A String object never changes after it is created. Every
method that seems to modify text, such as toUpperCase, strip,
replace or substring, returns a new String and leaves the original
untouched. A variable can be reassigned to refer to the new string;
the old object is unchanged. == compares references, so text must be
compared with equals (or equalsIgnoreCase).
In plain English. A string is printed on paper, not written on a whiteboard. Asking for an upper-case version prints a fresh sheet. If you don't keep the fresh sheet, by assigning it to a variable, nothing has changed.
Example. After String name = " ada "; name.strip(); the variable
still holds " ada ". name = name.strip(); keeps the result.
"ab-cd".replace("-", "") returns "abcd" and replaces every match.
Why this rule exists. Because no one can change a string behind your back, strings are safe to share and to cache, and to use as lookup keys.
Collections and generics
Rule
What the main collection operations cost
also known as: Big-O of ArrayList, LinkedList, HashMap, TreeMap
Formal. With n elements: ArrayList.get(i), set(i, x) and
add(x) (amortized) are O(1); add(0, x), remove(0) and
contains(x) are O(n). LinkedList.get(i) is O(n); adding or removing
at either end is O(1). HashMap.get, put and containsKey, and
HashSet.add and contains, are O(1) on average. TreeMap and
TreeSet operations are O(log n). PriorityQueue.peek is O(1);
offer and poll are O(log n).
In plain English. Jumping to a numbered slot or looking up a key in a hash table takes the same time however big the collection is. Searching a list checks every item, so it grows with the list. A sorted tree takes a few more steps each time the data doubles, about 20 steps for a million items.
Example. Checking a million IDs with list.contains(id) inside a
loop over another million IDs is quadratic: about a trillion steps.
Putting the IDs in a HashSet first makes each check constant and the
whole loop linear.
Why this rule exists. The costs follow from how each class stores
its elements: an array for ArrayList, a chain of nodes for
LinkedList, buckets chosen by hashCode for HashMap, a balanced tree
for TreeMap.
Lambdas, functional programming, and streams
Rule
The four core functional interfaces
also known as: Supplier, Consumer, Function, Predicate
Formal. In java.util.function: Supplier<T> has T get()
(nothing in, a value out); Consumer<T> has void accept(T t) (a value
in, nothing out); Function<T, R> has R apply(T t) (a value in, a
different value out); Predicate<T> has boolean test(T t) (a value
in, true or false out). UnaryOperator<T> is a Function<T, T>, and
BiFunction<T, U, R> takes two inputs.
In plain English. Name the interface by what goes in and what comes out. Nothing in and something out is a supplier. Something in and nothing out is a consumer. Something in and something else out is a function. Something in and a yes or no out is a predicate.
Example. () -> new ArrayList<String>() is a Supplier,
s -> IO.println(s) a Consumer, String::length a
Function<String, Integer>, and s -> s.isBlank() a Predicate<String>.
filter takes a Predicate, map a Function, forEach a Consumer.
Why this rule exists. Four standard shapes mean library methods and your own code agree on what a lambda receives and returns, without a new interface for every job.
Rule
Streams are lazy and single-use
also known as: intermediate and terminal operations
Formal. A stream pipeline is a source, zero or more intermediate
operations (filter, map, sorted, distinct, limit, skip), and
one terminal operation (toList, collect, count, forEach,
reduce, findFirst). Intermediate operations only record a step and
return a new stream; nothing is read from the source until the terminal
operation runs. A stream can be consumed once: reusing it throws
IllegalStateException. A stream never modifies its source.
In plain English. Writing the steps of a pipeline is like writing a recipe: nothing is cooked until the last line says "serve". And each stream is a single pass over the data: to go again, ask the collection for a new stream.
Example. orders.stream().filter(o -> o.total() > 100) on its own
does nothing. Adding .count() runs it. Calling count() twice on the
same stream object throws IllegalStateException: stream has already been operated upon or closed.
Why this rule exists. Laziness lets the JVM pass each element
through every step before reading the next one, and lets limit and
findFirst stop early, even on an endless source.
Exceptions and error handling
Rule
Checked exceptions must be caught or declared
also known as: the catch-or-declare rule, checked and unchecked exceptions
Formal. Every exception is a Throwable. Error and
RuntimeException, with all their subclasses, are unchecked. Every other
Throwable (for example IOException and InterruptedException) is
checked: if a statement can throw a checked exception, its method must
either catch it or declare it with a throws clause. The compiler checks
this one method at a time, from method headers.
In plain English. Some failures, like a missing file, are a normal
part of the world, and Java makes you write down a plan for them: handle
it here, or warn your callers in the method's header. Programming
mistakes, like a null where an object should be, aren't checked; the
fix is to correct the code.
Example. Files.readAllLines(path) is declared throws IOException,
so calling it without a try/catch or a throws IOException on the
enclosing method fails to compile with unreported exception IOException; must be caught or declared to be thrown. Integer.parseInt("x") throws
the unchecked NumberFormatException, and needs neither.
Why this rule exists. It makes the recoverable failures a method can cause part of its signature, so callers can't forget them.
Maven, Gradle, dependencies, and project structure
Rule
A Maven phase runs every phase before it
also known as: the Maven default lifecycle
Formal. Maven's default lifecycle is an ordered list of phases; the
ones typed most are validate, compile, test-compile, test,
package, verify, install and deploy. mvn <phase> runs every
phase of that lifecycle up to and including the named one, in order,
and stops at the first failure. clean belongs to a separate lifecycle,
so mvn clean package runs clean, then the default lifecycle up to
package. Phases do nothing by themselves: plugin goals bound to them
do the work.
In plain English. The phases are a checklist that always runs from
the top. Asking for package means "do everything up to and including
making the JAR", so the tests always run first, and a failing test stops
the build before any JAR is made.
Example. mvn package compiles, runs the unit tests, then builds the
JAR in target/. mvn install does all that, then verify, then copies
the JAR into ~/.m2/repository.
Why this rule exists. A fixed order means every developer and every build server produces the same artifact the same way, from one short command.
Networking, HTTP, and REST APIs
Rule
The first digit of an HTTP status is its class
also known as: HTTP status code classes
Formal. An HTTP status code's first digit is its class: 1xx informational, 2xx success, 3xx redirection, 4xx client error, 5xx server error. A client that doesn't know a code treats it as the x00 code of its class. A 4xx won't succeed if resent unchanged; a 5xx, 408 or 429 may succeed later.
In plain English. Read the first digit first. 2 means it worked, 3 means look somewhere else, 4 means your request was wrong, and 5 means the server went wrong. The first digit tells you whose problem it is and what to do next.
Example. 200 OK, 201 Created, 204 No Content; 301 Moved Permanently, 304 Not Modified; 400 Bad Request, 401 Unauthorized
(no valid credentials), 403 Forbidden (known, but not allowed), 404 Not Found, 409 Conflict; 500 Internal Server Error, 503 Service Unavailable.
Why this rule exists. New codes can be added without breaking old clients, because every client can still act on the class.
The JVM, memory, and performance
Rule
Only unreachable objects are collected
also known as: tracing garbage collection, GC roots
Formal. The garbage collector starts from the GC roots (references in
the local variables of running frames, static fields of loaded classes,
and references the JVM holds), follows every reference it can reach, and
marks what it finds. Every object it never reaches is unreachable, and
its memory may be reused. Cycles of objects with no path from a root are
collected. An object that is still reachable is never collected, whether
or not the program will use it again.
In plain English. Java keeps everything you can still get to by following references from your running code, and throws away the rest. A "memory leak" in Java is memory you forgot you were still holding, such as a list that only ever grows.
Example. After order = null;, if nothing else refers to the old
Order, it becomes garbage. Two objects that refer only to each other
are garbage too. A static List<Order> that is added to on every request
keeps every order alive forever.
Why this rule exists. Reachability is something the JVM can check exactly, so automatic memory management never frees an object your code could still use.
Packages, modules, and application distribution
Rule
A module reads what it requires and shows what it exports
also known as: module-info.java directives
Formal. A module is declared in module-info.java. requires m;
makes it read module m; requires transitive m; also grants that read
to every module that requires this one. exports p; makes the public
types of package p usable by other modules at compile time and run
time; exports p to m; limits that to the named modules. opens p;
additionally allows deep reflection into p at run time. A package that
isn't exported stays inside the module even when its types are
public. Exports name single packages, never subpackages.
In plain English. A module is a walled garden. requires lists the
gardens you visit, exports puts a door in your wall for one package,
and opens lets frameworks look inside that package with reflection.
public alone no longer means "everyone can see it".
Example. module com.shop.app { requires com.shop.model; } can use
com.shop.model.Order only if com.shop.model has exports com.shop.model;. Its package com.shop.model.internal stays hidden.
Why this rule exists. Strong encapsulation lets a library keep its internals private, so it can change them without breaking anyone.
Java design and architecture
Rule
The SOLID principles
also known as: SRP, OCP, LSP, ISP, DIP
Formal. Single responsibility: a class should have one reason to change. Open-closed: code should be open for extension and closed for modification, so a new kind of thing is added without editing working code. Liskov substitution: a subtype must work anywhere its supertype is expected, keeping its promises. Interface segregation: no client should be forced to depend on methods it doesn't use. Dependency inversion: high-level policy and low-level details should both depend on abstractions, with the interface owned by the high-level side.
In plain English. Five checks for a design: one job per class; add, don't edit; a subclass must really be usable as its parent; small interfaces; and the important code shouldn't know which database or web library it runs on.
Example. A ReportService that formats, emails and stores reports
has three reasons to change (SRP). A switch over payment types that
grows with every new type breaks OCP; a PaymentMethod interface with
one class per type doesn't.
Why this rule exists. Each principle limits how far a change has to spread, which is what makes a code base cheap to keep changing.