Upgrade from JoinedWorkz 1.3.80 to 1.3.81
This release strengthens configuration validation, outlet routing and CMN name validation. It also removes ambiguities that could previously produce order-dependent or target-language-invalid output.
Scope
This guide covers the public Base, Java and SpringBoot facilities and the documented experimental Quasar facility. It does not turn internal implementation details or other published artifacts into public API. Custom facilities need their own compatibility review.
1. Establish a recoverable baseline
Before changing a version:
- Commit or otherwise back up the CMN models, Maven POMs,
joinedworkz.properties, manual application sources and tests. - Classify existing outputs with the ownership reference.
- Preserve first-cut/manual files and every accepted versioned Flyway SQL migration with its matching JSON schema snapshot.
- Run the current release's normal generation, consumer build and tests.
- Record the exact command, Java/Maven versions and relevant
mvn dependency:treeoutput.
Do not begin an upgrade by deleting a complete generated module. A module can contain replaceable Java beside manual files or persistent migration history.
2. Align every JoinedWorkz artifact
Update the plugin and all facilities together:
<properties>
<joinedworkz.version>1.3.81</joinedworkz.version>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>Use the same property for cmn-maven-plugin and each JoinedWorkz facility. Do not mix releases and do not substitute a development build.
The public build baseline is Java 21 and Maven 3.9 or newer. If the project uses maven.compiler.release, also keep maven.compiler.source and maven.compiler.target aligned because the JoinedWorkz internal Java stub compiler reads the latter two settings.
3. Review joinedworkz.properties
Missing and empty files
A missing joinedworkz.properties and an empty file are now equivalent: registered defaults apply. Remove an otherwise unnecessary empty file only after confirming that no project-specific or custom facility configuration belongs in it.
Typed values and diagnostics
The profiles used during generation now contribute typed configuration metadata. Invalid files and invalid values stop generation. After a successful Maven generation, unknown and unused keys are reported with stable JW_CONFIG_* codes.
Review every warning instead of copying the key into another module. A key can be unknown because of a typo, an inactive platform or a facility version mismatch. A known key can be unused because its consuming cartridge is disabled or excluded.
Profile-declared pattern keys now require a non-empty value for every named placeholder. For example, override-package. is invalid because it omits the <originalPackagePrefix> part. Such keys stop generation with JW_CONFIG_INVALID_KEY; complete the key or remove the obsolete entry.
schemaMigration.mode remains the canonical migration key. The formerly mentioned joinedworkz.schemaMigration.mode spelling was not a working generator key and is not a compatibility alias.
Outlet paths
Outlet directories now use one strict cross-platform contract:
- paths are relative to the Maven module or Studio project root;
- deliberate
../sibling-module/...routes remain valid; - absolute paths, Windows drive/root paths and
${...}placeholders are rejected; and - the normalized target must not be the module/project root or one of its ancestors.
Replace ${baseDir}, absolute paths and unsafe root targets with explicit relative directories. See the joinedworkz.properties path contract for valid and invalid examples.
4. Migrate the SpringBoot API outlet
Replace:
outlet.generatedApiSource.directory=../api/src/generated/javawith:
outlet.generatedJavaSource.api.directory=../api/src/generated/javaThe legacy property still selects the compatibility outlet when the new property is absent. When both are present, the new generatedJavaSource.api route wins. Remove the legacy property rather than keeping both.
The replacement route covers API DTOs and SpringBoot-generated controller/API interface files. After changing it:
- remove stale API files only from output directories classified as replaceable;
- regenerate from the model module;
- confirm that the old target no longer contains stale duplicate classes; and
- compile and test all consuming modules.
No removal release for the legacy property has been announced.
5. Review CMN properties, names and method types
DTO naming and nested package overrides
Java and SpringBoot now provide one consistent DTO naming strategy. For packages outside the external layer and without a package override, the backward-compatible project default is:
java.dto.naming=decoratedThe advanced alternative plain omits the .dto package segment. For SpringBoot it also omits the Dto class-name suffix. A package can override the project default with skipDtoPostfix=true for plain naming or skipDtoPostfix=false for decorated naming. The external layer implicitly selects plain naming only when no package-level value is present.
Existing external models are a deliberate migration case. Earlier releases could generate a decorated DTO declaration while some SpringBoot references already expected its plain name. This release resolves that inconsistency by generating and referencing the plain form. Clean the affected replaceable output, regenerate and compile all consumers. Set skipDtoPostfix=false on the package when the decorated form must be retained.
This release also corrects the generic inheritance of CMN package properties. When a property is declared on both a nested package and one of its parents, the declaration closest to the concrete package wins. For example:
package com.example skipDtoPostfix=true
subpackage internal skipDtoPostfix=false {
type Customer {
}
}com.example.internal.Customer uses decorated naming; other subpackages that do not override the property inherit plain naming from com.example.
This correction applies generically to profile-defined package properties, not only to skipDtoPostfix. Review models that intentionally declare the same property with different values on a parent and nested package. Their effective value—and therefore generated output—can change during this upgrade. Clean the affected replaceable output, regenerate all models and compile every consumer. Do not delete first-cut files or persistent migration history.
The complete naming precedence is documented in the java.dto.naming reference.
Base property names
This release deprecates seven Base property names. Use the canonical spelling in every new or migrated model:
| Deprecated | Use instead |
|---|---|
minValue | min |
maxValue | max |
precision | maxDigits |
scale | decimals |
sorting | sort |
consumeHeader | consumeHeaders |
paging | pagination (behavioral migration) |
The first six replacements are compatibility aliases. A legacy declaration still supplies the canonical property and reports JW_DEPRECATED_PROPERTY. Declaring both names with equivalent values remains accepted during migration, but the warning remains; remove the deprecated declaration. Different values report JW_PROPERTY_ALIAS_CONFLICT and stop generation before Cartridge output.
paging is deliberately not an alias for pagination. It retains its legacy response contract and reports only the deprecation warning. Migrating context-free generated frontend handling to pagination changes the collection payload access from response.data.data to response.data.content. If the operation declares responseContext, that explicit wrapper takes precedence: the collection remains in response.data.data, and counts come from response.data.context.
The canonical Base ResponseContext fields are filteredElements and totalElements. Quasar also reads the former totalNumberOfFilteredItems and totalNumberOfItems names as a compatibility fallback. New backends should emit the canonical names. Review the operation, backend response and consuming frontend together before migrating.
No removal release has been announced for these compatibility names.
Component target namespace properties
Two component-composition properties now use names that distinguish generated target namespaces from CMN package and subpackage declarations:
| Deprecated scope and name | Use instead |
|---|---|
component basePackage | componentNamespace |
provide property subPackage | namespaceSuffix |
The semantics are unchanged. componentNamespace is the target namespace for artifacts derived from the component, and namespaceSuffix is appended for artifacts derived from one provided boundary. Neither property changes model identity, imports, or HTTP paths. Existing names remain deprecated aliases; use the canonical names when touching a component model.
See Components and applications for nested provided boundaries and the platform-independent distinction, and SpringBoot profile and modeling for generated Java targets.
OpenAPI primitive contracts
The updated generator derives an OpenAPI primitive's type, format, constraints and synthetic fallback example from one effective CMN type contract. The following corrections can change regenerated OpenAPI documents and clients:
Longis nowinteger/int64instead ofnumber/int64;- the generated
filteredElementsandtotalElementsquery-result fields are nowinteger/int64instead ofinteger/int32; - a timestamp type whose effective CMN type is
Longisinteger/int64, instead of a contradictorystring/int64; and - Boolean specializations are emitted as
boolean.
The Base Timestamp remains string/date-time because Base explicitly declares it with the time stereotype. Its wire value is an ISO-8601 timestamp. A javaType property only selects a target-language type and does not change this wire contract.
These integer corrections do not change JSON numbers themselves, but an OpenAPI client generator can select another target-language type after regeneration. Regenerate the specification, review the diff, regenerate any derived clients, and compile and test every consumer. Do not change a Long-based timestamp to Base Timestamp merely to retain an accidentally generated string schema; choose the CMN type definition that represents the intended wire value.
Modeled request/response, representation-type, field and simple-type examples keep their documented precedence over synthetic fallback examples. Existing model-derived descriptions are retained.
Documentation marker normalization
The updated transformation uses one normalized documentation representation for Descriptions, structured sections and Examples. This prevents example: control lines from also appearing in generated OpenAPI descriptions and emits valid JSON Examples as structured OpenAPI values.
When touching an existing documentation block, replace compatibility aliases:
@request:→@request;@response:→@response;- other section markers with a trailing colon → the same marker without
:; @example:→example:.
The old forms remain readable but produce a deprecation warning. New and updated models use only the canonical spelling documented under Documentation, descriptions and examples.
Page conditions
This release atomically renames Page States to Page Conditions. Replace the states block:
states {
editingLocked: draft.locked
}with:
conditions {
editingLocked: draft.locked
}There is no alias or compatibility syntax for states. Every Page Condition must evaluate to Boolean. Extensions using the generated model APIs must also replace PageState with PageCondition, CmnState with CmnCondition, and CmnPage.getStates()/setStates() with getConditions()/setConditions(). See Pages and Page Conditions for the platform-independent modeling contract.
Experimental Quasar widget references
An application-provided Quasar widget now uses componentRef instead of the custom stereotype. Replace, for example:
widget<custom> PropertyList {
}with:
widget PropertyList
componentRef='components/widgets.PropertyList' {
}The last dot separates the module path from the JavaScript component identifier. This example generates an import of components/widgets/PropertyList.vue, registers PropertyList and uses that identifier as the Vue tag. Keep the matching .vue file in the manual application shell, outside generatedFrontendSource.
A Quasar widget carrying custom is rejected even when it also declares a valid componentRef. This change is limited to widgets processed by the experimental Quasar facility; it does not remove the Base stereotype from services or change other facilities. See the Quasar widget contract.
Experimental Quasar action aliases
The updated Quasar validation checks a modeled page action before emitting its alias as a JavaScript binding and unqualified call. Existing aliases that are valid JavaScript binding identifiers remain unchanged. Rename aliases such as class, delete or await; they previously led to invalid generated source and now report quasar.invalidActionAlias on the action before output. Give each Action on one page a unique alias; a duplicate reports the same diagnostic on its second declaration.
Also rename a valid alias when it matches another binding generated for the same Page. Examples include Page fields, data and Conditions, event handlers and Page-specific query, filter, tree or lifecycle helpers. Such a collision now reports quasar.conflictingActionAlias on the Action. The available names are derived from the concrete Page generation path; imported and base Pages are not folded into the current Page's scope.
This check is specific to Action aliases. It does not apply the Resource operationName contract to actions.
Generated Quasar Actions now consistently return Promises. Empty Actions resolve immediately. Assignments and consumed Resource operations run in model order, Resource operations are awaited sequentially, and alternatives apply the same execution rules to the selected branch. The Promise resolves to the last asynchronous result. A previously generated multi-command Action could return from its first Resource operation and leave later commands unreachable; regenerate and test any such Action after upgrading.
Unsupported command or condition shapes now report quasar.unsupportedActionCommand on the owning Action rather than producing a partial JavaScript body. For a stereotyped Action, errors passed to the manual CommandHelper remain rejected instead of being converted to a successful Promise. Check callers that intentionally relied on the earlier swallowed rejection.
The existing Quasar table-selection pipeline remains compatible inside Action assignments. Expressions such as rows|?selected()|:id continue to select the current table rows before projecting a field; selected() must remain the first transformation in that pipeline.
See the Quasar action-alias contract.
Raw and opinionated DELETE
Release 1.3.80 supplied two Base method types named delete. Release 1.3.81 makes them unambiguous:
deleteis the raw HTTPDELETEmethod type; anddeleteInstanceis the opinionated item-deletion method with instance, result and status defaults.
Keep delete() only when the resource intentionally uses raw HTTP semantics. Change an opinionated item-deletion operation to:
package com.example.customer
resource /customers {
deleteInstance()
}Relative subpackages
A subpackage is relative to its effective parent package. For example:
package com.example
subpackage api {
// effective package: com.example.api
}The updated naming contract uses that effective name consistently in the Xtext index and local scope. Check imports and fully qualified references that previously depended on the raw, unqualified subpackage name. A nested package com.other remains an absolute package declaration.
Unique package names
Every effective CMN package name in one Maven generator build must be unique across project sources and visible CMN dependencies. The Maven generator checks this before cartridge execution and reports CMN_DUPLICATE_PACKAGE.
A different file name, layer or Maven artifact does not make an identical package name valid. Rename or separate the technical namespaces; do not rely on classpath order or local shadowing. This fatal Maven check is not disabled by failOnValidationError=false.
Generated operation names and IDs
The Base contract requires operationName to be a non-blank string with a valid static template. A code generator can add rules for its target language. SpringBoot therefore requires the fully resolved result to be a valid Java identifier and not a Java keyword. Base/OpenAPI does not apply Java identifier rules to an OpenAPI operationId.
The updated validation also requires operationId to be a quoted, non-blank string and resolves its templates strictly. Unknown or malformed variables and known variables without a value no longer disappear silently. A non-blank fallback can supply a missing value. If several operation parts are merged into one path and verb, different values for the same entity, consumes, or produces variable are reported as ambiguous; different operationId literals or templates on the merged operations are also rejected. JoinedWorkz does not select the first value. The supported variables are entity, resource, consumes, and produces, each optionally followed by [].
Correct the CMN operation name, its operationName template or the separate operationId template reported by the diagnostic. Do not patch a generated controller or service method. See Generated method names for syntax, variables and fallback rules.
SpringBoot ID generation strategies
This release defines the SpringBoot generation contract on the effective JPA key field. Existing models that omit the property remain assigned-ID models; do not add a value merely to preserve their current behavior. There is no ASSIGNED literal.
Review every explicit value during the upgrade:
- use the exact case-sensitive spelling
CUSTOM,UUID,AUTO,IDENTITY,SEQUENCEorTABLE; - restrict
CUSTOMto effective JavaUUIDandStringkeys; - use exactly one effective scalar key when an entity selects
generation; assigned composite models without that property are not newly rejected by this targeted guard, but no general composite-ID runtime support is implied; - treat JPA
UUIDas the focused advanced provider path; and - keep
AUTO,IDENTITY,SEQUENCEandTABLEas compatibility pass-throughs whose key-type, provider and database behavior must be tested by the application.
After changing a strategy, clean and regenerate the replaceable JPA entity and data-access output. For CUSTOM, test generated create(dto) with a missing ID and, if required, an explicit ID; direct save(...) and repository calls do not run that application-side hook. A package-replaced AbstractDataAccessService can deliberately define another lifecycle.
Do not expect a strategy change to produce Flyway SQL. JoinedWorkz does not derive sequences, identity declarations, generator tables or database UUID defaults from generation; maintain and verify required database objects separately. See SpringBoot ID generation strategies for the complete contract.
6. Review deterministic Java package overrides
For override-package.<originalPackagePrefix> mappings, JoinedWorkz now selects the longest matching original package prefix at a package-segment boundary and replaces only that leading prefix.
Release 1.3.80 did not define deterministic precedence for overlapping prefixes. After upgrading:
- inspect all mappings, including newly valid overlapping mappings;
- regenerate every affected replaceable Java output tree;
- review normal and static imports; and
- compile without the original helper dependency before considering the replacement complete.
A package override used during Java generation rejects an empty original prefix or a blank target prefix.
7. Preserve Flyway state
Configuration and generator upgrades do not make schema migration stateless.
- Preserve every accepted
V*SQL migration and matchingV*_<persistence>_schema.jsonsnapshot. - Treat
R__DEV_work.sqlandDEV_<persistence>_schema.jsonas one temporary DEVELOPMENT pair. - Keep migration and snapshot outlets separate from replaceable parent output trees.
- Review generated SQL against the real database dialect before accepting a migration.
The SpringBoot generator now renders direct UUID columns and reference columns targeting UUID identifiers as PostgreSQL UUID without a length modifier. Generation success is not proof that Flyway or PostgreSQL accepted the SQL. If a retained snapshot still records a length for a UUID reference, the first schema comparison after the upgrade can emit an explicit type change to parameterless UUID. Review that delta without rewriting accepted migration history or its matching snapshots. Follow the complete Flyway migration workflow.
7.1 Migrate integer-coded enum values
The SpringBoot generator now attaches an explicit JPA converter to supported enum<integer> fields. Earlier generated entities did not attach this converter and could persist JPA ordinals such as 0, 1, and 2 instead of the modeled codes.
Before deploying regenerated persistence code:
- identify every persisted
enum<integer>field; - inspect the values currently stored in each affected database column;
- map every existing representation to the intended explicit code;
- implement and test the required data migration; and
- deploy the migrated data and regenerated application as one compatible change.
Do not assume that an existing ordinal is the modeled code. A numeric-code change can require a data migration even when the SQL column type does not change and no structural Flyway difference is generated. Keep published codes stable unless a deliberate data migration accompanies the change.
The JSON and OpenAPI values remain the enum names. API clients need no change when those names remain unchanged. See Integer-coded enumeration persistence for the current supported field scope and converter behavior.
7.2 Migrate Java integer-enum glue references
Newly generated Java-platform enum<integer> types no longer implement the former org.iworkz.common.enumeration.MappedEnum<E, V> interface or register themselves in its global mapping registry. Their generated getMappedValue(), toMappedValue(...), fromMappedValue(...), and mappedValueOf(...) methods are self-contained and therefore require no Genesis runtime dependency.
SpringBoot-generated integer enums use the new package-overridable org.iworkz.core.enumeration.MappedEnum<V> contract. Handwritten code that is typed directly against the former interface or uses its global registry is not source-compatible with regenerated Java enums. Migrate that code to the generated mapping methods or to a compatible project-owned glue contract.
An already compiled Java-model enum remains usable by the new generated SpringBoot converter when it exposes the established static toMappedValue(...) and fromMappedValue(...) methods. That compiled enum can still retain its former interface dependency, so keep the corresponding runtime library until the enum is regenerated or replaced. Regenerate and compile all consumers before removing the former helper dependency; do not infer source compatibility from binary reuse alone.
8. Advanced facility-author migration
The Base transformed-model developer diagnostic is now named:
- cartridge:
CoreModelDiagnosticCartridge; - implementation:
org.joinedworkz.common.CoreModelDiagnosticCartridge; and - outlet:
generatedCoreModelDiagnostics.
It is disabled by default and writes versioned diagnostic JSON below target/joinedworkz/diagnostics/core-model. It is not a database schema or an application artifact.
Custom profiles that registered SchemaCartridge with the generatedSchema outlet should move to the new names. The old Java cartridge class is a deprecated compatibility wrapper for this transition release; standard profiles no longer register the old cartridge or outlet.
See the Profile reference for the current author-facing declaration.
Profile-controlled property propagation
Property-propagation clauses are now evaluated from the Profile property definition applicable to the source model element. This applies consistently to specialization, referencing, including, type, stereotype and methodtype relationships. Explicit positive and negative clauses are now also honored when a property crosses an explicit field include or flows from a field's declared type to that field. Wildcard field expansion remains outside this scalar property-propagation contract.
Property definitions without a propagated by ... or not propagated by ... clause keep the established unrestricted behavior. The same applies when no Profile definition can be found for a source property. If a custom Profile already contains an explicit clause, regenerate representative models and confirm both an allowed and a rejected relationship. A local value on the target continues to win.
Page content that references a field now receives that field's properties eligible for referencing before properties from an accompanying widget. The effective priority is local content value, referenced field, then widget. Review custom Cartridges that inspect content-element properties if they previously relied on the field metadata being absent.
See Profile-controlled property propagation for the complete advanced contract and its boundaries.
9. Regenerate and verify
Use the project's normal reactor command. A typical final check is:
mvn -e verifyThen:
- resolve every new validation error and review every
JW_CONFIG_*warning; - compare the complete generated diff with the baseline;
- confirm that first-cut/manual files and versioned migration history remain;
- compile and test every generated-code consumer;
- run database and application smoke tests where those runtime contracts matter; and
- commit the version change, configuration migration and reviewed output together according to the project's ownership policy.
Use an exact cleanup allowlist if clean regeneration is required. mvn clean alone does not classify or safely remove arbitrary source-tree outlets.
