Memra

Chaining, rethrow, custom hierarchies & logging

Wrap a low-level cause, design an exception family, and log a fault before re-throwing it.

Exception chaining: keep the original cause

When you catch a low-level exception and respond by throwing a more meaningful one, you must not *lose* the original — its stack trace is the evidence of what really happened. Java preserves it through the cause: pass the caught exception as the second constructor argument, or call initCause():

try {
    loadEvents(file);
} catch (IOException e) {
    throw new ControllerException("could not start: " + file, e);  // e is the cause
}

The new ControllerException now carries the IOException as its cause; getCause() returns it, and a printed stack trace shows both, joined by Caused by:. This is wrap-and-rethrow: translate a low-level failure into your domain's vocabulary without discarding the diagnostic trail. For chaining to work, your exception needs a constructor that accepts a cause and forwards it to super.

Designing a custom exception hierarchy

Real systems group related faults under a common base so a caller can catch the *whole family* with one handler. The Greenhouse fault types form such a family:

public class ControllerException extends Exception {
    public ControllerException(String why) { super(why); }
    public ControllerException(String why, Throwable cause) { super(why, cause); }
}

public class WindowMalfunction extends ControllerException {
    public WindowMalfunction(String why) { super(why); }
}

public class PowerOut extends ControllerException {
    public PowerOut(String why) { super(why); }
}

Now catch (ControllerException e) handles a WindowMalfunction or a PowerOut *or* a plain ControllerException — because catch matches the declared type and all its subtypes. A handler that needs to distinguish can add narrower catch clauses *above* the broad one (order matters: most specific first, or the broad clause shadows them and the compiler rejects the unreachable narrower catch).

Logging then re-throwing — the project's shutdown path

The Greenhouse controller's top level catches a fault, logs it with a timestamp and reason to error.log, and then re-throws (or proceeds to an emergency shutdown). The logging happens in the handler *before* recovery:

try {
    controller.run();
} catch (ControllerException e) {
    try (PrintWriter log =
             new PrintWriter(new FileWriter("error.log", true))) {  // true = append
        log.println(System.currentTimeMillis() + ": " + e.getMessage());
    } catch (IOException io) {
        System.err.println("could not write log: " + io.getMessage());
    }
    shutdown();   // emergency cleanup
}

Note the nested try-with-resources for the log writer (opened in append mode so each fault adds a line rather than truncating the file), and the inner catch so a logging failure can't mask the original fault. This is the shape the capstone expects: observe the fault, persist it, then recover — never silently swallow it.

NORMAL ~/memra/learn/comp-308/chaining-rethrow-custom-hierarchies-logging utf-8 LF