Changes since 25.2.0-alpha9
New features
-
Add pageVisibilitySignal() to Page for tracking browser tab visibility
Commit · Pull requestAdd a read-only signal-based API on Page that tracks whether the browser tab is visible and focused, visible but not focused, or hidden. Uses the browser Page Visibility API combined with focus/blur events, with a Firefox workaround for deferred visibilitychange. Client-side logic lives in page-visibility.js loaded via @jsmodule on UI. ---------
-
Add UI router state signal
Commit · Pull requestExpose UI.routerStateSignal() as a read-only Signal holding the current Location, RouteParameters, active chain and navigation target class. The signal is updated atomically alongside AfterNavigationEvent dispatch, so reactive consumers and listeners observe the same state. Lets components observe the active route via Signal.effect instead of registering an AfterNavigationListener and manually fetching the initial state from UIInternals on attach. Fine-grained projections (current location, route parameters, leaf view) are derivable with Signal.map; two-way URL/parameter binding is intentionally left for a follow-up so its API can be designed independently.
-
Adds relevant API to allow drag and drop to a certain location
Commit · Pull request · IssueExposes clientX/Y for all D&D events, to allow "repositioning" absolutely positioned drag and drop component. Also exposing offsets of the target and start element (if operation started on top of Vaadin component), so that one can build UIs where "things" are dragged on a specific location of another component. ## Type of change - [ ] Bugfix - [x] Feature ---------
-
Add addPositionListener to GeolocationTracker
Commit · Pull requestLets callers subscribe to tracking updates with plain callbacks instead of subscribing to valueSignal(), mirroring the W3C watchPosition(success, error) pair. Listeners persist across stop()/resume() cycles and never see the GeolocationPending state.
-
Add
getUI()toComponentEvent
Commit · Pull request · IssueComponentEventexposes the source component but offers no direct way to reach its UI. Listeners that need the UI must dereference anOptionalviaevent.getSource().getUI().ifPresent(...), which is verbose and silently swallows the case where the source is detached. AddComponentEvent.getUI()returningUIdirectly. When the source is itself aUI, it is returned as-is; otherwise the method returns the resolved UI or throwsIllegalStateExceptionwith a message pointing togetSource().getUI()for code that must handle a detached source. This matches the convention already used byAbstractAttachDetachEvent,UIInitEvent,BeforeEventand similar event types. -
Add GeolocationClient port for external test drivers
Commit · Pull requestExtract a
GeolocationClientport behind theGeolocationfacade andGeolocationTracker. Production wire behavior moves to a newBrowserGeolocationClient;executeJsexpressions, DOM event names and target elements are unchanged. - AddGeolocation.setClient(GeolocationClient)andGeolocationTracker.handle()as public framework-internal entry points so external browserless test drivers (e.g.vaadin/browserless-test) can swap the production client and reach the activeWatchHandlewithout reflection. - UseSerializableConsumer<T>for the port's listener types and markGeolocation.availabilitySubscriptiontransientso dev-mode UI/session serialization keeps working. Adds the API surface needed by the Geolocation browserless testing PRD requirement. The actual test driver lives invaadin/browserless-test(separate PR) — keeping it out offlowavoids dragging Selenium / TestBench into the dependency tree of consumers that only want browserless unit tests. ## Test plan - [ ]mvn test -pl :flow-server -Dtest=GeolocationTest— 33 wire-protocol assertions still pass (production behavior preserved byte-for-byte) - [ ]mvn test -pl :flow-server -Dtest=GeolocationClientSeamTest— 4 new tests pin thesetClient+handle()contract - [ ]mvn test -pl :flow-server -Dtest=SerializationTest,FlowClassesSerializableTest— dev-mode UI/session serialization still passes - [ ] End-to-end smoke: companion test driver branch invaadin/browserless-test(feat/geolocation-test-support) consumes this branch; ran against use-cases/geolocation, all 7 PRD use cases testable browserlessly (12 tests, 0 failures) -
Introduce HasComponentsOfType for typed child containers
Commit · Pull requestAdds HasComponentsOfType as the typed counterpart of HasComponents, for containers that should only accept a specific child component type (e.g. a breadcrumb trail that only accepts breadcrumb items). HasComponents now extends HasComponentsOfType and keeps the untyped add(String) convenience. All existing default methods are preserved via inheritance. ---------
-
Add bulk insert methods to ListSignal and SharedListSignal
Commit · Pull requestAdd insertAllLast, insertAllFirst, and insertAllAt to ListSignal that perform the entire batch with a single notification. Add insertAllLast, insertAllFirst and insertAllAt to SharedListSignal using transactions for atomicity. ---------
Fixes
-
Wrong existence check in getStaticResource
Commit · Pull requestOn Jetty 12.1.9, requests for static resources packaged inside a JAR (e.g.
vaadinPush.jsfromflow-push) fail withFileSystemNotFoundException.VaadinServletService.getStaticResourceverifies the URL returned byServletContext.getResourceviaPath.of(url.toURI()), which for ajar:file:...!/entryURI requires the JAR's NIOFileSystemto already be mounted in the JVM-wide cache. Jetty 12.1.8 incidentally kept those filesystems mounted during resource resolution; 12.1.9 no longer does, sogetFileSystemthrows and the existingcatch (URISyntaxException)lets the unchecked exception escape, producing HTTP 500. Probe the URL withURL.openStream()instead.JarURLConnectionandFileURLConnectionusejava.util.jar.JarFile/java.io.Filedirectly and are independent of the NIOFileSystemscache, so the check works uniformly forfile:andjar:file:URLs and on every Jetty 12 build. The catch is broadened toIOException, covering both missing files (the original Jetty 12 workaround) and missing JAR entries. -
Show validation error when null is bound to primitive bean property
Commit · Pull request · Issues 24253, 24253When a field bound to a primitive property (e.g. double) produces a null value through converters, the Binder now returns a descriptive validation error instead of throwing a NullPointerException. ## Description When a
Binderfield is bound to a primitive bean property (e.g.double) viabind("propertyName"), and the field value becomesnullthrough a converter chain (for exampleStringToBigDecimalConverter→BigDecimalToDoubleConverterproducingnull), the Binder was throwing aNullPointerExceptioninstead of showing a user-facing validation error. This fix detects primitive-typed properties inbind(String propertyName)and adds a converter that returns a descriptiveResult.error(...)when the value isnull, preventing the NPE and surfacing a meaningful message. ## Type of change - [x] Bugfix - [ ] Feature ## Checklist - [x] I have read the contribution guide: https://vaadin.com/docs/latest/guide/contributing/overview/ - [x] I have added a description following the guideline. - [x] The issue is created in the corresponding repository and I have referenced it. - [x] I have added tests to ensure my change is effective and works as intended. - [x] New and existing tests are passing locally with my change. - [x] I have performed self-review and corrected misspellings. --------- -
Always invoke callback / signal on JS-bridge failure
Commit · Pull requestGeolocation.get() and the watch path silently dropped the request when the JS bridge rejected (module not loaded, executeJs error, serialization mismatch). The application saw nothing — only a DEBUG log line. Now both paths surface the failure as a GeolocationError carrying the UNKNOWN error code, so apps see a consistent outcome via their existing callback or valueSignal.
-
Normalize bare stylesheet paths to prevent MalformedURLException warnings
Commit · Pull request · IssueBare paths like "lumo/lumo.css" from @Stylesheet annotations cause a MalformedURLException warning because ServletContext.getResource() requires a leading '/'. Normalize the path in ResourceContentHash before calling getStaticResource(), and document the '/' requirement in VaadinService.getStaticResource() javadoc. ---------
-
Replace setClient with Lookup-based factory SPI
Commit · Pull requestPromotes
GeolocationClientto a public SPI and adds aGeolocationClientFactoryextension point resolved throughcom.vaadin.flow.di.Lookup. -Geolocationqueries the factory at construction time and falls back to the built-in browser-backed client when none is registered. -setClientis retained as a package-private in-JAR seam for flow-server's own tests; external test drivers and native bridges register a factory viaMETA-INF/services. ## Why Follow-up to #24211. ThesetClientseam (originally package-private) breaks under split-classloader topologies (Quarkus, OSGi, JPMS): the JVM scopes runtime packages by classloader, so a class inflow-server.jarand one in an external JAR with the same package name end up in different runtime packages when their JARs load through different classloaders. Cross-JAR package-private access then throwsIllegalAccessErrorat class definition time. Reproduced locally and in CI on browserless-test PR #51:java.lang.IllegalAccessError: class com.vaadin.flow.component.geolocation.BrowserlessGeolocationClient cannot access its superinterface com.vaadin.flow.component.geolocation.GeolocationClient (BrowserlessGeolocationClient is in unnamed module of loader QuarkusClassLoader @34eb5d01; GeolocationClient is in unnamed module of loader QuarkusClassLoader @272778ae)Lookupis classloader-aware and explicitly designed for this kind of pluggable SPI — it's the canonical Flow pattern (InstantiatorFactory,RoutePathProvider,ResourceProvider,StaticFileHandlerFactory,BrowserLiveReloadAccessor). ## Changes -GeolocationClient→public interfacewith proper SPI Javadoc (no longer "framework internal"). -GeolocationClientFactory(new) →public interface, resolved viaLookup; documents the use cases (in-memory test drivers; native bridges in Cordova/Electron/Capacitor shells). -Geolocationconstructor callsresolveClient(ui)→ looks up a factory viaVaadinService.getCurrent()→Lookup.lookup(GeolocationClientFactory.class); falls back tonew BrowserGeolocationClient(ui, seed)when no factory is registered. -setClientstays package-private — flow-server's own tests use it; external code goes throughLookup. -BrowserGeolocationClientJavadoc updated to mention it's the no-factory default. -GeolocationClientSeamTestnow exercises both the public Lookup-factory path and the in-packagesetClientseam. NoMETA-INF/servicesfile is shipped from flow-server: production has no factory and uses the fallback. Implementations live in external JARs (e.g. browserless-test-shared). ## Test plan - [x]mvn test -pl :flow-server -Dtest='*Geolocation*'— 38/38 pass locally. - [x] No@SuppressWarnings("NullAway")introduced; constructor assigns theclientfield directly. - [x] Local end-to-end verification with browserless-test patched to register a factory: Quarkus suite that previously threwIllegalAccessErrornow passes (junit6 21/21, quarkus 11/11, includingQuarkusBrowserlessBaseClassTestandQuarkusUnitSecurityTestthat were failing). - [ ] Reviewer to confirm SPI shape (factory vs. direct service) is acceptable for Flow's public API surface. cc @mcollovati -
Emit servlet-relative href for AppShell @Stylesheet
Commit · Pull requestAppShellRegistry.resolveStyleSheetHref expanded context://-prefixed @Stylesheet values server-side using request.getContextPath() + "/", producing absolute server paths like that get baked into index.html. This breaks behind reverse proxies that don't preserve the servlet container's context path in the public URL: the server emits /foo/styles.css but the browser fetches it from the public host where /foo/ doesn't exist. Use service.getContextRootRelativePath(request) instead — the same servlet-relative path (./, ../, etc.) that the bootstrap callback populates into CONTEXT_ROOT_URL for the UIDL path. The resulting href is resolved by the browser against , which Vaadin sets from the actual request URL (honoring X-Forwarded-* headers). This brings AppShell-level @Stylesheet resolution in line with the component-level UIDL path, which already used the relative form via the client-side URIResolver. Test fixtures updated to reflect the new servlet-relative hrefs. AppShellRegistryAuraAutoLoadTest had a Mockito mock that returned null for getContextRootRelativePath; it now stubs "./". Related to #24218.
-
Handle Unicode classpath resource paths
Commit · Pull requestUnder a directory whose path contains encoded Unicode or decomposed Unicode characters.
CustomResourceLoaderpreviously compared resource paths fromURL#getPath(), which can leave parts of the classpath root percent-encoded. When Spring later returned class resources using a decoded path, the resource no longer matched its parent root and startup could fail withParent resource ... not found in the resources!. This can happen even when both paths refer to the same filesystem location. The issue is that the compared strings are not in the same representation: -URL#getPath()can return a path where non-ASCII characters are still percent-encoded, for example%C3%A7or%CC%A7. - Spring resource resolution can later return the corresponding class resource using a decoded filesystem path. - A raw string comparison then fails, even though both paths point to the same location. This is especially easy to reproduce with decomposed Unicode characters. A decomposed character is represented as a base character plus one or more combining marks instead of a single precomposed code point. For example,çcan be represented either as the single code pointU+00E7, or ascplus the combining cedillaU+0327. Visually the path can look correct, but the encoded URL path and the decoded filesystem path are different strings. The culprit was therefore not Unicode normalization itself, but comparing URL-encoded paths with decoded paths during parent/child classpath resource matching. This change normalizes resource paths throughURL#toURI().getPath()for comparisons, while keeping the original URL path for dev-mode cache keys. It also keeps the existing native-imagefile:///resources!handling and falls back to the original URL path if URI conversion is not possible. The key piece needed to make the fix work is using the decoded URI path for comparable resource paths:java resource.getURL().toURI().getPath()instead of relying on the raw URL path for matching:java resource.getURL().getPath()This follows the JDK recommendation for URL escaping handling.URLdoes not itself encode or decode URL components, and the recommended way to manage URL encoding and decoding is to useURIand convert betweenURLandURI. Recommended reference: https://docs.oracle.com/javase/8/docs/api/java/net/URL.html Closes #11871 ## Implementation note The regression test needs to exerciseCustomResourceLoaderdirectly. I found existing Flow tests using both patterns: some use reflection to reach private implementation details, and others keep implementation types package-private so same-package tests can instantiate them directly. I chose to makeCustomResourceLoaderpackage-private instead of using reflection, because it keeps the test simpler while still avoiding public API exposure. If the maintainers prefer preserving the private nested class, I can switch the test back to reflection and makeCustomResourceLoaderprivate again. The important behavioral change is limited to the comparable path used for parent/child resource matching. The original URL path is still preserved where the previous encoded form is required, such as dev-mode cache lookup keys. ## Testing - Reproduced the issue with a minimal Spring Boot/Vaadin application in a project path containingFrançoisand a decomposed Unicode segment. Before the fix, the app failed during test startup withParent resource ... not found in the resources!. - Verified the same minimal application starts/tests successfully after installing the fixed local Flow artifacts. - Added regression coverage inVaadinServletContextInitializerTestfor a classpath root containing Unicode and decomposed Unicode characters. - Verified the regression test is meaningful: with only the production not found in the resources!`; with the fix restored, the same test passes. Local checks run:bash mvn -q -P!install-git-hooks -pl vaadin-spring spotless:check mvn -q -P!install-git-hooks -pl vaadin-spring -Dtest=VaadinServletContextInitializerTest test mvn -q -P!install-git-hooks -pl vaadin-spring -Dtest=VaadinServletContextInitializerTest#customResourceLoader_classpathRootContainsUnicodeCombiningCharacter_resourcesAreMatched test mvn -q -P!install-git-hooks -pl vaadin-spring -am -DskipTests -Dexec.skip=true install## AI Disclosure Code drafted with OpenClaw/Codex for contributor review. Tests were added and run locally by the assistant. I reviewed the code and this description before opening the PR. -
Prevent deadlock on concurrent push and disconnect
Commit · Pull request · IssueThe previous
AtomicBooleanguard inAtmospherePushConnectionclosed only the disconnect-vs-disconnect race. A push thread that readsdisconnectingas false before a concurrentdisconnect()flips it can still proceed intosynchronized(lock)behind the disconnect thread, which is itself blocked insideresource.close()waiting for the servlet container's HTTP session lock held by the push thread — a two-lock cycle. Moveresource.close()out of the monitor: insidesynchronized(lock)capture the resource into a local and callconnectionLost()to transition the state, then release the monitor before invokingclose()on the stashed reference. Add a matching re-check ofisConnected()at the top of thesynchronizedblock inpush()so a push that waited for the monitor observes the late disconnect and defers viaPUSH_PENDING/RESPONSE_PENDINGinstead of NPEing on the cleared resource. Thedisconnectingflag stays set untilclose()returns so subsequent pushes take the fast path and no newdisconnect()re-enters whileclose()is still in flight.