Home » Java » java – Spring mvc thymeleaf. How to show whitelabel error page as modal or popup or toast-Exceptionshub

java – Spring mvc thymeleaf. How to show whitelabel error page as modal or popup or toast-Exceptionshub

Posted by: admin February 25, 2020 Leave a comment

Questions:

I have a problem with error displaying in my spring mvc app. So let me describe more detail about problem.

I already know what to do if i want to show custom error page instead of default whitelabel error page. I read about this here and watched some tutorials on youtube. It is simple, but the problem is that user will always redirected to new page, containing error description.

I want to display all my backend exceptions in specific format as a modal window, or alert message or toast as shown here but for thymeleaf. Best solution is modal pages, cause this give me more flexibility of content displaying. But modal can be invoked used JavaScript on frontend side, how to use controller exceptions as trigger for opening modal page (without using AJAX if possible)?

Here is what i want actually, when some method in controller, that can throw an exception is called and exception thrown

@GetMapping
public ModelAndView throwException() throws Exception {
    throw new Exception("Oops :( something went wrong");
}

something like this shall be displayed
error window

Please help me to find a solution. Thanks a lot for your attention.

P.S. if this is complicated or impossible i want to know atleast how to show error message from controller + some buttons or input fields related to error on current(same) page, without redirecting to new one?

How to&Answers:

TL;DR

No easy way out. If I were you I’d stick with generic error pages. You can customize them for diffent kind of errors.

Tooling inadequate to your problem.

IMO The problem you’ve described would be better suited for SPA (single page application) written
in any modern frontend framework with java backend exposing REST API. Normally enpoint would return
json containing actual, requested data. But in case of any error you could
simply return 4xx code with description of an error as a payload. Then
presentation logic in javascript could display it whatever you want without page reload.

Since you’ve decided to use thymeleaf which is templating engine you should accept its limitations. Most of calls to a controller
is a request for a view. Spring and Thymeleaf takes a template file, populates it with model’s data and
send back the whole page to your browser. If this process fails while collecting data the page couldn’t be correctly rendered. Therefore the most reasonable
thing to do is to redirect to the error page instead.

Reasonable way out

If generic error pages scares you off, some extra effort can be made. Instead of generic error handling, you
could handle exceptions in each Controller’s method.
On error you may provide different “errored” model that will be used to populate the template. It gives you
flexibility on how the error is displayed. It would depend on how you had prepared a template.

E.g.

view.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
</head>
<body>
    <h1>Data:</h1>
    <div th:if="${actualData}">
        <p th:text="${actualData}"></p>
    </div>
    <!-- Instead of div below, you could supply a modal and pice of javascript opening it -->
    <div th:if="${errorData}">
        <h3>Error occurred:</h3>
        <p th:text="${errorData}"></p>
    </div>
</body>
</html>

MyController.java

@Controller
class MyController {

    @GetMapping("/view")
    public String view(Model model) {
        try {
            model.addAttribute("actualData", mayThrowExceptionDataFetch());
        } catch(Exception e) {
            model.addAttribute("errorData", e.getMessage());
        }
        return "view";
    }

    private String mayThrowExceptionDataFetch() {
        // Either return a value or throw an exception
    }
}

That way a view will always be returned. Even when error happens it is handled and displayed.

Unreasonable way out

Important note: this is demo only. For sure I wouldn’t try it in production.

I found your requriements so unusual (controller exceptions as trigger for opening modal page) that I wanted to check
if something alike is even possible. I’ve imagined a solution may be something like:

  1. Frontend: subscribe to server send events endpoint (source of errors).
  2. Backend: while catching an exception, abort the request so that server doesn’t return anything.
  3. Backend: send a message containing error description to appriopriate client.
  4. Frontend: receive event, alert it somehow.

Then I come up with this working demo, that indeed doesn’t cause page reload yet displays an error:

index.html

<!DOCTYPE HTML>
<html xmlns:th="https://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" />
</head>
<body>
    <a th:href="@{/exception}">Click for exception</a>
    <script>
        var sse = new EventSource('http://localhost:8080/sse');
        sse.onmessage = function (evt) {
            alert(evt.data);
        };
    </script>
</body>
</html>

MyService.java

@Service
public class MyService {

    private final SseEmitter emitter = new SseEmitter();

    public SseEmitter getEmitter() {
        return emitter;
    }
}

MyExceptionHandler.java

@ControllerAdvice
public class MyExceptionHandler {

    private final MyService service;

    @Autowired
    public MyExceptionHandler(MyService service) {
        this.service = service;
    }

    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.CONTINUE) // I don't figure out how to abort request
    public ResponseEntity<Void> handle(Exception ex) {
        try {
            service.getEmitter().send(SseEmitter.event().data(ex.getMessage()));
        } catch (IOException e) {
            // TODO
        }
    }
}

MyController.java

@Controller
public class MyController {

    private final MyService service;

    @Autowired
    public MyCtrl(MyService service) {
        this.service = service;
    }

    @GetMapping("/sse")
    public SseEmitter sse() {
        return service.getEmitter();
    }

    @GetMapping("/index")
    public String index() {
        return "index";
    }

    @GetMapping("/exception")
    public String exception() {
        throw new RuntimeException("Oops :o");
    }
}

Everyting above is spring-boot project with spring-boot-starter-thymeleaf and spring-boot-starter-web dependencies.
Run application, go to localhost:8080/index then click a link. An alert should appear without page reload.