Skip to content

Stability & versioning

From the 3.0 beta on, Mateu commits to the rules on this page. Before it — the 3.0 alphas — the API changed in place; Migrating from alpha lists everything that did.

  • Releases are tagged vMAJOR.MINOR[-qualifier.N] and published to Maven Central under io.mateu, every artifact named mateu-* (mateu-uidl, mateu-mvc, mateu-vaadin…; the pre-beta ids are relocation poms — see Migrating from alpha), with the tag’s version (3.0-alpha.406, 3.0-beta.1, 3.0.0, 3.1.0…). Every module of one release shares that version — never mix versions of io.mateu artifacts in one app.
  • Major (3 → 4): may break the public API or the wire. Announced in advance, with a migration guide.
  • Minor (3.0 → 3.1): new features; no breaking change to the public API or the wire. May deprecate.
  • Patch (3.0.0 → 3.0.1): fixes only.
  • Pre-releases (-alpha.N, -beta.N): alphas may break anything; betas follow the deprecation policy below, except for fixes to defaults that were a security problem.

The first beta freezes the public surface listed under What is public API. From that tag on:

  • Stable API changes only through deprecation. A replacement is added, the old element is marked deprecated (naming the replacement) and both work side by side for at least one release — deprecated in 3.0-beta.N, removable in 3.0-beta.N+1 at the earliest; once 3.0.0 is out, the one-minor rule below applies. Nothing stable is renamed in place.
  • Experimental API can change in any beta (and later in any minor) — that is what the marker is for. The list below is the exact boundary.
  • The wire stays additive within wireVersion 3.x (see Wire compatibility), and YAML keys follow the same add-then-deprecate rule as Java names.
  • The compatibility check that enforces this for Java (Enforcement) is switched to blocking at the beta tag.

You can build on these; they follow the deprecation policy.

