Java Concepts

Core Java concepts explained in depth, one at a time.

Sort

Collections

1

The WeakHashMap Class

How WeakHashMap wraps each key in a WeakReference so entries are removed automatically once nothing outside the map holds the key strongly, and why a value referencing its own key or an interned key defeats that.

68
🔬 Lab

The TreeMap Class

How TreeMap keeps keys in ascending order via a red-black tree, why key equality there is decided by compareTo() rather than equals()/hashCode(), and the O(log n) cost that pays for.

69
🔬 Lab

The HashMap Class

How HashMap's hash-table storage gives average O(1) get/put with no iteration-order guarantee, and how put() silently replaces the value of an existing key.

70
🔬 Lab

The LinkedHashMap Class

How LinkedHashMap adds insertion-order (or access-order) iteration on top of HashMap's lookup performance, and how overriding removeEldestEntry() turns it into a bounded LRU cache.

85
🔬 Lab

Comparators and Collection Algorithms

How Comparator decouples ordering from a type entirely (unlike the one fixed Comparable.compareTo()), how comparing()/thenComparing() build multi-key comparators without a hand-written compare(), and what the static algorithms on Collections (sort, shuffle, unmodifiable/synchronized/checked views) actually guarantee.

88
🔬 Lab

The Collection Interface

The generic root interface of the Collections Framework — what add, remove, and bulk operations guarantee, and why several of them can throw at runtime instead of failing to compile.

89
🔬 Lab

The Queue Interface

Why Queue defines two parallel families of methods — one that throws on failure, one that reports it — and what that means for empty and fixed-length queues.

90
🔬 Lab

The PriorityQueue Class

How PriorityQueue orders elements by comparator instead of insertion order via a heap, and why iterating it does not visit elements in priority order the way repeated poll() does.

91
🔬 Lab

The Deque Interface

How Deque extends Queue into a double-ended queue that can also act as a stack, and how its addFirst/addLast, peek, and remove families each split into a throwing and a reporting version.

92
🔬 Lab

The ArrayDeque Class

How ArrayDeque's array-backed, capacity-unrestricted storage makes it the JDK-recommended stack/queue over legacy Stack or LinkedList when null elements aren't needed.

93
🔬 Lab

The Set Interface

How Set forbids duplicates, how SortedSet and NavigableSet layer ordering and closest-match lookups on top of it, and why their range views are backed by the original set.

94
🔬 Lab

The TreeSet Class

How TreeSet keeps elements in ascending order via a tree structure, why uniqueness there is decided by compareTo() rather than equals(), and the O(log n) cost that pays for.

95
🔬 Lab

The HashSet Class

How HashSet's hash-table storage gives average O(1) add/contains/remove with no iteration-order guarantee, and what breaks lookups when an element's hash-relevant fields mutate.

96
🔬 Lab

The LinkedHashSet Class

How LinkedHashSet adds insertion-order iteration on top of HashSet's lookup performance by threading a linked list through the hash table's entries.

97
🔬 Lab

The List Interface

How List builds a positional, duplicate-allowing sequence on top of Collection — indexed access, sublists, sort, and the unmodifiable lists produced by List.of().

98
🔬 Lab

Sequenced Collections: SequencedCollection, SequencedSet, SequencedMap

JEP 431's SequencedCollection/SequencedSet/SequencedMap give every collection with a real encounter order — List, Deque, LinkedHashSet, TreeSet, LinkedHashMap, TreeMap — one uniform getFirst/getLast/addFirst/addLast/reversed() API, deliberately excluding HashSet, HashMap, and PriorityQueue since none has a stable encounter order to expose.

99
🔬 Lab

The LinkedList Class

How LinkedList's node-based storage inverts ArrayList's trade-off — O(n) indexed access in exchange for cheap insert/remove — while implementing List, Deque, and Queue at once.

100
🔬 Lab

The ArrayList Class

How ArrayList's resizable-array storage gives O(1) indexed access at the cost of O(n) inserts/removes away from the end, and how to manage its capacity directly.

102
🔬 Lab

Iterator vs Iterable

What each interface is for, why Iterable forces you to implement Iterator, and why the JDK collections are built this way.

Concurrency

2

VarHandle: Lock-Free Access to Ordinary Fields

