Changes since 25.1.0-beta1
Breaking changes
-
Make localeSignal() read-only on VaadinSession and UI
Commit · Pull requestReturn Signal instead of SharedValueSignal/ValueSignal from localeSignal() so that all locale changes go through setLocale(), which handles UI propagation and LocaleChangeObserver notification. Add javadoc showing two-way binding pattern with bindValue and setLocale. Add tests for bindValue-based locale switching, UI locale independence from session, and session setLocale with per-UI override.
-
Split computed into separate cached and direct versions
Commit · Pull requestPreviously, the
computedmethod was just one out of many ways of creating a computed signal with the distinction that this computed signal was also caching. This made it difficult to understand when to use the method. computed signal and introducing a separatecachedmethod that creates a cached signal out of any other signal. signals
New features
-
Extract interface HierarchicalTreeData from TreeData
Commit · Pull request · IssueTreeDataProvider currently takes a TreeData as its source. But it relies only on a few methods of TreeData. This change extracts those methods into an interface. It then introduces a super class to TreeDataProvider which accepts a HierarchicalTreeData instead of a TreeData. A client has now the choice to either use the TreeData and the TreeDataProvider, or it can implement the HierarchicalTreeData interface and use the HierarchicalTreeDataProvider. This change introduces a new interface and a class but does not change or break any existing API.
-
BuildFrontend Incremental build
Commit · Pull requestAdd incremental build to the buildFrontend task. Closes #17354
-
Retain local signal values during hotswap
Commit · Pull request · IssueAutomatically transfer local signal values from old view fields to matching fields in the new view during hotswap refresh. ---------
-
Apply ElementEffect once when created
Commit · Pull request · IssueElementEffect is executed immediately once when created. This affects all Signal.effect calls with an owner component. Immediate execution ensures that exceptions can be thrown eagerly when e.g. bindText(signal) is called, not later when component is attached.
-
Throw binding exception if setItems is called while an active signal binding
Commit · Pull request · Issue -
Add local web components plugin to vite.generated.ts
Commit · Pull requestAuto-detects a web-components/node_modules directory in the project root and redirects polymer/* and vaadin/* imports to it in dev mode. A custom folder can be specified through vite.config.ts using the overrideVaadinConfig mechanism.
-
Add bindItems to HasDataProvider components
Commit · Pull request -
Add setTestId/getTestId convenience methods to Component
Commit · Pull request · Issue -
Add SignalBinding with rich BindingContext for bind methods
Commit · Pull requestAll Element bind methods (bindText, bindEnabled, bindVisible, bindProperty, bindAttribute, etc.), and Style.bind(), bindWidth(), bindHeight(), ThemeList.bind(), HasStyle.bindClassNames(), and HasTheme.bindThemeNames() now return SignalBinding object instead of void. SignalBinding exposes an onChange callback that receives a BindingContext with the old value, new value, whether it's the initial run, and the bound element/component. ElementEffect.bind() creates the SignalBinding internally and wires the effect registration into it so callers can control the effect lifecycle.
-
Allow setProperty if signal write callback is present
Commit · Pull request · IssueThis change allows setting element property value if signal binding's write callback is present (not null). Each Element setProperty method is updated to change the signal value with the same logic as with a property value change from the client did before (and same logic as in HasValue#bindValue two-way binding). One-way binding, with null write callback, works same as before and throws BindingActiveException.
-
Add content-hash cache busting for Stylesheet URLs
Commit · Pull request- In production mode, append a
?v-c=<8-char-sha256>query parameter toStyleSheetURLs so browsers reload only when file content changes, enabling aggressive caching withCache-Control: max-age=31536000, immutablefor versioned requests. - Content hashing is applied in both the AppShell path (AppShellRegistry.addStyleSheets) and the component dependency path (UidlWriter.dependencyToJson), covering allStyleSheetusage. -StaticFileServernow returns a 1-year immutable cache header when thev-cquery parameter is present in production mode, instead of the default short-lived cache.
- In production mode, append a
Fixes
-
Shade asm into flow-build-tools
Commit · Pull request · IssueShades ASM into flow-build-tools and relocates classes to an internal package. Prevents build time errors with projects that depend on older ASM versions.
-
Remove type parameter from Signal.effect
Commit · Pull request · IssueThe removal of type parameter T from the Signal.effect methods is binary-compatible and source-compatible.
-
Add missing
registerStylesimport to generated app-shell-imports.js
Commit · Pull request · IssueThe generated
app-shell-imports.jsfile was missing theTHEMABLE_MIXIN_IMPORTline (import { css, unsafeCSS, registerStyles }), causingReferenceError: registerStyles is not definedat startup in production mode whenCssImportwiththemeForwas used on anAppShellConfigurator. Also ensures import statements are moved to the top of the generated app shell file, consistent with other generated import files. -
Prevent duplicated CSS imports in web component generated file
Commit · Pull request · IssueWhen a CSS file is referenced by both a regular component and a WebComponentExporter (or AppShellConfigurator), the generated web component imports file contained duplicate import statements and duplicate style registrations for the same CSS. This happened because the web component output merges multiple independently generated files, and the existing string-based deduplication could not recognize that two imports for the same file were equivalent. The fix detects duplicate CSS file imports after merging and unifies their variable references so that identical style registrations are properly deduplicated, while preserving distinct registrations when the same CSS file is intentionally used with different configurations.
-
Use UI identity for background change detection in Effect
Commit · Pull request · IssueEffectContext.isBackgroundChange() always returned false for shared signals modified by another user's session because the check only looked at VaadinRequest.getCurrent(), which is non-null on any request thread. Now Effect tracks its owner UI and compares UI.getCurrent() against it, correctly detecting cross-session changes as background changes.
-
Prevent computed signal from being evaluated twice during binding
Commit · Pull request · IssuePreviously, when creating a signal binding (e.g., bindText, bindProperty), the signal was evaluated twice: 1. Once via signal.peek() to initialize the previousValue 2. Once via signal.get() during the first effect execution This was inefficient, especially for expensive computed signals. The fix removes the signal.peek() call and instead tracks whether the effect has run before using a hasRun flag. On the first execution, oldValue is set to newValue (maintaining the contract that they are equal on initial run). On subsequent executions, oldValue comes from the previousValue array. 🤖 Generated with Claude Code
-
Prevent stale JAR cache in
Reflectorunder Maven daemon (mvnd)
Commit · Pull requestMirror the Gradle daemon fix (33fa374) in the Maven plugin's
ReflectorandReflectorClassLoader/CombinedClassLoader: - ExtractReflectionsClassFinder.disableJarCaching()as a public utility so both plugins can reuse it. - Wrapjar:URLs inReflectorClassLoader.getResource()andgetResources()(flow-maven-plugin) andCombinedClassLoader(flow-dev-bundle-plugin) withuseCaches(false)to prevent staleJarFileFactoryentries across daemon builds. - MakeReflectorimplementCloseablewith aclose()method that releases theURLClassLoaderfile handles, and register aCleaneraction for best-effort GC cleanup of abandoned instances. - Close the temporaryReflectorinFlowModeAbstractMojo.isHillaAvailable(MavenProject)via try-with-resources. Releated to #15458 -
Prevent stale JAR cache in
ReflectionsClassFinderunder Gradle daemon
Commit · Pull request · IssueClose
URLClassLoaderon cleanup to release JAR file handles, and disable JVM-level JAR caching ingetResource()by wrappingjar:URLs with aURLStreamHandlerthat setsuseCaches(false). The Gradle daemon reuses JVMs across builds. When a sibling module's JAR is rewritten, two independent caching layers can hold stale file handles: 1.URLClassLoaderinternal cache (URLClassPath→JarLoader) 2.JarFileFactorystatic HashMap (populated viaJarURLConnection) TheURLClassLoader.close()call addresses layer 1, but layer 2 is JVM-global and independent of the class loader. SettinguseCaches(false)onjar:URL connections preventsJarFileFactoryfrom cachingJarFileinstances, matching the approach used by Spring'sPathMatchingResourcePatternResolver(SPR-4639). -
Delete flow-build-info.json after Maven session via AbstractMavenLifecycleParticipant
Commit · Pull request · IssueBuildFrontendUtil.updateBuildFile()callsdeleteOnExit()on the production token file, but this never fires when using the Maven Daemon (mvnd) because the JVM stays alive between builds. As a result,flow-build-info.jsonwithproductionMode: truepersists intarget/classes/META-INF/VAADIN/config/and causes incorrect production-mode behavior when starting the app from the IDE. AddsFlowLifecycleParticipant, anAbstractMavenLifecycleParticipantthat hooks intoafterSessionEnd()to delete the token file after every build session.BuildFrontendMojostores the resolved token file path in a Maven project property so the participant always uses the correct path even whenresourceOutputDirectoryis user-configured.AbstractMavenLifecycleParticipantonly activates when the plugin is declared as a Maven extension, so users must add<extensions>true</extensions>to the plugin declaration in theirpom.xmlto benefit from this fix. The existingdeleteOnExit()call acts as a transparent fallback for projects that do not opt in. -
Build should not depend on prepare
Commit · Pull requestRunning the build frontend task should not depend on the prepare task so that the tasks do not break each others caching. Also introduces a build service for token file deletion at the end of the build process. part of #17354
-
Prevent addon stylesheet links from being removed during CSS live reload
Commit · Pull request · IssueDuring CSS live reload,
PublicResourcesLiveUpdaterpushed null content for addon stylesheets not found in local source roots, andremoveOldLinks()in vaadin-dev-tools used substring matching (.includes()) which could incorrectly remove unrelated<link>tags. Server-side changes: - Add jar-resources folder as a watched source root inDevModeHandlerManagerImplso addon CSS changes are detected - Skip pushing null updates for stylesheets that exist on the classpath (e.g. from addon JARs) inPublicResourcesLiveUpdater- UseResourceProviderto check classpath existence before deciding to remove a stylesheet - Stripfrontend/prefix inPublicStyleSheetBundlerwhen resolving addon stylesheets against jar-resources roots, sinceTaskCopyFrontendFilesremoves that prefix during copy Client-side change: - Replace.includes(path)with path-suffix matching inremoveOldLinks()to only remove links whose href ends with'/' + pathor exactly equalspath, after stripping query strings and fragments -
Propagate forceInstantiation and recreateLayoutChain flags through forward/reroute
Commit · Pull request · IssueWhen refreshCurrentRoute(true) triggers a BeforeEnterObserver that calls forwardTo() or rerouteTo(), the forceInstantiation and recreateLayoutChain flags were lost because the redirect navigation events were created without them. This caused the redirect target to reuse existing component instances instead of creating new ones. Propagate the flags in three locations: - AbstractNavigationStateRenderer.getNavigationEvent() for both normal and error redirect paths - InternalRedirectHandler.handle() for internal redirects
-
Merge property descriptors when getter and setter come from different interfaces
Commit · Pull request · IssueBeanUtil's dedup loop was replacing a read-only descriptor with a write-only one (or vice versa) instead of merging them. This caused BeanPropertySet to lose the getter when a parent interface provided it and a child interface provided the setter.
-
Use targeted item refresh when signal-bound item value changes
Commit · Pull request · Issue- Add a two-argument
refreshItem(T newItem, T oldItem)overload toDataProvider,DataView, and related classes, enabling targeted refresh when an item's identity changes (e.g., signal-bound items replaced with new instances) -DataRefreshEventnow carries an optional old item reference viagetOldItem(), allowingDataCommunicatorandKeyMapperto remap from the old identity to the new one instead of failing to find the item -HierarchicalDataCommunicatorthrowsUnsupportedOperationExceptionfor identity-changing refreshes, deferring TreeGridbindItemssupport to a future change
- Add a two-argument
-
Move thread start out of DevServerWatchDog constructor
Commit · Pull requestStarting a thread in a constructor can expose a partially constructed object. Extract the thread start into a separate start() method that callers invoke after construction completes.
-
Use String type for Style.bind return value
Commit · Pull requestChange
Style.bind(String, Signal<String>)return type fromSignalBinding<?>toSignalBinding<String>since the method always accepts aSignal<String>and we always know the value type is String. This provides better type safety and eliminates the need for casts when using the binding'sonChangecallbacks. -
Route with context path name
Commit · Pull request · Issuethe same start as the context path from clearing context path out from the url when using react router.
-
Move import statements to top in generated web component imports file
Commit · Pull requestAbstractUpdateImports.process()already reordersimportlines to the top forgeneratedFlowImports, but the same sorting was not applied togeneratedFlowWebComponentImports. This caused interleavedimportand non-import lines (e.g.injectGlobalWebcomponentCss()calls mixed withimportstatements) in the web component output. Extract a reusablemoveImportsToTop()method and apply it both inprocess()for main imports and inmergeWebComponentOutputLines()after merging and deduplicating the web component sources. Related to #23689 (comment) -
Skip ElementEffect callback on reattach when no signals changed
Commit · Pull request · IssueAdd passivate/activate lifecycle to Effect so that ElementEffect can preserve tracked Usage instances across detach/attach cycles. On reattach, activate checks whether any dependency has changed since passivation: if so, the callback re-runs with isInitialRun=true; if not, only the dependency listeners are re-registered without invoking the callback, avoiding redundant work and confusing double invocations.
-
Improve npm resolution for non-windows
Commit · Pull requestAdds another potential path to the npm resolver algorithm to make it compatible with node installation performed by frontend-maven-plugin
-
Detect router-link attribute in ancestor elements during click navigation
Commit · Pull requestWhen a nested element (e.g., Button) inside a RouterLink is clicked, the navigation trigger was incorrectly reported as CLIENT_SIDE because only the direct click target was checked for the router-link attribute. Traverse the composed path instead so any ancestor with router-link is correctly identified as a ROUTER_LINK trigger.
-
Allow local signal reads in computed callbacks triggered from transaction commit
Commit · Pull request · IssueDuring the StagedTransaction commit phase, listener callbacks that re-evaluate computed signals could not read local signals because inExplicitTransaction() still returned true. The transaction is no longer staging commands at that point, so local signal access is safe.
-
Shared Signal.get() should throw outside reactive context with fallback transaction
Commit · Pull request · IssueThe reactive context check in AbstractSharedSignal.get() used Transaction.inTransaction() which returns true when a session-scoped fallback transaction is active. This caused shared signals to silently allow get() calls outside reactive contexts when the session lock was held (e.g. in onAttach), unlike local signals which correctly threw. Changed the check to use Transaction.inExplicitTransaction() so that only explicitly started transactions (via runInTransaction) count as a valid reactive context, matching the behavior of local signals.
-
Skip validation on signal effect initial run in Binder
Commit · Pull request · IssueThe reactive signal effect created by initInternalSignalEffectForValidators() was firing validation on its initial run when deferred to component attach, causing required fields to be marked invalid immediately on navigation. Use the context-aware Signal.effect() variant and skip fireValidationEvents on the initial run, so validation only triggers on actual value changes.
-
Suppress duplicate WEB-INF/lib JAR scan warnings in Jetty
Commit · Pull requestAdd webInfIncludeJarPattern to the Jetty plugin configuration in flow-tests pluginManagement. The regex pattern excludes JARs from the jetty_overlays directory while still scanning Maven-resolved dependencies. This prevents Jetty from scanning the same JARs twice when overlay WARs are used (Maven classpath + overlay WEB-INF/lib). This eliminates ~31,000 "scanned from multiple locations" warnings per CI build from the WAR overlay modules (test-router-custom-context-encoded and its prod variant).
-
Preserve scroll position of all scrollable elements after hot-swap
Commit · Pull request · IssueCapture and restore scroll positions of all scrollable elements during hot-swap UI refresh and full page reload. Elements are identified by CSS selector paths built from nth-of-type selectors anchored to the nearest ancestor with an ID, so elements without explicit IDs are also supported. The window scroll position uses a special window key. Only elements with non-zero scroll are captured, keeping the snapshot small. Both code paths are updated: the vaadin-dev-tools TypeScript (used for dev server WebSocket-triggered refreshes and full reloads via sessionStorage) and the Hotswapper Java inline JS (used for the vaadin-refresh-ui custom event).
-
Add SignalBindingFeature by default for text state node
Commit · Pull requestSupporting SignalBindingFeature by default removes danger to get IllegalStateException when using StateNode#getFeatureIfInitialized(Class) to get SignalBindingFeature.
-
Use absolute path for Aura CSS resource availability check
Commit · Pull requestThe isResourceAvailable call used the relative path "aura/aura.css" which, after URI resolution, was passed unchanged to ServletContext.getResource(). The Servlet specification requires paths to start with "/" and the missing leading slash caused a MalformedURLException, making the check always fail and flooding logs with warnings on every page load.
-
Exclude duplicate flow-build-tools JAR from WAR packaging
Commit · Pull requestThe maven-shade-plugin replaces the main artifact's file reference with shaded content during reactor builds, causing both flow-build-tools.jar and flow-build-tools-shaded.jar (byte-identical) to end up in test WAR WEB-INF/lib directories. This produces hundreds of duplicate-class warnings from Jetty's AnnotationParser. Add a packagingExcludes regex to the maven-war-plugin configuration in the root POM's pluginManagement to exclude the non-shaded JAR while keeping the shaded one.
Tests
-
Migrate flow-polymer-template tests to JUnit 6
Commit · Pull request -
Migrate flow-build-tools tests to JUnit 5
Commit · Pull request -
Migrate flow-data tests to JUnit 5
Commit · Pull requestConvert 71 test files from JUnit 4 to JUnit 5: - Replace JUnit 4 imports with JUnit 5 equivalents - Before/After -> BeforeEach/AfterEach - Test(expected=X.class) -> assertThrows(X.class, ...) - ExpectedException Rule -> assertThrows + message assertions - Assert.xxx() -> static imports from Assertions - Message-first assertions reordered to message-last - RunWith(Parameterized.class) -> ParameterizedTest + ValueSource for DataCommunicatorTest and DataCommunicatorAsyncTest - Visibility adjustments for cross-package inheritance
-
Migrate vaadin-dev-server tests to JUnit 6
Commit · Pull request