github vaadin/flow 25.2.0-alpha11
Vaadin Flow 25.2.0-alpha11

pre-release10 hours ago

Changes since 25.2.0-alpha9

All changes

New features

  • Add pageVisibilitySignal() to Page for tracking browser tab visibility
    Commit · Pull request

    Add 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 request

    Expose 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 · Issue

    Exposes 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 request

    Lets 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() to ComponentEvent
    Commit · Pull request · Issue

    ComponentEvent exposes the source component but offers no direct way to reach its UI. Listeners that need the UI must dereference an Optional via event.getSource().getUI().ifPresent(...), which is verbose and silently swallows the case where the source is detached. Add ComponentEvent.getUI() returning UI directly. When the source is itself a UI, it is returned as-is; otherwise the method returns the resolved UI or throws IllegalStateException with a message pointing to getSource().getUI() for code that must handle a detached source. This matches the convention already used by AbstractAttachDetachEvent, UIInitEvent, BeforeEvent and similar event types.

  • Add GeolocationClient port for external test drivers
    Commit · Pull request

    Extract a GeolocationClient port behind the Geolocation facade and GeolocationTracker. Production wire behavior moves to a new BrowserGeolocationClient; executeJs expressions, DOM event names and target elements are unchanged. - Add Geolocation.setClient(GeolocationClient) and GeolocationTracker.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 active WatchHandle without reflection. - Use SerializableConsumer<T> for the port's listener types and mark Geolocation.availabilitySubscription transient so dev-mode UI/session serialization keeps working. Adds the API surface needed by the Geolocation browserless testing PRD requirement. The actual test driver lives in vaadin/browserless-test (separate PR) — keeping it out of flow avoids 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 the setClient + 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 in vaadin/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 request

    Adds 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 request

    Add 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 request

    On Jetty 12.1.9, requests for static resources packaged inside a JAR (e.g. vaadinPush.js from flow-push) fail with FileSystemNotFoundException. VaadinServletService.getStaticResource verifies the URL returned by ServletContext.getResource via Path.of(url.toURI()), which for a jar:file:...!/entry URI requires the JAR's NIO FileSystem to 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, so getFileSystem throws and the existing catch (URISyntaxException) lets the unchecked exception escape, producing HTTP 500. Probe the URL with URL.openStream() instead. JarURLConnection and FileURLConnection use java.util.jar.JarFile / java.io.File directly and are independent of the NIO FileSystems cache, so the check works uniformly for file: and jar:file: URLs and on every Jetty 12 build. The catch is broadened to IOException, 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, 24253

    When 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 Binder field is bound to a primitive bean property (e.g. double) via bind("propertyName"), and the field value becomes null through a converter chain (for example StringToBigDecimalConverterBigDecimalToDoubleConverter producing null), the Binder was throwing a NullPointerException instead of showing a user-facing validation error. This fix detects primitive-typed properties in bind(String propertyName) and adds a converter that returns a descriptive Result.error(...) when the value is null, 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 request

    Geolocation.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 · Issue

    Bare 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 request

    Promotes GeolocationClient to a public SPI and adds a GeolocationClientFactory extension point resolved through com.vaadin.flow.di.Lookup. - Geolocation queries the factory at construction time and falls back to the built-in browser-backed client when none is registered. - setClient is retained as a package-private in-JAR seam for flow-server's own tests; external test drivers and native bridges register a factory via META-INF/services. ## Why Follow-up to #24211. The setClient seam (originally package-private) breaks under split-classloader topologies (Quarkus, OSGi, JPMS): the JVM scopes runtime packages by classloader, so a class in flow-server.jar and 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 throws IllegalAccessError at 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) Lookup is classloader-aware and explicitly designed for this kind of pluggable SPI — it's the canonical Flow pattern (InstantiatorFactory, RoutePathProvider, ResourceProvider, StaticFileHandlerFactory, BrowserLiveReloadAccessor). ## Changes - GeolocationClientpublic interface with proper SPI Javadoc (no longer "framework internal"). - GeolocationClientFactory (new) → public interface, resolved via Lookup; documents the use cases (in-memory test drivers; native bridges in Cordova/Electron/Capacitor shells). - Geolocation constructor calls resolveClient(ui) → looks up a factory via VaadinService.getCurrent()Lookup.lookup(GeolocationClientFactory.class); falls back to new BrowserGeolocationClient(ui, seed) when no factory is registered. - setClient stays package-private — flow-server's own tests use it; external code goes through Lookup. - BrowserGeolocationClient Javadoc updated to mention it's the no-factory default. - GeolocationClientSeamTest now exercises both the public Lookup-factory path and the in-package setClient seam. No META-INF/services file 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 the client field directly. - [x] Local end-to-end verification with browserless-test patched to register a factory: Quarkus suite that previously threw IllegalAccessError now passes (junit6 21/21, quarkus 11/11, including QuarkusBrowserlessBaseClassTest and QuarkusUnitSecurityTest that 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 request

    AppShellRegistry.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 request

    Under a directory whose path contains encoded Unicode or decomposed Unicode characters. CustomResourceLoader previously compared resource paths from URL#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 with Parent 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%A7 or %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 point U+00E7, or as c plus the combining cedilla U+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 through URL#toURI().getPath() for comparisons, while keeping the original URL path for dev-mode cache keys. It also keeps the existing native-image file:///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. URL does not itself encode or decode URL components, and the recommended way to manage URL encoding and decoding is to use URI and convert between URL and URI. Recommended reference: https://docs.oracle.com/javase/8/docs/api/java/net/URL.html Closes #11871 ## Implementation note The regression test needs to exercise CustomResourceLoader directly. 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 make CustomResourceLoader package-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 make CustomResourceLoader private 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 containing François and a decomposed Unicode segment. Before the fix, the app failed during test startup with Parent resource ... not found in the resources!. - Verified the same minimal application starts/tests successfully after installing the fixed local Flow artifacts. - Added regression coverage in VaadinServletContextInitializerTest for 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 · Issue

    The previous AtomicBoolean guard in AtmospherePushConnection closed only the disconnect-vs-disconnect race. A push thread that reads disconnecting as false before a concurrent disconnect() flips it can still proceed into synchronized(lock) behind the disconnect thread, which is itself blocked inside resource.close() waiting for the servlet container's HTTP session lock held by the push thread — a two-lock cycle. Move resource.close() out of the monitor: inside synchronized(lock) capture the resource into a local and call connectionLost() to transition the state, then release the monitor before invoking close() on the stashed reference. Add a matching re-check of isConnected() at the top of the synchronized block in push() so a push that waited for the monitor observes the late disconnect and defers via PUSH_PENDING/RESPONSE_PENDING instead of NPEing on the cleared resource. The disconnecting flag stays set until close() returns so subsequent pushes take the fast path and no new disconnect() re-enters while close() is still in flight.

Don't miss a new flow release

NewReleases is sending notifications on new releases.