VarHandle (JEP 193) lets a program apply atomic and volatile-strength access to a plain field or array element, at a chosen memory-ordering strength, without wrapping it in a class like AtomicInteger.

6

ThreadLocal: Per-Thread State, Leaks, and InheritableThreadLocal

ThreadLocal gives each thread its own isolated copy of a variable via a per-thread map, which is powerful but leaks on pooled threads unless paired with remove, and InheritableThreadLocal copies that value into child threads only at creation time.

12

The Java Memory Model: Happens-Before and Reordering

The formal happens-before rules from JLS Chapter 17 that determine when a write in one thread is guaranteed visible to a read in another.

33

Thread Stack Memory: Locals, Frames, and Why It's Not Shared

Why each thread's stack — its local variables, parameters, and call frames — is private to that thread and never a source of race conditions, while references stored on the stack can still point to shared, racy state on the heap.

39
🔬 Lab

CompletableFuture: Composing Asynchronous Work

How thenApply, thenCompose, thenCombine, allOf, and the exception handlers compose async work without blocking — and the nested-future, common-pool, and swallowed-exception traps that come with them.

41
🔬 Lab

Scoped Values: A Modern Replacement for ThreadLocal

ScopedValue (finalized in Java 25 by JEP 506) replaces ThreadLocal's mutable, leak-prone per-thread slot with an immutable binding whose lifetime is exactly the dynamic extent of a run()/call() lambda — no set(), no remove() to forget, and automatic inheritance by subtasks forked in a StructuredTaskScope.

42
🔬 Lab

Structured Concurrency: StructuredTaskScope

StructuredTaskScope (still preview in Java 25, JEP 505) makes a fan-out of concurrent subtasks a try-with-resources block that owns their lifetime — no task can outlive its scope, siblings are cancelled automatically on failure, and the whole operation shows up nested in a thread dump instead of as a flat pool of Futures.

45
🔬 Lab

Configuring and Sizing Thread Pools

Covers the JCIP formula for computing an optimal thread pool size and the ThreadPoolExecutor API for work queues, saturation policies, thread factories, and extension hooks.

46

Reducing Lock Contention: Scope, Granularity, and Striping

Concrete techniques for shrinking a contended lock's impact — narrowing its scope, splitting or striping it, avoiding hot fields, and when to swap it for a read-write lock or atomic instead.

47

Shutting Down an ExecutorService, and How the JVM Decides to Exit

Compares graceful shutdown() vs aggressive shutdownNow(), the awaitTermination()/close() idiom, poison pills for queue-based consumers, uncaught exception handlers, and JVM shutdown hooks and daemon threads.

48
🔬 Lab

Task Cancellation: Interruption Done Right

How Thread.interrupt() actually works as a cooperative status flag, why swallowing InterruptedException is the most common concurrency bug, and how to cancel tasks correctly via interruption policy and Future.cancel().

49
🔬 Lab

BlockingQueue and the Producer-Consumer Pattern

How BlockingQueue's blocking put/take turn the producer-consumer pattern into a few lines of code, and why bounded queues provide real backpressure instead of just delaying an OutOfMemoryError.

50
🔬 Lab

Concurrent Collections: Why Thread-Safe Isn't Enough, and What ConcurrentHashMap Does Differently

Why a compound action built from thread-safe calls on a synchronized collection can still race, and how ConcurrentHashMap and CopyOnWriteArrayList solve it with weakly consistent iteration and atomic compound-action methods instead.

51
🔬 Lab

Deadlock: Lock Ordering, Open Calls, and How to Avoid It

How lock-ordering deadlocks happen between threads, and how consistent lock ordering, open calls, and timed lock attempts prevent and diagnose them.

52

AbstractQueuedSynchronizer: What's Underneath Lock and Semaphore

The shared internal machinery — a state field, a thread queue, and a handful of overridable hooks — that ReentrantLock, Semaphore, and CountDownLatch are all built on top of.

53

Visibility and Safe Publication Across Threads

Explains why unsynchronized reads across threads can see stale or torn values, what volatile actually guarantees, and how to publish an object to another thread safely.

65
🔬 Lab

The Fork/Join Framework

A divide-and-conquer engine for true multi-core parallelism, where a ForkJoinPool runs recursively-split RecursiveAction/RecursiveTask work via work-stealing.

66
🔬 Lab

The Concurrency Utilities: Executors, Synchronizers, and Locks

