Skip to content

Java Reference

Modern Java (17–21+): records, sealed classes, pattern matching, streams, optionals, generics, concurrency, and the build tool commands you run every day.

Java versions — what to know per release
VersionLTSKey features
Java 8Yes (EOL)Lambdas, streams, Optional, default methods, java.time
Java 11Yes (EOL community)var, String methods, HTTP Client API, removed JavaFX/Applets
Java 17Yes (active)Records, sealed classes, text blocks, pattern matching instanceof
Java 21Yes (current LTS)Virtual threads, sequenced collections, pattern matching switch, string templates (preview)
Java 25Yes (2025)Upcoming LTS — value types, string templates stable

Run LTS releases in production. Use Java 21 for new projects — virtual threads alone are worth the upgrade if you do any I/O-heavy work.

Modern language features — records, sealed, pattern matching
// Records (Java 16+) — immutable data carrier
// Generates constructor, getters, equals, hashCode, toString automatically
record Point(double x, double y) {
    // Compact constructor for validation
    Point {
        if (Double.isNaN(x) || Double.isNaN(y)) throw new IllegalArgumentException("NaN");
    }
    // Additional methods are fine
    double distance() { return Math.sqrt(x * x + y * y); }
}

var p = new Point(3.0, 4.0);
p.x();          // 3.0 (getter — not getX())
p.distance();   // 5.0

// Sealed classes (Java 17+) — restrict which classes can extend
sealed interface Shape permits Circle, Rectangle, Triangle {}

record Circle(double radius) implements Shape {}
record Rectangle(double width, double height) implements Shape {}
record Triangle(double base, double height) implements Shape {}

// Pattern matching — switch (Java 21+)
double area(Shape shape) {
    return switch (shape) {
        case Circle c          -> Math.PI * c.radius() * c.radius();
        case Rectangle r       -> r.width() * r.height();
        case Triangle t        -> 0.5 * t.base() * t.height();
    };   // exhaustive — no default needed with sealed hierarchy
}

// Pattern matching — instanceof (Java 16+)
if (obj instanceof String s && s.length() > 3) {
    System.out.println(s.toUpperCase());   // s is already cast
}

// Text blocks (Java 15+) — multi-line strings
String json = """
        {
            "name": "Alice",
            "role": "admin"
        }
        """;   // trailing """ sets indentation — content is dedented
Streams — map, filter, collect
import java.util.stream.*;
import java.util.*;

List users = List.of(
    new User("Alice", "admin", 95),
    new User("Bob",   "user",  72),
    new User("Carol", "user",  88)
);

// Basic pipeline: source → intermediate ops → terminal op
List adminNames = users.stream()
    .filter(u -> u.role().equals("admin"))
    .map(User::name)
    .sorted()
    .collect(Collectors.toList());

// Collectors
Collectors.toList()
Collectors.toSet()
Collectors.toUnmodifiableList()
Collectors.joining(", ", "[", "]")
Collectors.counting()
Collectors.summingInt(User::score)
Collectors.averagingDouble(User::score)

// groupingBy — build a Map>
Map> byRole = users.stream()
    .collect(Collectors.groupingBy(User::role));

// counting per group
Map countByRole = users.stream()
    .collect(Collectors.groupingBy(User::role, Collectors.counting()));

// toMap
Map byId = users.stream()
    .collect(Collectors.toMap(User::id, u -> u));

// reduce
int totalScore = users.stream()
    .mapToInt(User::score)
    .sum();   // or .average(), .max(), .min()

// flatMap — flatten nested collections
List allTags = users.stream()
    .flatMap(u -> u.tags().stream())
    .distinct()
    .collect(Collectors.toList());

// Parallel streams — use for CPU-bound work on large collections
long count = users.parallelStream()
    .filter(u -> expensiveCheck(u))
    .count();

// Stream.of, Stream.iterate, Stream.generate
Stream.of(1, 2, 3)
Stream.iterate(0, n -> n + 2).limit(10)   // 0, 2, 4, ..., 18
Stream.generate(Math::random).limit(5)
Optional — avoiding NullPointerException
import java.util.Optional;

// Create
Optional empty    = Optional.empty();
Optional present  = Optional.of("value");    // throws NPE if null
Optional nullable = Optional.ofNullable(maybeNull);

// Consume
opt.isPresent()           // true if value present
opt.isEmpty()             // true if empty (Java 11+)
opt.get()                 // get value — throws NoSuchElementException if empty
opt.orElse("default")     // value or default
opt.orElseGet(() -> compute())   // lazy default — only evaluated if empty
opt.orElseThrow()                // throw NoSuchElementException if empty
opt.orElseThrow(() -> new NotFoundException("not found"))

// Transform
opt.map(String::toUpperCase)        // Optional
opt.flatMap(s -> parse(s))          // flatMap when f returns Optional
opt.filter(s -> s.length() > 3)     // Optional or empty

// ifPresent / ifPresentOrElse
opt.ifPresent(System.out::println);
opt.ifPresentOrElse(
    value -> process(value),
    () -> handleEmpty()
);

// Stream integration (Java 9+)
opt.stream()   // returns Stream of 0 or 1 elements — useful in stream pipelines

