Skip to content

docs: add a Building Apps guide on application-wide error handling - #6321

Merged
peholmst merged 4 commits into
mainfrom
docs/error-handling-guide
Oct 9, 2026
Merged

peholmst merged 4 commits into
mainfrom
docs/error-handling-guide

Conversation

@peholmst

@peholmst peholmst commented Oct 9, 2026 •

Copy link
Copy Markdown
Member

Adds a how-to guide on application-wide error handling for Flow applications, and replaces the TODO in Protect Views that was waiting for it.

Part of #6292 (the security/protect-views.adoc#L291 TODO) and #6288 ("Application-wide error handling and recovery").

What's in the guide

New page: articles/building-apps/ui-basics/handle-errors.adoc (Handle Errors)

  • Copy-paste example:

    • ErrorReporter logs each error with a short error ID and builds the user message, which includes the exception in development mode only.
    • ApplicationErrorHandler shows that message in a closable notification.
    • ErrorView replaces InternalServerError with a standalone, @AnonymousAllowed page.
    • ErrorHandlingConfig installs the handler for every session.
  • What users see by default, in development vs. production mode, for listener exceptions, not-found routes, access-denied navigation, and exceptions thrown during navigation.

  • Unexpected errors: guidance on what not to show users, error IDs, the null-UI case, an error handler that must never throw, unwrapping the cause chain, the exact-type error-view redirect that the default handler does, @ControllerAdvice not applying to Flow views, background threads, the internal error and session expired system messages, and monitoring.

  • Navigation errors:

    • a custom not-found view, and why users denied access to a view also land on it;
    • an optional access-denied view, with @AccessDeniedErrorRouter for views that should stay hidden;
    • relaxing the VaadinSecurityConfigurer catch-all rule for typed or bookmarked URLs;
    • why the catch-all error view stays out of the main layout, and why it allows anonymous access.

    It also notes that the HTTP status an error view returns doesn't reach the browser on the initial page load (Status returned by HasErrorParameter#setErrorParameter is ignored. flow#13421).

  • Exceptions from services:

    • a table of where to handle what, with user-facing exception types as an alternative for business rules;
    • method security's AccessDeniedException goes to the error handler, with a pointer to splitting the handler into per-exception handlers as it grows;
    • OptimisticLockingFailureException is caught in the view that saves.

Changes from external research

A second commit applies findings from the Vaadin forum, Martin Vysny's error-handling article, the Jmix and Error Window add-on approaches, Flow GitHub issues, and internal Slack threads in #flow-user.

  • Catch-all ErrorView out of the main layout. Since MainLayout unresponsive after unhandled exception in afterNavigation() flow#22146, if the layout itself throws, Flow falls back to InternalServerError, which shows the exception message in production.
  • One shared ErrorReporter for both the handler and the error view. In #flow-user, developers asked for one example that covers both error paths.
  • Exception details in development mode only.
  • An error handler that never throws. A throwing handler makes the browser show the internal-error system message instead.
  • System messages, the HTTP status caveat, @ControllerAdvice, signal effects, and the two alternatives for larger applications.

Placement

Under UI Basics, after Show a Notification and the other UI how-tos (order 40):

  • The guide is about how the UI reacts when something fails: notifications, error views, and the session error handler. Business Logic covers writing services and background jobs, not how views react to their exceptions.
  • Views & Navigation would only fit the error-view part.
  • The guide builds directly on Show a Notification, which already lives in UI Basics.

Overlap with #6320

#6320 also replaces the TODO in security/protect-views.adoc. This PR uses the same paragraph wording but changes its link from Custom Error Handling to the new guide. It also adds one sentence that points to the guide's section on access-denied navigation. Git can't merge the two edits to that line automatically, so whichever PR merges second gets a one-line conflict. To resolve it, keep this PR's version of the paragraph.

The guide also links to protect-services#testing-method-security, a section that #6320 adds. If this PR merges first, the link opens Protect Services but doesn't jump to the section until #6320 is merged.

