Catching practice

Estimated read time: 9 min

Originally published on July 6th, 2012 (Last updated on May 17th, 2020)

I recent­ly answered a ques­tion on Stack­Over­flow, ask­ing if catch­ing an Error would be rea­son­able in a par­tic­u­lar case. The orig­i­nal ques­tion and my answer can be found here, how­ev­er I felt that my answer could be extend­ed with a more gen­er­al dis­cus­sion of the impli­ca­tions and addi­tion­al examples.

Baseballs by Mike Rastiello (CC BY-NC-ND 2.0)

Clar­i­fi­ca­tion: Throw­able, Error, Exception #

In Java, errors and excep­tions (which are the main types) are thrown using the throw-key­word. Every class which extends the basic java.lang.Throwable can be thrown.

In the Stan­dard Library, there are two class­es which inher­it from the basic Throwable-class: Exception and Error. Their seman­tics are explained in the respec­tive documentation:

Error

An Error is a sub­class of Throwable that indi­cates seri­ous prob­lems that a rea­son­able appli­ca­tion should not try to catch. Most such errors are abnor­mal conditions. […]

Excep­tion

The class Exception and its sub­class­es are a form of Throwable that indi­cates con­di­tions that a rea­son­able appli­ca­tion might want to catch.

So, it appears, that it’s gen­er­al­ly a bad idea to catch an Error on purpose.

Why do we distinguish? #

Addi­tion­al­ly, excep­tions are fur­ther divid­ed into checked excep­tions” (which inher­it from the basic java.lang.Exception-class) and run­time excep­tions” (which inher­it from java.lang.RuntimeException).

The dif­fer­ence being that when a checked excep­tion” is thrown, it must be han­dled (by a try/catch”-block), where­as a run­time excep­tion” should not be han­dled at all.

The rea­son why there are mul­ti­ple dif­fer­ent types of Throwable is, because they all have dif­fer­ent origins:

  • An Error rep­re­sents a crit­i­cal low-lev­el prob­lem (prob­a­bly with the VM) which an appli­ca­tion is most like­ly not able to recov­er from.
  • A checked excep­tion” indi­cates a high-lev­el prob­lem, which the appli­ca­tion is like­ly to recov­er from.
  • A run­time excep­tion” indi­cates a pro­gram­ming error, which the appli­ca­tion should not recov­er from.

The key­word here real­ly is recov­er.

Can we recover? #

The ques­tion to ask when catch­ing any kind of Throwable is always the fol­low­ing: Can my appli­ca­tion recov­er from it? If the appli­ca­tion can recov­er, it should catch. If it can’t (or is most like­ly unable to) recov­er, it shouldn’t.

From the (excel­lent) Effec­tive Java — Sec­ond Edi­tion” by Joshua Bloch, Chap­ter 9, Item 58:

If a pro­gram throws an unchecked excep­tion or an error, it is gen­er­al­ly the case that recov­ery is impos­si­ble and con­tin­ued exe­cu­tion would do more harm than good.

Now, the task is to deter­mine whether we can recov­er or not. The type of Throwable gives us a hint, but it’s real­ly depend­ing on the actu­al situation.

Exam­ples #

Error #

This exam­ple is from the orig­i­nal ques­tion linked above:

[…] I’m cur­rent­ly load­ing a DLL that is not guar­an­teed to be on the PATH, and would like to switch to a user-con­fig­ured loca­tion in the case that it isn’t.

The code is giv­en as follows:

try {
    System.loadLibrary("HelloWorld");
} catch(UnsatisfiedLinkError ule){
    System.load("C:/libraries/HelloWorld.dll");
}

Quot­ing from the java.lang.System-JavaDoc, the UnsatisfiedLinkError is thrown…

Throws: Unsat­is­fiedLink­Er­ror — if the library does not exist.

So, can we recov­er? The appli­ca­tion tries to load the library from the PATH. If this does not suc­ceed, there is a fall­back loca­tion to look for the library. Hence, the appli­ca­tion is able to recov­er from the error-condition.

So, in this exam­ple, it’s per­fect­ly rea­son­able to catch the Error. Whether or not the library is guar­an­teed to be at the fall­back loca­tion is out­side the scope of this example!

Run­time­Ex­cep­tion #

Imag­ine you’re try­ing to parse a CSV-file con­tain­ing inte­gers. Read­ing the file line by line using a BufferedReader, we get a string per line. If you want inte­gers, you need to parse.

Unfor­tu­nate­ly, it’s not guar­an­teed that what you expect to be an inte­ger in the CSV file is actu­al­ly even a num­ber. One pos­si­bil­i­ty is, that the val­ue is emp­ty, in which case you want to use a default val­ue instead:

try {
    last_int = Integer.parseInt(foo); // Where foo is a string-value from the CSV
} catch (NumberFormatException e){
    last_int = 1; // As our default value
}

From the JavaDoc of java.lang.Integer#parseInt(String):