List values = optionals.stream()
    .flatMap(Optional::stream)   // filter out empties and unwrap
    .collect(Collectors.toList());

// Common pattern: find in collection
Optional user = users.stream()
    .filter(u -> u.id() == targetId)
    .findFirst();

Don't use Optional as a field or parameter type — it's designed for return values. Don't call .get() without checking .isPresent() — use orElse/orElseThrow instead.

Generics
// Generic class
class Box {
    private T value;
    Box(T value) { this.value = value; }
    T get() { return value; }
     Box map(Function f) { return new Box<>(f.apply(value)); }
}

// Generic method
> T max(T a, T b) {
    return a.compareTo(b) >= 0 ? a : b;
}

// Bounded type parameters
 double sum(List list) {
    return list.stream().mapToDouble(Number::doubleValue).sum();
}

// Wildcards
// ? extends T — read (covariant / producer) — PECS: Producer Extends
void printAll(List animals) {
    animals.forEach(a -> System.out.println(a.name()));
}

// ? super T — write (contravariant / consumer) — PECS: Consumer Super
void addAll(List list) {
    list.add(new Dog("Rex"));
}

// PECS — Producer Extends, Consumer Super
// Copy from source (producer) to dest (consumer):
 void copy(List src, List dest) {
    for (T item : src) dest.add(item);
}

// Type erasure — generics are compile-time only, erased at runtime
// Cannot do: new T(), instanceof List, T.class
// Can do: new ArrayList<>(), obj instanceof List
Functional interfaces and lambdas
import java.util.function.*;

// Core functional interfaces
Function       T -> R           apply(T t)
Consumer          T -> void        accept(T t)
Supplier          () -> T          get()
Predicate         T -> boolean     test(T t)
BiFunction    (T,U) -> R
UnaryOperator     T -> T           (extends Function)
BinaryOperator    (T,T) -> T

// Use in methods
void process(List users, Predicate filter, Consumer action) {
    users.stream().filter(filter).forEach(action);
}
process(users, u -> u.active(), System.out::println);

// Method references
String::toUpperCase           // instance method of type
user::getName                 // instance method of specific object
Integer::parseInt             // static method
ArrayList::new                // constructor

// Compose functions
Function trim  = String::trim;
Function upper = String::toUpperCase;
Function prep  = trim.andThen(upper);
prep.apply("  hello  ");   // "HELLO"

Predicate notEmpty = s -> !s.isEmpty();
Predicate notNull  = Objects::nonNull;
Predicate valid    = notNull.and(notEmpty);

// Custom @FunctionalInterface
@FunctionalInterface
interface Transformer {
    T transform(T input);
    default Transformer andThen(Transformer after) {
        return input -> after.transform(this.transform(input));
    }
}
Collections — List, Map, Set patterns
// Immutable collections (Java 9+)
List list = List.of("a", "b", "c");        // null-hostile, fixed size
Map map = Map.of("a", 1, "b", 2);
Set set = Set.of("x", "y", "z");
Map big = Map.ofEntries(           // for >10 entries
    Map.entry("key1", 1), Map.entry("key2", 2)
);

// Mutable copies
List mutable = new ArrayList<>(List.of("a", "b"));
Map mutableMap = new HashMap<>(Map.of("a", 1));

// List operations
list.add("d");
list.addAll(otherList);
list.remove(0);                   // by index
list.remove("element");           // by value
list.set(0, "replacement");
list.subList(1, 3);              // view, not copy
Collections.sort(list);
Collections.shuffle(list);
Collections.unmodifiableList(list);

// Map operations
map.put("key", value);
map.putIfAbsent("key", defaultValue);
map.getOrDefault("key", fallback);
map.computeIfAbsent("key", k -> new ArrayList<>());   // get or create
map.merge("count", 1, Integer::sum);                  // upsert with merge fn
map.forEach((k, v) -> System.out.println(k + "=" + v));

// Sequenced collections (Java 21) — getFirst/getLast/addFirst/addLast
list.getFirst();   // replaces list.get(0)
list.getLast();    // replaces list.get(list.size()-1)

// Choose the right implementation
ArrayList        — fast random access, slow insert/delete middle
LinkedList       — fast insert/delete at ends, slow random access
ArrayDeque       — queue/stack operations (prefer over Stack/LinkedList)
HashMap          — O(1) put/get, unordered
LinkedHashMap    — insertion-order iteration
TreeMap          — sorted by key, O(log n)
HashSet          — O(1) contains, unordered
EnumMap/EnumSet  — fast, use when keys are enums
Concurrency — virtual threads and modern patterns
import java.util.concurrent.*;
import java.util.concurrent.locks.*;

// Virtual threads (Java 21) — lightweight, millions per JVM
// Platform thread: one OS thread, expensive
// Virtual thread: JVM-managed, cheap — perfect for I/O-bound work

// Create a virtual thread
Thread vt = Thread.ofVirtual().start(() -> handleRequest());

// ExecutorService with virtual threads (drop-in replacement for thread pools)
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    for (Request req : requests) {
        executor.submit(() -> process(req));
    }
}   // try-with-resources: waits for all tasks then shuts down

