I/O, Serialization & Networking

I/O Streams

Byte streams (InputStream/OutputStream) move raw bytes; the library builds everything else by decorating them — buffering, compression, encryption — one wrapper at a time. Understand the decorator stack and all of java.io falls into place.
  • InputStream/OutputStream: bytes. Reader/Writer: characters (Readers Writers)
  • Streams are composable decorators: wrap a raw stream to add capability
  • Always buffer file and network streams — unbuffered reads are per-byte syscalls
  • Close streams with try-with-resources; closing the outermost closes the chain
  • transferTo(out) copies a whole stream in one call (Java 9+)
The decorator stack
try (var in = new DataInputStream(
                new BufferedInputStream(
                  new GZIPInputStream(
                    Files.newInputStream(path))))) {
    // bytes flow: file → gunzip → buffer → typed reads
    int magic = in.readInt();
    double price = in.readDouble();
}

Each wrapper adds one responsibility — the decorator pattern at its best. BufferedInputStream batches syscalls; GZIPInputStream decompresses; DataInputStream adds typed reads. You assemble exactly the pipeline you need, and the same wrappers work over files, sockets, and byte arrays alike.

Bulk copying
try (InputStream in = url.openStream();
     OutputStream out = Files.newOutputStream(target)) {
    in.transferTo(out);              // the modern one-liner
}
byte[] all = in.readAllBytes();      // small payloads: slurp
Sources
  • Core Java, Volume II: Advanced Features (13th ed.)Ch. 2.1 — Input/Output Streams
  • Learning Java (6th ed.)Ch. 11 — Networking and I/O
I/O Streams · I/O, Serialization & Networking · Java::Compendium

I/O, Serialization & Networking

I/O Streams

Byte streams (InputStream/OutputStream) move raw bytes; the library builds everything else by decorating them — buffering, compression, encryption — one wrapper at a time. Understand the decorator stack and all of java.io falls into place.
  • InputStream/OutputStream: bytes. Reader/Writer: characters (Readers Writers)
  • Streams are composable decorators: wrap a raw stream to add capability
  • Always buffer file and network streams — unbuffered reads are per-byte syscalls
  • Close streams with try-with-resources; closing the outermost closes the chain
  • transferTo(out) copies a whole stream in one call (Java 9+)
The decorator stack
try (var in = new DataInputStream(
                new BufferedInputStream(
                  new GZIPInputStream(
                    Files.newInputStream(path))))) {
    // bytes flow: file → gunzip → buffer → typed reads
    int magic = in.readInt();
    double price = in.readDouble();
}

Each wrapper adds one responsibility — the decorator pattern at its best. BufferedInputStream batches syscalls; GZIPInputStream decompresses; DataInputStream adds typed reads. You assemble exactly the pipeline you need, and the same wrappers work over files, sockets, and byte arrays alike.

Bulk copying
try (InputStream in = url.openStream();
     OutputStream out = Files.newOutputStream(target)) {
    in.transferTo(out);              // the modern one-liner
}
byte[] all = in.readAllBytes();      // small payloads: slurp
Sources
  • Core Java, Volume II: Advanced Features (13th ed.)Ch. 2.1 — Input/Output Streams
  • Learning Java (6th ed.)Ch. 11 — Networking and I/O
