Changes since 25.2.0
New features
-
Allow setting frontend build toggles via system properties (#24786) (CP: 25.2)
Commit · Pull requestSeveral
build-frontendtoggles could only be configured through a POM<configuration>block, making it hard to override them for a single build. Add apropertyattribute togenerateBundle,runNpmInstall,generateEmbeddableWebComponents,optimizeBundleandeagerServerLoadso they can be set at runtime, e.g.-Dvaadin.generateBundle=false. -
Warn when an addon uses the legacy META-INF/resources/frontend layout (#24655) (CP: 25.2)
Commit · Pull requestTaskCopyFrontendFiles still scans META-INF/resources/frontend/ for backwards compatibility, but that location is deprecated. Emit a once-per-jar/dir warning recommending the split layout: bundle sources for
@JsModule/@CssImportunder META-INF/frontend/, and runtime resources for@StyleSheet/@JavaScriptunder META-INF/resources/ (served directly by the servlet container). -
Support custom timeout for DevTools license download (#24722) (CP: 25.2)
Commit · Pull request · IssueDevTools triggers an asynchronous license download that opens the system browser so the user can sign in and fetch a license. The license-checker waits a fixed 60 seconds for that to complete, which is too short for first-time users who still need to register a Vaadin account before they can download the license. When the timeout elapses the download fails and the popup that initiated it becomes stale, forcing a page reload and a repeat of the whole flow. Allow callers of downloadLicense in the dev tools to pass an optional timeout. The value is forwarded to the server and passed on to LicenseChecker.checkLicenseAsync; when omitted the previous default behavior is kept.
-
Add RouterState.isNavigationPending() (#24771) (CP: 25.2)
Commit · Pull request · IssueAdd RouterState.isNavigationPending() to detect pre-navigation state, so user code can detect the pre-navigation state without an NPE.
-
Fail loudly when a trigger is created without an action (#24716) (CP: 25.2)
Commit · Pull requestA Trigger that is never armed with an action does nothing on the client, which is almost always a forgotten terminal binding call (e.g. Clipboard.onClick(button) with no following writeText(...)). Detect this in the Trigger base class — so it covers every trigger/action user, not just clipboard — by deferring a check to beforeClientResponse and throwing an IllegalStateException if no action was committed. Track arming as an intent flag set the moment a binding commits to an action, and add Trigger.triggersWhenAttached(target, supplier) so a binding that legitimately defers wiring until a target component attaches (Fullscreen.enter(detachedPanel)) is not mistaken for a forgotten one.
Fixes
-
Write frontend build files into build dir when it is outside project dir (#24805) (CP: 25.2)
Commit · Pull request · IssueWhen the build dir is relocated outside the project dir, vaadinPrepareFrontend wrote vaadin-dev-server-settings.json and the dev-bundle output paths underthe source tree. buildFolder() is now always relative to projectDir.
-
Cancel pending validation effect listener on binding removal (#24791) (CP: 25.2)
Commit · Pull request · IssueBinding a not-yet-attached field schedules the internal validation signal effect via a component attach listener instead of creating it immediately. That listener registration was not tracked, so unbind() could not cancel it. After removeBinding(), the stale listener stayed on the field and fired on a later attach, creating an effect for an already-unbound binding. The effect then read the binding's now-null field, throwing a NullPointerException wrapped in "This binding is already unbound" (only reproducible when the attach happened outside the removing UI.access() scope). The attach listener registration is now stored and removed in unbind(), so removing a binding fully tears down its pending effect setup.
-
Support the configuration cache when packaging the production jar (#24795) (CP: 25.2)
Commit · Pull request · IssueThe production-mode token-restore action was added as a Jar.doFirst {} whose lambda captures the whole Project, which can't be serialized for the configuration cache. It's moved to the block where the vaadinBuildFrontendToken service is already in scope, and now captures that service provider instead of the Project.
-
Improve shared signal transaction error message (#24663) (CP: 25.2)
Commit · Pull requestClarify error when updating multiple shared signals in a transaction. Explain shared signal transaction restriction with cluster context.
-
Declare signal class tokens as Class<
@NonNullT> (#24548) (CP: 25.2)
Commit · Pull requestNullAway 0.13.2+ analyzes constructor diamond operators and rejects passing String.class (
Class<String>) to aSharedXSignal<@Nullable String>whose constructor takesClass<T>, sinceClass<T>is invariant. Per JSpecify guidance (NullAway uber/NullAway#1595), declare the type-token parameter (and backing field) asClass<@NonNull T>-- the projection use case. A class token never carries nullness, so this lets callers pass a plain class literal for a nullable element type with no cast. -
Omit feature flag updater script when no flags are enabled (#24522) (CP: 25.2)
Commit · Pull request · IssueThe feature flag bootstrap script was always written into
index.html, even when no feature flags were enabled. This left a redundant registration in the production document:js window.Vaadin = window.Vaadin || {}; window.Vaadin.featureFlagsUpdaters = window.Vaadin.featureFlagsUpdaters || []; window.Vaadin.featureFlagsUpd aters.push((activator) => {});Since feature flags default to disabled, every default production app shipped this dead script. ## FixfeatureFlagsInitializer(...)now returns an empty string when no enabled features exist, andinitializeFeatureFlags(...)only adds the<script>element when there is something to emit. The generatedfeature-flags.jsalready guards on the presence of thewindow.Vaadin.featureFlagsUpdatersarray (if (window.Vaadin.featureFlagsUpdaters) { ... }), so omitting it is safe. ## Testing UpdatedIndexHtmlRequestHandlerTestto assert the updater script is omitted when no feature flags are enabled (previously it asserted the opposite).mvn spotless:checkpasses and the test is green. � Generated with Claude Code -
Fire cancelable event on license download instead of forcing reload (#24721) (CP: 25.2)
Commit · Pull request · IssueDevTools reloaded the page when a license was downloaded, which prevented consumers (such as Copilot) from resuming an ongoing operation. Now a cancelable 'vaadin-license-download-completed' event is dispatched on the document. If no listener calls preventDefault(), the previous reload behavior is kept as the default. Documented that a refresh/reload is necessary so that components replaced by placeholders are recreated and shown again.
-
Send browser details as plain query parameters during v-r=init (#24636) (CP: 25.2)
Commit · Pull request · IssueThe initial
v-r=initbootstrap request appended browser details as a single JSON-encodedv-browserDetailsURL query parameter. The resulting URL contained many percent-encoded escape sequences (%7B,%22,%3A, ...), which some firewalls/WAFs (e.g. Sophos) flag and block, failing the application on the very first page load. Browserdetails are now appended as individual, unwrappedv-*query parameters (e.g.&v-sw=1641&v-tzid=Europe%2FBerlin). Plainkey=valuepairs contain no JSON braces/quotes,
so the bootstrap URL no longer carries the percent-encoded JSON pattern. There is a single transport with no fallback. The server reconstructs the details from everyv-*request parameter except thev-rrequest-type marker. -
Write generated frontend files atomically (#24667) (CP: 25.2)
Commit · Pull requestGenerated frontend files (vaadin.ts, index.ts, generated-flow-imports, etc.) were written via Files.writeString, which truncates the target and rewrites it in place. A file system watcher such as Vite's can observe that intermediate state and read an empty or partially written file, crashing the dev server with errors like "Failed to resolve import './index' from 'generated/vaadin.ts'". This is hit when Java is recompiled in the background (IDE or agent) while the dev server runs. FileIOUtils.writeIfChanged now writes to a temp file in the same directory and moves it over the target with an atomic move (falling back to a regular replace when the file system does not support atomic moves), so watchers only ever see the complete old or complete new content.