Throws: Num­ber­For­ma­tEx­cep­tion — if the string does not con­tain a parsable integer.

And, believe it or not, an emp­ty string is not a parsable integer:

Exception in thread "main" java.lang.NumberFormatException: For input string: ""
    at java.lang.NumberFormatException.forInputString(Unknown Source)
    at java.lang.Integer.parseInt(Unknown Source)

So again, we’re catch­ing an excep­tion which we are (by con­ven­tion) not sup­posed to. In this case again, we’re able to recov­er from it and can con­tin­ue the exe­cu­tion with­out problems.

One could now argue that it might be a design-error to make the NumberFormatException-class a run­time excep­tion, but as men­tioned above, run­time excep­tions indi­cate pro­gram­ming errors. And since it is pos­si­ble to know if a string is a real inte­ger before pars­ing it, it can be con­sid­ered a pro­gram­ming error to not check first. For fur­ther read­ing, check this SO ques­tion.

Addi­tion­al­ly, using checked excep­tions exten­sive­ly tends to clut­ter call­ing code with try/catch blocks or add ridicu­lous amounts of throws dec­la­ra­tions on meth­ods. Check the design advices” below for fur­ther reading.

Excep­tion #

If an appli­ca­tion is like­ly to not recov­er from a thrown, checked, Exception, then mak­ing it a checked excep­tion was a design-error in the first place.

None the less, such cas­es need to be han­dled, since the com­pil­er wont let you com­pile the pro­gram with­out catch­ing this exception. 

Since we’ve already estab­lished that we aren’t able to recov­er from the Exception and should real­ly stop the pro­gram, let’s do so using a RuntimeException but give all avail­able infor­ma­tion by spec­i­fy­ing the orig­i­nal excep­tion as the cause:

try {
    ifThisDoesntWorkWereScrewed();
} catch (UnrecoverableCheckedException e) {
  throw RuntimeException("Can't recover from this!", e);
}

This way the pre­vi­ous­ly checked excep­tion now behaves as it should, while no infor­ma­tion is lost because it’s stack-trace is includ­ed in the new­ly cre­at­ed RuntimeException. Note that this is a workaround and should ide­al­ly nev­er be necessary!

Design advices #

To round every­thing up, some advices if you’re writ­ing an API or library yourself.

Mak­ing the ball #

As a gen­er­al advice when cre­at­ing a library/​API: Favor the use of stan­dard excep­tions. Again, quot­ing from Effec­tive Java — Sec­ond Edi­tion”, Chap­ter 9, Item 60:

Reusing pre­ex­ist­ing excep­tions has sev­er­al ben­e­fits. Chief among these, it makes your API eas­i­er to learn and use because it match­es estab­lished con­ven­tions with which pro­gram­mers are already famil­iar. A close sec­ond is that pro­grams using your API are eas­i­er to read because they aren’t clut­tered with unfa­mil­iar excep­tions. […]

But, aside from this, there are cas­es when you need to cre­ate your own excep­tions because you want to express some­thing more spe­cif­ic, or there is just no stan­dard excep­tion which meets your needs.

In these cas­es, do so, but nev­er extend Throwable direct­ly! It’s not that the com­pil­er wont let you (it will work like a checked excep­tion”, just like the Exception-class), the Java Lan­guage Specs do not address such throw­ables direct­ly. So, you’re going into unde­fined behav­ior, which should always be avoided.

Instead, extend spe­cif­ic sub­class­es of Exception or RuntimeException (to get a new checked-” or unchecked-excep­tion” respec­tive­ly). It’s by con­ven­tion not wrong to make your new CSVParsingException extend the basic Exception-class, but extend­ing the more spe­cif­ic ParseException (which is a sub­class of Exception — there­for a checked excep­tion) makes for a clean­er picture.

Of course if you can’t find any more spe­cif­ic” sub­class­es, you’ll need to extend the basic Exception- or RuntimeException-class­es.

Also, be sure to add a con­struc­tor which takes a Throwable cause-para­me­ter to pre­serve infor­ma­tion from the orig­i­nal­ly thrown Excep­tion when re-throw­ing (see fur­ther down). The basic Exception-class offers such a con­struc­tor which you can sim­ply overwrite.

A word on extend­ing Error: Don’t do it. As men­tioned above, errors are (by con­ven­tion) reserved for low-lev­el prob­lems (with the VM). Instead, use a RuntimeException.

Throw­ing the ball #

Think twice before you throw. Checked excep­tions are a pow­er­ful lan­guage-fea­ture, because oth­er than return types, you can force the devel­op­er to han­dle them. This can be nice but can also make the result­ing code very bloat­ed, as every thrown excep­tion needs to be han­dled in it’s own catch-block (yes, there is a Java 7 fea­ture for that…).

So, think­ing about whether to throw a checked or unchecked excep­tion can make your API/​library more pleas­ant to use. As a rule of thumb con­sid­er the following:

