Create a project with Maven
This page is the manual Maven baseline for a new project with a custom layout. It explains the minimal plugin and dependency setup, the model module layout and the generic configuration concepts needed to adapt that baseline.
It goes deeper than the Quickstart, but it is not the full reference for every adoption path:
- for incremental adoption, use Integrate JoinedWorkz into an existing Java project;
- for complete sibling-module routing and cleanup rules, use Multi-module outlet routing.
This page requires Java 21 and Maven 3.9+. For a complete minimal project, start with the Quickstart. For sibling-module routing, continue with the linked outlet guide.
New runnable project
For a new runnable Spring Boot project with the standard module layout, use the JoinedWorkz Maven Archetypes. This page remains the reference for integrating JoinedWorkz into an existing project or designing a custom Maven/module layout.
Prerequisites
Before you start, make sure you have:
- Java 21
- Maven 3.9 or newer
- access to the JoinedWorkz Maven artifacts (for example via the public repository or your organisation's repository manager)
If you have not done so yet, you may want to walk through the Quickstart once to get a feeling for the overall flow (model → Maven build → generated artifacts).
1. Minimal Maven setup
The smallest useful Maven setup looks like this:
- a project
pom.xmlwith- the JoinedWorkz Maven plugin,
- the
common-basedependency, - your normal Java build configuration;
- a
modeldirectory that contains your.cmnmodel files; - optionally, a
joinedworkz.propertiesfile in the project root when the project needs documented outlet overrides or other generator configuration.
Missing and empty joinedworkz.properties files use the same optional-file contract. Add the file only when the project needs configuration; see the joinedworkz.properties reference.
1.1 pom.xml template
The following pom.xml is a good starting point for a simple project:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>my-joinedworkz-project</artifactId>
<version>1.0.0</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<joinedworkz.version>1.3.81</joinedworkz.version>
</properties>
<build>
<resources>
<!-- model files are treated as resources so they are available on the classpath -->
<resource>
<directory>model</directory>
</resource>
<!-- generated artifacts (e.g. OpenAPI) are also added as resources -->
<resource>
<directory>src/generated/resources</directory>
</resource>
</resources>
<plugins>
<!-- JoinedWorkz generator plugin -->
<plugin>
<groupId>org.joinedworkz.cmn</groupId>
<artifactId>cmn-maven-plugin</artifactId>
<version>${joinedworkz.version}</version>
<executions>
<execution>
<?m2e ignore?>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<!-- modeling and generation tools (provided) -->
<dependency>
<groupId>org.joinedworkz.facilities</groupId>
<artifactId>common-base</artifactId>
<version>${joinedworkz.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>Key points:
- The JoinedWorkz generator plugin (
cmn-maven-plugin) contains the parser, model transformation and orchestration of generators. - The
common-basedependency contributes shared models and runtime needed by your own.cmnmodels. - Additional platforms and generators are simply added as further dependencies; the plugin will pick them up at runtime and execute the cartridges configured by your platform definitions.
1.2 Project layout
A typical layout for a simple project is:
pom.xml
joinedworkz.properties # optional
model/
application-model.cmn
src/
main/
java/ # your handwritten Java code
resources/ # additional handwritten resources
generated/
resources/ # generated artifacts (e.g. OpenAPI)You can choose different directories if you prefer; just make sure that:
- the
modeldirectory in the POM matches where you place your.cmnfiles, - any directories with generated resources or sources are registered as Maven
resource/sourcedirectories if you want to include them in your build artifacts, - when used,
joinedworkz.propertiesis located in the project root (the model module root in a multi-module build), where the generator reads it directly.
Directory placement does not by itself define who owns a file. Before adding clean rules or manual code, apply the generated-output ownership matrix.
2. Adding JoinedWorkz to an existing Maven project
The following checklist shows how the minimal setup transfers to an existing build. For source-root registration, generated/manual ownership, migration steps and verification in an established project, follow the dedicated existing-project integration guide.
To add JoinedWorkz to an existing Maven project:
- Add the
joinedworkz.versionproperty (or reuse your own version management mechanism). - Add the
cmn-maven-pluginto the<build><plugins>section. - Add
common-baseand any platform/generator dependencies that you need. - Create a
modeldirectory and put your.cmnfiles there. - Optionally, register
src/generated/resources(or your preferred output directory) as a Mavenresource. - Add
joinedworkz.propertiesin the project root only when you need configuration.
After that you can run:
mvn clean packageJoinedWorkz will:
- load the models from the
modeldirectory, - transform them,
- determine the referenced platforms,
- read
joinedworkz.propertiesdirectly from the project root when present, - and execute the corresponding cartridges and generators.
3. Multi-module projects
Many real-world systems are built as multi-module Maven projects. You can use JoinedWorkz in different ways in such setups. The patterns below help you choose a structure; the complete outlet precedence, path and cleanup contract is documented in Multi-module outlet routing.
3.1 Model in a dedicated module
One common pattern is to have a dedicated model module:
parent-pom/
pom.xml
model/
pom.xml
joinedworkz.properties # only when configuration is needed
model/ # CMN models
service-api/
pom.xml # consumes generated OpenAPI or DTOs
service-impl/
pom.xml # generated + handwritten implementationIn this pattern:
- the
modelmodule contains the JoinedWorkz plugin and all model files; joinedworkz.propertiesis added there when the module needs configuration;- the generators produce artifacts (for example OpenAPI or DTO JARs);
- other modules depend on those artifacts.
Benefits:
- clear separation between model and implementation;
- the model module can be reused in other projects.
3.2 Model and implementation in the same module
For smaller application projects it is perfectly fine to keep the model and implementation in a single module:
- the model drives generation of OpenAPI and/or code,
- handwritten code lives alongside the generated code/resources,
- the module produces a single deployable artifact.
This is an application-project pattern, not the Base-only Quickstart. The Quickstart generates one OpenAPI specification and diagrams but no Java implementation or runnable application.
Choose the structure that best fits your build and team.
3.3 Model module with outlet overrides (no module dependencies)
In some cases you do not want other modules to depend directly on the model module. Instead, you keep all models in a dedicated module but let the generators write their outputs into sibling modules.
This is achieved by overriding outlet directories via properties in the joinedworkz.properties file of the model module.
Consider a parent POM with modules:
parent-pom/
pom.xml # aggregator
model/
pom.xml
joinedworkz.properties
model/ # CMN models
my-commons-module/
pom.xml # uses generated Java sources
my-service-module/
pom.xml # uses generated Java sources
my-webapp-module/
pom.xml # uses generated Java sourcesThe model module does not appear as a Maven dependency of the other modules. To make sure generation runs before the modules that consume the generated artifacts, list the modules in the parent POM in the correct order:
<modules>
<module>model</module>
<module>my-commons-module</module>
<module>my-service-module</module>
<module>my-webapp-module</module>
</modules>Maven respects this order when there are no dependency relationships that override it, so the model module is built first.
The actual target directories for generated artifacts are then controlled by outlet override properties in joinedworkz.properties (see the next section).
4. Outlet directories and joinedworkz.properties
Each cartridge defines one or more outlets, for example an outlet for generated Java sources. The cartridge provides default paths for these outlets, but you can override their directories via properties.
Outlet directory overrides and other generator configuration are defined in a joinedworkz.properties file in the root folder of the project (or in the root of the model module in multi-module setups).
At build time the generator reads this file directly from the module base directory and passes the properties to cartridges and generators.
For the complete key inventory, value rules, path contract and diagnostics, use the joinedworkz.properties reference.
4.1 Concepts that must not be confused
Four related-looking concepts affect different parts of generation:
| Concept | Example | Effect |
|---|---|---|
| CMN package | package com.example.customer | Defines the model namespace. It can determine a generated Java package and the relative path inside an outlet, but does not select the outlet's target module or root directory. |
| CMN layer | shared package com.example.customer | Classifies a model and normally becomes the effective layer for its generated output. A generator can assign a different effective layer to a particular output; a facility can also assign additional semantics to selected layer names. |
| Outlet directory override | outlet.generatedJavaSource.shared.directory=... | Selects a target directory for one exact outlet and one exact effective layer. It does not rename Java packages. |
| Package override | override-package.org.iworkz.core=com.example.glue.core | Rewrites matching imports emitted by the common Java class generator. It does not select an outlet or target directory. |
An outlet directory override is therefore a filesystem-routing configuration. A package override is an import-rewriting configuration. They are independent and can be used separately or together.
4.2 Global outlet directory overrides
A global override defines the directory fallback for one exact outlet:
# joinedworkz.properties
# common outlet overrides
outlet.generatedJavaSource.directory=../my-service-module/src/generated/javaIn this example, the outlet generatedJavaSource of the Java cartridge is configured to write its output into the src/generated/java directory of the sibling module my-service-module.
The key does not automatically configure differently named outlets. For example, a SpringBoot-specific outlet remains separately configurable even when it specializes a Java outlet in the profile.
4.3 Layer-specific outlet directory overrides
The optional identifier before package in a CMN header is the model's layer:
shared package com.example.sharedA layer name is not selected from a fixed global list. You can use any valid CMN identifier that expresses your project structure, such as shared, reporting or webapp.
The declared CMN layer normally becomes the effective layer for generated output. A generator can assign a different effective layer to a particular file. You can route output by an exact outlet/effective-layer match:
# joinedworkz.properties
# layer-specific overrides for the effective layers shared and webapp
outlet.generatedJavaSource.shared.directory=../my-shared-module/src/generated/java
outlet.generatedJavaSource.webapp.directory=../my-webapp-module/src/generated/javaHere:
- files written to
generatedJavaSourcewith effective layersharedgo tomy-shared-module/src/generated/java, - files written with effective layer
webappgo tomy-webapp-module/src/generated/java.
Effective layer names are case-sensitive and do not form a global enumeration. The configured name must exactly match the effective layer of the generated file. This is normally the model layer, such as shared. A specialized generator can instead assign another effective layer, and output without any effective layer uses the global outlet route.
Every routed replaceable directory needs a corresponding, explicit cleanup policy. Do not route replaceable output into a directory that also contains manual, first-cut or versioned migration files.
A matching layer-specific override takes precedence over the global directory of the same outlet. If there is no matching mapping, the outlet's global directory—either its project override or its profile default—remains the fallback.
Layer names can have additional semantics. The routing mechanism itself does not assign an architectural meaning to
shared,reporting,coreor any other name. A facility or cartridge can nevertheless interpret specific names. SpringBoot gives documented, case-sensitive additional semantics toapiandmapping:apiparticipates in API/domain mapping and API-specific outlet selection,mappingselects the mapping outlet for DTO/mapper generation. The Java facility independently definesexternalas a DTO-naming convention: it implicitly selects plain naming unless a package-levelskipDtoPostfixvalue overrides it. Names such ascore,sharedandwebappare useful conventions but have no additional hard-coded meaning in the Base, Java and SpringBoot cartridges. See SpringBoot DTO naming for the complete precedence.
The effective layer is evaluated for each output file and exact outlet name. The routing contract is:
- generated output normally retains the CMN model layer;
- SpringBoot controller and API-interface files are explicitly assigned the effective layer
api, including when their component model has no declared layer; - a generator can assign another effective layer for its own output; and
- a model without a declared layer uses the global outlet route unless its generator assigns one.
See the multi-module outlet guide for concrete routing examples and release-specific behavior.
4.4 Package overrides for helper libraries
Package overrides are unrelated to source folders, outlets and effective layers. They redirect matching imports in generated Java code to a compatible, project-owned helper or glue library:
override-package.org.iworkz.core=com.example.glue.coreThe two sides are non-empty Java package prefixes, not layer names. The mapping applies independently of the model's layer and does not change:
- the CMN package or layer;
- the outlet or target directory;
- the package declaration of the generated class itself.
JoinedWorkz selects the longest matching original package prefix and replaces only the leading prefix. See Package overrides for helper libraries for the complete workflow and remaining boundaries.
5. Further Maven and generator configuration
The minimal configuration uses the default behavior of the cmn-maven-plugin. For more advanced setups you can:
- configure different output directories for specific outlets globally and by effective layer as shown above,
- pass additional configuration parameters understood by your platforms and cartridges.
These options are defined by the plugin and the specific platforms you use. Refer to the joinedworkz.properties reference, the Maven plugin reference and the applicable facility documentation. The central reference is deliberately limited to generic runtime patterns and the facilities documented on this site; other facilities can define additional keys.
Tip: Keep the configuration in your
pom.xmlsmall and place documented project-level generator settings injoinedworkz.properties. A.profiledefines facilities and platforms and belongs to the advanced facility/cartridge-authoring workflow; it is not the normal location for an application's project-specific settings.
6. Next steps
Once your Maven project is set up, continue with:
- Maven Archetypes for a generated, runnable Spring Boot or experimental Quasar starter.
- Quickstart for a concrete end-to-end example.
- Modeling overview for CMN syntax, layers and platforms.
- Integrate into an existing Java project for incremental adoption in an existing system.
Need help?
If you run into issues with Maven setup or project structure, you can use the JoinedWorkz Assistant:
👉 https://chatgpt.com/g/g-69d7ffb919808191b9ea54e6010e67f1-joinedworkz-assistant
It can help you:
- debug build and configuration issues
- understand project structure and modules
- clarify platform and generator setup
