Skip to content

Create a project with Maven

This page explains how to set up a JoinedWorkz-enabled project with Maven.

It goes deeper than the Quickstart:

  • the Quickstart shows a minimal, opinionated example;
  • this page describes the general Maven setup that you can adapt for real projects and multi-module builds.

Release baseline

This page targets JoinedWorkz 1.3.80, Java 21 and Maven 3.9+. Its release-specific Maven, properties and outlet statements were checked against the 1.3.80 sources and focused disposable builds by 2026-07-26. The exact minimal build is verified in the Quickstart; multi-module routing has its own explicitly bounded verification in the linked outlet guide.


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.xml with
    • the JoinedWorkz Maven plugin,
    • the common-base dependency,
    • your normal Java build configuration;
  • a model directory that contains your .cmn model files;
  • optionally, a joinedworkz.properties file in the project root when the project needs documented outlet overrides or other generator configuration.

Release 1.3.80 limitation: the Base-only Quickstart works without this file, but missing and empty files are not yet equivalent for every platform. SpringBoot generation can fail when the file is absent. Keep an empty file in SpringBoot 1.3.80 projects that do not need overrides; a follow-up release will restore the intended optional contract.

1.1 pom.xml template

The following pom.xml is a good starting point for a simple project:

xml
<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.80</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-base dependency contributes shared models and runtime needed by your own .cmn models.
  • 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:

text
pom.xml
joinedworkz.properties       # optional; required for some 1.3.80 platforms
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 model directory in the POM matches where you place your .cmn files,
  • any directories with generated resources or sources are registered as Maven resource / source directories if you want to include them in your build artifacts,
  • when used, joinedworkz.properties is located in the project root (the model module root in a multi-module build), where the generator reads it directly.

2. Adding JoinedWorkz to an existing Maven project

To add JoinedWorkz to an existing Maven project:

  1. Add the joinedworkz.version property (or reuse your own version management mechanism).
  2. Add the cmn-maven-plugin to the <build><plugins> section.
  3. Add common-base and any platform/generator dependencies that you need.
  4. Create a model directory and put your .cmn files there.
  5. Optionally, register src/generated/resources (or your preferred output directory) as a Maven resource.
  6. Add joinedworkz.properties in the project root only when you need configuration. SpringBoot 1.3.80 projects should keep it present even when empty because of the release limitation described above.

After that you can run:

bash
mvn clean package

JoinedWorkz will:

  • load the models from the model directory,
  • transform them,
  • determine the referenced platforms,
  • read joinedworkz.properties directly 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.

3.1 Model in a dedicated module

One common pattern is to have a dedicated model module:

text
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 implementation

In this pattern:

  • the model module contains the JoinedWorkz plugin and all model files;
  • joinedworkz.properties is 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:

text
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 sources

The 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:

xml
<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.

4.1 Concepts that must not be confused

Four related-looking concepts affect different parts of generation:

ConceptExampleEffect
CMN packagepackage com.example.customerDefines 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 layershared package com.example.customerClassifies a model. A tag-aware generator can pass it to an outlet as a source tag; a facility can also assign additional semantics to particular layer names.
Outlet directory overrideoutlet.generatedJavaSource.shared.directory=...Selects a target directory for one exact outlet and, when present, one exact source tag. It does not rename Java packages.
Package overrideoverride-package.org.iworkz.core=com.example.glue.coreRewrites 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:

properties
# joinedworkz.properties
# common outlet overrides
outlet.generatedJavaSource.directory=../my-service-module/src/generated/java

In 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 Source-tag-specific outlet directory overrides

The optional identifier before package in a CMN header is the model's layer:

cmn
shared package com.example.shared

A 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.

In release 1.3.80, the common Java class generators pass the model layer to their outlet as the default source tag. You can therefore route their generated Java files by an exact layer/source-tag match:

properties
# joinedworkz.properties
# source-tag-specific overrides for the layers shared and webapp
outlet.generatedJavaSource.shared.directory=../my-shared-module/src/generated/java
outlet.generatedJavaSource.webapp.directory=../my-webapp-module/src/generated/java

Here:

  • Java classes generated through generatedJavaSource for a model in the shared layer go to my-shared-module/src/generated/java,
  • model packages in the webapp layer generate into my-webapp-module/src/generated/java.

A source-tag name is likewise not a global enumeration. It must exactly match the tag that the responsible generator emits. When the common Java class generator derives that tag from a CMN layer, a freely chosen layer such as shared becomes the matching source tag. Other generators can emit their own tags or no tag at all.

The outlet runtime handles a supplied tag generically. In release 1.3.80, OutletImpl does not assign a meaning to the tag; it writes through the exact combination of source tag and outlet name. The generator or cartridge decides whether it supplies a tag and where that tag originates. For the common Java class generators, the default origin is the CMN layer. OutletImpl does not discover that layer on its own in release 1.3.80, so a layer is not automatically propagated to every cartridge output.

A matching source-tag-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, core or any other name. A facility or cartridge can nevertheless interpret specific names. Source review of SpringBoot 1.3.80 shows case-sensitive special handling for api, mapping and external: api participates in API/domain mapping and API-specific outlet selection, mapping selects the mapping outlet for DTO/mapper generation, and external changes the expected DTO package/name convention. Names such as core, shared and webapp are useful conventions but have no additional hard-coded meaning in the inspected Base, Java and SpringBoot cartridges.

Source-tag routing is not a generic promise for every outlet. A generator must pass a matching source tag when it writes a file. In release 1.3.80:

  • the common Java class generators use the CMN layer as their default source tag;
  • SpringBoot DTOs and mappers from an api model as well as controllers and API interfaces use the distinct generatedApiSource outlet, which must be configured separately;
  • the Base OpenAPI, OpenAPI HTML and diagram generators do not pass the CMN layer and must be configured with a global outlet.<outletName>.directory override.

See the multi-module outlet guide for verified routing examples and release-specific boundaries.

4.4 Package overrides for helper libraries

Package overrides are unrelated to source folders, outlets, layers and source tags. They redirect matching imports in generated Java code to a compatible, project-owned helper or glue library:

properties
override-package.org.iworkz.core=com.example.glue.core

The 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.

Release 1.3.80 supports one mapping or several mappings with non-overlapping original prefixes. 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 behaviour of the cmn-maven-plugin. For more advanced setups you can:

  • configure different output directories for specific outlets globally and, for tag-aware generators, by source tag 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 Maven plugin reference and facility documentation for the currently documented and verified options. The configuration reference is being expanded; do not infer additional public keys from implementation details.

Tip: Keep the configuration in your pom.xml small and place documented project-level generator settings in joinedworkz.properties. A .profile defines 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:

  • Quickstart for a concrete end-to-end example.
  • the Modeling section for details on CMN syntax, layers and platforms.
  • the How-to guides for tasks like implementing a custom generator or integrating JoinedWorkz into an existing Java 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