Verification

  • I checked every Vaadin API against the Vaadin MCP (get_java_symbol, 25.3). I also read the Flow 25.3.3 sources for DefaultErrorHandler, ErrorHandlerUtil, Router, InternalServerError, AbstractRouteNotFoundError, RouteAccessDeniedError, NavigationAccessControl, AnnotatedViewAccessChecker, UI.access(), and the error-target registry, and the Spring Security 7.1 sources for method-security denial.
  • I compiled all the code snippets with javac, including the revised copy-paste classes and the system messages bean, together with minimal stubs (MainLayout, HomeView, ProposalService, …), against this repo's dependency classpath (Vaadin 25.4.0-alpha1, Spring Boot 4.1). They compile with no deprecation warnings. VaadinIcon.CLOSE_SMALL is deprecated, so the code uses VaadinIcon.CLOSE.
  • Vale reports 0 errors, 0 warnings, and 0 suggestions on the new page.
  • Asciidoctor parses both changed files without warnings. Every xref target file and anchor exists.
  • I didn't run a full local DS Publisher build. The preview deployment covers it.

🤖 Generated with Claude Code

peholmst and others added 2 commits October 9, 2026 09:14
Add articles/building-apps/ui-basics/handle-errors.adoc, a how-to guide
for Flow applications covering the session ErrorHandler, custom
not-found, access-denied, and unexpected-error views, the Spring
Security catch-all rule, and where to handle exceptions thrown by
application services (method security and optimistic locking).

Replace the TODO in security/protect-views.adoc that waited for this
page with a paragraph linking to it.

Part of #6292 and #6288.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…curity tests

Use the wording that #6320 gives the paragraph replacing the
protect-views TODO, pointing its link at the new Handle Errors guide, so
the overlap between the two PRs reduces to a one-line conflict. Link
the guide to the Testing Method Security section that #6320 adds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Preview Deployment

This PR has been deployed for preview.

URL: https://docs-preview-pr-6321.fly.dev

Changed pages

Added content is highlighted in green; removed content is marked in red on each page.

Built from 373eebe

peholmst and others added 2 commits October 9, 2026 10:50
- Move the catch-all ErrorView into the copy-paste example, out of the
  main layout (since vaadin/flow#22146, a failing layout makes Flow fall
  back to InternalServerError, which shows the exception message in
  production), and allow anonymous access to it.
- Share logging, error IDs, and the user message between the error
  handler and the error view through a new ErrorReporter, which also
  adds exception details in development mode.
- Make the error handler catch exceptions thrown while showing the
  message.
- Add a section on customizing the internal error and session expired
  system messages.
