Skip to content

Multi-module setup with outlet overrides

This guide shows how to use JoinedWorkz in a multi-module Maven project and route generated artefacts (Java sources, OpenAPI, diagrams, …) into different modules using outlet overrides. For generated Java classes, CMN layers can additionally act as source tags.

The goal is:

  • keep models in a dedicated module
  • keep generated code close to the modules that use it (domain, API, documentation)
  • avoid copying or manually moving generated files

It builds on:

Release and verification boundary

This guide targets JoinedWorkz 1.3.80, Java 21 and Maven 3.9+. On 2026-07-25, a disposable copy of joinedworkz-examples/example-spring-boot on release/1.3.80 at commit df7cabf7f21b verified the separate routing of core Java output and the global SpringBoot controller/API outlet. The four-module my-app layout is an architectural template, not a separately built example. OpenAPI, viewer and diagram entries below are configured destinations to inspect, not claimed outputs of that focused routing test.


1. Example project structure

We’ll use a typical layered project as running example:

text
my-app/
  pom.xml                 (parent)
  model/                  (CMN models + JoinedWorkz plugin)
  backend/                (domain + business logic)
  webapp/                 (Spring Boot application, controllers, HTTP APIs)
  docs/                   (optional: documentation / diagrams)

High-level responsibilities:

  • model
    • contains CMN models (.cmn)
    • runs the JoinedWorkz Maven plugin
  • backend
    • contains domain code (entities, services, persistence)
    • receives generated domain-related Java code
  • webapp
    • contains the Spring Boot application and HTTP boundary
    • receives generated controller/API sources and OpenAPI
  • docs (optional)
    • receives generated diagrams or HTML OpenAPI viewers

Routing is configured in joinedworkz.properties in the root of the model module. Global outlet overrides apply to all files written to an outlet. Release 1.3.80 additionally supports verified layer-based routing for Java classes written through tag-aware outlets.


2. Parent POM and module order

In my-app/pom.xml you declare the modules in a sensible order:

xml
<modules>
    <module>model</module>
    <module>backend</module>
    <module>webapp</module>
    <module>docs</module>
</modules>

Important:

  • The model module must appear before the modules that consume generated artefacts (backend, webapp, docs), so that generation runs before they are compiled.

Maven will then:

  1. build model (runs the JoinedWorkz plugin)
  2. place generated outputs into the configured directories (possibly in sibling modules)
  3. compile backend, webapp, docs using the generated code/resources

3. Model module setup

In model/pom.xml you configure:

  • the JoinedWorkz facilities (e.g. spring-boot)
  • the cmn-maven-plugin
  • the model/ folder as a resource root

Example:

xml
<project>
    ...
    <properties>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <joinedworkz.version>1.3.80</joinedworkz.version>
    </properties>

    <dependencies>
        <!-- Spring Boot facility (brings in Java + Base) -->
        <dependency>
            <groupId>org.joinedworkz.facilities</groupId>
            <artifactId>spring-boot</artifactId>
            <version>${joinedworkz.version}</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <resources>
            <resource>
                <directory>model</directory>
            </resource>
        </resources>

        <plugins>
            <plugin>
                <groupId>org.joinedworkz.cmn</groupId>
                <artifactId>cmn-maven-plugin</artifactId>
                <version>${joinedworkz.version}</version>
                <executions>
                    <execution>
                        <?m2e ignore?><!-- ignore this execution in Eclipse -->
                        <goals>
                            <goal>generate</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

The model module itself typically does not contain Java production code. Its job is to:

  • hold the CMN models
  • run the generators
  • route outputs into other modules

4. Layers and CMN package headers

Layers are defined in the header of CMN files. Generators can use them as source tags, but this is an outlet- and generator-specific capability.

A common pattern in a multi-module Spring Boot project:

  • core – internal domain types
  • api – external API DTOs and resources
  • an untagged model – components, applications, technical backend configuration

Example CMN headers:

cmn
core package com.example.customer

import org.joinedworkz.facilities.common.base
import org.joinedworkz.facilities.common.base.api
import org.joinedworkz.facilities.profiles.springboot

platform SpringBoot
cmn
api package com.example.customer.api

