2

I've been taught that exceptions should be have concise meanings and should contain a message that explains to the client what the exceptional situation is. I am wondering, since I found a piece of code that contains a couple of lines into a try catch block and in the end catches a generic exception with the message: An error occurred performing the task

This is written in a library and somehow I trigger this exception. How am I supposed to fix what is broken if I do not know what is broken? Generally I know that this is not a good practice. Are there any situations that throwing/catching generic exceptions is a good practice?

1
  • What does a root superclass have to do with the rest of this example? I asked another clarification question in my answer. Dec 2, 2015 at 17:07

3 Answers 3

4

An exception should be descriptive. This is a combination of three things:

  • The identity (type) of the exception.
  • The message contained in the exception.
  • Other metadata contained in the exception such as a chained exception or error code.

Please note that different languages and runtimes have slightly different exception semantics, but these three ideas are fairly universal. At the very least, an exception has a type and usually has a message, but additional data is not always relevant. Also note that some languages such as C++ do not have a "root" exception type: while C++ does have std::exception, one may throw anything including primitives.

Picking on Java, there are four core types that form the backbone of the exception hierarchy: Throwable, Error, Exception, and RuntimeException. While none are abstract, none are intended to be thrown directly: one should use a more specific type. That being said, the fact that none are abstract means it is possible to instantiate and throw them directly.


Back to your original point: generally, one should throw specific exception subtypes because it makes it easier for specific exceptions to bubble up the call stack to be caught at the appropriate level. Consider the following Java example:

public void doSomething() throws Exception {
  doOneThing(); // Throws Exception
  doAnotherThing(); // Throws IOException
}

If doSomething() is not an appropriate place to handle the exception, the method calling it must know what exceptions to catch. The order of the catch clauses is also important: they must go from specific to general. Catching Exception first will swallow all exceptions, including IOException, in the same block. This becomes a little tougher to figure out what to catch and what to do with the exception once caught. Consider this code instead:

public void doSomething() throws SQLException, IOException {
  doOneThing(); // Throws SQLException
  doAnotherThing(); // Throws IOException
}

It is now far less ambiguous what the possible errors are, and there is no coupling in terms of the order in which they are caught. Instead of this:

try {
  doSomething();
}
// Order matters!
catch (IOException e) {
  // Obviously some sort of file or socket I/O error.
}
catch (Exception e) {
  // What are we dealing with here?
}

We have this:

try {
  doSomething();
}
catch (IOException e) {
  // Obviously some sort of file or socket I/O error.
}
catch (SQLException e) {
  // Now we can get the error code and do something meaningful.
}

You can also allow some exceptions to pass through up the call stack, and others to be handled in that try/catch block. For example, the first example would make it difficult to allow SQLException to pass up the call stack while handling IOException, because it is not declared properly. While possible, it would require casting, type checking, and would be brittle and unclean code.

The second example is more clear in terms of intent and allows more useful operations to be performed using the exception object.


Note that the above examples make the point that you might want to let some exceptions pass through, or you might want to handle different exceptions differently (e.g. show an error message for one, retry the operation for another). Some languages allow catching multiple types of exceptions without catching all exceptions. That can be a good compromise between a catch-all block and a very verbose "catch this, then this, then ..." block.

For example, consider the following block:

try {
  // open a file, read some XML, and instantiate
  // some classes referenced therein using reflection.
  initializeModule();
}
catch (IOException e) {
  // Do something
}
catch (ReflectiveOperationException e) {
  // Do something
}
catch (XMLStreamException e) {
  // Do something
}

Java 8 allows the following instead:

try {
  // open a file, read some XML, and instantiate
  // some classes referenced therein using reflection.
  initializeModule();
}
catch (IOException | ReflectiveOperationException | XMLStreamException e) {
  // Do something
}

This can be a good compromise because it catches specific exceptions in a single block without catching too much which might not be a clean design or the right place to catch other exceptions. For example, the code above lets RuntimeException classes (e.g. null pointers) pass up the call stack. This can be annoying to do in Java because RuntimeException inherits from Exception. Catching Exception will also catch RuntimeExceptions, which is not always what one wants to do.


The only time where I find catching all exceptions to be good practice is as a last resort before dying:

public static void main(String[] args) {
  try {
    // Invoke program logic here.
  }
  catch (Throwable t) {
    // Last-ditch logging before letting the program die.
  }
}
1
  • As you have already gone into such depth about catching, you could have mentioned the catch multiple feature as well - for the case when you want to catch multiple but not all Exception types in one block.
    – Hulk
    Dec 2, 2015 at 13:19
2

Using catch-all exception handlers is important in a couple of cases:

  • Your software needs to log all exceptions. Therefore, you will catch all exceptions at the outermost layer, log them, and rethrow them.

  • Your software is a long-running daemon or server that must not go down when some task cannot be completed. When you spawn a task, you will capture any exceptions thrown by that task and carry on regardless of failure of that task. E.g. a HTTP web server should not go down if any request leads to an exception. Instead, it responds with a 500 internal server error and continues to serve the other requests. Details of the original error are available in a log, and not passed on.

  • Your software is a library or layer. When an exception occurs, wrapping any exception in a domain-specific exception can provide more information than a naked stack trace. E.g. an FileNotFoundException("ad83jfw8.rsa could not be opened") is irrelevant to an external user when compared with a

    IllegalConfigurationValue(
      key="crypto.secretKey",
      value="ad83jfw8.rsa",
      file="myApp.cfg",
      line=13,
      message="file could not be opened",
      cause=FileNotFoundException(…),
    )
    

    The important point when substituting errors with custom exceptions is that the original problem is not swallowed but wrapped! The original exception should be passed along as a field, and probably be printed out in addition to a stack trace. E.g. in Java, the standard exception classes can take a Throwable cause in addition to a message. This design absolutely should be copied in custom exception classes!

In other cases, there is no excuse for swallowing an error. At the very minimum, the error must be wrapped or logged.

1

a couple of lines into a try catch block and in the end catches a generic exception with the message: An error occurred performing the task

Do you mean something like:

try {
    thingWhichThrowsDescriptiveException();
} catch (Throwable t) {
    throw new Exception("An error occurred");
}

If so, then my answer is No. You are entitled to whatever righteous indignation you feel when you see this. It just throws away the details of the exception and replaces them with garbage.

If you don't want an exception in your method signature, do this instead: Workaround for Java checked exceptions

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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