7

Let's say I have an interface describing a simple service

public interface AccountService
{
    public int getUserId(String userName) throws UserNotFoundException;

    //...
}

I've written the UserNotFoundException exception myself and it is specific to this service. Apart from that, anything else that could go wrong is implementation specific.

If I have a

public class DatabaseAccountService
{
    @Override
    public int getUserId(String userName) throws UserNotFoundException
    {
        //some stuff that might throw an SQLException
    }
}

The connection could be broken, the table could not exist, etc. While with a FileAccountService instead, the file could have permission problems, it could exist but be in an invalid format, etc. Lots of implementation-specific problems could happen in both cases.

The thing is, I can't describe all the possible problems of all the possible implementations into my interface. I need someway to describe "things that went wrong and are implementation-specific". Should I wrap such exceptions into a RuntimeException or add another exception to my signature like OperationFailedException ?

Wrapping the implementation specific exception into a UserNotFoundException is not even an option. It would be lying, potentially saying an user does not exists while the file may be just not readable.

The problem I see with the RuntimeExceptions is that they might not be caught by the user, maybe leading in an application crash when maybe there was something better to do.

The problem I see with an OperationFailedException is that it's super generic and doesn't add any information to my methods signatures.

public interface AccountService
{
    public int getUserId(String userName) throws UserNotFoundException, 
                                                 OperationFailedException; //dumb, any method can fail
}

Also, I feel like if adding an OperationFailedException was the best thing to do, such generic exception wrapping type would exist in the standard library. However, as far as I know, such generic exception does not exist, suggesting any exception should be as specific as possible to help programmers.

Do I have any other option? Which one is the best if I want anyone to except as much from my code as from the Java standard library?

  • Can you wrap it? – Zymus Aug 6 '17 at 2:24
  • @Zymus I can but the point of the question is if I should and it what way would it be best for the user of my interface. – Winter Aug 6 '17 at 2:25
  • 1
    Samuel has provided a good answer. I would just add to the list that throwing an unexpected exception from a specialization actually violates the Liskov substitution principle. – Andy Aug 6 '17 at 7:04
  • The first question I have with such design is: Is it really an exception when a user die not exist? Isn't that just wrapping control flow in Exceptions? – johannes Sep 30 '17 at 11:49
8

Should I wrap such exceptions into a RuntimeException or add another exception to my signature like OperationFailedException ?

I would recommend something like the latter. Create a more generic exception for your AccountService. Name it something like AccountServiceException or ServiceException, and have each method in AccountService declare it as a checked exception. This is similar to your idea of using OperationFailedException, but it's more specific to your application, and you can tailor it to AccountService.

interface AccountService
{
    int getUserId(String userName) throws UserNotFoundException, AccountServiceException;

    Bar foo() throws AccountServiceException;
    //...
}

Each layer should catch and handle or wrap exceptions from the layer beneath. This will decouple you from implementation-specific exceptions, and using checked exceptions will ensure that exception cases are handled and not missed.

I feel like if adding an OperationFailedException was the best thing to do, such generic exception wrapping type would exist in the standard library.

OperationFailedException is esentially just java.lang.Exception. java.lang.Exception is the class used to describe an operation that failed without providing any more context about the issue. AccountServiceException is much more descriptive and can be caught without catching exceptions from other services in your application. If you through Exception or OperationFailedException from all your classes, catching exceptions would become a nightmare.

This is the same pattern used elsewhere in the JRE. SQLException and JAXBException are two examples of exceptions that are specific to the domain they are defined in, but thrown by many methods in that domain.

  • Thanks ! Following your idea, should I make UserNotFoundException an AccountServiceException if both require a really different type of attention? (One simply indicates that the user does not exist while the second might indicate a database failure). – Winter Aug 6 '17 at 3:32
  • 2
    I would have both exceptions on the method signature like this: public int getUserId(String userName) throws UserNotFoundException, AccountServiceException. I wouldn't have UserNotFoundException extend AccountServiceException if that's what you were asking – Samuel Aug 6 '17 at 4:34
  • 1
    I would make UserNotFoundException a child of AccountServiceException but declare both of the exceptions as part of the interface. – Andy Aug 6 '17 at 7:06
  • @Winter I would't do it. UserNotFoundException is sort of IllegalArgumentException (cf. Integer.parseInt("one")) while the latter is a system failure. – maaartinus Aug 6 '17 at 16:59
  • @maaartinus UserNotFoundException still requires a database call, it's not just an argument check – Winter Aug 6 '17 at 17:25
3

In my opinion, an API design should be driven by its potential users. So I'll take on a user's (caller's) point of view. I am well aware that this can lead to debates...

  1. I don't like to write extensive exception lists in my throws clause. That's not laziness - I don't want to force my caller to deal with 20 different exception types that are mostly irrelevant to him.

  2. I don't like to catch, wrap and rethrow exceptions, just because the method signatures force me to do that. That's not only ugly to me, but also leads to wrapping many exception layers around the original one, making logs hard to read.

  3. If some of my method calls fail, normally I don't have an alternative way to fulfill my contract, so I just have to notify my caller that I couldn't do my job. Here I prefer to simply let the exception I got ripple through to my caller. To me, that's the biggest benefit of exceptions over the old-fashioned check-for-failure-results style of languages like C.

  4. If some of my method calls fail and I have an alternative way for that step, I'll try that, no matter why the first way failed.

  5. So typically I don't care about the exception type I got. It's important for logging but not for me as the caller.

  6. For logging purposes, sometimes it makes sense that I add information to the exception I got (e.g. the call arguments that led to the exception). Then I'll surround my whole method body with try-catch and throw a wrapper exception with the context info visible in its toString() and the original exception as its cause.

What dows that mean to your question?

  • Having to deal with multiple unrelated exceptions from one method call is a no-go to me, because it forces me to take path 1. or 2., which I don't like. So UserNotFoundException and AccountServiceException should have a common base class, at least.

  • If every service in your application has its own, unrelated exception class tree, I still have to deal with many different exception types in my method (because surely my method will not only call the AccountService), so it's still the same problem.

  • I'd prefer to have a common root exception class ApplicationException where all application-specific types derive from (I don't know if you have control over all your application so that you can do that). That allows me to let exceptions ripple through to my caller with a short throws list, tending to become throws ApplicationException higher up in the software layers. And this base class can support things like user-notification texts, containing context information and so on.

  • If you can't introduce a common exception base class for your application, I'd like to get RuntimeExceptions (or specific subclasses) because that supports my 95-percent use case (let the exception ripple through) best. Don't fear "that they might not be caught by the user, maybe leading in an application crash". It's a bad programmer's fault if the top-level activities of your application crash when getting a RuntimeException (e.g. NullPointerException) and don't take proper action.

So I'd go for either an ApplicationException base class usable throughout your application (preferred) or for RuntimeException subclasses.

  • I appreciate the way of thinking from the programmer that just want to use the API without handling errors but I am using this code in an application where I really want error to be properly handled. Also, it shouldn't change anything but, it's not even an API, it's my own code for my application. I just want to make it clean and rigorous to prevent any issues in the future. I don't think that an API's code should be different from a desktop application. Any code should be written in the most intelligent way, it saves so much time of debugging. – Winter Aug 6 '17 at 16:51
  • 1
    @Winter Then, it's simple. Write a few exception handlers and look how many times you need to differentiate between these two exceptions and how many times you can do the same thing. Fearing an application crash makes no sense as there must be some catch-them-all on the top level, anyway. – maaartinus Aug 6 '17 at 17:09
  • 2
    In my opinion, every single layer of an application is an API. It creates the language that you (or your colleagues) want to use at the next-higher level, and to some degree that's personal style. As @maartinus suggested, introduce a proper error handling the way you feel comfortable with, and for every try-catch block and every throws clause ask yourself "do I like to write it that way?". If your answer is "yes", then it's perfect. – Ralf Kleberhoff Aug 6 '17 at 19:04
  • Well, a common thread of your answer is "checked exceptions are the work of the devil". – Deduplicator Aug 6 '17 at 19:24
  • 1
    @Deduplicator: Yes (mainly). Checked Exceptions more often than not lead to code cluttered with exception handling instead of concentrating on the task. – Ralf Kleberhoff Aug 6 '17 at 19:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.