Many knows the tradeoff of using exceptions while designing an application:
- On one hand, using try-catch block nicely segregates between regular code and exception handling code
- On the other hand, using exceptions has a definite performance cost for the JVM
Every time I’ve been facing this quandary, I’ve ruled in favor of the former, because "premature optimization is evil". However, this week has proved me that exception handling in designing an API is a very serious decision.
I’ve been working to improve the performances of our application and I’ve noticed many silent catches coming from the Spring framework (with the help of the excellent dynaTrace tool).
The guilty lines comes from the RequestContext.initContext()
method:
if (this.webApplicationContext.containsBean(REQUEST_DATA_VALUE_PROCESSOR_BEAN_NAME)) {
this.requestDataValueProcessor = this.webApplicationContext.getBean(
REQUEST_DATA_VALUE_PROCESSOR_BEAN_NAME, RequestDataValueProcessor.class);
}
Looking at the JavaDocs, it is clear that this method (and the lines above) are called each time the Spring framework handles a request.
For web applications under heavy load, this means quite a lot!
I provided a pass-through implementation of the RequestDataValueProcessor
and patched one node of the cluster.
After running more tests, we noticed response times were on average 5% faster on the patched node compared to the un-patched node.
This is not my point however.
Should an exception be thrown when the bean is not present in the context? I think not… as the above snippet confirms. Other situations e.g. injecting dependencies, might call for an exception to be thrown, but in this case, it has to be the responsibility of the caller code to throw it or not, depending on the exact situation.
There are plenty of viable alternatives to exceptions throwing:
- Returning
null
-
This means the intent of the code is not explicit without looking at the JavaDocs, and so the worst option on our list
- Returning an
Optional<T>
-
This makes the intent explicit compared to returning
null
. Of course, this requires Java 8 - Return a Guava’s
Optional<T>
-
For those of us who are not fortunate enough to have Java 8
- Returning one’s own
Optional<T>
-
If you don’t use Guava and prefer to embed your own copy of the class instead of relying on an external library
- Returning a
Try
-
Cook up something like Scala’s
Try
, which wraps either (hence its old name -Either
) the returned bean or an exception. In this case, however, the exception is not thrown but used like any other object - hence there will be no performance problem.
Conclusion: when designing an API, one should really keep using exceptions for exceptional situations only.
As for the current situation, Spring’s BeanFactory
class lies at center of a web of dependencies and its multiple getBean()
method implementation cannot be easily replaced with one of the above options without forfeiting backward compatibility.
One solution, however, would be to provide additional getBeanSafe()
methods (or a better relevant name) using one of the above options, and then replace usage of their original counterpart step by step inside the Spring framework.