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.
Bindercan 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.
Breaking Changes
-
Pin npm versions from a
versionsfolder in every jar (#25308)Flow now reads every
.jsonfile inMETA-INF/VAADIN/versions/on the classpath and merges them, instead of looking upvaadin-core-versions.json/vaadin-versions.jsonat the classpath root. Any jar — platform, commercial add-on or component — can pin its own npm versions. -
Imageis now anHtmlComponent(#24261)<img>is a void element, sosetTextandadd(Component)no longer exist onImage. -
Spring
@Configurationclasses promoted to@AutoConfiguration(#24304)All four
vaadin-springconfigurations are now registered through Spring Boot auto-configuration withproxyBeanMethods=false. Code that relied on full bean-method interception of those classes will see different behavior.
Deprecations and Removals
-
The
NativeTablefamily is deprecated for removal in V26 (#25383)The new
Tablefamily covers everythingNativeTabledid, 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,RpcInvocationListenerandRpcInvocationEventare superseded by theVaadinServiceevent 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.serveStaticOrWebJarRequestis deprecated (#25065) — static resources are served throughStaticFileHandler.serveStaticResourcedirectly. -
ValueChangeMode.ON_BLURis deprecated for removal in V27 (#25165) — useON_CHANGE, which also fires on focus loss but only on an actual change, and covers casesON_BLURmisses such as clicking the clear button. -
The remaining user agent and platform detection methods are deprecated (#25152) —
WebBrowser.isIPhone()andExtendedClientDetails.isIPad()/isIOS()are built onwindow.navigator.platform, which browsers themselves have deprecated. -
Warnings for the V26 defaults of
TreeDataProviderandsetItems(#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
Tablecomponent 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/headersassociation, signal-bound text and children, and matching TestBench elements. DeprecatesNativeTable.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, whilevalidate(...)/isValid(...)run a one-shot check against stricter groups that should only block saving. Required indicators follow the groups in effect, including@GroupSequenceexpansion.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-devdev loop (#25376)A CLI over a long-running daemon (
flow-devloop-daemon, installed withmvn 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:
0live,1failed,4superseded.
Push and server events
-
Add Server-Sent Events as a push transport option (#24484)
Transport.SERVER_SENT_EVENTSgives HTTP streaming for environments where proxies drop WebSockets, without long-polling latency. Experimental — enable thecom.vaadin.experimental.ssePushTransportfeature flag first.@Push(transport = Transport.SERVER_SENT_EVENTS) public class AppShell implements AppShellConfigurator { }
-
Give
VaadinServicean 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 addshasListener(Class)andfireEventInReverseOrder(...). The session lock and RPC invocation callbacks introduced in 25.2 becameSessionLock*Event/RpcInvocation*Eventon 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.whenAttachedfor attach-scoped setup and cleanup (#25294)The handler runs on every attach and the
Registrationit returns runs on the matching detach, instead of splitting one concern acrossonAttach,onDetachand a field.whenAttached(ui -> broker.subscribe(msg -> ui.access(() -> show(msg))));
-
Add
InputModeenum and support it inInput(#24856) — hint the on-screen keyboard. -
Add
ExtendedClientDetails.getBrowserTime()returning anInstant(#25480). -
Support parameterized value types in shared signals through Jackson
TypeReference(#25297).
Build, frontend and tooling
- Compress static resources (#25034) —
META-INF/resourcesis pre-compressed to Brotli and Gzip in a production build. - Support not having a project-specific
index.html(#24469) — the generated shell lives infrontend/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
AppShellConfiguratorannotation 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
NodeInstallationabstraction 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)