SurfaceWhere it lives
The authoring API — annotations (@UI, @Action, @Section…), interfaces (CrudStore, Listing and its capability interfaces, suppliers…), the fluent component records and data typesthe io.mateu:mateu-uidl artifact (io.mateu.uidl.*)
The archetypes and orchestrators you extend — AutoCrud, FilteredAutoCrud, Crud, Wizard, Dashboard, Foldout, HeroSearch, CollectionDetail, …their public/protected members in io.mateu:mateu-core (io.mateu.core.infra.declarative.orchestrators.*)
The wire — the JSON exchanged between backend and rendererthe wire specification, versioned by wireVersion; compatibility rules below. The Java DTO classes of io.mateu:mateu-dtos that model it are not under the Java binary-compatibility promise — see The wire DTOs
The authoring schemas — uidl-schema.json, routes-schema.json, sources-schema.json, specs-schema.json (YAML specs/ui/**)backend/shared/uidl/*.json, generated from the records
The HTTP contract — POST {baseUrl}/mateu/v3/…the wire specification
Build integration — annotation processor coordinates and options, mateu-bundle-maven-plugin goals and parameterstheir Maven coordinates
Adapter configuration — the mateu.* properties the guides document (mateu.cors.allowed-origins, mateu.ui.modules…) and @EnableMateuUiModules (Spring MVC / WebFlux)the adapter artifacts (mateu-mvc, mateu-webflux, …); everything else in them is internal
The .NET and Python authoring APIs — Mateu.Uidl attributes/types, mateu_uidl decorators/markersfollow the same policy once published as packages (see the release notes)

mateu-dtos is public — you can read it, and a renderer or a non-Java producer can build on the JSON it describes — but its contract is the JSON wire, not the Java classes. The wire is versioned by wireVersion and stays additive within a major (Wire compatibility); the Java records that model it are not covered by the binary-compatibility promise:

  • a new optional wire field adds a component to its DTO record, which replaces the record’s canonical constructor — harmless for the JSON, binary-incompatible for Java code that called that constructor;
  • so build DTOs through their builders (AppDto.builder()…), or better, don’t build them at all: hand Mateu the authoring API (mateu-uidl) and let it map.

The compatibility check still compares mateu-dtos against the last release and publishes the report, but it never fails the build for it, even with enforcement on. Deprecations in mateu-dtos (an enum constant that is no longer sent, an unreachable DTO) still follow the deprecation policy.

The three public modules depend on each other in one direction only, and a test (LayeringTest in mateu-uidl) keeps it that way:

  • mateu-dtos — the wire — depends on nothing Mateu. A renderer or a non-Java producer can read it without the authoring API.
  • mateu-uidl — the authoring API — depends on mateu-dtos only through a closed set of wire-boundary types: the escape hatches that hand Mateu a ready-made wire object (DtoSupplier, MapsToDto, CardRow), the request as received (HttpRequest.runActionRq() / getUiRq(), used by SearchableSelection) and the long-task stream (LongTask, ProgressReporter). Everything else in mateu-uidl is wire-agnostic; a new import of io.mateu.dtos outside that list fails the build. It never depends on mateu-core.
  • mateu-core depends on both, and maps the one onto the other.

An app module that only declares UIs needs mateu-uidl (which brings mateu-dtos along for those boundary types); it never needs mateu-core at compile time.

Several concepts exist both as an annotation and as a fluent record — @Badge on a field and new Badge(…) in a component tree — so the same simple name lives in two packages. With two star imports the bare name is ambiguous and does not compile. Java’s rule settles it: a single-type import always wins over a star import. Star-import the package you use most and import the other type explicitly:

import io.mateu.uidl.annotations.*; // @Section, @Toolbar, @Badge…
import io.mateu.uidl.data.*;
import io.mateu.uidl.data.Badge; // the record wins for the bare name `Badge`…
// …and the annotation is then written qualified: @io.mateu.uidl.annotations.Badge

The shared names (pinned by NameCollisionsTest in mateu-uidl, so a new one is a reviewed decision):

PackagesNames
annotations / dataAvatar, Badge, Breadcrumb, Breadcrumbs, BulletedList, Button, Details, FormLayout, Icon, KPI, Menu, Notice, RestAction, Rule, Status, Tab, Text, Tooltip, Validation
annotations / fluentAction, Trigger, UI
annotations / interfacesApp, Filterable, Searchable
fluent / interfacesListing (the fluent listing component / the listing contract)
data / interfacesPage (a page of rows / the routed-page marker)
data / fluentStep
with java.utilList (@List), Map, Calendar — with import java.util.* also star-imported, import java.util.List/Map explicitly

Not public — may change in any release, without deprecation:

  • anything under io.mateu.core other than the archetypes above (mappers, use cases, resolvers, infra.* internals), the adapters’ internals (mateu-mvc, mateu-webflux, …) and the generated controllers and resolvers;
  • the renderers’ JavaScript/TypeScript internals (libs/mateu, apps/*) — the contract with a renderer is the wire, not the code;
  • anything marked @Deprecated(forRemoval = true) or @Experimental (see below), or “experimental” or “internal” in its Javadoc or in these docs;
  • log messages and the text of framework-generated error messages.

Some public surfaces are still being shaped. They are marked with @io.mateu.uidl.annotations.Experimental (on a type — which covers all its members — or on a single member), and the rule for them is:

@Experimental API may change or be removed in a minor release, without the one-minor deprecation period below. Everything else in the public API follows the deprecation policy.

The API compatibility check (see Enforcement) skips anything carrying the annotation. When an experimental API is promoted, the annotation is removed in a minor release and the CHANGELOG says so; from then on it is stable.

Experimental today:

SurfaceWhere
The AI assistant chat panel and its MCP wiring@AI, the Chat component (io.mateu.uidl); the MCP endpoint (POST /mateu/mcp, off by default) and the tool projection it serves; the agent-cli modules (not published)
The new YAML authoring cataloguesfield types (types.yaml: FieldTypeEntry, FieldTypeCatalog, FieldTypeCatalogSupplier, GridColumn.tones), translations (Translations, TranslationsSupplier), environments (Environment), YAML access keys (Access), the project descriptor (ProjectSettings, ProjectRenderer) and sample data on REST sources (RestDataSource.sample, RestSourceEntry.sample) — the YAML shapes and the Java types behind them
Development toolinglive reload (dev mode, /mateu/dev/*), the Redwood embedded <mateu-ui> JET component
The Figma design-to-code pipelinethe contract packaged at META-INF/mateu/contract.json in the uidl jar, the Figma plugin and the modux importer/codegen
The pattern-gap and PMS-parity features of 3.0-alpha.409 — weeks old, not yet proven in productionthe archetype display options (Toggle, CrudDisplay, WizardDisplay, GeneralOverviewDisplay), wizard drafts and navigation hooks (Draftable, stepSkippable, beforeStepNavigate, @WizardCompletionAction(availableFromStep)), the record switcher (RecordSwitcher, RecordSwitcherSupplier), docked panels (DockedPanel), the @Section affordances (editAction/addAction/viewMoreAction), hero tones, screen-reader announcements (UICommand.announce), pre-search content, the action panel, the matrix grid, map markers, drag rows to a drop zone, access keys, card menus, calendar views, hover popovers and cell tooltips, row tones, reorderable dashboards and the shared action catalogue (actions.yaml, ActionCatalog)

The exact list, type by type, is generated from the code.

Everything public in these packages is stable unless it is listed as experimental here or is deprecated. The table and the lists are generated from the @Experimental / @Deprecated markers in the sources (by ApiStabilityListTest in mateu-uidl, which fails the build when this page is stale), so they are exactly what the compatibility check enforces. The .NET (Mateu.Uidl) and Python (mateu_uidl) ports follow the same boundary for the same concepts.

ModulePackagePublic typesStableExperimentalDeprecatedInternal
mateu-core (archetypes)io.mateu.core.infra.declarative.orchestrators22000
mateu-core (archetypes)io.mateu.core.infra.declarative.orchestrators.calendar11000
mateu-core (archetypes)io.mateu.core.infra.declarative.orchestrators.collectiondetail11000
mateu-core (archetypes)io.mateu.core.infra.declarative.orchestrators.crud116005
mateu-core (archetypes)io.mateu.core.infra.declarative.orchestrators.crud.actionhandlers1100011
mateu-core (archetypes)io.mateu.core.infra.declarative.orchestrators.crud.routeresolvers70007
mateu-core (archetypes)io.mateu.core.infra.declarative.orchestrators.dashboard31002
mateu-core (archetypes)io.mateu.core.infra.declarative.orchestrators.datamanagement11000
mateu-core (archetypes)io.mateu.core.infra.declarative.orchestrators.editableview22000
mateu-core (archetypes)io.mateu.core.infra.declarative.orchestrators.foldout11000
mateu-core (archetypes)io.mateu.core.infra.declarative.orchestrators.ganttpage11000
mateu-core (archetypes)io.mateu.core.infra.declarative.orchestrators.generaloverview11000
mateu-core (archetypes)io.mateu.core.infra.declarative.orchestrators.herosearch11000
mateu-core (archetypes)io.mateu.core.infra.declarative.orchestrators.importwizard88000
mateu-core (archetypes)io.mateu.core.infra.declarative.orchestrators.itemoverview11000
mateu-core (archetypes)io.mateu.core.infra.declarative.orchestrators.masterdetail22000
mateu-core (archetypes)io.mateu.core.infra.declarative.orchestrators.smartsearch11000
mateu-core (archetypes)io.mateu.core.infra.declarative.orchestrators.todolist11000
mateu-core (archetypes)io.mateu.core.infra.declarative.orchestrators.welcome31002
mateu-core (archetypes)io.mateu.core.infra.declarative.orchestrators.wizard22000
mateu-dtosio.mateu.dtos2762480280
mateu-uidlio.mateu.uidl76100
mateu-uidlio.mateu.uidl.annotations172162730
mateu-uidlio.mateu.uidl.data3152634660
mateu-uidlio.mateu.uidl.di22000
mateu-uidlio.mateu.uidl.fluent3431030
mateu-uidlio.mateu.uidl.interfaces10896822
mateu-uidlio.mateu.uidl.layout30003
mateu-uidlio.mateu.uidl.reflection30003
mateu-uidlio.mateu.uidl.security22000
Total983844624235

Experimental types (the whole type and its members):

  • io.mateu.uidl: LiveHeaderValues
  • io.mateu.uidl.annotations: AI, AppShortcut, AppShortcuts, DragRows, MobileApp, RowStatus, Tooltip
  • io.mateu.uidl.data: Access, ActionCatalog, ActionPanel, ActionPanelCategory, ActionPanelItem, Announcement, AppContextSelector, AppGlobalSearchSource, AppNotificationsSource, AppShortcutEntry, CalendarDay, CalendarView, Chat, CrudDisplay, DockedPanel, Document, DocumentDisposition, DropZone, Environment, FieldTypeCatalog, FieldTypeEntry, GeneralOverviewDisplay, HeaderValue, HeroTone, MapMarker, MatrixCell, MatrixColumn, MatrixGrid, MatrixRow, MatrixSection, MenuDisplay, MenuPresentation, MergeField, MergeRecord, NotFound, PageFab, PageSetup, PopoverTrigger, ProjectRenderer, ProjectSettings, RecordMerge, RecordSwitcher, SwitcherType, Toggle, Translations, WizardDisplay
  • io.mateu.uidl.interfaces: ActionCatalogSupplier, DocumentRenderer, Draftable, FieldTypeCatalogSupplier, HeaderValuesSupplier, KeepAliveHandler, RecordSwitcherSupplier, TranslationsSupplier

Experimental members of otherwise stable types:

  • io.mateu.core.infra.declarative.orchestrators.calendar.CalendarPage: actionOnDay, days, daysClickable, openCalendarDay, switchCalendarView, views
  • io.mateu.core.infra.declarative.orchestrators.crud.Crud: display, nextIdAfter, saveAndNextLabel
  • io.mateu.core.infra.declarative.orchestrators.dashboard.Dashboard: reorderable
  • io.mateu.core.infra.declarative.orchestrators.datamanagement.DataManagement: bottomPanel, endPanel, toggleBottomPanel, toggleEndPanel
  • io.mateu.core.infra.declarative.orchestrators.foldout.Foldout: panelSummary
  • io.mateu.core.infra.declarative.orchestrators.generaloverview.GeneralOverview: display, info, infoWidth
  • io.mateu.core.infra.declarative.orchestrators.smartsearch.SmartSearchPage: preSearchContent
  • io.mateu.core.infra.declarative.orchestrators.welcome.Welcome: heroTone
  • io.mateu.core.infra.declarative.orchestrators.wizard.Wizard: beforeStepNavigate, display, stepSkippable
  • io.mateu.uidl.annotations.App: accessKeys, idleSignOutUrl, idleTimeoutMinutes, idleWarningSeconds, liveUpdates, mobileQr
  • io.mateu.uidl.annotations.Menu: display, image
  • io.mateu.uidl.annotations.Section: addAction, editAction, viewMoreAction
  • io.mateu.uidl.annotations.WelcomeBanner: tone
  • io.mateu.uidl.annotations.WizardCompletionAction: availableFromStep
  • io.mateu.uidl.data.AppHeaderAction: route
  • io.mateu.uidl.data.Calendar: dayActionId, days, view, views
  • io.mateu.uidl.data.CalendarEvent: endDate, endTime, startTime
  • io.mateu.uidl.data.FieldLink: presentation
  • io.mateu.uidl.data.FoldoutPanel: summary
  • io.mateu.uidl.data.GridColumn: tones
  • io.mateu.uidl.data.HeroSection: tone
  • io.mateu.uidl.data.Map: attribution, markerActionId, markers, tileUrl
  • io.mateu.uidl.data.Menu: presentation
  • io.mateu.uidl.data.MetricCard: timeframe
  • io.mateu.uidl.data.PlanningBlock: icon, summary
  • io.mateu.uidl.data.PlanningBoard: attributeColumns, openActionId, rangeSelectActionId, resizeActionId
  • io.mateu.uidl.data.PlanningResource: attributes, icon
  • io.mateu.uidl.data.Popover: trigger
  • io.mateu.uidl.data.RecordSwitcher: actionId, optionsSource, route
  • io.mateu.uidl.data.ResponsiveGrid: asReorderable, reorderable
  • io.mateu.uidl.data.RestDataSource: sample
  • io.mateu.uidl.data.RestSourceEntry: sample
  • io.mateu.uidl.data.RouteEntry: access
  • io.mateu.uidl.data.RouteLink: presentation
  • io.mateu.uidl.data.UICommand: announce, announceAssertive, print, updateHeaderValue
  • io.mateu.uidl.fluent.AppShell: accessKeys, contextSelectors, globalSearch, headerActions, headerValues, idleSignOutUrl, idleTimeoutMinutes, idleWarningSeconds, liveUpdates, mobileQr, notifications, shortcuts
  • io.mateu.uidl.fluent.Form: badges, banners, fabs, kpis, overline, peerNav, recordSwitcher, timestamp, titlePlaceholder
  • io.mateu.uidl.fluent.Listing: dragType, preSearch, rowStatusField
  • io.mateu.uidl.interfaces.Actionable: presentation

The Internal column counts types that are public only because Java has no module-private: the reflection helpers in io.mateu.uidl.reflection, the layout heuristics in io.mateu.uidl.layout, IconConverter (Jackson plumbing of IconKey) and RoutedClassProvider (implemented by the code the annotation processors generate), and the machinery behind the archetypes in core — the CRUD actionhandlers / routeresolvers packages, the *Composer and Inferred* bridges, CapabilityCrud, CrudAdapterHelper, FilterCriteriaBuilder, ListingSummarySpec and SearchableValues. They are not part of the promise, the compatibility check skips them, and they may change in any release. The extension points you implement — InstanceFactory, RouteResolver / RouteValue / CompiledRouteValue, BeanProvider — are stable.

  • An API is deprecated for at least one minor release before it is removed — e.g. deprecated in 3.1, removable in 3.2 at the earliest. Removal only in a minor or major, never in a patch.
  • A deprecation always names its replacement (@Deprecated + @deprecated use … instead in Javadoc, the [Obsolete] message in .NET, a DeprecationWarning in Python) and is listed in the CHANGELOG.
  • Where a rename can be bridged, the old name keeps working as an alias for the deprecation period.
  • A default that changes behaviour is treated like a removal: announced one minor ahead, with a switch to keep the old behaviour for at least that minor. Security fixes are the exception: an insecure default can change in a patch, and the release notes say how to opt back in.

Every build checks the Java public API against the last released version with japicmp: mvn verify compares the mateu-uidl jar and the archetype packages of core (io.mateu.core.infra.declarative.orchestrators.*) — and, report-only, the mateu-dtos jar (why) — against <mateu.japicmp.baseline> (in backend/pom.xml, currently 3.0-alpha.411) and writes a report to target/japicmp/api-compatibility.{md,html,diff} of each module; CI publishes it in the job summary.

  • Until the beta the check is report-only (mateu.api.enforce=false): the alphas change the API in place, and Migrating from alpha is the record of it.
  • The blocking configuration is ready behind one switch: -Dmateu.api.enforce=true (the mateu.api.enforce property of backend/pom.xml; the plugin’s own mateu.japicmp.enforce follows it). With it, a binary- or source-incompatible change to the non-@Experimental surface of mateu-uidl and the archetypes fails mvn verify (mateu-dtos stays report-only), unless it is listed in backend/api-compat/japicmp-exclusions.properties — the reviewed allowlist. Adding an entry is how an intentional break (the end of a deprecation period, a major release) gets through; the comment block above the list names the release or PR that accepted each group, and the list is reset whenever the baseline moves. The Internal types are excluded from the check.
  • Marking an existing stable API @Experimental is itself reported as a removal — japicmp drops the annotated element on the new side only — so an API cannot leave the promise silently: such a mark needs an allowlist entry until the baseline moves past it.

Flipping it on at the beta — one commit, at the beta tag:

  1. set <mateu.japicmp.baseline> to the last release before the beta (or to the beta itself once it is on Maven Central, for the betas that follow);
  2. set <mateu.api.enforce>true</mateu.api.enforce> in backend/pom.xml;
  3. empty japicmp-exclusions.properties down to the sentinel (its entries described the diff against the old baseline), run mvn verify -pl shared/dtos,shared/uidl,shared/core, and re-add only the breaks that are deliberate. (Since 3.0-alpha.412 the list holds only the sentinel and -Dmateu.api.enforce=true passes against 3.0-alpha.411.)

Run it locally with mvn verify -pl shared/uidl (add -Dmateu.api.enforce=true to see what would fail; -Dmateu.japicmp.skip=true skips it, e.g. offline — the baseline jar comes from Maven Central).

The wire is versioned separately from the artifacts: every response carries wireVersion (currently 3.0). The normative rules are in the wire specification; in short:

  • Additive within a wire major. A minor Mateu release may add optional fields, new component types and new commands. It never removes a field, changes its type or changes its meaning.
  • Consumers must be tolerant: ignore unknown fields, render unknown component types as a placeholder and keep going. Every first-party renderer does; a custom renderer must too.
  • Consumers check the major. The web (Vaadin) and Redwood renderers, React Native and the IntelliJ plugin compare the response’s wireVersion with the major they were built for: the same major is fine whatever the minor; another major shows the user a clear message (“This app’s server speaks Mateu wire 4.x; this renderer supports 3.x”) once, instead of a broken screen. The wire major only changes with a Mateu major.
  • Producers must be conservative: a backend never requires a field a renderer of the same wire major might not send.
  • A backend and a renderer of the same wire major interoperate. In practice: keep the backend and the renderer artifact on the same Mateu release (they ship together), and the .NET and Python backends speak the same wire as the Java one of the same release.
  • The conformance corpus is the executable definition of “wire-equivalent”: a change that makes it fail is a breaking change.

The authoring schemas are generated from the records and checked by a test, so they always match the code of their release. Within a major, a key may be added; a key is only removed or renamed through the deprecation policy. A YAML file valid against one release’s schema stays valid in the next minor.

Security fixes go to the latest minor of the current major; see SECURITY.md for the current table and how to report a vulnerability.