github vaadin/flow 25.3.0
Vaadin Flow 25.3.0

3 hours ago

Overview

What changed in 25.3, in plain words:

  • Tables you write in Java. Real HTML tables — captions, headers, merged cells — built from Java code instead of hand-written markup, with the compiler refusing to let you build a table the browser cannot render.
  • Form validation with more than one set of rules. Binder can now validate a bean against different validation groups: the everyday rules while the user types, and the stricter ones only when they press Save.
  • A much shorter wait between editing code and seeing it run. Until now, changing a line meant rebuilding and restarting the application and then guessing from the screen whether the new code was really running. A new background helper watches the project, compiles in the background, slips the change into the app that is already running, refreshes the browser — and then says outright whether your change is live, failed to compile, or was already replaced by a newer edit. This matters most for AI coding assistants, which previously had no reliable way to tell whether what they were looking at was their own change.
  • The browser half of Vaadin was rewritten. The code that runs inside the browser is now written in the language the rest of the frontend uses, so problems are far easier to trace, and the old engine is gone. Nothing to change in your own code.
  • Server-Sent Events as a third way to push updates to the browser, next to WebSocket and long polling — for corporate networks and proxies that drop WebSocket connections.
  • Applications can watch what the framework is doing — requests, locks, database queries behind a table — through one place instead of a handful of separate hooks.
  • A view can find out how much room it actually got on the screen and lay itself out accordingly, without any hand-written browser code.
  • Static files are served pre-compressed — a production build ships Brotli and Gzip copies, so the browser downloads less. Plus the usual round of updated dependencies.

All changes

