180

There are many well-known best practices about exception handling in isolation. I know the "do's and don'ts" well enough, but things get complicated when it comes to best practices or patterns in larger environments. "Throw early, catch late" - I've heard many times and it still confuses me.

Why should I throw early and catch late, if at a low-level-layer a null pointer exception is thrown? Why should I catch it at a higher layer? It doesn't make sense for me to catch a low-level exception at a higher level, such as a business layer. It seems to violate the concerns of each layer.

Imagine the following situation:

I have a service which calculates a figure. To calculate the figure the service accesses a repository to get raw data and some other services to prepare calculation. If something went wrong at the data retrieval layer, why should I throw a DataRetrievalException to higher level? In contrast I would prefer to wrap the exception into a meaningful exception, for example a CalculationServiceException.

Why throw early, why catch late?

shylynx
  • 2,134

8 Answers8

135

In my experience, its best to throw exceptions at the point where the errors occur. You do this because it's the point where you know the most about why the exception was triggered.

As the exception unwinds back up the layers, catching and rethrowing is a good way to add additional context to the exception. This can mean throwing a different type of exception, but include the original exception when you do this.

Eventually the exception will reach a layer where you are able to make decisions on code flow (e.g a prompt the user for action). This is the point where you should finally handle the exception and continue normal execution.

With practice and experience with your code base it becomes quite easy to judge when to add additional context to errors, and where it's most sensible to actually, finally handle the errors.

Catch → Rethrow

Do this where you can usefully add more information that would save a developer having to work through all the layers to understand the problem.

Catch → Handle

Do this where you can make final decisions on what is an appropriate, but different execution flow through the software.

Catch → Error Return

Whilst there are situations where this is appropriate, catching exceptions and returning an error value to the caller should be considered for refactoring into a Catch → Rethrow implementation.

Michael Shaw
  • 10,114
63

You want to throw an exception as soon as possible because that makes it easier to find the cause. For example, consider a method that could fail with certain arguments. If you validate the arguments and fail at the very beginning of the method, you immediately know the error is in the calling code. If you wait until the arguments are needed before failing, you have to follow the execution and figure out if the bug is in the calling code (bad argument) or the method has a bug. The earlier you throw the exception, the closer it is to its underlying cause and the easier it is to figure out where things went wrong.

The reason exceptions are handled at higher levels is because the lower levels don't know what's the appropriate course of action to handle the error. In fact, there could be multiple appropriate ways to handle the same error depending on what the calling code is. Take opening a file for example. If you're trying to open a config file and it's not there, ignoring the exception and proceeding with the default configuration can be an appropriate response. If you're opening a private file that's vital to the program's execution and it's somehow missing, your only option is probably to close the program.

Wrapping the exceptions in the right types is a purely orthogonal concern.

Doval
  • 15,487
30

Others have summarized quite well why to throw early. Let me concentrate on the why to catch late part instead, for which I haven't seen a satisfying explanation for my taste.

SO WHY EXCEPTIONS?

There seems to be quite a confusion around why exceptions exist in the first place. Let me share the big secret here: the reason for exceptions, and exception handling is... ABSTRACTION.

Have you seen code like this:

static int divide(int dividend, int divisor) throws DivideByZeroException {
    if (divisor == 0)
        throw new DivideByZeroException(); // that's a checked exception indeed

    return dividend / divisor;
}

static void doDivide() {
    int a = readInt();
    int b = readInt(); 
    try {
        int res = divide(a, b);
        System.out.println(res);
    } catch (DivideByZeroException e) {
        // checked exception... I'm forced to handle it!
        System.out.println("Nah, can't divide by zero. Try again.");
    }
}

That's not how exceptions should be used. Code like the above do exist in real life, but they are more of an aberration, and are really the exception (pun). The definition of division for example, even in pure mathematics, is conditional: it is always the "caller code" who has to handle the exceptional case of zero to restrict the input domain. It's ugly. It's always pain for the caller. Still, for such situations the check-then-do pattern is the natural way to go:

static int divide(int dividend, int divisor) {
    // throws unchecked ArithmeticException for 0 divisor
    return dividend / divisor;
}

static void doDivide() {
    int a = readInt();
    int b = readInt();
    if (b != 0) {
        int res = divide(a, b);
        System.out.println(res);
    } else {
        System.out.println("Nah, can't divide by zero. Try again.");
    }
}

Alternatively, you can go full commando on OOP style like this:

static class Division {
    final int dividend;
    final int divisor;

    private Division(int dividend, int divisor) {
        this.dividend = dividend;
        this.divisor = divisor;
    }

    public boolean check() {
        return divisor != 0;
    }

    public int eval() {
        return dividend / divisor;
    }

    public static Division with(int dividend, int divisor) {
        return new Division(dividend, divisor);
    }
}

static void doDivide() {
    int a = readInt();
    int b = readInt(); 
    Division d = Division.with(a, b);
    if (d.check()) {
        int res = d.eval();
        System.out.println(res);
    } else {
        System.out.println("Nah, can't divide by zero. Try again.");
    }
}

As you see, the caller code has the burden of the pre-check, but does not do any exception handling after. If an ArithmeticException ever comes from calling divide or eval, then it is YOU who has to do the exception handling and fix your code, because you forgot the check(). For the similar reasons catching a NullPointerException is almost always the wrong thing to do.

Now there are some folks who say they want to see the exceptional cases in the method / function signature, i.e. to explicitly extend the output domain. They are the ones who favor checked exceptions. Of course, changing the output domain should force any direct caller code to adapt, and that would indeed be achieved with checked exceptions. But you don't need exceptions for that! That's why you have Nullable<T> generic classes, case classes, algebraic data types, and union types. Some OO people might even prefer returning null for simple error cases like this:

static Integer divide(int dividend, int divisor) {
    if (divisor == 0) return null;
    return dividend / divisor;
}

static void doDivide() {
    int a = readInt();
    int b = readInt(); 
    Integer res = divide(a, b);
    if (res != null) {
        System.out.println(res);
    } else {
        System.out.println("Nah, can't divide by zero. Try again.");
    }
}

Technically exceptions can be used for the purpose like the above, but here is the point: exceptions do not exist for such usage. Exceptions are pro abstraction. Exception are about indirection. Exceptions allow for extending the "outcome" domain without breaking direct client contracts, and deferring error handling to "somewhere else". If your code throws exceptions which are handled in direct callers of the same code, without any layers of abstraction in between, then you are doing it W. R. O. N. G.

HOW TO CATCH LATE?

So here we are. I have argued my way through to show that using exceptions in the above scenarios is not how exceptions are meant to be used. There exists a genuine use case though, where the abstraction and indirection offered by exception handling is indispensable. Understanding such usage will help understanding the catch late recommendation too.

That use case is: Programming Against Resource Abstractions...

Yeah, business logic should be programmed against abstractions, not concrete implementations. Top level IOC "wiring" code will instantiate the concrete implementations of the resource abstractions, and pass them down to the business logic. Nothing new here. But the concrete implementations of those resource abstractions can potentially be throwing their own implementation specific exceptions, can't they?

So who can handle those implementation specific exceptions? Is it possible to handle any resource specific exceptions at all in the business logic then? Nope, it is not. The business logic is programmed against abstractions, which does exclude the knowledge of those implementation specific exception details.

"Aha!", you might say: "but that's why we can subclass exceptions and create exception hierarchies" (check out Mr. Spring!). Let me tell you, that's a fallacy. Firstly, every reasonable book on OOP says concrete inheritance is bad, yet somehow this core component of JVM, exception handling, is closely tied with concrete inheritance. Ironically, Joshua Bloch could not have written his Effective Java book before he could get the experience with a working JVM, could he? It is more of a "lessons learnt" book for the next generation. Secondly, and more importantly, if you catch a high-level exception then how are you going to HANDLE it? PatientNeedsImmediateAttentionException: do we have to give her a pill or amputate her legs!? How about a switch statement over all the possible subclasses? There goes your polymorphism, there goes the abstraction. You got the point.

