tool / 68

Java Essentials

Strings, collections, streams, records, virtual threads, time API — modern Java with examples and version notes.

All local
30/30
Versions2
LTS Versions

Java's LTS releases ship every 2 years. Production code typically targets the latest LTS.

Java 8   (2014) — Streams, Lambdas, Optional, Date/Time API
Java 11  (2018) — var, HttpClient, String methods
Java 17  (2021) — Records, Sealed classes, Pattern matching
Java 21  (2023) — Virtual threads, Sequenced collections, Pattern matching for switch
Java 25  (2025) — next planned LTS
var (local-variable type inference)Java 10

Type inferred from initializer. Local scope only — never for fields or method signatures.

var users = new ArrayList<User>();   // ArrayList<User>
var name = "Ada";                     // String
for (var i = 0; i < 10; i++) { }      // int
Strings3
String.format / formatted

Printf-style string formatting. As of 15, .formatted() reads more naturally.

String s = String.format("User: %s, age: %d", name, age);
String s2 = "User: %s, age: %d".formatted(name, age);  // Java 15+
Text BlocksJava 15

Multi-line string literals — preserves formatting, no escaping.

String json = """
    {
      "name": "Ada",
      "age": 36
    }
    """;
Common String Methods

The methods you'll reach for most.

"  hello  ".strip()           // "hello" (Unicode-aware, prefer over trim)
"hello".isBlank()             // false (Java 11)
"hello".repeat(3)             // "hellohellohello"
"a,b,c".split(",")            // ["a","b","c"]
String.join(",", list)        // "a,b,c"
"hello".chars()               // IntStream of code points
"abc".equalsIgnoreCase("ABC") // true
Collections4
Immutable Factory MethodsJava 9

Concise immutable collection literals.

List<String> names = List.of("Ada", "Grace");
Set<Integer> nums = Set.of(1, 2, 3);
Map<String, Integer> ages = Map.of("Ada", 36, "Grace", 85);
Map<String, Integer> bigger = Map.ofEntries(
  Map.entry("Ada", 36),
  Map.entry("Grace", 85)
);
List.copyOf / unmodifiable

Defensive copies to expose immutable views.

var snapshot = List.copyOf(mutableList);   // immutable copy
var view = Collections.unmodifiableList(list);  // immutable view (throws on modify)
forEach vs. for-each

Both work. forEach with method reference is shortest for read-only ops.

// classic for-each
for (User u : users) { System.out.println(u.name()); }

// forEach + lambda
users.forEach(u -> System.out.println(u.name()));

// method reference
users.forEach(System.out::println);
Sequenced CollectionsJava 21

Unified API for collections with a defined encounter order.

var list = new ArrayList<>(List.of(1, 2, 3));
list.getFirst();     // 1
list.getLast();      // 3
list.reversed();     // [3, 2, 1]
list.addFirst(0);    // [0, 1, 2, 3]
Streams4
Stream PipelineJava 8

Source → intermediate ops → terminal op. Lazy until terminal.

List<String> result = users.stream()
    .filter(u -> u.age() >= 18)
    .map(User::name)
    .sorted()
    .toList();   // Java 16+ (immutable)
Collectors

Most common collectors.

// to a Map
Map<String, User> byEmail = users.stream()
    .collect(Collectors.toMap(User::email, u -> u));

// group by
Map<String, List<User>> byCity = users.stream()
    .collect(Collectors.groupingBy(User::city));

// joining strings
String csv = names.stream().collect(Collectors.joining(","));

// counting
Map<String, Long> counts = words.stream()
    .collect(Collectors.groupingBy(w -> w, Collectors.counting()));
reduce / sum / max

Folding a stream into a single value.

int total = nums.stream().mapToInt(Integer::intValue).sum();
int max = nums.stream().max(Integer::compare).orElse(0);
int product = nums.stream().reduce(1, (a, b) -> a * b);
flatMap

Flatten nested streams.

List<String> tags = posts.stream()
    .flatMap(p -> p.tags().stream())
    .distinct()
    .toList();
Optional2
OptionalJava 8

Container that may or may not hold a value. Avoid the antipatterns.

Optional<User> u = repo.findById(id);

// good — pipeline
String email = u.map(User::email).orElse("unknown");

// good — execute if present
u.ifPresent(this::sendEmail);

// avoid — defeats the purpose
if (u.isPresent()) { return u.get(); }
Optional pipelines