import com.example.customer
import org.joinedworkz.facilities.common.base.api
import org.joinedworkz.facilities.profiles.springboot

platform SpringBoot
cmn
package com.example.customer.backend

import com.example.customer.api
import org.joinedworkz.facilities.profiles.springboot

platform SpringBoot

Typical usage:

  • core: entities, value objects, domain model
  • api: DTOs (CustomerView, OrderSummary, …) and resources
  • untagged backend model: components (component CustomerBackend), applications, etc.

This matches the structure of the release 1.3.80 Spring Boot example. Its component model has no layer, so generated outputs derived from that component do not receive a backend source tag merely because they represent backend code.


5. Outlets: what you can override

Outlets define where generated artefacts are written, e.g.:

  • generatedJavaSource – Java sources (from Java/SpringBoot)
  • generatedApiSource – SpringBoot controller and API Java sources
  • generatedOpenApi – OpenAPI YAML
  • generatedOpenApiHtml – HTML OpenAPI viewer
  • generatedDiagram – diagrams

The Base and SpringBoot profiles define default directories, for example:

  • ./src/generated/resources/openapi
  • ./diagram/api
  • ./diagram

In a multi-module setup these defaults are often not what you want. Use joinedworkz.properties to define a global directory for an outlet:

Pattern:

properties
outlet.<outletName>.directory=...

The configuration loader also accepts source-tag mappings:

properties
outlet.<outletName>.<sourceTag>.directory=...

Such a mapping is selected only when the generator passes the matching source tag while writing the file. In release 1.3.80 this is verified for Java class generation, where the CMN layer is used as the source tag. The Base OpenAPI, OpenAPI HTML and diagram generators write without a source tag. Configure those outputs globally; a layer-specific mapping would not be selected.


6. joinedworkz.properties – routing examples

Create model/joinedworkz.properties in the model module.

6.1 Verified routing for SpringBoot Java sources

Assume:

  • core types (domain) should go to backend/src/generated/java
  • controller/API sources should go to webapp/src/generated/java
properties
# fallback for Java generators writing to generatedJavaSource
outlet.generatedJavaSource.directory=src/generated/java

# domain-related Java code (core layer) to backend module
outlet.generatedJavaSource.core.directory=../backend/src/generated/java

# SpringBoot controller/API output to web module
outlet.generatedApiSource.directory=../webapp/src/generated/java

This split was verified on 2026-07-25 with a disposable copy of the release 1.3.80 example-spring-boot project:

  • generatedJavaSource.core received the entity, repository, data access service, mapper and DTO classes derived from the core model;
  • global generatedApiSource received the generated controller and API interface derived from the untagged component model.

The second override is deliberately global. The component model in the verified example has no layer, so a source-tag-specific mapping would not select its controller/API output. Do not substitute outlet.generatedJavaSource.api.directory: SpringBoot writes these files to the distinct generatedApiSource outlet.

6.2 Routing OpenAPI and HTML viewers globally

OpenAPI and the HTML viewer usually live in the web boundary module (webapp):

properties
# All OpenAPI YAML
outlet.generatedOpenApi.directory=../webapp/src/generated/resources/openapi

# All HTML OpenAPI viewers
outlet.generatedOpenApiHtml.directory=../webapp/diagram/api

These are global overrides because the release 1.3.80 OpenAPI generators do not pass the CMN layer as a source tag.

6.3 Routing diagrams to docs

If you have a docs module that collects diagrams:

properties
# diagrams (e.g. component / application diagrams)
outlet.generatedDiagram.directory=../docs/diagram

These overrides are also global. Release 1.3.80 does not provide verified layer-specific routing for the Base diagram generator.


7. Configuring target modules

The modules that receive generated artefacts must register the directories in their POMs.

7.1 Backend module: generated Java sources

backend/pom.xml:

xml
<build>
    <plugins>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>build-helper-maven-plugin</artifactId>
            <version>3.6.0</version>
            <executions>
                <execution>
                    <id>add-generated-source</id>
                    <phase>generate-sources</phase>
                    <goals>
                        <goal>add-source</goal>
                    </goals>
                    <configuration>
                        <sources>
                            <source>${basedir}/src/generated/java</source>
                        </sources>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

