Router Exception Handling

Router provides special support for navigation target exceptions. When an unhandled exception is thrown during navigation, the user is shown an error view.

Exception targets generally work in the same way as regular navigation targets, except they typically do not have a specific @Route because they are shown for arbitrary URLs.

Error Resolving

Errors in navigation are resolved to a target that is based on the exception type thrown during navigation.

At startup, all classes implementing the HasErrorParameter<T extends Exception> interface are collected for use as exception targets during navigation. Example classes include RouteNotFoundError for NotFoundException.

Example: RouteNotFoundError defines the default target for the NotFoundException that is shown when there is no target for the given URL.

Java

  1. @Tag(Tag.DIV)
  2. public class RouteNotFoundError extends Component
  3. implements HasErrorParameter<NotFoundException> {
  4. @Override
  5. public int setErrorParameter(BeforeEnterEvent event,
  6. ErrorParameter<NotFoundException> parameter) {
  7. getElement().setText("Could not navigate to '"
  8. + event.getLocation().getPath()
  9. + "'");
  10. return HttpServletResponse.SC_NOT_FOUND;
  11. }
  12. }
  • This returns a 404 HTTP response and displays the setText to the user.

The exception matching order is first by exception cause and then by exception super type.

The 404 RouteNotFoundError (for NotFoundException), and 500 InternalServerError (for java.lang.Exception) are implemented by default.

Custom Exception Handlers

You can override the default exception handlers by extending them.

Example: Custom route not found handler that uses a custom application layout

Java

  1. @ParentLayout(MainLayout.class)
  2. public class CustomNotFoundTarget
  3. extends RouteNotFoundError {
  4. @Override
  5. public int setErrorParameter(BeforeEnterEvent event,
  6. ErrorParameter<NotFoundException> parameter) {
  7. getElement().setText(
  8. "My custom not found class!");
  9. return HttpServletResponse.SC_NOT_FOUND;
  10. }
  11. }

Note:

  • Only extending instances are allowed.

  • Exception targets may define ParentLayouts. BeforeNavigationEvent and AfterNavigationEvent are still sent, as in the case of normal navigation.

  • One exception may only have one exception handler.

Advanced Exception Handling Example

The following example assumes an application Dashboard that collects and shows widgets to users. Only authenticated users are allowed to see protected widgets.

If the collection instantiates a ProtectedWidget in error, the widget itself will check authentication on creation and throw an AccessDeniedException.

The unhandled exception propagates during navigation and is handled by the AccessDeniedExceptionHandler that keeps the MainLayout with its menu bar, but displays information that an exception has occurred.

Java

  1. @Route(value = "dashboard", layout = MainLayout.class)
  2. @Tag(Tag.DIV)
  3. public class Dashboard extends Component {
  4. public Dashboard() {
  5. init();
  6. }
  7. private void init() {
  8. getWidgets().forEach(this::addWidget);
  9. }
  10. public void addWidget(Widget widget) {
  11. // Implementation omitted
  12. }
  13. private Stream<Widget> getWidgets() {
  14. // Implementation omitted, gets faulty state
  15. // widget
  16. return Stream.of(new ProtectedWidget());
  17. }
  18. }
  19. public class ProtectedWidget extends Widget {
  20. public ProtectedWidget() {
  21. if (!AccessHandler.getInstance()
  22. .isAuthenticated()) {
  23. throw new AccessDeniedException(
  24. "Unauthorized widget access");
  25. }
  26. // Implementation omitted
  27. }
  28. }
  29. @Tag(Tag.DIV)
  30. public abstract class Widget extends Component {
  31. public boolean isProtected() {
  32. // Implementation omitted
  33. return true;
  34. }
  35. }
  36. @Tag(Tag.DIV)
  37. @ParentLayout(MainLayout.class)
  38. public class AccessDeniedExceptionHandler
  39. extends Component
  40. implements HasErrorParameter<AccessDeniedException>
  41. {
  42. @Override
  43. public int setErrorParameter(BeforeEnterEvent event,
  44. ErrorParameter<AccessDeniedException>
  45. parameter) {
  46. getElement().setText(
  47. "Tried to navigate to a view without "
  48. + "correct access rights");
  49. return HttpServletResponse.SC_FORBIDDEN;
  50. }
  51. }

Rerouting to an Error View

It is possible to reroute from the BeforeEnterEvent and BeforeLeaveEvent to an error view registered for an exception.

You can use one of the rerouteToError method overloads. All you need to add is the exception class to target and a custom error message, where necessary.

Example: Reroute to error view

Java

  1. public class AuthenticationHandler
  2. implements BeforeEnterObserver {
  3. @Override
  4. public void beforeEnter(BeforeEnterEvent event) {
  5. Class<?> target = event.getNavigationTarget();
  6. if (!currentUserMayEnter(target)) {
  7. event.rerouteToError(
  8. AccessDeniedException.class);
  9. }
  10. }
  11. private boolean currentUserMayEnter(
  12. Class<?> target) {
  13. // implementation omitted
  14. return false;
  15. }
  16. }

If the rerouting method catches an exception, you can use the rerouteToError(Exception, String) method to set a custom message.

Example: Blog sample error view with a custom message

Java

  1. @Tag(Tag.DIV)
  2. public class BlogPost extends Component
  3. implements HasUrlParameter<Long> {
  4. @Override
  5. public void setParameter(BeforeEvent event,
  6. Long parameter) {
  7. removeAll();
  8. Optional<BlogRecord> record =
  9. getRecord(parameter);
  10. if (!record.isPresent()) {
  11. event.rerouteToError(
  12. IllegalArgumentException.class,
  13. getTranslation("blog.post.not.found",
  14. event.getLocation().getPath()));
  15. } else {
  16. displayRecord(record.get());
  17. }
  18. }
  19. private void removeAll() {
  20. // NO-OP
  21. }
  22. private void displayRecord(BlogRecord record) {
  23. // NO-OP
  24. }
  25. public Optional<BlogRecord> getRecord(Long id) {
  26. // Implementation omitted
  27. return Optional.empty();
  28. }
  29. }
  30. @Tag(Tag.DIV)
  31. public class FaultyBlogPostHandler extends Component
  32. implements HasErrorParameter<IllegalArgumentException>
  33. {
  34. @Override
  35. public int setErrorParameter(BeforeEnterEvent event,
  36. ErrorParameter<IllegalArgumentException>
  37. parameter) {
  38. Label message = new Label(
  39. parameter.getCustomMessage());
  40. getElement().appendChild(message.getElement());
  41. return HttpServletResponse.SC_NOT_FOUND;
  42. }
  43. }