Skip to content

Java Reference

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
Version LTS Key features
Java 8 Yes (EOL) Lambdas, streams, Optional, default methods, java.time
Java 11 Yes (EOL community) var, String methods, HTTP Client API, removed JavaFX/Applets
Java 17 Yes (active) Records, sealed classes, text blocks, pattern matching instanceof
Java 21 Yes (current LTS) Virtual threads, sequenced collections, pattern matching switch, string templates (preview)
Java 25 Yes (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.

Founded

2023 in London, UK

Contact

hello@releaserun.com