Thymeleaf 3 Exception Handling

classic Classic list List threaded Threaded
11 messages Options
Reply | Threaded
Open this post in threaded view
|

Thymeleaf 3 Exception Handling

dtrunk90
When rendering templates and an exception happens the <error-page> (web.xml) is getting rendered directly there where the exception happened. With status code 200 (OK). I tried to do this: http://www.mkyong.com/spring-mvc/how-to-register-a-servlet-filter-in-spring-mvc/ which is giving an IllegalStateException: Cannot forward after response has been committed.

What I want to do is displaying the error page with status code 500 if a thymeleaf exception occurs.

My current infrastructure: Spring 4.2.6 + Thymeleaf 3. I've already created a @ControllerAdvice class with a @ExceptionHandler(Exception.class) method as well as a @RequestMapping for /error.html. But that only triggers inside Spring MVC.

How to handle Thymeleaf exceptions?
Reply | Threaded
Open this post in threaded view
|

Re: Thymeleaf 3 Exception Handling

HersheySquirt
You could try adding an ExceptionHandler to your controller or base controller class to log the exception and return an error page or re-throw the exception to let it percolate up to the error page as defined in your web.xml.

    @ExceptionHandler(RuntimeException.class)
    @ResponseBody
    public RuntimeException processRuntimeException(final HttpServletRequest req, final RuntimeException ex) {
        LOG.error("Uncaught exception", ex);
        throw ex;
    }
Reply | Threaded
Open this post in threaded view
|

Re: Thymeleaf 3 Exception Handling

dtrunk90
This post was updated on .
Template Rendering isn't part of the controller layer. That's cause an @ExceptionHandler neither in my @Controller nor in an @ControllerAdvice class is getting triggered.
Reply | Threaded
Open this post in threaded view
|

Re: Thymeleaf 3 Exception Handling

danielfernandez
Administrator
Your case seems to be that Thymeleaf has already created output enough for your output buffer to be commited when the exception happens. This is in relation to the new architecture in Thymeleaf 3.0, which streamlines the creation of output during processing. See https://github.com/thymeleaf/thymeleaf/issues/389

Whereas Thymeleaf 2.1 had to wait for the entire template to be processed before starting to send template output to the output channels, this new architecture reduces template latency by producing output as soon as possible. This actually matches what other template engines do (e.g. JSP).

Reply | Threaded
Open this post in threaded view
|

Re: Thymeleaf 3 Exception Handling

mrusso
So how are we supposed to do proper error handling in Thymeleaf 3?

When an error occurs during template processing, I want to show a correctly formatted error page instead of the partially rendered page containing an error.

In Thymeleaf 3, I instead get invalid HTML because the error page is appended to the partially formatted page.

page with error:
<html>
<body>
<div class="content">
  <span th:text=${bad-expression}></span>
<div>
</body>
</html>

default error page:
<html>
<body>
<div>
  An error occurred.
<div>
</body>
</html>

How Thymeleaf 3 renders this page:
<html>
<body>
<div class="content">
  <html>
<body>
<div>
  An error occurred.
<div>
</body>
</html>

Thymeleaf 2 would only show the error page. I don't know how to produce a proper error page given Thymeleaf 3's error handling behavior.
Reply | Threaded
Open this post in threaded view
|

Re: Thymeleaf 3 Exception Handling

mrusso
This is a pretty serious issue for us. Are we doing something wrong or is this the intended behavior of error handling with Thymeleaf 3?
Reply | Threaded
Open this post in threaded view
|

Re: Thymeleaf 3 Exception Handling

danielfernandez
Administrator
More than an "intended behaviour", it is the natural consequence of the adoption of a streamlined execution flow for templates. If by the time an error appears so much content has been written to the response output stream that the buffer has filled up and been flushed (and therefore content has been sent to the browser), the response itself can no longer change to be an HTTP error response instead of an HTTP OK response. The HTTP OK has already been sent along with the committed partial content.

Note this is the behaviour of almost every other server-side Java template system. Also note this only affects errors happening at the view layer, not the controller or any other layers of the software.

If you need any view-layer-triggered errors to be mandatorily converted to HTTP ERROR responses at any point in the construction of the response, then I'd suggest generating output as a String using the corresponding ITemplateEngine.process() methods that do not receive a Writer as an argument, surrounding such generation with a try...catch, and only if no errors have appeared writing the result String to the response output writer.

Regards,
Daniel.
Reply | Threaded
Open this post in threaded view
|

Re: Thymeleaf 3 Exception Handling

mrusso
Ok, thanks for the clarification. I understand the behavior now. I guess I've been away from non-Thymeleaf template systems long enough to forget about this common behavior. :-)
Reply | Threaded
Open this post in threaded view
|

Re: Thymeleaf 3 Exception Handling

dtrunk90
In reply to this post by danielfernandez
Could you please elaborate more on how to use another ITemplateEngine.process() method at user side?
Reply | Threaded
Open this post in threaded view
|

Re: Thymeleaf 3 Exception Handling

mrusso
This post was updated on .
If you're calling the ITemplateEngine directly, it's pretty easy. Just call templateEngine.process(template, context) -- without passing in a Writer -- and it returns a String.

If you're using Spring MVC, that framework is configured to integrate with Thymeleaf via the ThymeleafViewResolver, which by default uses the ThymeleafView class. This is the code that calls ITemplateEngine.process(), passing in a Writer.

So you could customize this behavior by setting a custom impl of the templateEngine or viewClass in the ThymeleafViewResolver. I've tested this out with a custom template engine to replace the SpringTemplateEngine. It uses delegation to pass-through most calls to the SpringTemplateEngine, but it overrides the process(..., Writer writer) calls, instead calling the String-based process call and writing this full value to the result.

Why not just extend SpringTemplateEngine and @Override the necessary process methods? Those methods are actually implemented in TemplateEngine and are marked final.

Here's an example:

public class CustomTemplateEngine implements ITemplateEngine, MessageSourceAware, InitializingBean {
   
    private final SpringTemplateEngine templateEngine = new SpringTemplateEngine();
   
    // simply delegate most calls, like this:
    @Override
    public String process(String template, IContext context) {
        return templateEngine.process(template, context);
    }
   
    // customize methods that pass a writer to use the String-based call instead
    @Override
    public void process(String template, IContext context, Writer writer) {
        String result = templateEngine.process(template, context);
        try {
            writer.write(result);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
   
    @Override
    public void process(String template, Set<String> templateSelectors, IContext context, Writer writer) {
        String result = templateEngine.process(template, templateSelectors, context);
        try {
            writer.write(result);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
   
    @Override
    public void process(TemplateSpec templateSpec, IContext context, Writer writer) {
        String result = templateEngine.process(templateSpec, context);
        try {
            writer.write(result);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
   
    // delegate all other overrides
   
    @Override
    public void afterPropertiesSet() throws Exception {
        templateEngine.afterPropertiesSet();
    }
}

Not saying this is a recommended approach or without its own issues, but it did work for me as a test.
Reply | Threaded
Open this post in threaded view
|

Re: Thymeleaf 3 Exception Handling

dtrunk90
This post was updated on .
That's a nonsatisfying solution for such a common use-case. If an error happens at view layer, the servlet dispatcher should forward to the error page with HTTP status code 500. I've created an issue at GitHub: https://github.com/thymeleaf/thymeleaf-spring/issues/113