So who can handle the resource specific exceptions? It must be the one who knows the concretions! The one who instantiated the resource! The "wiring" code of course! Check this out:

Business logic coded against abstractions... NO CONCRETE RESOURCE ERROR HANDLING!

static interface InputResource {
    String fetchData();
}

static interface OutputResource {
    void writeData(String data);
}

static void doMyBusiness(InputResource in, OutputResource out, int times) {
    for (int i = 0; i < times; i++) {
        System.out.println("fetching data");
        String data = in.fetchData();
        System.out.println("outputting data");
        out.writeData(data);
    }
}

Meanwhile somewhere else the concrete implementations...

static class ConstantInputResource implements InputResource {
    @Override
    public String fetchData() {
        return "Hello World!";
    }
}

static class FailingInputResourceException extends RuntimeException {
    public FailingInputResourceException(String message) {
        super(message);
    }
}

static class FailingInputResource implements InputResource {
    @Override
    public String fetchData() {
        throw new FailingInputResourceException("I am a complete failure!");
    }
}

static class StandardOutputResource implements OutputResource {
    @Override
    public void writeData(String data) {
        System.out.println("DATA: " + data);
    }
}

And finally the wiring code... Who handles concrete resource exceptions? The one who knows about them!

static void start() {
    InputResource in1 = new FailingInputResource();
    InputResource in2 = new ConstantInputResource();
    OutputResource out = new StandardOutputResource();

    try {
        ReusableBusinessLogicClass.doMyBusiness(in1, out, 3);
    }
    catch (FailingInputResourceException e)
    {
        System.out.println(e.getMessage());
        System.out.println("retrying...");
        ReusableBusinessLogicClass.doMyBusiness(in2, out, 3);
    }
}

Now bear with with me. The above code is simplistic. You might say you have an enterprise application / web container with multiple scopes of IOC container managed resources, and you need automatic retries and reinitialization of session or request scope resources, etc. The wiring logic on the lower level scopes might be given abstract factories to create resources, therefore not being aware of the exact implementations. Only higher level scopes would really know what exceptions those lower level resources can throw. Now hold on!

Unfortunately, exceptions only allow indirection over the call stack, and different scopes with their different cardinalities usually run on multiple different threads. No way to communicate through that with exceptions. We need something more powerful here. Answer: async message passing. Catch every exception at the root of the lower level scope. Don't ignore anything, don't let anything slip through. This will close and dispose all resources created on the call stack of the current scope. Then propagate error messages to scopes higher up by using message queues / channels in the exception handling routine, until you reach the level where the concretions are known. That's the guy who knows how to handle it.

SUMMA SUMMARUM

So according to my interpretation catch late means to catch exceptions at the most convenient place WHERE YOU ARE NOT BREAKING ABSTRACTION ANYMORE. Do not catch too early! Catch exceptions at the layer where you create the concrete exception throwing instances of the resource abstractions, the layer which knows the concretions of the abstractions. The "wiring" layer.

HTH. Happy coding!

11

To answer this question properly, let's take a step back and ask an even more fundamental question.

Why is it that we have exceptions in the first place?

We throw exceptions to let the caller of our method know that we couldn't do what we were asked to do. The type of the exception explains why we couldn't do what we wanted to do.

Let's take a look at some code:

double MethodA()
{
    return PropertyA - PropertyB.NestedProperty;
}

This code can obviously throw a null reference exception if PropertyB is null. There are two things that we could do in this case to "correct" for this situation. We could:

  • Automatically create PropertyB if we don't have it; or
  • Let the exception bubble up to the calling method.

Creating PropertyB here could be very dangerous. What reason does this method have to create PropertyB? Surely this would violate the single responsibility principle. In all likelihood, if PropertyB does not exist here, it indicates that something has gone wrong. The method is being called on a partially constructed object or PropertyB was set to null incorrectly. By creating PropertyB here, we could be hiding a much bigger bug which could bite us later on, such as a bug which causes data corruption.

