Customizing Generated Code
ZenWaveSDK adapts to your team's coding style, preferences and custom libraries.
Like many great open source projects, ZenWaveSDK was created to solve real problems. It's designed to be easily customizable for any team or organization - whether you need specific coding styles, custom libraries, annotations, or internal frameworks.
By default, ZenWaveSDK generates a complete SpringBoot backend application following industry best practices, with a minimal dependency set: Spring-Data JPA and MongoDB, SpringMVC, SpringCloudStreams, MapStruct and optional Lombok integration.
You can customize the generated code in several ways:
- Configuring different Project Layouts: CleanHexagonal, Layered, SimpleDomain, HexagonalArchitecture, CleanArchitecture...
- Customizing Standard Templates: providing your own versions of any standard template
- Full Source Code Customization: see SpringBoot with Kotlin Customization as an example.
NOTE: The less customization you need, the less effort you'll spend creating and maintaining it.
Configuring different Project Layouts
By default, ZenWave SDK generates a Spring Boot/Spring Cloud backend application using a flexible hexagonal/clean architecture approach. However, the toolkit offers several alternative layouts to match your preferred architectural style:
config {title "My Backend Application"basePackage "com.example.my.backend"// Available layout options:// - DefaultProjectLayout// - CleanHexagonalProjectLayout// - LayeredProjectLayout// - SimpleDomainProjectLayout// - HexagonalProjectLayout// - CleanArchitectureProjectLayoutlayout CleanHexagonalProjectLayout}
You can customize any package path in your chosen layout through properties in the config section or as parameters in the CLI plugin:
config {//...layout CleanHexagonalProjectLayoutlayout.entitiesPackage "{{basePackage}}.core.model"layout.openApiApiPackage "{{basePackage}}.web"layout.openApiModelPackage "{{basePackage}}.web.dtos"}
For complete customization, you can create your own implementation of the ProjectLayout class.
Tip: You can organize your application into multiple modules, each using a different layout.
CleanHexagonalProjectLayout
This is the default project layout, which the source code templates are designed around. It implements a pragmatic hexagonal/clean architecture with:
- A central
core
package containing domain entities and aggregatesinbound
(primary ports) andoutbound
(secondary ports) interfaces- and the internal
implementation
ofinbound
interfaces, which uses theoutbound
interfaces to interact with external services.
π¦ {{basePackage}}π¦ adaptersββ web| ββ RestControllers (spring mvc)ββ eventsββ *EventListeners (spring-cloud-streams)π¦ coreββ π¦ domain| ββ (entities and aggregates)ββ π¦ inbound| ββ dtos/| ββ ServiceInterface (inbound service interface)ββ π¦ outbound| ββ mongodb| | ββ *RepositoryInterface (spring-data interface)| ββ jpa| ββ *RepositoryInterface (spring-data interface)ββ π¦ implementationββ mappers/ββ ServiceImplementation (inbound service implementation)π¦ infrastructureββ mongodb| ββ CustomRepositoryImpl (spring-data custom implementation)ββ jpaββ CustomRepositoryImpl (spring-data custom implementation)

Notes about Generated Clean/Hexagonal Architecture
To balance pragmatism with architectural purity, this implementation makes two practical compromises:
- Domain entities directly use JPA or SpringData MongoDB annotations
- Spring Data Repository interfaces serve as outbound adapters/secondary ports, while their implementations remain outside the core domain
These design choices significantly reduce the complexity of your codebase by minimizing the number of mappers and DTOs needed, while still maintaining the key benefits of hexagonal/clean architecture - clear separation between core domain and external systems.
For testing, ZenWave SDK provides in-memory implementations of Spring Data Repository interfaces, eliminating the need for a database or mocking frameworks. This demonstrates that your core business logic remains decoupled from specific persistence technologies.
If you believe your project genuinely requires the ability to swap persistence implementations (a rare need in most applications), and you're willing to accept the additional complexity of extra mapping layers, you can create custom templates as described in the Full Source Code Customization section.
LayeredProjectLayout
This is the classical layered architecture with domain
, repository
, events
, commands
, service
and web
layers.
π¦ {{basePackage}} # Root packageπ¦ config # Spring Boot configuration, security, etc.π¦ domain # Domain Layer (Business Entities and Events)ββ *Entitiesββ events/ββ *DomainEventsπ¦ repository # Repository Layer (Persistence and Data Access)ββ {{persistence}}/| ββ *RepositoryInterface # Persistence interface (Spring Data, etc.)| ββ *RepositoryImpl # Repository implementationπ¦ events # Events Layer (Internal and Async API Events)ββ *EventListeners # Event listenersπ¦ commands # Command Layer (Command Handlers)ββ *CommandHandlers # Command handlers (e.g., CQRS commands)π¦ service # Service Layer (Business Logic and DTOs)ββ dtos/| ββ *DTOs # Data Transfer Objectsββ impl/| ββ *ServiceImplementation # Service implementationsββ impl/mappers/ββ *Mappers # Object mappers for transformationsπ¦ web # Web Layer (Controllers and API)ββ *RestControllers # REST controllers (Spring MVC, etc.)ββ mappers/ββ *WebMappers # Mappers for web layer transformations
SimpleDomainProjectLayout
This is a very simple and flat structure, with no particular layering. It's useful for small services or modules with just one entity or aggregate.
π¦ {{basePackage}}ββ π¦ configββ π¦ model (entities and aggregates)ββ π¦ dtosββ π¦ eventsββ π¦ mappersββ EventListeners (spring-cloud-streams)ββ RestControllers (spring mvc)ββ ServiceImplementationββ RepositoryInterface
HexagonalProjectLayout
This layout follows a stricter naming convention for hexagonal architecture, with domain, ports, application core, and adapters.
π¦ {{basePackage}}π¦ domain # Domain model (Entities, Aggregates, Value Objects)ββ *Entities|π¦ ports # Port interfacesββ inbound # Primary ports (driving adapters)| ββ UserServicePort # Interface for business logic (input)ββ outbound # Secondary ports (driven adapters)ββ UserRepositoryPort # Interface for persistence (output)|π¦ application # Application core (business logic services)ββ services # Service implementations| ββ UserServiceImpl # Implements UserServicePort, uses UserRepositoryPortββ mappers # Optional: Mapping between entities and DTOs|π¦ adapters # Interface adapters (controllers, repositories, listeners)ββ web # Web adapter (e.g., REST)| ββ UserController # Calls UserServicePortββ persistence # Persistence adapters| ββ mongodb/| | ββ MongoUserRepository (implements UserRepositoryPort)| ββ jpa/| ββ JpaUserRepository (implements UserRepositoryPort)ββ events # Event-driven adaptersββ UserEventListener # Listens to events, calls UserServicePort|π¦ config # Spring Boot configurations
CleanArchitectureProjectLayout
This layout follows the Clean Architecture principles, with a core domain, application layer, and interface adapters.
π¦ {{basePackage}}π¦ domain # Core business entities and aggregates (Domain Layer)ββ *Entitiesπ¦ application # Application layer (Use Cases)ββ services/| ββ *UseCase (service interfaces with input/output models)ββ dtos/π¦ adapters # Interface Adaptersββ web # Web Adapter (Controllers)| ββ RestControllersββ events # Event-driven Adapter| ββ *EventListenersββ persistence # Persistence Adapterββ mongodb/| ββ MongoRepositoryInterface| ββ MongoRepositoryImplββ jpa/ββ JpaRepositoryInterfaceββ JpaRepositoryImplπ¦ config # Spring Boot configuration, security, etc.
Customizing Standard Templates
To override standard templates, simply place your customized versions in the .zenwave/templates
folder at your project's root, maintaining the same directory structure as in the original plugin's source code.
For instance, if you need to customize Spring Data repositories with custom annotations, locate the original template at EntityRepository.java.hbs, copy it to .zenwave/templates/io/zenwave360/sdk/plugins/BackendApplicationDefaultGenerator/src/main/java/core/outbound/jpa/imperative/EntityRepository.java.hbs
in your project, and add your custom code.
// this is the source code template you get from the original pluginpackage ;import .*;import java.math.*;import java.time.*;import java.util.*;import org.springframework.data.jpa.repository.*;import org.springframework.stereotype.Repository;import my.custom.annotation.*;/*** Spring Data JPA repository for the entity.*/@SuppressWarnings("unused")@Repository@MyCustomAnnotation // <-- This is the custom annotation -->public interface Repository extends JpaRepository<, > {default Optional<> findById( id) {return findById(id).map(::new);};}
Adding Extra Templates to an Existing Plugin
To extend an existing plugin with additional templates, create a subclass of the plugin's templates class and add your custom templates to the appropriate template collections.
public class MyCustomBackendTemplates extends BackendApplicationProjectTemplates {protected Function<Map<String, Object>, Boolean> skipMyEntity = (model) -> is(model, "vo", "input");public MyCustomBackendTemplates() {super();this.addTemplate(this.entityTemplates, "src/main/java", "core/domain/{{persistence}}/MyExtraEntityTemplate.java",layoutNames.entitiesPackage, "My{{entity.name}}ExtraEntityClass.java", JAVA, skipMyEntity, false);}@Overridepublic List<Object> getTemplateHelpers(Generator generator) {var helpers = super.getTemplateHelpers(generator);helpers.add(new MyCustomHandlerBarsHelper());return helpers;}}
Then you can reference it in your zw
scripts file:
config {plugins {BackendApplicationDefaultPlugin {templates "new com.mycompany.MyCustomBackendTemplates()"}}}
ZenWave SDK organizes templates into various collections that you can target when adding custom templates:
public class ProjectTemplates {//....public List<TemplateInput> aggregateTemplates = new ArrayList<>();public List<TemplateInput> domainEventsTemplates = new ArrayList<>();public List<TemplateInput> entityTemplates = new ArrayList<>();public List<TemplateInput> enumTemplates = new ArrayList<>();public List<TemplateInput> inputTemplates = new ArrayList<>();public List<TemplateInput> inputEnumTemplates = new ArrayList<>();public List<TemplateInput> eventEnumTemplates = new ArrayList<>();public List<TemplateInput> outputTemplates = new ArrayList<>();public List<TemplateInput> serviceTemplates = new ArrayList<>();public List<TemplateInput> externalEventsTemplates = new ArrayList<>();public List<TemplateInput> producerTemplates = new ArrayList<>();public List<TemplateInput> producerByServiceTemplates = new ArrayList<>();public List<TemplateInput> producerByOperationTemplates = new ArrayList<>();public List<TemplateInput> consumerTemplates = new ArrayList<>();public List<TemplateInput> consumerByServiceTemplates = new ArrayList<>();public List<TemplateInput> consumerByOperationTemplates = new ArrayList<>();public List<TemplateInput> allEntitiesTemplates = new ArrayList<>();public List<TemplateInput> allEnumsTemplates = new ArrayList<>();public List<TemplateInput> allInputsTemplates = new ArrayList<>();public List<TemplateInput> allOutputsTemplates = new ArrayList<>();public List<TemplateInput> allServicesTemplates = new ArrayList<>();public List<TemplateInput> allDomainEventsTemplates = new ArrayList<>();public List<TemplateInput> allExternalEventsTemplates = new ArrayList<>();public List<TemplateInput> singleTemplates = new ArrayList<>();}
Each template collection is processed for specific model element types (entities, services, events, etc.).
NOTE: To remove templates from an existing plugin, you'll need to iterate through the template collections and filter out unwanted templates by their file names - a more complex process than adding templates.
Full Source Code Customization
ZenWave SDK allows unlimited customization of generated code.
For example, check out the Kotlin backend application customization which demonstrates a complete transformation:
This project provides complete template sets for three plugins (BackendApplicationDefaultGenerator
, OpenAPIControllersGenerator
, and SpringWebTestClientGenerator
), enabling generation of a full SpringBoot application in Kotlin instead of Java.
// this is how it looks like the template classpublic class BackendApplicationKotlinTemplates extends ProjectTemplates {//...@Overridepublic List<Object> getTemplateHelpers(Generator generator) {var helpers = new ArrayList<>(super.getTemplateHelpers(generator));helpers.add(new BackendApplicationDefaultHelpers((BackendApplicationDefaultGenerator) generator));helpers.add(new BackendApplicationDefaultJpaHelpers((BackendApplicationDefaultGenerator) generator));helpers.add(new BackendApplicationKotlinHelpers((BackendApplicationDefaultGenerator) generator));return helpers;}public BackendApplicationKotlinTemplates() {setTemplatesFolder("io/zenwave360/sdk/plugins/kotlin/BackendApplicationDefaultGenerator");// templates list goes here}}
To use these custom templates, reference them in your zw
scripts file:
@import("io.zenwave360.sdk.plugins.customizations:kotlin-backend-application:2.1.0-SNAPSHOT")config {zdlFile "zenwave-model.zdl"plugins {BackendApplicationDefaultPlugin {templates "new io.zenwave360.sdk.plugins.kotlin.BackendApplicationKotlinTemplates()"haltOnFailFormatting false--force // overwite all files}OpenAPIControllersPlugin {openapiFile "src/main/resources/public/apis/openapi.yml"templates "new io.zenwave360.sdk.plugins.kotlin.OpenAPIControllersKotlinTemplates()"}SpringWebTestClientPlugin {openapiFile "src/main/resources/public/apis/openapi.yml"templates "new io.zenwave360.sdk.plugins.kotlin.SpringWebTestClientKotlinTemplates()"}SpringWebTestClientPlugin {openapiFile "src/main/resources/public/apis/openapi.yml"templates "new io.zenwave360.sdk.plugins.kotlin.SpringWebTestClientKotlinTemplates()"groupBy businessFlowbusinessFlowTestName CreateUpdateDeleteCustomerIntegrationTestoperationIds createCustomer,updateCustomer,deleteCustomer,getCustomer}}}
NOTE: Add the dependency io.zenwave360.sdk.plugins.customizations:kotlin-backend-application:2.1.0-SNAPSHOT
to ZenWave using JBang's dependency mechanism. Our team is actively improving the developer experience for customizing and extending ZenWave SDK.