Compose multiple Optional-returning calls without nesting.

String city = repo.findById(id)
    .flatMap(User::address)
    .map(Address::city)
    .orElse("unknown");
Records & Sealed3
RecordsJava 16

Immutable data carriers — auto-generates constructor, accessors, equals, hashCode, toString.

public record User(String name, int age) {}

var u = new User("Ada", 36);
u.name();      // "Ada"
u.equals(...); // structural
Compact Constructor

Validate or normalize record fields without writing the canonical constructor.

public record User(String email, int age) {
  public User {
    Objects.requireNonNull(email);
    if (age < 0) throw new IllegalArgumentException("age");
    email = email.toLowerCase();
  }
}
Sealed ClassesJava 17

Restrict which classes can extend a hierarchy. Enables exhaustive pattern matching.

public sealed interface Shape permits Circle, Square {}
public record Circle(double r) implements Shape {}
public record Square(double s) implements Shape {}
Pattern Matching3
Pattern matching for instanceofJava 16

Test type and bind variable in one expression.

if (obj instanceof String s) {
  System.out.println(s.toUpperCase());
}
Pattern matching for switchJava 21

Type patterns and exhaustiveness checking in switch.

String describe(Shape shape) {
  return switch (shape) {
    case Circle c -> "circle r=" + c.r();
    case Square s -> "square s=" + s.s();
  };
}
Record PatternsJava 21

Destructure records inline.

if (shape instanceof Circle(double r)) {
  System.out.println(r);
}

double area(Shape s) = switch (s) {
  case Circle(double r) -> Math.PI * r * r;
  case Square(double l) -> l * l;
};
Concurrency3
Virtual ThreadsJava 21

Lightweight threads — millions per JVM, ideal for blocking I/O.

Thread.startVirtualThread(() -> {
  // blocking I/O — virtual threads make this cheap
  HttpResponse<String> r = http.send(req, BodyHandlers.ofString());
});

// or via executor
try (var exec = Executors.newVirtualThreadPerTaskExecutor()) {
  exec.submit(() -> doWork());
}
CompletableFuture

Composable async pipelines without callback hell.

CompletableFuture.supplyAsync(() -> fetchUser(id))
    .thenApply(User::email)
    .thenAccept(System.out::println)
    .exceptionally(ex -> { ex.printStackTrace(); return null; });
Concurrent Collections

Use these instead of synchronizing manually.

var map = new ConcurrentHashMap<String, Integer>();
map.computeIfAbsent("key", k -> expensiveLookup(k));

var queue = new LinkedBlockingQueue<Task>();
queue.put(task);   // blocks if full
Files & I/O3
Read a File

Modern Files API — concise and correct.

Path path = Path.of("data.txt");
String all = Files.readString(path);
List<String> lines = Files.readAllLines(path);

try (Stream<String> s = Files.lines(path)) {
  s.forEach(System.out::println);  // streaming
}
Write a File

One-liner writes for text and bytes.

Files.writeString(Path.of("out.txt"), "hello");
Files.write(Path.of("out.bin"), bytes);
Files.writeString(Path.of("log.txt"), line + "\n", StandardOpenOption.APPEND);
HttpClientJava 11

Built-in modern HTTP client. No more URLConnection.

var client = HttpClient.newHttpClient();
var req = HttpRequest.newBuilder(URI.create("https://api.example.com"))
    .header("Accept", "application/json")
    .GET()
    .build();
var res = client.send(req, BodyHandlers.ofString());
System.out.println(res.body());
Time API3
LocalDate / LocalDateTime / Instant

Modern, immutable, timezone-aware date and time. Never use Date again.

LocalDate today = LocalDate.now();
LocalDateTime now = LocalDateTime.now();
Instant epoch = Instant.now();             // wall-clock UTC
ZonedDateTime z = ZonedDateTime.now(ZoneId.of("Europe/London"));
Date Arithmetic

Add, subtract, compare with explicit units.

LocalDate next = today.plusDays(7).minusMonths(1);
Duration d = Duration.between(start, end);
Period p = Period.between(birth, today);
boolean before = today.isBefore(deadline);
Format & Parse

Use DateTimeFormatter, never SimpleDateFormat.

var fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
String s = now.format(fmt);
LocalDateTime back = LocalDateTime.parse(s, fmt);

// ISO formats are predefined
String iso = now.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);