If, instead, we let the null reference bubble up, we are letting the developer who called this method know, as soon as we can, that something went wrong. A vital pre-condition of calling this method has been missed.

So in effect, we are throwing early because it separates our concerns much better. As soon as a fault has occurred, we are letting the upstream developers know about it.

Why we "catch late" is a different story. We don't really want to catch late, we really want to catch as early as we know how to handle the issue properly. Some of the time this will be fifteen layers of abstraction later and some of the time this will be at the point of creation.

The point is that we want to catch the exception at the layer of abstraction that allows us to handle the exception at the point where we have all the information we need to handle the exception properly.

Stephen
  • 8,868
  • 3
  • 31
  • 43
6

Throw as soon as you see something worth throwing for to avoid putting objects in an invalid state. Meaning that if a null pointer was passed you check for it early and throw a NPE before it has a chance to trickle down to the low level.

Catch as soon as you know what to do to fix the error (this is generally not where you throw otherwise you could just use a if-else), if an invalid parameter was passed then the layer that provided the parameter should deal with the consequences.

ratchet freak
  • 25,986
4

A valid business rule is 'if the lower level software fails to calculate a value, then ...'

This can only be expressed at the higher level, otherwise the lower level software is trying to change its behavior based on its own correctness, which is only going to end in a knot.

gnat
  • 20,543
  • 29
  • 115
  • 306
soru
  • 3,655
2

First of all, exceptions are for exceptional situations. In your example, no figure can be calculated if the raw data is not present because it could not be loaded.

From my experience, it's good practice to abstract exceptions while walking up the stack. Typically the points where you want to do this is whenever an exception crosses the boundary between two layers.

If there's an error with gathering your raw data in the data layer, throw an exception to notify whoever requested the data. Do not attempt to work around this issue down here. The complexity of the handling code can be very high. Also the data layer is only responsible for requesting data, not for handling errors that occure while doing this. This is what meant by "throw early".

In your example the catching layer is the service layer. The service itself is a new layer, sitting over the data access layer. So you want to catch the exception there. Perhaps your service has some failover infrastructure and tries to request the data from another repository. If this also fails, wrap the exception inside something the caller of the service understands (if it's an web service this might be an SOAP fault). Set the original exception as inner exception so that the later layers can log exactly what went wrong.

The service fault may be catched by the layer calling the service (for example the UI). And this is what meant by "catch late". If you are not able to handle the exception in an lower layer, rethrow it. If the top most layer can't handle the exception, handle it! This might include logging or presenting it.

The reason why you should rethrow exceptions (like described above by wrapping them in more general exeptions) is, that the user is very likely not able to understand that there was an error because, for example, a pointer pointed to invalid memory. And he doesn't care. He only care's that the figure could not be calculated by the service and this is the information that should be displayed to him.

Going futher you can (in an ideal world) completely leave out try/catch code from the UI. Instead use an global exception handler that is able to understand the exceptions that are possibly thrown by the lower layers, writes them into some log and wraps them into error objects that contain meaningful (and possibly localized) information of the error. Those objects can easily be presented to the user in any form you wish (message boxes, notifications, message toasts, and so on).

Aschratt
  • 254
1

Throwing exceptions early in general is a good practice because you don't want broken contracts to flow through the code further than necessary. For example, if you expect a certain function parameter to be a positive integer then you should enforce that constraint at the point of the function call instead of waiting until that variable is used somewhere else in the code stack.

Catching late I can't really comment on because I have my own rules and it changes from project to project. The one thing I try to do though is separate the exceptions into two groups. One is for internal use only and the other is for external use only. Internal exception are caught and handled by my own code and external exception are meant to be handled by whatever code is calling me. This is basically a form of catching things later but not quite because it offers me the flexibility to deviate from the rule when necessary in the internal code.