The java.util.concurrent toolkit — executors and thread pools, Callable/Future, coordination primitives like CountDownLatch and Semaphore, explicit Locks, and atomic variables — as the higher-level alternative to hand-rolled Thread and synchronized code.

79
🔬 Lab

Thread Model: Legacy Control vs. Virtual Threads

Why Thread.suspend/resume/stop were deprecated for corrupting shared state, the cooperative-shutdown pattern that replaced them, and why virtual threads solve a different problem (thread cost) rather than that same correctness problem.

Language Features

4

Unicode Code Points and Surrogate Pairs in String

String stores text as 16-bit UTF-16 code units, so characters outside the Basic Multilingual Plane like most emoji need a two-char surrogate pair to represent a single Unicode code point.

8

Stream Collectors and Parallel Streams

Deep dive into multi-level Collectors (groupingBy, partitioningBy, toMap, teeing, custom Collector.of) and the mechanics and pitfalls of parallel streams.

15

The String Pool and Interning

String literals are automatically interned into a shared pool, so == is true between two literals with the same content but false against a new String(...) with identical content — a direct, predictable consequence of where each string came from, not a language quirk.

22

Default, Static, and Private Interface Methods

How default, static, and private interface methods let an interface carry behavior so a published API can grow without breaking implementers, and the exact rules Java uses when two inherited defaults collide.

23

Flexible Constructor Bodies

Since JDK 25 a constructor may run statements before super(...) or this(...), splitting its body into a prologue that validates arguments and initializes its own fields and an epilogue that can finally use the object under construction.

24

Nested and Inner Classes

The four kinds of class-inside-a-class — static nested, inner, local, and anonymous — differ by how much enclosing context each captures, and knowing which you wrote tells you what it can reach, what it keeps alive in memory, and whether a lambda or record would say it better.

35
🔬 Lab

Switch Expressions: yield, Arrow Labels, and Exhaustiveness

The switch statement and the switch expression are two constructs sharing one keyword: colon form falls through and produces nothing, while an arrow-form expression runs exactly one arm, produces a value via yield when the arm is a block, and must be provably exhaustive or the compiler rejects it.

36
🔬 Lab

Text Blocks: Multi-Line String Literals

How the triple-quote text block writes multi-line SQL, JSON, and HTML without \n escapes or + concatenation — and the precise incidental-whitespace algorithm behind it, where the closing delimiter's own column decides how much indentation is stripped, trailing spaces vanish unless fenced with \s, and line terminators are normalized to \n. Still exactly a String at runtime, with no interpolation and no way to tell it apart from a regular literal.

38

var: Local Variable Type Inference

var lets the compiler infer a local variable's static type from its initializer — it is not dynamic typing and not Object, and combining it with the diamond operator quietly yields ArrayList<Object>.

56

Method Overloading: Compile-Time Resolution and How It Surprises

Overload resolution happens at compile time based on the declared type of arguments, not runtime type — unlike overriding — and that gap shapes how method signatures should be designed.

58

Varargs: Performance Cost and Generic Heap Pollution

Varargs implicitly allocate an array on every call and, when combined with generics, can let a ClassCastException slip past the compiler — here's why, and what @SafeVarargs actually promises.

59

Enum Patterns: Strategy Bodies, EnumSet, and EnumMap

Use constant-specific method bodies for per-constant behavior, and EnumSet/EnumMap instead of bit fields and ordinal-indexed arrays.

80
🔬 Lab

Stream API Fundamentals

Why a stream is a lazy, single-use conduit rather than a data structure, how the pipeline shape (source → intermediate ops → terminal op) makes filter-then-findFirst short-circuit instead of scanning everything, how Collectors and the primitive stream specializations work under the hood, and when parallel streams actually help.

81
🔬 Lab

Lambda Expressions

How a lambda expression implements a functional interface's single abstract method, the expression- vs. block-body syntax forms, the java.util.function catalogue, the effectively-final rule behind variable capture, and the four kinds of method references.

82

Project Valhalla: Value Classes

Why objects cost more than primitives (identity, header, heap allocation, GC pressure), how Value Classes let the JVM drop that overhead for types that are just their data, and why List<int> still doesn't exist because of generics' type erasure.

86
🔬 Lab

Records and Sealed Types

