Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java exceptions wrapping: bad practice?

Coming from a PHP world where there's only one way to write exceptions handling.. I found the wrapping of exceptions in Java a bit "ugly":

public void exampleOneException(String input) throws MyBusinessException {
    try {
        // do something
    } catch (NumberFormatException e) {
        throw new MyBusinessException("Error...", e);
    }
}

I prefer to use this style instead:

public void exampleTwoException() {
    try {
        // do something
    } catch (MyBusinessException e) {
        log.error("Error...: " + e);
    } catch (NumberFormatException e) {
        log.error("Error...: " + e);
    }
}

Is there any difference or best practices on those approaches on handling exceptions?

like image 889
numediaweb Avatar asked Nov 16 '17 08:11

numediaweb


3 Answers

These are both valid approaches for two different scenarios.

In the first scenario, the method can't do anything intelligent about the exception, but it has to "report" it upwards. That way the caller can catch the exception and decide what to do with it (e.g. cancel the flow, pop a message up to the user, log it, etc.)

In the second scenario, you catch the exception and log it, effectively hiding the failure from the caller. This type of handling may be useful if the caller doesn't really care if the operation succeeded or not.

like image 153
Mureinik Avatar answered Sep 20 '22 09:09

Mureinik


The first example would generally be seen as the better approach.

You should also not consider the MyBusinessException to be wrapping the NumberFormatException, but rather the NumberFormatException is the cause of the MyBusinessException.

Exceptions should be appropriate for the interface being exposed. A caller of your interface should not need to know, or deal with, the implementation details. Unless a NumberFormatException genuinely makes sense as a type of error when calling exampleOneException, it should be translated into a more suitable exception.

A more concrete example typically includes differing implementations, where the users of the interface should not be required to deal with the implementation specifics (which may not even be known at compile time).

interface MyRepository {
    Object read(int id) throws ObjectNotFoundException;
}

// a sql backed repository
class JdbcRepository implements MyRepository {
    public Object read(int id) throws ObjectNotFoundException {
        try { ... }
        catch (SQLException ex) {
            throw new ObjectNotFoundException(ex);
        }
    }
 }

// a file backed repository
class FileRepository implements MyRepository {
    public Object read(int id) throws ObjectNotFoundException {
        try { ... }
        catch (FileNotFoundException ex) {
            throw new ObjectNotFoundException(ex)
        }
    }
}

Because the interface declares the types of errors it can return, the clients of that interface can be consistent and rational. Adding code to handle FileNotFoundException and SQLException then the actual implementation may be either, or neither, is not fantastic.

Consider if there were multiple places in the implementation of FileRepository which could throw FileNotFoundException. Does that imply each and every one of them implies object not found?

When considering option two, exampleTwoException, it's important to realise that your catch block is effectively stating that the effects of whatever error occurred have been mitigated. There are cases where checked exceptions are reasonably ignored, but it is far more likely that a simple

try { ... }
catch (SomeException ex) {
    log.error("caught some exception", ex);
}

is actually the result of a developer not considering the consequences of the exception, or the code should have included a FIXME.

This is even more true when you see catch (Exception ex), or the unconscionable catch (Throwable ex).

And finally, would you want to be the person to dig through an application finding all the places where you need to add (yet) another catch block to handle a new implementation? Perhaps that's a cause of catch (Exception ex)...

Always throw exceptions appropriate to the abstraction

like image 34
ptomli Avatar answered Sep 21 '22 09:09

ptomli


I would say both NumberFormatException and MyBusinessException are useful but in different cases.

They usually appear at different levels of class hierarchy: for example NumberFormatException is a lower-level exception and you might not want to expose it at a higher level (e.g. user interface) if the user of it has no power to recover from it. In this case it is more elegant to just throw MyBusinessException and display an informative message that explains for example that something in a previous step was badly supplied or some internal processing error occurred and he/she needs to restart the process.

On the other hand, if your function is used at an intermediate-level (e.g. API) and the developer has the means to recover from the exceptional behavior, NumberFormatException is more useful, as it can be dealt with programmatically and the flow of the application might continue with minimal interruption (e.g. supply a default valid number). Alternatively, this can indicate a flaw/bug in the code that should be fixed.

For details about how to follow best practice in using exceptions, read Item 61 - Throw exceptions appropriate to the abstraction from Effective Java by Joshua Bloch.

like image 27
aUserHimself Avatar answered Sep 23 '22 09:09

aUserHimself