Java Essentials
Strings, collections, streams, records, virtual threads, time API — modern Java with examples and version notes.
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
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++) { } // intPrintf-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+Multi-line string literals — preserves formatting, no escaping.
String json = """
{
"name": "Ada",
"age": 36
}
""";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") // trueConcise 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)
);Defensive copies to expose immutable views.
var snapshot = List.copyOf(mutableList); // immutable copy var view = Collections.unmodifiableList(list); // immutable view (throws on modify)
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);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]
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)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()));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);
Flatten nested streams.
List<String> tags = posts.stream()
.flatMap(p -> p.tags().stream())
.distinct()
.toList();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(); }Compose multiple Optional-returning calls without nesting.
String city = repo.findById(id)
.flatMap(User::address)
.map(Address::city)
.orElse("unknown");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(...); // structuralValidate 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();
}
}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 {}Test type and bind variable in one expression.
if (obj instanceof String s) {
System.out.println(s.toUpperCase());
}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();
};
}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;
};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());
}Composable async pipelines without callback hell.
CompletableFuture.supplyAsync(() -> fetchUser(id))
.thenApply(User::email)
.thenAccept(System.out::println)
.exceptionally(ex -> { ex.printStackTrace(); return null; });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 fullModern 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
}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);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());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"));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);
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);