new ImportFailure("Import failed") is all that reaches the diagnostic log once the import tool catches a low-level failure and replaces it. The caller has no business seeing local file details. The person investigating the failure still needs to know what went wrong underneath.
Keep the domain exception, and in its constructor pass the original failure to Throwable as the cause. Java gives you that causal link without turning the public exception category into the lower-level one.
final class ImportFailure extends Exception {
ImportFailure(String message, Throwable cause) {
super(message, cause);
}
}
try {
readInput(path);
} catch (IOException e) {
throw new ImportFailure("Import failed", e);
}
That is the whole change on the throwing side. I'd also delete the constructor that drops the cause, since leaving it there invites the next catch block to use it.
The public response then uses the domain category and a message someone chose on purpose. It should never borrow its wording from getCause().getMessage(), because the cause may hold the very local details we meant to keep out. Internal diagnostics need deliberate handling as well. "Print every exception value" is not a policy.
Richer diagnostics come with access and redaction rules, and that burden is real. Every place the chained exception travels to has to be checked for what it prints, and some of those places print more than their authors expect. Dropping the cause avoids all of that and leaves you investigating from two words. I'll take the redaction work, but I want the list of destinations written down before the first cause gets attached.