Documentation

Documentation versions (currently viewingVaadin 24)

Router Exception Handling

Exception handling by the Router class.

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

Exception targets work in the same way as regular navigation targets, but they don’t typically have a specific @Route because they’re shown for arbitrary URLs.

Error Resolving

Errors in navigation are resolved to a target that’s 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. An example of such a class is RouteNotFoundError. It’s included by default in the framework and is used to resolve errors related to the router’s NotFoundException.

In the example here, RouteNotFoundError defines the default target for the NotFoundException that’s shown when there is no target for the given URL:

@Tag(Tag.DIV)
public class RouteNotFoundError extends Component
       implements HasErrorParameter<NotFoundException> {

    @Override
    public int setErrorParameter(BeforeEnterEvent event,
          ErrorParameter<NotFoundException> parameter) {
        getElement().setText("Could not navigate to '"
                    + event.getLocation().getPath()
                    + "'");
        return HttpServletResponse.SC_NOT_FOUND;
    }
}

This returns a 404 HTTP response and displays the text specified in the parameter to setText().

Exceptions are matched first by exception cause, then by exception super type.

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

Also by default, the initial request first loads the client-side bootstrapping with an HTTP 200 response. An error page is rendered later in another request which returns an initial JSON data fragment, including the error page. The HTTP 404 response from RouteNotFoundError is only processed on the server side. It isn’t returned to the browser as an HTTP code of the response.

If you enable the eagerServerLoad property, the initial JSON data fragment is part of the first request’s response. It includes the RouteNotFoundError page with an HTTP 404 response.

Custom Exception Handlers

You can override the default exception handlers by extending them.

The example here shows a custom "route not found" handler that uses a custom application layout:

@ParentLayout(MainLayout.class)
public class CustomNotFoundTarget
        extends RouteNotFoundError {

    @Override
    public int setErrorParameter(BeforeEnterEvent event,
          ErrorParameter<NotFoundException> parameter) {
        getElement().setText(
                "My custom not found class!");
        return HttpServletResponse.SC_NOT_FOUND;
    }
}
Note
Only extending instances are allowed. Exception targets may define ParentLayouts. BeforeNavigationEvent and AfterNavigationEvent are still sent, as in 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 checks authentication on creation and throws an CustomAccessDeniedException.

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.

@Route(value = "dashboard", layout = MainLayout.class)
@Tag(Tag.DIV)
public class Dashboard extends Component {
    public Dashboard() {
        init();
    }

    private void init() {
        getWidgets().forEach(this::addWidget);
    }

    public void addWidget(Widget widget) {
        // Implementation omitted
    }

    private Stream<Widget> getWidgets() {
        // Implementation omitted, gets faulty state
        // widget
        return Stream.of(new ProtectedWidget());
    }
}

public class ProtectedWidget extends Widget {
    public ProtectedWidget() {
        if (!AccessHandler.getInstance()
                .isAuthenticated()) {
            throw new CustomAccessDeniedException(
                    "Unauthorized widget access");
        }
        // Implementation omitted
    }
}

@Tag(Tag.DIV)
public abstract class Widget extends Component {
    public boolean isProtected() {
        // Implementation omitted
        return true;
    }
}

@Tag(Tag.DIV)
@ParentLayout(MainLayout.class)
public class AccessDeniedExceptionHandler
     extends Component
     implements HasErrorParameter<CustomAccessDeniedException>
{

    @Override
    public int setErrorParameter(BeforeEnterEvent event,
            ErrorParameter<CustomAccessDeniedException>
                    parameter) {
        getElement().setText(
            "Tried to navigate to a view without "
            + "correct access rights");
        return HttpServletResponse.SC_FORBIDDEN;
    }
}

public class CustomAccessDeniedException extends RuntimeException {
}

Rerouting to Error View

It’s 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. You need only to add the exception class to the target, and a custom error message, where necessary.

This example shows how to reroute to an error view:

public class AuthenticationHandler
        implements BeforeEnterObserver {
    @Override
    public void beforeEnter(BeforeEnterEvent event) {
        Class<?> target = event.getNavigationTarget();
        if (!currentUserMayEnter(target)) {
            event.rerouteToError(
                    CustomAccessDeniedException.class);
        }
    }

    private boolean currentUserMayEnter(
            Class<?> target) {
        // implementation omitted
        return false;
    }
}

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

This next example shows a blog sample error view with a custom message:

@Tag(Tag.DIV)
public class BlogPost extends Component
        implements HasUrlParameter<Long> {

    @Override
    public void setParameter(BeforeEvent event,
            Long parameter) {
        removeAll();

        Optional<BlogRecord> record =
                getRecord(parameter);

        if (!record.isPresent()) {
            event.rerouteToError(
                   IllegalArgumentException.class,
                   getTranslation("blog.post.not.found",
                        event.getLocation().getPath()));
        } else {
            displayRecord(record.get());
        }
    }

    private void removeAll() {
        // NO-OP
    }

    private void displayRecord(BlogRecord record) {
        // NO-OP
    }

    public Optional<BlogRecord> getRecord(Long id) {
        // Implementation omitted
        return Optional.empty();
    }
}

@Tag(Tag.DIV)
public class FaultyBlogPostHandler extends Component
  implements HasErrorParameter<IllegalArgumentException>
{

    @Override
    public int setErrorParameter(BeforeEnterEvent event,
            ErrorParameter<IllegalArgumentException>
                    parameter) {
        Label message = new Label(
                parameter.getCustomMessage());
        getElement().appendChild(message.getElement());

        return HttpServletResponse.SC_NOT_FOUND;
    }
}

Show Error View for Exception during RPC Call

To use registered error views outside routing and rerouting, the application can set the enableErrorHandlerRedirect parameter to true.

This enables updating the current view content to a registered HasErrorParameter<T extends Exception> that handles the exact exception thrown for RPC events.

If you’re using a custom ErrorHandler, see the Showing Error Parameter Views For Non-Navigation Exceptions page on this feature.

F4039D66-C9C5-4CEE-B49A-F1224B46C5E8