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-booton release/1.3.80 at commitdf7cabf7f21bverified the separate routing ofcoreJava output and the global SpringBoot controller/API outlet. The four-modulemy-applayout 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:
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
- contains CMN models (
- 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:
<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:
- build
model(runs the JoinedWorkz plugin) - place generated outputs into the configured directories (possibly in sibling modules)
- compile
backend,webapp,docsusing 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:
<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 typesapi– external API DTOs and resources- an untagged model – components, applications, technical backend configuration
Example CMN headers:
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 SpringBootapi package com.example.customer.api
import com.example.customer
import org.joinedworkz.facilities.common.base.api
import org.joinedworkz.facilities.profiles.springboot
platform SpringBootpackage com.example.customer.backend
import com.example.customer.api
import org.joinedworkz.facilities.profiles.springboot
platform SpringBootTypical 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 sourcesgeneratedOpenApi– OpenAPI YAMLgeneratedOpenApiHtml– HTML OpenAPI viewergeneratedDiagram– 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:
outlet.<outletName>.directory=...The configuration loader also accepts source-tag mappings:
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:
coretypes (domain) should go tobackend/src/generated/java- controller/API sources should go to
webapp/src/generated/java
# 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/javaThis split was verified on 2026-07-25 with a disposable copy of the release 1.3.80 example-spring-boot project:
generatedJavaSource.corereceived the entity, repository, data access service, mapper and DTO classes derived from thecoremodel;- global
generatedApiSourcereceived 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):
# All OpenAPI YAML
outlet.generatedOpenApi.directory=../webapp/src/generated/resources/openapi
# All HTML OpenAPI viewers
outlet.generatedOpenApiHtml.directory=../webapp/diagram/apiThese 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:
# diagrams (e.g. component / application diagrams)
outlet.generatedDiagram.directory=../docs/diagramThese 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:
<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)
<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:
<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:
mvn clean packageFor 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
- should contain only CMN models and
backend/src/generated/java- received the entity, repository, data access, mapper and DTO output routed from the
coremodel in the focused verification
- received the entity, repository, data access, mapper and DTO output routed from the
webapp/src/generated/java- received controller/API output routed through
generatedApiSourcein the focused verification
- received controller/API output routed through
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
generatedJavaSourcesource-tag mappings, check that the layer in the CMN package header matches the mapping name - for SpringBoot controller/API code, check the distinct
generatedApiSourceoutlet 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
generatedOpenApiandgeneratedOpenApiHtml - skip
generatedJavaSourceand source registration in target modules
- use global overrides for
Multiple bounded contexts Use separate packages per bounded context. Where generated Java classes need different targets, assign layers and configure matching
generatedJavaSourcesource-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:
- Keep models in a dedicated module that runs the JoinedWorkz plugin.
- Use layers such as
coreorapiin 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. - Configure
joinedworkz.propertiesto 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.
- Register generated directories as sources/resources in the target modules’ POMs.
- 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.
