Router Exception Handling
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, except that they don’t typically have a specific @Route
, because they are 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
, which is included by default in the framework and is used to resolve errors related to the router’s NotFoundException
.
Example: 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 tosetText()
.
Exceptions are matched in the order below:
-
By exception cause.
-
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
@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
andAfterNavigationEvent
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 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.
@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 AccessDeniedException(
"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<AccessDeniedException>
{
@Override
public int setErrorParameter(BeforeEnterEvent event,
ErrorParameter<AccessDeniedException>
parameter) {
getElement().setText(
"Tried to navigate to a view without "
+ "correct access rights");
return HttpServletResponse.SC_FORBIDDEN;
}
}
Rerouting to an 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.
All you need to add is the exception class to target, and a custom error message, where necessary.
Example: Reroute to error view
public class AuthenticationHandler
implements BeforeEnterObserver {
@Override
public void beforeEnter(BeforeEnterEvent event) {
Class<?> target = event.getNavigationTarget();
if (!currentUserMayEnter(target)) {
event.rerouteToError(
AccessDeniedException.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.
Example: 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;
}
}
F4039D66-C9C5-4CEE-B49A-F1224B46C5E8