Breaking Changes

  • Pin npm versions from a versions folder in every jar (#25308)

    Flow now reads every .json file in META-INF/VAADIN/versions/ on the classpath and merges them, instead of looking up vaadin-core-versions.json / vaadin-versions.json at the classpath root. Any jar — platform, commercial add-on or component — can pin its own npm versions.

  • Image is now an HtmlComponent (#24261)

    <img> is a void element, so setText and add(Component) no longer exist on Image.

  • Spring @Configuration classes promoted to @AutoConfiguration (#24304)

    All four vaadin-spring configurations are now registered through Spring Boot auto-configuration with proxyBeanMethods=false. Code that relied on full bean-method interception of those classes will see different behavior.

Deprecations and Removals

  • The NativeTable family is deprecated for removal in V26 (#25383)

    The new Table family covers everything NativeTable did, with the structure enforced by types. NativeTable, NativeTableRow, the section components and their TestBench elements are deprecated, so an integration test migrating off them gets a compiler signal now rather than discovering the removal in 26.

  • The 25.2 session lock and RPC invocation listener APIs are deprecated for removal (#25268)

    SessionLockListener, SessionLockEvent, RpcInvocationListener and RpcInvocationEvent are superseded by the VaadinService event bus. Existing registrations keep working — they listen for the new events and forward to the old callbacks.

  • The GWT client engine is removed (#24955)

    The com.vaadin.client.* sources, the GWT build and test wiring, and the server code that loaded the compiled engine are gone now that the browser client runs from TypeScript. Internal API — no application impact.

  • VaadinServlet.serveStaticOrWebJarRequest is deprecated (#25065) — static resources are served through StaticFileHandler.serveStaticResource directly.

  • ValueChangeMode.ON_BLUR is deprecated for removal in V27 (#25165) — use ON_CHANGE, which also fires on focus loss but only on an actual change, and covers cases ON_BLUR misses such as clicking the clear button.

  • The remaining user agent and platform detection methods are deprecated (#25152) — WebBrowser.isIPhone() and ExtendedClientDetails.isIPad()/isIOS() are built on window.navigator.platform, which browsers themselves have deprecated.

  • Warnings for the V26 defaults of TreeDataProvider and setItems (#24908, #24940) — the behavior changes in 26, and the warning names the replacement now.

  • Polymer 1 DOM API and HTML import loading removed (#25145) — dead code: HTML imports can no longer be loaded and Polymer 3 never defines the global the check looked for.

New Features

HTML Table component family

  • Add the Table component family (#25380)

    A complete, typed set of semantic table components — Table, TableCaption, TableColumnGroup/TableColumn, TableHead/TableBody/TableFoot, TableRow, TableHeaderCell/TableDataCell — where the structure the HTML specification allows is the only structure the API allows, with spans, scope/headers association, signal-bound text and children, and matching TestBench elements. Deprecates NativeTable.

    Table table = new Table();
    table.setAriaLabel("Planets");
    table.addCaption(new Text("Data about the planets of our solar system"));
    
    TableRow header = table.getHead().addRow();
    header.addColumnHeaderCells("Name", "Mass (10^24 kg)", "Diameter (km)");
    
    table.getBody().bindChildren(planets, signal -> {
        Planet planet = signal.peek();
        TableRow row = new TableRow();
        row.addRowHeaderCell(planet.name());   // <th scope="row">
        row.addDataCells(String.valueOf(planet.mass()),
                String.valueOf(planet.diameter()));
        return row;
    });

Forms and data binding

  • Support JSR-303 validation groups in BeanValidationBinder (#25187)

    One bean can carry more than one rule set: setValidationGroups(...) chooses the groups enforced as the user types, while validate(...) / isValid(...) run a one-shot check against stricter groups that should only block saving. Required indicators follow the groups in effect, including @GroupSequence expansion.

    var binder = new BeanValidationBinder<>(Article.class);
    binder.setValidationGroups(Default.class);           // while editing
    
    saveButton.setEnabled(binder.isValid(Default.class));
    publishButton.addClickListener(e -> {
        if (binder.validate(Default.class, Publish.class).isOk()) {
            publish(binder.getBean());
        }
    });
  • Reject uploads with a synchronous validator (#24925) — refuse a file before it is stored, from the receiving thread.

Development loop

  • vaadin-dev dev loop (#25376)

    A CLI over a long-running daemon (flow-devloop-daemon, installed with mvn vaadin:install-dev-cli) that owns background compilation, hot swap or restart, CSS and theme push, and browser refresh — and answers authoritatively whether the last change is live, so coding agents stop guessing from screenshots.

    $ .vaadin/vaadin-dev apply
    change-set: 2 file(s): src/main/java/.../TaskListView.java, src/main/frontend/themes/app/styles.css
    compiling → runtime → Stable   (1.2s)
    hot-reload: redefineClasses(1); onHotswap completed=true

    The exit code is the verdict: 0 live, 1 failed, 4 superseded.

Push and server events

  • Add Server-Sent Events as a push transport option (#24484)

    Transport.SERVER_SENT_EVENTS gives HTTP streaming for environments where proxies drop WebSockets, without long-polling latency. Experimental — enable the com.vaadin.experimental.ssePushTransport feature flag first.

    @Push(transport = Transport.SERVER_SENT_EVENTS)
    public class AppShell implements AppShellConfigurator {
    }
  • Give VaadinService an event bus and route its listeners through it (#25268)

    VaadinService#getEventBus() dispatches standalone events by exact runtime type, is safe to use from several request threads, and adds hasListener(Class) and fireEventInReverseOrder(...). The session lock and RPC invocation callbacks introduced in 25.2 became SessionLock*Event / RpcInvocation*Event on the bus, and the old listener APIs are deprecated.

    service.getEventBus().addListener(RpcInvocationEndedEvent.class,
            event -> metrics.record(event.getInvocation(), event.getDuration()));
  • Report data provider queries on the service event bus (#25262) — count and fetch queries are observable for instrumentation.

  • Report synchronized property updates to RPC invocation observers (#25237).

  • Warn about JavaScript invocations that are never delivered to the client (#25252).

Components, elements and signals

  • Element.sizeSignal() for tracking element size (#23618)

    A read-only Signal<Size> holding the size the browser reports for an element, so a view can lay itself out by the pixel size it really got — no application JavaScript needed.

    Signal<Size> size = layout.getElement().sizeSignal();
    Signal.effect(layout,
            () -> layout.setClassName("narrow", size.get().width() < 600));
  • Element.whenAttached / Component.whenAttached for attach-scoped setup and cleanup (#25294)

    The handler runs on every attach and the Registration it returns runs on the matching detach, instead of splitting one concern across onAttach, onDetach and a field.

    whenAttached(ui -> broker.subscribe(msg -> ui.access(() -> show(msg))));
  • Add HasAriaRole (#24782) and HasAriaDescription (#25117).

  • Add InputMode enum and support it in Input (#24856) — hint the on-screen keyboard.

  • Add ExtendedClientDetails.getBrowserTime() returning an Instant (#25480).

  • Support parameterized value types in shared signals through Jackson TypeReference (#25297).

Build, frontend and tooling

  • Compress static resources (#25034) — META-INF/resources is pre-compressed to Brotli and Gzip in a production build.
  • Support not having a project-specific index.html (#24469) — the generated shell lives in frontend/generated/ again and a project file still overrides it.
  • Remove unused Node.js installations and leftover archives from ~/.vaadin (#25261).
  • Unify file and folder deletion, with an actionable message when it fails (#25078) — symlinks and junctions handled everywhere.
  • Improve AppShellConfigurator annotation error details (#25390).
  • Add a playable game to the PWA offline page (#25526).

Major Refactorings

  • Client engine ported from GWT to TypeScript (#24933, #24947, #24948, #24949, #24950, #24951, #24952, #24953)

    The whole browser-side engine — reactive core, state tree and JSON codec, DOM binding, resource loading, executeJs, the network layer and the bootstrap — is now TypeScript, which makes it debuggable with real source maps. The GWT engine was deleted afterwards (#24955). Purely internal: no application code changes required.

  • Enforce leaf-lock to tree-lock ordering to catch ABBA deadlocks (#25171).

  • Introduce a NodeInstallation abstraction and read platform versions, classpath folders and the JBoss VFS each in one place (#25266, #25348, #25347, #25367, #25381).

  • Move usage statistics collection out of VaadinService (#25453).

  • Set the page title through Page.executeJs (#25249) — one place where a pending JavaScript invocation comes into existence, not three.

  • Use the elemental DOM API directly in the client engine (#25146) and name npm version pinning consistently (#25332).

Dependency Updates

Compared with 25.2.0:

  • TypeScript updated to 7.0.2 (from 6.0.3)
  • Vite updated to 8.3.0 (from 8.0.16)
  • React Router updated to 8.4.0 (from 7.17.0), React to 19.3.0
  • Node.js updated to 24.21.0 (from 24.17.0)
  • pnpm updated to 11.26.0 (from 11.6.0)
  • Tailwind CSS updated to 4.3.3 (from 4.3.1)
  • Spring Boot updated to 4.1.1 (from 4.1.0)
  • Jackson BOM updated to 3.1.5 (from 3.1.3)
  • Jetty updated to 12.1.13 (from 12.1.9)
  • JUnit Jupiter updated to 6.1.3 (from 6.1.0), with all JUnit artifacts managed through junit-bom
  • Hibernate Validator updated to 9.1.3.Final (from 9.1.0.Final)
  • Guava updated to 33.7.1-jre (from 33.6.0-jre)
  • jsoup updated to 1.23.2 (from 1.22.2)
  • SLF4J updated to 2.0.19 (from 2.0.18)
  • TestBench updated to 25.3 (from 25.2)
  • Kotlin updated to 2.4.20 (web push module)

Changes since 25.3.0-rc2

All changes

Don't miss a new flow release

NewReleases is sending notifications on new releases.