The bur­den [of han­dling a checked excep­tion] is jus­ti­fied if the excep­tion­al con­di­tion can­not be pre­vent­ed by prop­er use of the API and the pro­gram­mer using the API can take some use­ful action once con­front­ed with the excep­tion. Unless both of these con­di­tions hold, an unchecked excep­tion is more appropriate.

From Effec­tive Java — Sec­ond Edi­tion”, Chap­ter 9, Item 59

Just like in the above Mak­ing the ball” sec­tion, a short word on throw­ing Error: Don’t do it. Since we already clar­i­fied that errors are reserved for low-lev­el prob­lems, there should be no need to throw them yourself.

Catch­ing the ball #

Excep­tions tend to be ignored. For unchecked excep­tions and errors, this might be desired (in most cas­es, see above), but it com­plete­ly defeats the pur­pose of throw­ing a checked excep­tion. I have seen this in var­i­ous places (even in the Java Docs):

// Bad practice example. DON'T DO THIS!
try {
    somethingThatThrows();
} catch (SomeException ignored){
    // Can't happen...
}

… until the day it does. Also bad:

// Bad practice example. DON'T DO THIS!
try {
    somethingThatTrows();
} catch (SpecificException e){
    throw new OtherException("Doesn't work");
}

While this exam­ple will throw an excep­tion, it total­ly dis­cards any (use­ful) infor­ma­tion from the pre­vi­ous­ly thrown SpecificException. It’s not gen­er­al­ly wrong to re-throw an excep­tion, but you should either print the stack-trace of the orig­i­nal one, or (even bet­ter) include it as the cause in the new Excep­tion. Every excep­tion from the stan­dard API has a con­struc­tor which accepts a Throwable cause as an argu­ment (and your cus­tom ones should, too). Use it!

throw new OtherException("Doesn't work", e);

And now, the worst pos­si­ble approach:

// Bad practice example. DON'T DO THIS!
try {
    somethingThatThrows();
    somethingElseThatThrows();
} catch (Exception ignore) {
    // Ignored.
}

The last exam­ple (some­times referred to as Poke­mon Excep­tion Han­dling”) is gen­er­al­ly dis­card­ing all pos­si­bly thrown excep­tions (includ­ing the unchecked ones). If some­thing goes wrong in this piece of code, your pro­gram wont crash but it wont behave as expect­ed either. And you won’t get any infor­ma­tion about it.

Excep­tions are not your ene­mies. They are a help­ing hand from the run­time. So don’t aban­don them. If there is real­ly noth­ing you can (or want) to do about an excep­tion, at least print the stack-trace (when in devel­op­ing stage) or log them any­where (when in pro­duc­tive state). Oth­er­wise, some fine day, you might suf­fer from the revenge of the for­got­ten ones.

Catch­ing generally #

Last but not least, an advice on catch­ing more gen­er­al errors: Do it with extreme caution

Third par­ty code in your appli­ca­tion (such as libraries or even the stan­dard library) might change with a new ver­sion of the library. A gen­er­al catch­ing block could swal­low a new­ly added excep­tion and sud­den­ly your appli­ca­tions code (which was­n’t even touched) stops work­ing as expected.

The only thing you should do in gen­er­al­ized catch-blocks is log­ging. For exam­ple, the fol­low­ing is per­fect­ly reasonable:

try {
    somethingThatThrows();
} catch (SpecificException e){
    // Handle the specific exception here
} catch (OtherSpecificException e){
    // Handle the other specific exception here
} catch (GeneralException e){
    e.printStackTrace();
    logger.error(e);
}

The impor­tant part is, that the more spe­cif­ic excep­tions need to be caught before the more gen­er­al one. And when doing so, GeneralException should be as spe­cif­ic as you can make it.

Return­ing from finally #

As you are han­dling excep­tions in your code, you should remem­ber to nev­er return in a finally-block!. This can become a seri­ous prob­lem, as out­lined in James Stauf­fer­’s arti­cle:

If you return in a final­ly block then any Throwables that aren’t caught (in a catch block that is part of the same try state­ment as the finally block) will be com­plete­ly lost. The real­ly bad part about this is that it looks so inno­cent.
[…]
Note this also applies to oth­er things that trans­fer con­trol (con­tin­ue, throw, etc).

So keep an eye out for that.

Con­clu­sion #

  • If you can recov­er from an exception/​error, don’t hes­i­tate to catch it.
  • Pre­fer the default excep­tions over cre­at­ing your own.
  • When cre­at­ing your own excep­tions, don’t extend Throwable direct­ly and don’t extend Error.
  • Many checked excep­tions make the call­ing code bloated.
  • Nev­er ignore or dis­card excep­tions with­out at least log­ging them.
  • Nev­er return from with­in a finally-block.

Posted by Lukas Knuth

Comments

No com­ment sec­tion here 😄

You can reach me over at @knuth_dev or send me an Email.