An exception makes failure explicit when ordinary execution cannot continue. Treat that failure path as part of the design, not a patch after the successful path: decide whether an operation succeeds, fails, or recovers, and release any files, connections, or sockets before it ends. section 2.5 section 7.0
Exceptions are especially awkward inside functional pipelines. A thrown exception is a side effect that interrupts the expectation of a predictable output for every input. In Java, checked exceptions such as IOException must be handled; unchecked runtime exceptions may avoid that compiler complaint but still rupture the pipeline. section 6.3.2
Make a file-read failure an explicit result
Rather than letting a stream explode while reading files, isolate the risky operation in a named method and return a ReadResult containing either content or an exception. The pipeline can then map filenames to valid outputs, while the boundary between file I/O and the rest of the flow remains visible. section 6.3.2
List<ReadResult> contents = filenames.stream()
.map(filename -> {
try {
List<String> content = Files.readAllLines(Path.of(filename));
return new ReadResult(Optional.of(content), #1
Optional.empty()); #1
} catch (IOException e) {
return new ReadResult(Optional.empty(), #2
Optional.of(e)); #2
}
})
.toList();
This version catches IOException at the file-read operation and models both content and error as explicit fields of a ReadResult. section 6.3.2
Handle exceptions according to the kind of failure and the audience for the outcome:
- Keep protected blocks narrow and avoid generic catches unless there is a clear justification; a small boundary makes the failure path readable. section 7.3
- Translate low-level technical failures at architectural boundaries into meaningful domain exceptions or controlled responses, while preserving the original cause for diagnosis. section 7.3 section 7.1.1
- Do not show raw stack traces to end users. They can reveal filesystem paths, hostnames, SQL queries, and other internal details; provide an understandable response instead. section 7.1.1
- Log only when it adds diagnostic value, with useful context and without sensitive data; route production logs to collection, persistence, and monitoring outputs. section 7.3
Not every failure deserves the same response. Business-rule violations, technical problems, programming bugs, and fatal errors call for different strategies. A useful heuristic is to make anticipated failure explicit, keep implementation details from leaking across boundaries, and choose recovery or propagation deliberately. section 7.3 section 7.1.1
