How a record turns a data-holder class into one line (canonical/compact constructors, free equals/hashCode/toString, shallow immutability), and how sealed classes/interfaces replace "anyone can extend this" with an explicit, exhaustive permits list — the pairing that makes closed hierarchies safe to model.

87
🔬 Lab

Generics: Bounded Types, Wildcards, and Erasure

How parameterized types move casts and type-mismatch checks from runtime to compile time, what bounded types and PECS wildcards (extends/super) actually constrain, and why erasure means there's really only one class file per generic type at runtime.

101
🔬 Lab

Pattern Matching

How instanceof, switch, and record patterns let you test an object's structure and extract its data in one step, instead of casting after a type check.

API Design & Craft

5

Object.clone() and the Cloneable Anti-Pattern

Object.clone() and the Cloneable marker interface perform a shallow, constructor-bypassing copy with structural pitfalls around inheritance and final fields, which copy constructors and static factories avoid.

18

Data-Oriented Programming

Data-Oriented Programming deliberately separates immutable, transparent data (records inside a sealed hierarchy) from the logic that acts on it, so an exhaustive switch with record patterns lets the compiler guarantee every variant is handled.

40
🔬 Lab

Optional: Correct Usage and the API's Own Warnings

Optional exists to make 'this might not have a value' part of a method's return type instead of a null a caller has to remember to check — and why the JDK itself warns against using it as a field, a parameter, or inside a collection, plus the isPresent()-then-get() anti-pattern and the orElse-vs-orElseGet eagerness trap that trip up most first uses.

54
🔬 Lab

Serialization: Why It's Dangerous and How to Contain It

Implementing Serializable bypasses your constructor's invariant checks and exposes internals as permanent API — see why, and how readObject discipline, the serialization proxy pattern, and ObjectInputFilter contain the risk.

55

Exception Design: When to Throw, and Checked vs. Unchecked as a Choice

How to decide when a failure should be an exception at all, and whether a new exception type should be checked or unchecked.

57

Parameter Validation and Failing Fast

Validate a method's or constructor's parameters at the top of the body and throw immediately with a clear exception, instead of letting a bad argument propagate into confusing failures or corrupted object state.

60

Composition Over Inheritance, and Interfaces vs. Abstract Classes

Extending a class you don't control is fragile because it depends on undocumented internal call patterns, and composition with forwarding avoids that risk — the same single-inheritance limitation also explains why interfaces, not abstract classes, are usually the right way to define a public type.

61

Immutable Classes and Defensive Copying

Making a class truly immutable takes more than final fields — it means copying mutable arguments on the way in and mutable fields on the way out, so no other reference into an object's state ever escapes.

62
🔬 Lab

The equals(), hashCode(), and toString() Contracts

The documented contracts behind Object's three most-overridden methods, why breaking symmetry or skipping hashCode() fails silently instead of at compile time, and how records satisfy all three for free.

63

Singletons and Noninstantiable Utility Classes

Compare the three ways to write a Java singleton — public field, static factory, single-element enum — and the private-constructor idiom for noninstantiable utility classes.

64
🔬 Lab

Static Factory Methods and the Builder Pattern

Named static methods can replace or supplement constructors for clearer, cacheable, subtype-flexible object creation, and the Builder pattern tames classes with many optional fields.

67

Exception Handling Fundamentals

Java's structured mechanism for run-time errors — try/catch/finally, throw/throws, checked vs unchecked exceptions, custom exception subclasses, and chained exceptions.

Core APIs & Tooling

3

Arrays: Sorting, Searching, and Deep Comparison

Covers Arrays.sort's primitive-vs-Comparator split, the silent undefined behavior of binarySearch on unsorted data, and why equals/toString need their deep variants for nested arrays.

7

Weak, Soft, and Phantom References: GC-Aware Reference Types

Explains the strong/soft/weak/phantom reference hierarchy, ReferenceQueue polling, and how Cleaner replaces Object.finalize() for deterministic native-resource cleanup.

10

Cryptography in Java: MessageDigest, Cipher, and KeyStore

Covers the JCA/JCE provider architecture and the correct use of MessageDigest, SecureRandom, Cipher, Signature, and KeyStore, including why plain hashing and Random are unsafe substitutes for password hashing and secure token generation.

11

Classloading: The Delegation Model and Custom Class Loaders