- Note that the HTTP status of an error view doesn't reach the browser
  on the initial page load (vaadin/flow#13421).
- Mention that @ControllerAdvice doesn't apply to Flow views, that
  signal effects also reach the error handler, and two alternatives for
  larger applications: user-facing exception types and per-exception
  handlers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…uide

# Conflicts:
#	articles/building-apps/security/protect-views.adoc
@peholmst peholmst added the target/v25.3 Automatically cherry-pick to the v25.3 branch label Oct 9, 2026
@peholmst
peholmst merged commit 7648ee6 into main Oct 9, 2026
11 checks passed
@peholmst
peholmst deleted the docs/error-handling-guide branch October 9, 2026 09:50
peholmst added a commit that referenced this pull request Oct 9, 2026
Fixes six inaccuracies in reference pages about error handling, found
while writing the error-handling guide in #6321. Each was checked
against the Flow 25.3.3 sources (`com.vaadin:flow-server`), and the
Observability Kit item was also checked against the kit's own bytecode.

| Page | Was | Now |
|---|---|---|
| `tools/observability/reference.adoc` | Listed a throwing
`beforeEnter()` among the failures routed to the session error handler |
`Router.navigate()` catches every navigation exception and renders an
error view through `handleExceptionNavigation()`, so these never reach
the `ErrorHandler` and `vaadin.errors` doesn't count them. A short
paragraph now says so and points to the navigation timer. In the kit,
only `ErrorMetricsBinder` (the decorated handler) and
`RequestMetricsBinder` (the request interceptor) increment
`vaadin.errors`. |
| `flow/configuration/properties.adoc` | Documented
`enableErrorHandlerRedirect` (default `false`) | Removed.
`InitParameters` has no such parameter. It was added in
vaadin/flow#17791 and removed in vaadin/flow#18105 before 24.3.0
shipped. `DefaultErrorHandler.error()` always calls
`ErrorHandlerUtil.handleErrorByRedirectingToErrorView()`. The sentence
in `custom-error-handler.adoc` that said this behavior had to be
"enabled" is reworded to match. |
| `flow/routing/exceptions.adoc` | "Only extending instances are
allowed." | Any `HasErrorParameter` for the same exception type replaces
a `@DefaultErrorHandler` view, whether it extends it or not
(`AbstractRouteRegistry.handleRegisteredExceptionType`). Two of your own
handlers for the same type must be in a subclass relationship (the
subclass wins), or startup fails with
`InvalidRouteConfigurationException`. The same paragraph also named two
classes that don't exist, `ParentLayouts` and `BeforeNavigationEvent`.
They're now `@ParentLayout` and `BeforeEnterEvent`. |
| `flow/advanced/custom-error-handler.adoc` | Wrapped
`Notification.show()` in `UI.getCurrent().access()`, and the message had
no space between "occurred." and "Contact" | Wrapper removed, because
Flow calls the handler with the session locked. Space added. |
| `flow/security/enabling-security.adoc` | Custom access-denied views
returned `UNAUTHORIZED` (401) | `FORBIDDEN` (403), which fits an
authenticated user who lacks permission. |
| `building-apps/ui-basics/show-notification.adoc` |
`VaadinIcon.CLOSE_SMALL` and `ButtonVariant.LUMO_TERTIARY_INLINE` |
`VaadinIcon.CLOSE` (`CLOSE_SMALL` is `@Deprecated(since = "25.3",
forRemoval = true)`) and the theme-neutral `ButtonVariant.TERTIARY`. |

Vale reports no alerts on the changed lines.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
peholmst added a commit that referenced this pull request Oct 9, 2026
…y, use Master-Detail Layout for form drawers (#6330)

Closes #6292. This is the second batch. The first one was #6320, and
#6321 added the error-handling guide.

## Section landing pages

Seven Building Apps section landing pages had front matter but no
content: UI Basics, Views & Navigation, Forms & Data, Business Logic,
Security, Integration, and Components. Each now has a short
introduction, where to start, links to related sections, and a `==
Topics` outline. This follows the pattern of the Testing and React Views
pages.

Front matter fixes on these pages:
- **Forms & Data** and **Add a Form**: removed the promise of forms
"using both Flow and Hilla". This covers that bullet in #6293.
- **Integration**: the section has a single page, on exposing a REST
API, so the page title, description, and meta-description now say that
instead of "integrate with other systems". The intro says that calls to
other systems belong in application services.
- **Security**: the intro links the OAuth2 reference page, which Add
Login doesn't link (#6288, finding 4). It also links both
security-testing pages.
- Meta-descriptions are now 150–160 characters. The Components
meta-description is still 126, as before; I didn't change it.

## Strong Consistency

Replaced the stub and its TODO ("monoliths and self-contained systems")
with four sections:
- **Transactions**, and how application services define them
- **Where Strong Consistency Ends**: a monolith or self-contained system
can use one local transaction, while microservices can't, which leads to
Eventual Consistency
- **Concurrent Updates**: lost updates, optimistic locking, and
pessimistic locking
- **Stale Data in the User Interface**: strong consistency doesn't
refresh views that are already open; server push does

## Dialogs & Drawers

The drawer section had a TODO saying "Write about the new master-detail
layout" and built the drawer by hand. It used `setVisible()` and
`LumoUtility` classes. It now uses `MasterDetailLayout`:
- The view extends `MasterDetailLayout`. It calls `setDetail(drawer)`
when a proposal is selected and `setDetail(null)` when the selection is
cleared.
- The backdrop-click and Escape listeners clear the selection when the
drawer is shown as an overlay.
- It links to the Add a Master-Detail View guide.
- It removes the `LumoUtility` styling (part of #6293).
- It fixes `setAriaLabeledBy`, which doesn't exist, to
`setAriaLabelledBy`.

The example uses only `MasterDetailLayout` API that is available from
25.0. `setDetailPlaceholder()` (25.2) is avoided, so no since badge is
needed.

## Checks

- Compiled `ProposalDrawer` and `ProposalView`, extracted from the page,
with `javac -Xlint:deprecation` against this repo's dependency
classpath. The only stubs were `Proposal`, `ProposalForm`, and
`ProposalService`. There were no errors or warnings.
- All 52 added xrefs and anchors resolve.
- Vale reports nothing on the changed lines.
- No pages moved or removed, so no redirects are needed.

After this PR, the only `// TODO` left in Building Apps and Getting
Started is the one in `add-login.adoc`. It's a placeholder inside the
code sample for the reader to fill in, so it stays.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
peholmst added a commit that referenced this pull request Oct 9, 2026
…6321) (CP: v25.3) (#6331)

Co-authored-by: Petter Holmström <petter@vaadin.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cherry-picked-v25.3 target/v25.3 Automatically cherry-pick to the v25.3 branch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants