3

According to Servlet Specification:

A servlet or filter may throw the following exceptions during processing of a request:

  1. runtime exceptions or errors
  2. ServletExceptions or subclasses thereof
  3. IOExceptions or subclasses thereof

If we look at org.springframework.web.servlet.FrameworkServlet#processRequest, we'll see that Spring throws ServletException and IOException, but wraps others including RuntimeExceptions:

try {
    doService(request, response);
} catch (ServletException | IOException ex) {
    failureCause = ex;
    throw ex;
} catch (Throwable ex) {
    failureCause = ex;
    throw new NestedServletException("Request processing failed", ex);
}

Why doesn't Spring handle RuntimeException like IOException?

UPD: In other words, what wrong would happen if they handle exceptions this way:

try {
    doService(request, response);
} catch (ServletException | IOException | RuntimeException ex) {
    failureCause = ex;
    throw ex;
} catch (Throwable ex) {
    failureCause = ex;
    throw new NestedServletException("Request processing failed", ex);
}
  • 3
    https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/util/NestedServletException.html – Sotirios Delimanolis Sep 25 '19 at 15:01
  • @SotiriosDelimanolis, thanks, I've already read this, but I still don't understand why spring do that. For instance, why it doesn't wrap IOException and ServletException then? – Aleksandr Semyannikov Sep 25 '19 at 15:09
  • What value would a `NestedServletException` with a root cause of `ServletException` (with no root cause of its own) have over just the `ServletException`? The `IOException` doesn't have the limitation described in the linked javadoc and so also doesn't need to be wrapped. – Sotirios Delimanolis Sep 25 '19 at 15:11
  • @SotiriosDelimanolis, what limitation RuntimeException has if compared to IOException? If you would be kind to explain it in your answer I would be happy to mark it as right answer, because what is written in javadoc about the reasons is convoluted for me. – Aleksandr Semyannikov Sep 25 '19 at 15:19
  • 1
    By reading the javadoc it seems that ServletException is indeed the one with certain limitations (no root cause or message) yet is still not wrapped. Not sure which limitation were attributed to IOException in previous comments, to me is the same as ServletException being direct descendant of Exception. Finally I believe it to be a design choice, I personally wrap exception together when I want my system to react to a set of exception in the same way. I include runtime exception when I want my system to react gracefully and not simply blow up. Servlet and IO will have specific treatment. – Sergio Arrighi Nov 19 '19 at 09:39

1 Answers1

0

This is required because the protected abstract doService method invoked inside processRequest can throw any checked or unchecked Exception.

protected abstract void doService(HttpServletRequest request, HttpServletResponse response) throws Exception;

Now, imagine that you override doService like:

@Override
protected void doService(Long request, Long response) throws ClassNotFoundException {
// whatever ...
}

This would be perfectly legitimate but then processRequest will be potentially throwing a ClassNotFoundException which you will have to declare as a checked exception. And because processRequest is used by doGet, doPost and the other API methods, these will have to declare ClassNotFoundException as well, breaking the specification.

Note that this applies mostly to the case when doService is not the implementation provided by org.springframework.web.servlet.DispatcherServlet. doService in DispatcherServlet calls doDispatch and doDispatch already catches everything but ServletException or IOException. However, there is still the need of catching the exceptions again because the declaration of doService and doDispatch do not restrict the list of their checked exceptions to ServletException and IOException.

Edit 21/11/2019:

I do not see any trouble in your alternative exception handling convention. Indeed is closer to the spec interpreted literally. The implementation is just wrapping everything which is not a ServletException or IOException. It might be because later a handler is sensitive to the exception type. But this is not the case at least for the DefaultHandlerExceptionResolver used by the DispatcherServlet.

I can't see neither any obvious problem which could affect error handling in web.xml with <error-page><exception-type>...</exception-type></error-page> or using a @Component implementing ErrorPageRegistrar.

Serg M Ten
  • 5,568
  • 4
  • 25
  • 48
  • Hello, thank you for paying attention to this question. I think you misunderstood it a little, the question is: why they dont handle runtime exceptions as ServletException or IOException, they could do it like `catch (ServletException | IOException | RuntimeException ex)`. Javadoc says that a root cause can be missed, but why it can't be missed if IOException occurred? – Aleksandr Semyannikov Nov 20 '19 at 09:57
  • Hi! Because `RuntimeException` is a subclass of `Exception` and thus `(ServletException | IOException | RuntimeException ex)` would not catch every possible exception thrown by `doService` And because it wants to apply a `NestedServletException` wrapper only when actually needed in order to minimize the stack trace length in the logs. In my opinion, it goes too far with `catch (Throwable ex)` because this will catch really bad things like `java.lang.VirtualMachineError` I think it should better do just `catch (Exception ex)`. – Serg M Ten Nov 20 '19 at 10:19
  • About the root cause, what the spec v3.1 section 9.5 says is : " If the servlet ... throws a runtime exception or a checked exception of type ServletException or IOException, it should be propagated to the calling servlet. All other exceptions should be wrapped as ServletExceptions and the root cause of the exception set to the original exception, as it should not be propagated". So an `IOException` may or may not have a cause. It is only for the exceptions wrapped with a subclass of `ServletException` for which the cause is mandatory being this cause the original RuntimeException. – Serg M Ten Nov 20 '19 at 10:19
  • I've updated the post, could you please modify your answer according to that? – Aleksandr Semyannikov Nov 20 '19 at 11:45