Java if vs. try/catch overhead
Is there any overhead in Java for using a try/catch block, as opposed to an if block (assuming that the enclosed code otherwise does not request so)?
For example, take the following two simple implementations of a "safe trim" method for strings:
public String tryTrim(String raw) {
try {
return raw.trim();
} catch (Exception e) {
}
return null;
}
public String ifTrim(String raw) {
if (raw == null) {
return null;
}
return raw.trim();
}
If the raw
input is only rarely null
, is there any performance difference between the two methods?
Furthermore, is it a good programming pattern to use the tryTrim()
approach for simplifying the layout of code, especially when many if blocks checking rare error conditions can be avoided by enclosing the code in one try/catch block?
For example, it is a common case to have a method with N parameters
, which uses M <= N
of them near its start, failing quickly and deterministically if any such parameter is "invalid" (e.g., a null or empty string), without affecting the rest of the code.
In such cases, instead of having to write k * M
if blocks (where k
is the average number of checks per parameter, e.g. k = 2
for null or empty strings), a try/catch block would significantly shorten the code and a 1-2 line comment could be used to explicitly note the "unconventional" logic.
Such a pattern would also speed up the method, especially if the error conditions occur rarely, and it would do so without compromising program safety (assuming that the error conditions are "normal", e.g. as in a string processing method where null or empty values are acceptable, albeit seldom in presence).
Solution 1:
I know you're asking about performance overhead, but you really should not use try
/catch
and if
interchangeably.
try
/catch
is for things that go wrong that are outside of your control and not in the normal program flow. For example, trying to write to a file and the file system is full? That situation should typically be handled with try
/catch
.
if
statements should be normal flow and ordinary error checking. So, for example, user fails to populate a required input field? Use if
for that, not try
/catch
.
It seems to me that your example code strongly suggests that the correct approach there is an if
statement and not a try
/catch
.
To answer your question, I would surmise that there is generally more overhead in a try
/catch
than an if
. To know for sure, get a Java profiler and find out for the specific code you care about. It's possible that the answer may vary depending on the situation.
Solution 2:
This question has almost been "answered to death", but I think there are a few more points that could usefully be made:
-
Using
try / catch
for non-exceptional control flow is bad style (in Java). (There is often debate about what "non-exceptional" means ... but that's a different topic.) -
Part of the reason it is bad style is that
try / catch
is orders of magnitude more expensive than an regular control flow statement1. The actual difference depends on the program and the platform, but I'd expect it to be 1000 or more times more expensive. Among other things, the creation the exception object captures a stack trace, looking up and copying information about each frame on the stack. The deeper the stack is, the more that needs to be copied. -
Another part of the reason it is bad style is that the code is harder to read.
1 - The JIT in recent versions of Java can optimize exception handling to drastically reduce the overheads in some cases. However, these optimizations are not enabled by default.
There are also issues with the way that you've written the example:
-
Catching
Exception
is very bad practice, because there is a chance that you will catch other unchecked exceptions by accident. For instance, if you did that around a call toraw.substring(1)
you would also catch potentialStringIndexOutOfBoundsException
s ... and hide bugs. -
What your example is trying to do is (probably) a result of poor practice in dealing with
null
strings. As a general principle, you should try to minimize the use ofnull
strings, and attempt to limit their (intentional) spread. Where possible, use an empty string instead ofnull
to mean "no value". And when you do have a case where you need to pass or return anull
string, document it clearly in your method javadocs. If your methods get called with anull
when they shouldn't ... it is a bug. Let it throw an exception. Don't try to compensate for the bug by (in this example) returningnull
.
My question was more general, not only for
null
values.
... and most of the points in my answer are not about null
values!
But please bear in mind that there are many cases where you want to allow the occasional null value, or any other value that could produce an exception, and just ignore them. This is the case, for example, when reading key/pair values from somewhere and passing them to a method like the tryTrim() above.
Yes there are situations where null
values are expected, and you need deal with them.
But I would argue that what tryTrim()
is doing is (typically) the wrong way to deal with null
. Compare these three bits of code:
// Version 1
String param = httpRequest.getParameter("foo");
String trimmed = tryTrim(param);
if (trimmed == null) {
// deal with case of 'foo' parameter absent
} else {
// deal with case of 'foo' parameter present
}
// Version 2
String param = httpRequest.getParameter("foo");
if (param == null) {
// deal with case of 'foo' parameter absent
} else {
String trimmed = param.trim();
// deal with case of 'foo' parameter present
}
// Version 3
String param = httpRequest.getParameter("foo");
if (param == null) {
// treat missing and empty parameters the same
param = "";
}
String trimmed = param.trim();
Ultimately you have to deal with the null
differently from a regular string, and it is usually a good idea do this as soon as possible. The further the null
is allowed to propagate from its point origin, the more likely it is that the programmer will forget that a null
value is a possibility, and write buggy code that assumes a non-null value. And forgetting that an HTTP request parameter could be missing (i.e. param == null
) is a classic case where this happens.
I'm not saying that tryTrim()
is inherently bad. But the fact that you feel the need write methods like this is probably indicative of less than ideal null handling.
Finally, there are other ways to model "there isn't a value" in an API. These include:
- Test for unexpected
null
in setters and constructors, and either throw an exception or substitute a different value. - Add an
is<Field>Set
method ... and throwing an exception inget<Field>
if the result would benull
. - Use the Null Object Pattern.
- Use empty strings, empty collections, zero length arrays, etc instead of
null
strings, collections or arrays1. - Using
Optional
.
1 - To mind, this is different from the Null Object Pattern because there isn't necessarily just one instance of the type to denote "nullity". But you can combine the two ideas ... if you are disciplined about it.
Solution 3:
Use the second version. Never use exceptions for control flow when other alternatives are available, as that is not what they are there for. Exceptions are for exceptional circumstances.
While on the topic, do not catch Exception
here, and especially do not swallow it. In your case, you would expect a NullPointerException
. If you were to catch something, that is what you would catch (but go back to paragraph one, do not do this). When you catch (and swallow!) Exception
, you are saying "no matter what goes wrong, I can handle it. I don't care what it is." Your program might be in an irrevocable state! Only catch what you are prepared to deal with, let everything else propogate to a layer that can deal with it, even if that layer is the top layer and all it does is log the exception and then hit the eject switch.