— so a pattern that would find a substring match with `find()` can fail `matches()` entirely if it does not account for the rest of the string, a very common source of \"why doesn't my regex match\" confusion."},{"text":"Named groups `(?\u003cyear>\\d{4})` beat numbered groups for readability","detail":"Numbered groups (`group(1)`, `group(2)`, ...) require counting parentheses and re-counting every time the pattern changes — insert one more group earlier in the pattern and every later numbered reference silently shifts to the wrong group. Named groups are immune to that renumbering hazard and self-document what each captured piece means."},{"text":"Greedy `*` vs reluctant `*?` — the classic HTML-tag mismatch","detail":"A greedy quantifier tries to consume as much input as possible first, then backtracks only as far as needed to let the rest of the pattern still match — against text with multiple similar delimiters (like several HTML tags on one line), that \"as much as possible\" instinct is exactly what makes `\u003c.*>` swallow far more than intended."},{"text":"Catastrophic backtracking: nested quantifiers can hang on crafted input","detail":"When a quantified group is itself repeated — like `(a+)+` — there can be an exponential number of ways to partition the same matched text among the nested repetitions, and on non-matching input the engine tries combination after combination before giving up; a handful of extra characters can turn a sub-millisecond match into a multi-minute hang."}],"blocks":[{"kind":"code","title":"The working set","code":"private static final Pattern DATE =\n Pattern.compile(\"(?\u003cyear>\\\\d{4})-(?\u003cmonth>\\\\d{2})-(?\u003cday>\\\\d{2})\");\n\nMatcher m = DATE.matcher(input);\nif (m.matches()) { // entire input must match\n int year = Integer.parseInt(m.group(\"year\"));\n}\n\nwhile (m.find()) { // every occurrence\n System.out.println(m.group());\n}\n\nString cleaned = input.replaceAll(\"\\\\s+\", \" \");\nString[] cells = row.split(\"\\\\s*,\\\\s*\");\nList\u003cString> words = DATE.splitAsStream(text).toList();"},{"kind":"paragraph","text":"In string literals every regex backslash doubles: the regex `\\d` is written `\"\\\\d\"`. Character classes `[a-z]`, predefined `\\d \\w \\s` (and negations `\\D \\W \\S`), quantifiers `* + ? {n,m}`, alternation `|`, grouping `(...)`, lookarounds `(?=...) (?\u003c=...)`. Flags like `Pattern.CASE_INSENSITIVE` or inline `(?i)`."},{"kind":"pitfall","title":"Greedy quantifiers eat too much","text":"Against `\u003cb>bold\u003c/b> and \u003ci>italic\u003c/i>`, the pattern `\u003c.*>` matches the **entire string** — `*` grabs maximally, then backtracks just enough. You wanted `\u003c.*?>` (reluctant) or better `\u003c[^>]*>` (possessive-ish character class, no backtracking).","detail":"Regex engines are not \"smart\" about tag structure — `\u003c.*>` has no concept of matching tags, it is just \"less-than, then any characters, then greater-than\" — so the greedy `.*` stretches across every `\u003c`...`>` pair in the input to find the last possible `>`, unless you make the quantifier reluctant or, better, restrict the character class so it cannot cross a `>` at all."},{"kind":"pitfall","title":"Catastrophic backtracking (ReDoS)","text":"Patterns with nested quantifiers — `(a+)+ I/O Streams · I/O, Serialization & Networking · Java::Compendium

I/O, Serialization & Networking

I/O Streams

Byte streams (InputStream/OutputStream) move raw bytes; the library builds everything else by decorating them — buffering, compression, encryption — one wrapper at a time. Understand the decorator stack and all of java.io falls into place.
  • InputStream/OutputStream: bytes. Reader/Writer: characters (Readers Writers)
  • Streams are composable decorators: wrap a raw stream to add capability
  • Always buffer file and network streams — unbuffered reads are per-byte syscalls
  • Close streams with try-with-resources; closing the outermost closes the chain
  • transferTo(out) copies a whole stream in one call (Java 9+)
The decorator stack
try (var in = new DataInputStream(
                new BufferedInputStream(
                  new GZIPInputStream(
                    Files.newInputStream(path))))) {
    // bytes flow: file → gunzip → buffer → typed reads
    int magic = in.readInt();
    double price = in.readDouble();
}

Each wrapper adds one responsibility — the decorator pattern at its best. BufferedInputStream batches syscalls; GZIPInputStream decompresses; DataInputStream adds typed reads. You assemble exactly the pipeline you need, and the same wrappers work over files, sockets, and byte arrays alike.

Bulk copying
try (InputStream in = url.openStream();
     OutputStream out = Files.newOutputStream(target)) {
    in.transferTo(out);              // the modern one-liner
}
byte[] all = in.readAllBytes();      // small payloads: slurp
Sources
  • Core Java, Volume II: Advanced Features (13th ed.)Ch. 2.1 — Input/Output Streams
  • Learning Java (6th ed.)Ch. 11 — Networking and I/O
, `(\\s*,?)*` — explode exponentially on non-matching input: seconds, then minutes of CPU on a 40-character string. Any regex applied to *user input* must avoid ambiguous nesting; possessive quantifiers (`a++`) and atomic groups `(?>...)` cut off backtracking.","detail":"The exponential blowup comes from ambiguity: if the engine has many different ways to split the same substring across repeated groups, and none of them lead to an overall match, it tries all of them before failing. An attacker who can supply a regex's input (a search box, a validation field) can weaponize an innocuous-looking pattern into a denial-of-service with a short crafted string."},{"kind":"note","title":"When not to regex","text":"`contains`, `startsWith`, `indexOf`, and `split` on a literal cover most real cases faster and clearer. Parsing nested structures (JSON, XML, code) with regex is a category error — use a parser. And for one-off scans, `String.matches` recompiles the pattern per call: hoist the `Pattern` to a constant.","detail":"Nested/structured formats have recursive, context-sensitive grammars that regular expressions — which are fundamentally about matching flat, regular patterns — cannot correctly express in general. A regex that \"mostly works\" on such input will eventually break on some valid-but-unusual document, whereas a real parser handles the grammar completely."}],"refs":[{"book":"core-java-2","chapter":"Ch. 2.7 — Regular Expressions"},{"book":"learning-java","chapter":"Ch. 8 — Text and Core Utilities"}],"related":["strings-text","readers-writers"]},{"id":"sockets-networking","domainId":"io","title":"Sockets & Networking","summary":"TCP programming in Java is `Socket` + streams on the client, `ServerSocket.accept` + a thread per connection on the server. Set timeouts on everything, and let virtual threads make thread-per-connection scale again.","keyPoints":[{"text":"Client: `new Socket(host, port)` → `getInputStream()`/`getOutputStream()`","detail":"Constructing a `Socket` this way immediately attempts the TCP handshake and blocks until it completes or fails — once connected, the socket exposes the connection as an ordinary `InputStream`/`OutputStream` pair, so everything you already know about `java.io` streams (buffering, decorators) applies directly to network communication."},{"text":"Server loop: `accept()` blocks → hand the socket to a worker","detail":"`accept()` blocks the calling thread until a client connects, then returns a brand-new `Socket` representing just that one connection (the `ServerSocket` itself keeps listening for the next one) — the classic pattern hands that new socket to a separate thread so the accept loop can immediately go back to waiting."},{"text":"**Always set timeouts** — `connect(addr, ms)` and `setSoTimeout(ms)`; defaults block forever","detail":"Without an explicit timeout, both connecting and reading can block indefinitely if the remote host is unreachable, slow, or has silently disappeared — from the JVM's perspective a hung connection looks identical to a slow but healthy one, so an explicit deadline is the only way to bound how long you are willing to wait."},{"text":"Thread-per-connection is simple; [[virtual-threads]] make it scale to 100k+ connections","detail":"With platform threads, each blocked connection permanently occupies an OS thread (with its multi-MB stack) for as long as it is open, so a server handling tens of thousands of slow/idle connections needs tens of thousands of platform threads — impractical. Virtual threads unmount from their carrier while blocked on I/O, so the same simple code scales to vastly more connections on the same hardware."},{"text":"`InetAddress` resolves names; UDP uses `DatagramSocket`/`DatagramPacket`","detail":"`InetAddress` wraps the DNS lookup that turns a hostname into the IP address actually used to connect; UDP's API looks nothing like TCP's stream-based `Socket` because UDP itself has no connection or ordering guarantees — you send and receive discrete packets, each of which might arrive out of order, duplicated, or not at all."}],"blocks":[{"kind":"code","title":"Client with timeouts","code":"try (var socket = new Socket()) {\n socket.connect(new InetSocketAddress(\"time.example.com\", 8013), 5_000);\n socket.setSoTimeout(10_000); // reads may not hang forever\n try (var in = new Scanner(socket.getInputStream(), StandardCharsets.UTF_8)) {\n while (in.hasNextLine()) System.out.println(in.nextLine());\n }\n} // SocketTimeoutException on silence — recoverable, unlike an eternal hang"},{"kind":"code","title":"Server: one virtual thread per connection (Java 21)","code":"try (var server = new ServerSocket(8189);\n var executor = Executors.newVirtualThreadPerTaskExecutor()) {\n while (true) {\n Socket client = server.accept(); // blocks until a connection\n executor.submit(() -> handle(client)); // cheap thread per client\n }\n}"},{"kind":"paragraph","text":"The blocking model reads top-to-bottom and handles one client per thread. Historically it capped at a few thousand platform threads, pushing servers toward NIO selectors and callback labyrinths. Virtual threads ([[virtual-threads]]) restored the simple model: the JVM parks the virtual thread during blocking I/O and the carrier thread moves on — thread-per-connection semantics at event-loop scale."},{"kind":"pitfall","title":"Half-closed connections and partial reads","text":"TCP is a byte stream, not a message stream: one `write` can arrive as several `read`s and vice versa. Protocols need explicit framing (length prefixes or delimiters). And a peer that vanishes without FIN leaves your read blocked — which is why `setSoTimeout` is non-negotiable in production.","detail":"TCP guarantees that bytes arrive in the order sent, but makes no promise about how those bytes are grouped into individual `read()`/`write()` calls — the OS is free to coalesce or split them based on network conditions, buffer sizes, and timing. Any protocol that assumes \"one write equals one read\" will eventually break; you need your own framing to know where one logical message ends and the next begins."},{"kind":"note","title":"The URL family","text":"`URI` parses and builds identifiers (use it); `URL.openStream()` is the quick way to fetch a resource; but for HTTP work, the modern [[http-client]] supersedes `URLConnection` entirely.","detail":"`URLConnection` predates and awkwardly overlaps with both `URI` (parsing) and `HttpClient` (actual HTTP transport) — a legacy, low-level abstraction that mixes URI parsing with protocol handling and lacks HTTP/2, proper timeouts, and a fluent API. New code should use `URI` purely for parsing and `HttpClient` for the actual request/response exchange."}],"refs":[{"book":"core-java-2","chapter":"Ch. 4.1–4.2 — Connecting to a Server; Implementing Servers"},{"book":"learning-java","chapter":"Ch. 11 — Networking and I/O"}],"related":["http-client","virtual-threads","io-streams"]},{"id":"http-client","domainId":"io","title":"The HTTP Client","summary":"`java.net.http.HttpClient` (Java 11) is the modern built-in: fluent requests, HTTP/2, synchronous or `CompletableFuture` async, and pluggable body handlers. No third-party dependency needed for straightforward HTTP.","keyPoints":[{"text":"Build one `HttpClient`, reuse it — it pools connections","detail":"`HttpClient` instances are immutable and thread-safe by design specifically so they can be shared: internally they maintain a connection pool (keep-alive TCP connections, HTTP/2 multiplexed streams) that only pays off if the same instance is reused across many requests — creating a new one per call throws away that pool and pays the connection-setup cost every single time."},{"text":"`send(request, bodyHandler)` blocks; `sendAsync` returns `CompletableFuture\u003cHttpResponse\u003cT>>`","detail":"`send()` ties up the calling thread until the full response is received, which is fine (and simple) when running inside a cheap virtual thread; `sendAsync` returns immediately with a `CompletableFuture` you can chain, combine with other futures, or attach timeouts to, without ever blocking a thread while waiting on the network."},{"text":"Body handlers: `ofString`, `ofFile`, `ofInputStream`, `ofLines`; publishers for upload","detail":"A `BodyHandler` decides how the response body gets materialized — as a fully-buffered `String`, streamed straight to a file, exposed as a lazy `InputStream`, or split into lines — so you pick the handler based on expected response size and whether you need it all in memory at once. `BodyPublishers` are the upload-side mirror."},{"text":"HTTP/2 by default with HTTP/1.1 fallback; redirects and timeouts are opt-in configuration","detail":"The client negotiates HTTP/2 automatically when the server supports it, falling back to HTTP/1.1 transparently otherwise — this brings multiplexing, multiple requests sharing one TCP connection without head-of-line blocking at the application level. But redirect-following and request/connect timeouts must be explicitly configured on the builder; the out-of-the-box defaults do neither."},{"text":"Check `response.statusCode()` — non-2xx does **not** throw","detail":"`HttpClient` treats \"the HTTP exchange completed\" and \"the HTTP exchange succeeded\" as separate concerns: a 404 or 500 response is still a fully successful exchange from the client's point of view, so it returns normally with that status code rather than throwing — only network-level failures (timeouts, connection refused, etc.) throw exceptions."}],"blocks":[{"kind":"code","title":"GET and POST","code":"HttpClient client = HttpClient.newBuilder()\n .connectTimeout(Duration.ofSeconds(5))\n .followRedirects(HttpClient.Redirect.NORMAL)\n .build();\n\nHttpRequest get = HttpRequest.newBuilder(URI.create(\"https://api.example.com/users\"))\n .header(\"Accept\", \"application/json\")\n .timeout(Duration.ofSeconds(10))\n .GET().build();\n\nHttpResponse\u003cString> resp = client.send(get, HttpResponse.BodyHandlers.ofString());\nif (resp.statusCode() == 200) process(resp.body());\n\nHttpRequest post = HttpRequest.newBuilder(URI.create(\"https://api.example.com/users\"))\n .header(\"Content-Type\", \"application/json\")\n .POST(HttpRequest.BodyPublishers.ofString(json))\n .build();"},{"kind":"code","title":"Async composition","code":"List\u003cCompletableFuture\u003cString>> futures = urls.stream()\n .map(u -> client.sendAsync(reqFor(u), BodyHandlers.ofString())\n .thenApply(HttpResponse::body))\n .toList();\n\nCompletableFuture.allOf(futures.toArray(CompletableFuture[]::new)).join();"},{"kind":"paragraph","text":"The async form rides on [[completable-future]] — compose, time out (`orTimeout`), and fan out without blocking threads. With [[virtual-threads]], the **synchronous** API inside cheap threads is often the most readable concurrent design: straight-line code, per-request thread, no callback chains."},{"kind":"pitfall","title":"One client per request","text":"Building a new `HttpClient` per call discards connection pooling and HTTP/2 multiplexing, and leaks its executor threads until GC. Create it once (it's immutable and thread-safe), share it application-wide — same rule as for `ObjectMapper` or an SSLContext.","detail":"Every new `HttpClient` spins up its own connection pool and (for the async API) its own executor/selector threads behind the scenes; creating one per request means every call pays the full TCP+TLS handshake cost from scratch and leaves behind threads/resources that only get cleaned up once the object is garbage collected — the exact inverse of what the immutable, poolable design was meant to encourage."},{"kind":"note","title":"Timeout taxonomy","text":"`connectTimeout` (client) bounds the TCP handshake; `timeout` (request) bounds the full exchange — absent, a stuck server holds your thread or future forever. Production checklists treat missing HTTP timeouts as a defect (they cascade into thread-pool exhaustion; see [[scalability-patterns]]).","detail":"`connectTimeout` only bounds the initial TCP+TLS handshake — once connected, a request with no request-level timeout can still hang forever waiting for the server to send a response, because that phase is a completely separate timer that `connectTimeout` has no control over; both need to be set for a genuinely bounded call."}],"refs":[{"book":"core-java-2","chapter":"Ch. 4.4 — The HTTP Client"},{"book":"learning-java","chapter":"Ch. 12 — Programming for the Web"}],"related":["completable-future","sockets-networking","virtual-threads","cloud-native-java"]}]}