How the JVM loads, links, and initializes classes through a parent-first delegation hierarchy, and what happens when you write a custom class loader or load the same class twice.

13

Formatter and Format Specifiers

String.format/printf/Formatter share one conversion engine — width vs. precision mean different things per conversion, the comma/+/space/( flags cover most real formatting needs, and n$/< argument indexing lets one argument be reused across specifiers without repeating it.

14

The Scanner Class

Scanner tokenizes input from System.in, a String, or a file via hasNextX()/nextX() pairs, with one classic trap: nextInt() leaves the trailing newline unconsumed, so a following nextLine() reads an empty string instead of the next real line.

16

Runtime Environment Introspection: System Properties, Environment Variables, and Runtime

System.getenv() (OS environment, inherited by child processes), System.getProperty() (JVM-scoped, set via -D), and Runtime (live facts like availableProcessors() and Runtime.version()) are three distinct channels for asking a JVM what it's running on and how it was configured.

19

Logging in Java: SLF4J, Logback, and java.util.logging

The JDK's built-in java.util.logging is rarely called directly; real code logs through the SLF4J facade against a swappable backend (usually Logback), using {} placeholders and MDC for structured, correlatable output.

20

jlink and jdeps: Custom Runtime Images

jlink links JDK and application modules into a self-contained runtime image containing only what's reachable, with jdeps discovering the real module list first — shipping a stripped-down Java runtime instead of a full JDK.

21

GraalVM Native Image Compilation

GraalVM bundles a JIT (the Graal compiler) plus native-image, an ahead-of-time compiler that turns a Java app into a self-contained native executable — trading a closed-world assumption over reflection, proxies, and resources for near-instant startup and a much smaller footprint.

25

Method Handles and Runtime Class Generation

Beyond inspect-and-invoke reflection: method handles with lookup-time access checks, setAccessible against the module system's InaccessibleObjectException and --add-opens, and three ways to manufacture a class at runtime — a custom ClassLoader, the in-memory JavaCompiler, and the standard java.lang.classfile Class-File API.

26

Vector API: Explicit SIMD Arithmetic

Using the incubating jdk.incubator.vector API to express array arithmetic as explicit SIMD lane operations instead of relying on the JIT to auto-vectorise a scalar loop.

27
🔬 Lab

Exact Arithmetic with BigDecimal and BigInteger

How java.math.BigDecimal and BigInteger give arbitrary precision and explicit rounding where double and long silently lose money or overflow.

28

Resource Bundles and Locale

How Locale identifies a language and region, how ResourceBundle expands one request into a chain of candidate .properties files, and why the JVM default locale is tried before the language-neutral base bundle.

31

Foreign Function and Memory API

The java.lang.foreign API, standard since JDK 22, lets pure Java call native library functions and manage off-heap memory with arena-scoped lifetimes, replacing JNI's hand-written C glue code.

32
🔬 Lab

JSON Processing in Java

The JDK still has no JSON support, so JSON work means picking a library and one of three processing models — data binding, the tree model, or streaming — all of which Jackson provides as the de facto standard.

34

The Java Platform Module System (JPMS)

What module-info.java actually declares — requires, requires transitive, exports, opens, uses/provides — and why a non-exported package is invisible to other modules even when every type in it is public. Covers the unnamed module (where most application code still runs, with none of the enforcement), automatic modules as a migration bridge, jlink for minimal runtime images, and the honest trade-offs: forbidden split packages, the tension between opens and strong encapsulation, and how slow real-world adoption has been.

37
🔬 Lab

java.time: Dates, Times, and Durations

java.time replaced the mutable, thread-unsafe, 0-indexed-month java.util.Date and Calendar with a family of small immutable types that each model exactly one idea — why an Instant has no getYear(), why Duration and Period genuinely cannot be the same type, what "plus one day" means across a DST transition, and why a static final DateTimeFormatter is safe where a shared SimpleDateFormat was a production bug.

43

Java Naming Conventions

Java's typographical rules (package, class, method, field, and type-parameter casing) and grammatical rules (noun phrases for classes, verb phrases for action methods, is/has for booleans) that keep code readable and predictable.

44

Writing Doc Comments for Exposed API Elements

How to write effective Javadoc doc comments — contracts, tags, summary descriptions, and inheritance — for every exported class, method, and field.

72
🔬 Lab

Classpath Scanning via Reflection

How a component scan actually works underneath: a recursive directory walk over compiled .class files, Class.forName + isAnnotationPresent to find marker-annotated classes, and why real frameworks read bytecode metadata via ASM instead of loading every candidate class.

74
🔬 Lab

JShell: The Java REPL

How JShell lets you evaluate expressions, declare variables/methods/classes, and get immediate feedback without a full class/main-method wrapper — the result-variable convention, forward references, essential /commands, and how it differs from single-file source-code launching.

75
🔬 Lab

java.lang Essentials: Comparable, AutoCloseable, StackWalker, ProcessBuilder

The Comparable<T> natural-ordering contract and its equals-consistency recommendation, AutoCloseable vs. Closeable idempotency rules, StackWalker as the lazy, modern replacement for stack-trace inspection, and ProcessBuilder/Process/ProcessHandle for launching and managing OS processes.

76
🔬 Lab

Annotations: Retention, Meta-Annotations, and Reflection

How @Retention/RetentionPolicy control whether an annotation survives past source, the @Target/@Inherited/@Repeatable meta-annotations, reading annotations at runtime via AnnotatedElement, and the built-in @Deprecated/@SafeVarargs/@FunctionalInterface/@Documented annotations.

83
🔬 Lab

Regular Expressions with Pattern and Matcher

Why Pattern.compile() and Matcher exist as two separate objects (compile once, match many), the difference between find()/matches()/lookingAt(), and how capturing groups — numbered and named — pull substrings out of a match instead of just testing yes/no.

I/O & Networking

9

NIO Channels and Buffers: The Non-Blocking I/O Model

Explains the buffer-oriented Channel model in java.nio.channels, covering ByteBuffer state, direct vs heap buffers, FileChannel zero-copy and memory-mapped I/O, non-blocking sockets, and scatter/gather reads and writes.

17

URI, URL, and URN

URI is pure syntax, URL additionally knows how to locate and open a resource, and URN names a resource without saying where to find it — and since JDK 20 the JDK steers construction through URI.toURL() rather than URL's now-deprecated constructors.

29

Multi-Client Servers and Unix Domain Sockets

Splitting a ServerSocket's accept loop from its per-client conversation so a server actually serves clients concurrently, and using Unix domain sockets (JEP 380, JDK 16) as a faster, filesystem-permissioned, strictly local alternative to TCP loopback.

30

The JDK's Built-in HTTP Server

The JDK ships an officially supported HTTP server in the jdk.httpserver module — HttpServer, HttpExchange, HttpsServer with an SSLContext for TLS, and SimpleFileServer/jwebserver — giving a zero-dependency HTTP endpoint that the docs scope explicitly to testing, development, and debugging rather than production.

71
🔬 Lab

HTTP Sessions Under the Hood

How session state is faked on top of stateless HTTP: a random session ID, a Set-Cookie/Cookie round trip, and a server-side map keyed by that ID — plus what's missing from the minimal version (expiration, invalidation, externalized storage) and current cookie security attributes (Secure, SameSite).

73
🔬 Lab

Sockets and the Raw Anatomy of an HTTP Request

How ServerSocket/Socket implement the accept-connect-read-write primitive under every web server, and the literal byte-level shape of an HTTP/1.1 request/response (request line, headers, blank-line terminator, Content-Length/chunked bodies, status code classes).

77
🔬 Lab

java.io: Streams, Closing Resources, and Serialization

The byte-stream vs. character-stream split (InputStream/OutputStream vs. Reader/Writer), how Closeable/AutoCloseable/Flushable and try-with-resources guarantee resource cleanup with suppressed exceptions, buffering and PrintWriter, and how Serializable/ObjectInputFilter turn an object graph into bytes while guarding against deserialization attacks.

78
🔬 Lab

NIO.2: The Path and Files API

How java.nio.file replaces java.io.File with informative, specific exceptions instead of boolean returns, adds DirectoryStream for closeable/filterable directory iteration, first-class symbolic-link support, and WatchService for reacting to filesystem changes instead of polling.

84
🔬 Lab

HttpClient: The Modern java.net.http API

How HttpClient's builder pattern replaced HttpURLConnection's low-level, awkward API — synchronous send() vs. asynchronous sendAsync() returning a CompletableFuture, and how BodyHandlers decides what shape the response body arrives in.