You now can use generated domain types or DTOs from the backend module as if they were hand-written sources.

7.2 Webapp module: generated Java + resources

webapp/pom.xml should handle:

  • generated controller/API Java sources
  • generated resources (OpenAPI YAML, HTML)
xml
<build>
    <resources>
        <!-- your existing resources -->
        <resource>
            <directory>src/main/resources</directory>
        </resource>

        <!-- generated resources (OpenAPI, etc.) -->
        <resource>
            <directory>src/generated/resources</directory>
        </resource>
    </resources>

    <plugins>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>build-helper-maven-plugin</artifactId>
            <version>3.6.0</version>
            <executions>
                <execution>
                    <id>add-generated-source</id>
                    <phase>generate-sources</phase>
                    <goals>
                        <goal>add-source</goal>
                    </goals>
                    <configuration>
                        <sources>
                            <source>${basedir}/src/generated/java</source>
                        </sources>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

Now:

  • the generated controller/API sources become part of the webapp’s codebase
  • the generated OpenAPI YAML is packaged as a resource, and can be served or used by other tools

7.3 Docs module: diagrams

In docs/pom.xml, register the diagram directory:

xml
<build>
    <resources>
        <resource>
            <directory>diagram</directory>
        </resource>
    </resources>
</build>

You can then publish or serve the generated diagrams together with other documentation.


8. Build and verify

From the project root:

bash
mvn clean package

For a project adapted to this template, check the configured destinations. Only the two Java-routing bullets are results of the focused disposable verification; the remaining bullets describe the intended global routing:

  • model/

    • should contain only CMN models and joinedworkz.properties
    • may have temporary build output (e.g. target), but no production code
  • backend/src/generated/java

    • received the entity, repository, data access, mapper and DTO output routed from the core model in the focused verification
  • webapp/src/generated/java

    • received controller/API output routed through generatedApiSource in the focused verification
  • webapp/src/generated/resources/openapi

    • should receive OpenAPI YAML through the configured global outlet
  • webapp/diagram/api

    • should receive HTML OpenAPI viewers through the configured global outlet
  • docs/diagram

    • should receive diagrams through the configured global outlet

If something is missing:

  • check the outlet names used in joinedworkz.properties
  • for generatedJavaSource source-tag mappings, check that the layer in the CMN package header matches the mapping name
  • for SpringBoot controller/API code, check the distinct generatedApiSource outlet and whether the producing model actually has a source tag
  • for OpenAPI, OpenAPI HTML and diagrams, check the global outlet override rather than adding a layer-specific key
  • check that the target modules include the directories as sources/resources

9. Variants and tips

A few common variations:

  • Only OpenAPI, no Java If you only want centralised OpenAPI, you can:

    • use global overrides for generatedOpenApi and generatedOpenApiHtml
    • skip generatedJavaSource and source registration in target modules
  • Multiple bounded contexts Use separate packages per bounded context. Where generated Java classes need different targets, assign layers and configure matching generatedJavaSource source-tag mappings. Configure other SpringBoot outlets separately and only use source-tag mappings after verifying that their generator passes the expected tag.

  • Platform combinations Verify the outlet names and source-tag behaviour of every facility used. Source-tag support in one generator does not establish the same behaviour for another generator.

  • Gradual migration Start with a minimal multi-module setup (model + one target module), then introduce more modules, global outlet overrides and verified Java source-tag mappings as the system evolves.


10. Summary

In a multi-module setup, outlet overrides control where generated artefacts end up:

  1. Keep models in a dedicated module that runs the JoinedWorkz plugin.
  2. Use layers such as core or api in CMN headers to express architectural intent and, for tag-aware Java outputs, select a matching source-tag mapping. A model may also be deliberately untagged.
  3. Configure joinedworkz.properties to route:
    • generated Java classes by their verified layer/source tag,
    • OpenAPI and OpenAPI HTML globally to a web/API module,
    • diagrams globally to a docs module.
  4. Register generated directories as sources/resources in the target modules’ POMs.
  5. Let the multi-module build take care of calling the generators first and then compiling all modules against the generated code.

This keeps your project structure clean and lets you scale modelling and generation across multiple modules without losing track of where artefacts come from or where they belong.