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.
Version numbers
Section titled “Version numbers”- Releases are tagged
vMAJOR.MINOR[-qualifier.N]and published to Maven Central underio.mateu, every artifact namedmateu-*(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 ofio.mateuartifacts 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.
What “beta” means
Section titled “What “beta” means”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 in3.0-beta.N+1at the earliest; once3.0.0is 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
wireVersion3.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.
What is public API
Section titled “What is public API”You can build on these; they follow the deprecation policy.
| Surface | Where it lives |
|---|---|
The authoring API — annotations (@UI, @Action, @Section…), interfaces (CrudStore, Listing and its capability interfaces, suppliers…), the fluent component records and data types | the 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 renderer | the 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 parameters | their 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/markers | follow the same policy once published as packages (see the release notes) |
The wire DTOs: JSON, not Java binaries
Section titled “The wire DTOs: JSON, not Java binaries”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.
Module layering
Section titled “Module layering”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 onmateu-dtosonly 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 bySearchableSelection) and the long-task stream (LongTask,ProgressReporter). Everything else inmateu-uidlis wire-agnostic; a new import ofio.mateu.dtosoutside that list fails the build. It never depends onmateu-core.mateu-coredepends 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.
Imports: names shared by two packages
Section titled “Imports: names shared by two packages”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.BadgeThe shared names (pinned by NameCollisionsTest in mateu-uidl, so a new one is a reviewed
decision):
| Packages | Names |
|---|---|
annotations / data | Avatar, Badge, Breadcrumb, Breadcrumbs, BulletedList, Button, Details, FormLayout, Icon, KPI, Menu, Notice, RestAction, Rule, Status, Tab, Text, Tooltip, Validation |
annotations / fluent | Action, Trigger, UI |
annotations / interfaces | App, Filterable, Searchable |
fluent / interfaces | Listing (the fluent listing component / the listing contract) |
data / interfaces | Page (a page of rows / the routed-page marker) |
data / fluent | Step |
with java.util | List (@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.coreother 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.
Experimental API
Section titled “Experimental API”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:
@ExperimentalAPI 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:
| Surface | Where |
|---|---|
| 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 catalogues | field 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 tooling | live reload (dev mode, /mateu/dev/*), the Redwood embedded <mateu-ui> JET component |
| The Figma design-to-code pipeline | the 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 production | the 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.
Stable and experimental API, by package
Section titled “Stable and experimental API, by package”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.
| Module | Package | Public types | Stable | Experimental | Deprecated | Internal |
|---|---|---|---|---|---|---|
mateu-core (archetypes) | io.mateu.core.infra.declarative.orchestrators | 2 | 2 | 0 | 0 | 0 |
mateu-core (archetypes) | io.mateu.core.infra.declarative.orchestrators.calendar | 1 | 1 | 0 | 0 | 0 |
mateu-core (archetypes) | io.mateu.core.infra.declarative.orchestrators.collectiondetail | 1 | 1 | 0 | 0 | 0 |
mateu-core (archetypes) | io.mateu.core.infra.declarative.orchestrators.crud | 11 | 6 | 0 | 0 | 5 |
mateu-core (archetypes) | io.mateu.core.infra.declarative.orchestrators.crud.actionhandlers | 11 | 0 | 0 | 0 | 11 |
mateu-core (archetypes) | io.mateu.core.infra.declarative.orchestrators.crud.routeresolvers | 7 | 0 | 0 | 0 | 7 |
mateu-core (archetypes) | io.mateu.core.infra.declarative.orchestrators.dashboard | 3 | 1 | 0 | 0 | 2 |
mateu-core (archetypes) | io.mateu.core.infra.declarative.orchestrators.datamanagement | 1 | 1 | 0 | 0 | 0 |
mateu-core (archetypes) | io.mateu.core.infra.declarative.orchestrators.editableview | 2 | 2 | 0 | 0 | 0 |
mateu-core (archetypes) | io.mateu.core.infra.declarative.orchestrators.foldout | 1 | 1 | 0 | 0 | 0 |
mateu-core (archetypes) | io.mateu.core.infra.declarative.orchestrators.ganttpage | 1 | 1 | 0 | 0 | 0 |
mateu-core (archetypes) | io.mateu.core.infra.declarative.orchestrators.generaloverview | 1 | 1 | 0 | 0 | 0 |
mateu-core (archetypes) | io.mateu.core.infra.declarative.orchestrators.herosearch | 1 | 1 | 0 | 0 | 0 |
mateu-core (archetypes) | io.mateu.core.infra.declarative.orchestrators.importwizard | 8 | 8 | 0 | 0 | 0 |
mateu-core (archetypes) | io.mateu.core.infra.declarative.orchestrators.itemoverview | 1 | 1 | 0 | 0 | 0 |
mateu-core (archetypes) | io.mateu.core.infra.declarative.orchestrators.masterdetail | 2 | 2 | 0 | 0 | 0 |
mateu-core (archetypes) | io.mateu.core.infra.declarative.orchestrators.smartsearch | 1 | 1 | 0 | 0 | 0 |
mateu-core (archetypes) | io.mateu.core.infra.declarative.orchestrators.todolist | 1 | 1 | 0 | 0 | 0 |
mateu-core (archetypes) | io.mateu.core.infra.declarative.orchestrators.welcome | 3 | 1 | 0 | 0 | 2 |
mateu-core (archetypes) | io.mateu.core.infra.declarative.orchestrators.wizard | 2 | 2 | 0 | 0 | 0 |
mateu-dtos | io.mateu.dtos | 276 | 248 | 0 | 28 | 0 |
mateu-uidl | io.mateu.uidl | 7 | 6 | 1 | 0 | 0 |
mateu-uidl | io.mateu.uidl.annotations | 172 | 162 | 7 | 3 | 0 |
mateu-uidl | io.mateu.uidl.data | 315 | 263 | 46 | 6 | 0 |
mateu-uidl | io.mateu.uidl.di | 2 | 2 | 0 | 0 | 0 |
mateu-uidl | io.mateu.uidl.fluent | 34 | 31 | 0 | 3 | 0 |
mateu-uidl | io.mateu.uidl.interfaces | 108 | 96 | 8 | 2 | 2 |
mateu-uidl | io.mateu.uidl.layout | 3 | 0 | 0 | 0 | 3 |
mateu-uidl | io.mateu.uidl.reflection | 3 | 0 | 0 | 0 | 3 |
mateu-uidl | io.mateu.uidl.security | 2 | 2 | 0 | 0 | 0 |
| Total | 983 | 844 | 62 | 42 | 35 |
Experimental types (the whole type and its members):
io.mateu.uidl:LiveHeaderValuesio.mateu.uidl.annotations:AI,AppShortcut,AppShortcuts,DragRows,MobileApp,RowStatus,Tooltipio.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,WizardDisplayio.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,viewsio.mateu.core.infra.declarative.orchestrators.crud.Crud:display,nextIdAfter,saveAndNextLabelio.mateu.core.infra.declarative.orchestrators.dashboard.Dashboard:reorderableio.mateu.core.infra.declarative.orchestrators.datamanagement.DataManagement:bottomPanel,endPanel,toggleBottomPanel,toggleEndPanelio.mateu.core.infra.declarative.orchestrators.foldout.Foldout:panelSummaryio.mateu.core.infra.declarative.orchestrators.generaloverview.GeneralOverview:display,info,infoWidthio.mateu.core.infra.declarative.orchestrators.smartsearch.SmartSearchPage:preSearchContentio.mateu.core.infra.declarative.orchestrators.welcome.Welcome:heroToneio.mateu.core.infra.declarative.orchestrators.wizard.Wizard:beforeStepNavigate,display,stepSkippableio.mateu.uidl.annotations.App:accessKeys,idleSignOutUrl,idleTimeoutMinutes,idleWarningSeconds,liveUpdates,mobileQrio.mateu.uidl.annotations.Menu:display,imageio.mateu.uidl.annotations.Section:addAction,editAction,viewMoreActionio.mateu.uidl.annotations.WelcomeBanner:toneio.mateu.uidl.annotations.WizardCompletionAction:availableFromStepio.mateu.uidl.data.AppHeaderAction:routeio.mateu.uidl.data.Calendar:dayActionId,days,view,viewsio.mateu.uidl.data.CalendarEvent:endDate,endTime,startTimeio.mateu.uidl.data.FieldLink:presentationio.mateu.uidl.data.FoldoutPanel:summaryio.mateu.uidl.data.GridColumn:tonesio.mateu.uidl.data.HeroSection:toneio.mateu.uidl.data.Map:attribution,markerActionId,markers,tileUrlio.mateu.uidl.data.Menu:presentationio.mateu.uidl.data.MetricCard:timeframeio.mateu.uidl.data.PlanningBlock:icon,summaryio.mateu.uidl.data.PlanningBoard:attributeColumns,openActionId,rangeSelectActionId,resizeActionIdio.mateu.uidl.data.PlanningResource:attributes,iconio.mateu.uidl.data.Popover:triggerio.mateu.uidl.data.RecordSwitcher:actionId,optionsSource,routeio.mateu.uidl.data.ResponsiveGrid:asReorderable,reorderableio.mateu.uidl.data.RestDataSource:sampleio.mateu.uidl.data.RestSourceEntry:sampleio.mateu.uidl.data.RouteEntry:accessio.mateu.uidl.data.RouteLink:presentationio.mateu.uidl.data.UICommand:announce,announceAssertive,print,updateHeaderValueio.mateu.uidl.fluent.AppShell:accessKeys,contextSelectors,globalSearch,headerActions,headerValues,idleSignOutUrl,idleTimeoutMinutes,idleWarningSeconds,liveUpdates,mobileQr,notifications,shortcutsio.mateu.uidl.fluent.Form:badges,banners,fabs,kpis,overline,peerNav,recordSwitcher,timestamp,titlePlaceholderio.mateu.uidl.fluent.Listing:dragType,preSearch,rowStatusFieldio.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.
Deprecation policy
Section titled “Deprecation policy”- An API is deprecated for at least one minor release before it is removed — e.g. deprecated in
3.1, removable in3.2at the earliest. Removal only in a minor or major, never in a patch. - A deprecation always names its replacement (
@Deprecated+@deprecated use … insteadin Javadoc, the[Obsolete]message in .NET, aDeprecationWarningin 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.
Enforcement
Section titled “Enforcement”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(themateu.api.enforceproperty ofbackend/pom.xml; the plugin’s ownmateu.japicmp.enforcefollows it). With it, a binary- or source-incompatible change to the non-@Experimentalsurface ofmateu-uidland the archetypes failsmvn verify(mateu-dtosstays report-only), unless it is listed inbackend/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
@Experimentalis 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:
- 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); - set
<mateu.api.enforce>true</mateu.api.enforce>inbackend/pom.xml; - empty
japicmp-exclusions.propertiesdown to the sentinel (its entries described the diff against the old baseline), runmvn 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=truepasses against3.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).
Wire compatibility
Section titled “Wire compatibility”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
wireVersionwith 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.
Schemas and YAML
Section titled “Schemas and YAML”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.
Supported versions
Section titled “Supported versions”Security fixes go to the latest minor of the current major; see SECURITY.md for the current table and how to report a vulnerability.