// Traditional thread pool (still useful for CPU-bound work)
ExecutorService pool = Executors.newFixedThreadPool(
    Runtime.getRuntime().availableProcessors()
);

// CompletableFuture — async composition
CompletableFuture future = CompletableFuture
    .supplyAsync(() -> fetchUser(id))                 // async task
    .thenApplyAsync(user -> enrich(user))             // transform result
    .thenCombine(fetchPermissions(id), (u, p) ->
        new UserWithPerms(u, p))                      // combine two futures
    .exceptionally(err -> UserWithPerms.empty());     // handle errors

UserWithPerms result = future.get(5, TimeUnit.SECONDS);

// Structured concurrency (Java 21 preview / 22+)
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    var user  = scope.fork(() -> fetchUser(id));
    var prefs = scope.fork(() -> fetchPrefs(id));
    scope.join().throwIfFailed();
    return new Dashboard(user.get(), prefs.get());
}

// Synchronized (avoid when possible — use Lock or atomic types)
synchronized (this) { count++; }

// ReentrantLock — more control than synchronized
Lock lock = new ReentrantLock();
lock.lock();
try { /* critical section */ }
finally { lock.unlock(); }   // always unlock in finally

// Atomic variables — lock-free counters
AtomicInteger counter = new AtomicInteger(0);
counter.incrementAndGet();
counter.compareAndSet(expected, newValue);
Maven and Gradle — essential commands
// ——————— Maven ———————
mvn compile                          // compile main sources
mvn test                             // compile + run tests
mvn package                          // compile + test + package JAR/WAR
mvn package -DskipTests              // skip tests
mvn install                          // package + install to local ~/.m2 repo
mvn clean                            // delete target/
mvn clean package                    // clean build (most common)
mvn dependency:tree                  // show full dependency tree
mvn dependency:tree -Dincludes=log4j // filter tree
mvn versions:display-dependency-updates  // check for newer dependency versions
mvn spring-boot:run                  // run Spring Boot app
mvn help:effective-pom               // show resolved POM (after parent/imports)

// Maven wrapper (use instead of system mvn)
./mvnw clean package

// ——————— Gradle ———————
./gradlew build                      // compile + test + assemble
./gradlew build -x test              // skip tests
./gradlew test                       // run tests
./gradlew clean                      // delete build/
./gradlew clean build
./gradlew dependencies               // show dependency tree
./gradlew dependencies --configuration runtimeClasspath
./gradlew bootRun                    // run Spring Boot app
./gradlew tasks                      // list available tasks
./gradlew tasks --all                // include non-default tasks
./gradlew :module:task               // run task in specific submodule

// Common Gradle flags
--info      // more logging
--debug     // full debug
--scan      // build scan (upload to scans.gradle.com)
-p path     // specify project dir

// ——————— Java CLI ———————
java -version
javac -version
java -jar app.jar
java -cp "lib/*:." com.example.Main
java -Xmx512m -Xms256m -jar app.jar   // heap settings
java -XX:+UseG1GC -jar app.jar         // GC tuning
jps                                    // list running JVM processes
jstack                            // thread dump
jmap -dump:format=b,file=heap.hprof   // heap dump
Exception handling
// Checked vs unchecked exceptions
// Checked: extends Exception — must be declared or caught
// Unchecked: extends RuntimeException — no requirement to handle

// try-with-resources (Java 7+) — auto-close Autocloseable
try (
    var conn = dataSource.getConnection();
    var stmt = conn.prepareStatement(sql)
) {
    // conn and stmt closed automatically in reverse order
    stmt.setInt(1, userId);
    return stmt.executeQuery();
} catch (SQLException e) {
    throw new DataAccessException("Query failed", e);
}

// Multi-catch (Java 7+)
catch (IOException | ParseException e) {
    logger.error("Parse or IO failure", e);
}

// Custom exceptions
class ServiceException extends RuntimeException {
    private final String code;
    ServiceException(String code, String message) {
        super(message);
        this.code = code;
    }
    ServiceException(String code, String message, Throwable cause) {
        super(message, cause);
        this.code = code;
    }
    String getCode() { return code; }
}

// Re-throw with context (preserve original cause)
try {
    repository.save(entity);
} catch (DataAccessException e) {
    throw new ServiceException("DB_SAVE_FAILED",
        "Failed to save user " + entity.id(), e);
}

// Suppressed exceptions — added when both try and close fail
try (var res = openResource()) {
    throw new RuntimeException("primary");
} catch (Exception e) {
    // e.getSuppressed() contains the close() exception
    for (Throwable suppressed : e.getSuppressed()) {
        logger.warn("Suppressed: {}", suppressed.getMessage());
    }
    throw e;
}

Audit your Java deps: Use the Maven Dependency Health Checker to find outdated Spring Boot, Hibernate, log4j, and other pom.xml dependencies against Maven Central.

🔍 Free tool: Maven pom.xml Batch Health Checker — paste your entire pom.xml and get health grades for all dependencies at once.

🔍 Free tool: Gradle Dependency Health Checker — paste your build.gradle and grade all Java dependencies at once via Maven Central.