Build a CRUD API with Spring Boot facility
This guide shows how to build a CRUD REST API with the JoinedWorkz SpringBoot platform using the example-spring-boot project as a concrete generation and compilation example.
The focus is on:
- modelling an entity and its API
- using the SpringBoot CRUD method types
- understanding the generated controllers, data access services and mappers
Release baseline and verification
This page uses
example-spring-bootfromrelease/1.3.80, commitdf7cabf7f21b. Generation, main-source compilation and packaging were verified with Java 21.0.8 and Maven 3.9.16 on 2026-07-26. The generatedCustomersResourceITis not registered as a Maven test source and was therefore neither compiled nor run. Runtime startup and CRUD requests have not passed the planned smoke-test gate.
For general background on the Spring Boot facility, see:
spring-boot-facility.mdFacilities and platformsModel a REST API step by step
1. Prerequisites
You should have:
- Java 21
- Maven 3.9 or newer
- JoinedWorkz 1.3.80 with:
- Base, Java and SpringBoot platforms/facilities
- the
SpringBootApi.cmnmodel withcreateEntity,readEntity, …
- A working JoinedWorkz generator setup (Maven plugin)
- Optional but recommended: JoinedWorkz Studio for editing
.cmnmodels
Example project:
The guide assumes you use that module as-is, but the pattern is the same for your own projects.
2. Get the example project
Clone the examples repository and build the Spring Boot example:
git clone --branch release/1.3.80 --single-branch \
https://gitlab.com/joinedworkz/joinedworkz-examples.git
cd joinedworkz-examples/example-spring-boot
# run model generation + Java build
mvn clean verify
After the build you should see:
src/generated/resources/openapi/org.joinedworkz.examples.customer.api.yaml;src/generated/resources/openapi/org.joinedworkz.examples.backend_customerbackend.yaml;- generated Java sources for the entity, DTOs, controller, API interface, data access service, repository and mapper;
- compiled main classes and the repackaged Spring Boot JAR.
This command does not currently compile or run the generated integration test.
3. Model the domain: Customer entity
In the example the domain model lives in the core layer. The canonical release model contains:
core package org.joinedworkz.examples.customer
import org.joinedworkz.facilities.common.base
import org.joinedworkz.facilities.profiles.springboot
platform SpringBoot
enum SetupType {
NONE value="none"
NP value="NP"
JP value="JP"
}
type Address {
street*: String(200)
zipCode: String(10)
city*: String(100)
country: String(2)
}
type<entity> Customer {
id**: Id
firstName*: Name
lastName*: Name
email: String(255)
setupType: SetupType
mainAddress: Address
}Key points:
type<entity> Customerdefines a complex type with the stereotypeentity.id**marks the field as the entity/resource key. An explicit<key>stereotype is separate syntax.- Other fields model the customer data, including the embedded
Address. - The layer (
core) indicates that this type is part of the internal domain model.
For the canonical release example, the Java and persistence generators create:
- the JPA entity
Customer; CustomerDataAccessServicewith CRUD operations.
4. Import Spring Boot API helpers
For REST APIs the example uses:
- the SpringBoot platform profile
- the CRUD method types from
SpringBootApi.cmn
In the API model file you import both:
api package org.joinedworkz.examples.customer.api vendorSpecificMimeType=true
import org.joinedworkz.examples.customer
import org.joinedworkz.facilities.profiles.springboot
import org.joinedworkz.facilities.springboot.api
platform SpringBootHere:
apiis the layer for external API types and resources.org.joinedworkz.examples.customerbrings theCustomerentity into scope.org.joinedworkz.facilities.springboot.apiprovides the method types:createEntity,readEntity,updateEntity,queryEntities,deleteEntity.
5. Model the CRUD resource with SpringBoot helpers
The main REST API for customers is defined as a resource that uses the CRUD method types:
resource /customers as Customer[] by id {
queryEntities()
createEntity()
readEntity()
updateEntity()
deleteEntity()
}
abstract resource /address as Address { }Explanation:
resource /customers as Customer[] by id/customersis a collection resourceCustomer[]indicates that the default representation is a list ofCustomerby iddefines the identifier for individual items in the collection
- The methods:
queryEntities()→ lists customers with paging/sorting/filteringcreateEntity()→ creates a new customerreadEntity()→ reads one customer by idupdateEntity()→ updates an existing customerdeleteEntity()→ deletes a customer by id
abstract resource /address as Address { }- defines a reusable sub-resource pattern for customer addresses (used in more advanced scenarios)
Behind the scenes these method types are defined like this (abridged excerpt from release 1.3.80 SpringBootApi.cmn; the referenced semantic constants are defined later in that file):
package org.joinedworkz.facilities.springboot.api
methodtype createEntity POST
consumes='*'
produces='*'
success=201
handler='${entity}DataAccessService.create'
semantic=SEM_CRUD_CREATE
methodtype readEntity GET
instance=true
produces='*'
success=200
operationId='get${produces}ById'
operationName='get${produces}ById'
handler='${entity}DataAccessService.read'
semantic=SEM_CRUD_READ
methodtype updateEntity PUT
instance=true
consumes='*'
produces='*'
success=200
handler='${entity}DataAccessService.update'
operationName='update${entity}'
semantic=SEM_CRUD_UPDATE
methodtype queryEntities GET
produces='*[]'
success=200
pagination=true
sort=true
filter=true
handler='${entity}DataAccessService.search'
operationId='query${produces}Entities'
operationName='query${produces[]}'
semantic=SEM_CRUD_QUERY
methodtype deleteEntity DELETE
instance=true
success=204
handler='${entity}DataAccessService.delete'
semantic=SEM_CRUD_DELETEImportant conventions:
${entity}is substituted with the backing entity type (Customer).- The
handlerproperty points to${entity}DataAccessServicemethods:create,read,update,search,delete. pagination,sort,filterflags drive how query parameters are interpreted and how the generated API behaves.
5.1 Bind the resource to a component
The API resource alone does not define a generated controller. The release example contains a third model in which a component provides the resource:
package org.joinedworkz.examples.backend
import org.joinedworkz.examples.customer.api
import org.joinedworkz.facilities.profiles.springboot
platform SpringBoot
component CustomerBackend
basePackage='org.joinedworkz.examples.customer.webapp' {
provide /customers
subPackage='customers.v1'
controller="CustomerV1Controller" {
}
}The provide /customers boundary drives controller/API-interface generation. It also creates an additional component-scoped OpenAPI document that aggregates all endpoints provided by CustomerBackend. This is separate from the model-scoped document generated from the non-abstract resources declared in the API model. An application can compose the component for diagrams, but does not create another OpenAPI aggregate in release 1.3.80.
6. Generated Spring Boot artefacts
From the domain, API and component models the SpringBoot facility generates the following concrete Main sources in the canonical release module:
6.1 Controller
A Spring MVC controller for /customers:
src/generated/java/org/joinedworkz/examples/customer/webapp/customers/v1/controller/CustomerV1Controller.javasrc/generated/java/org/joinedworkz/examples/customer/webapp/customers/v1/api/CustomerV1Api.java
Responsibilities:
- define the generated controller contract in
CustomerV1Api; - expose HTTP endpoints for:
GET /customers(queryEntities)POST /customers(createEntity)GET /customers/{id}(readEntity)PUT /customers/{id}(updateEntity)DELETE /customers/{id}(deleteEntity)
- map HTTP details (path variables, query parameters, request body) to Java types
- call
CustomerDataAccessServicedirectly
6.2 Data access service
A generated service class encapsulates persistence logic:
src/generated/java/org/joinedworkz/examples/customer/das/CustomerDataAccessService.java
The generated class provides create, read, update, search and delete operations over the generated DTO and identifier types. Exact Java signatures are generator output and should be read from the generated class for the selected model. In the release 1.3.80 example, the identifier type is UUID and create/update use CustomerDto; it is not a create(Customer entity) API.
Internally this class uses:
src/generated/java/org/joinedworkz/examples/customer/repository/CustomerRepository.java;src/generated/java/org/joinedworkz/examples/customer/entity/Customer.java.
6.3 Mapper interface
The generator creates:
src/generated/java/org/joinedworkz/examples/customer/mapper/CustomerMapper.java;src/generated/java/org/joinedworkz/examples/customer/dto/CustomerDto.java.
Responsibilities:
- map between the JPA entity
CustomerandCustomerDto - update an existing entity from
CustomerDtowhile preserving its identifier
MapStruct generates CustomerMapperImpl during compilation. In more advanced setups you can define additional DTO types and use the same mapper pattern.
7. Runtime verification gate
The runtime startup command and concrete CRUD requests will be added after the example has passed a dedicated startup and endpoint smoke test. Until then, use the generated OpenAPI only as generation output, not as evidence that the example's persistence setup and every request flow work end to end.
8. Adapting the pattern to your own project
To use the same approach in your own application:
Add facilities
Add the Spring Boot facility as a Maven dependency (transitively bringing in Base and Java).Model your entities
In a non-apilayer (e.g.core), define your entities withtype<entity> ...and key fields (**).Model your API layer
- Use an
apilayer for external DTOs and resources. - Import:
- your domain package(s)
org.joinedworkz.facilities.profiles.springbootorg.joinedworkz.facilities.springboot.api
- Define resources that use:
createEntity,readEntity,updateEntity,queryEntities,deleteEntity.
- Use an
Configure outlets when adapting to multiple modules
The canonical release example is a single Maven module and does not use sibling-module outlet overrides. Follow the Multi-module setup with outlet overrides for a separately verified routing example and the release 1.3.80 limitations.
Generate and add manual code
- Run the JoinedWorkz Maven plugin (
cmn-maven-plugin:generate). - Inspect the generated controllers, data access services and mappers.
- Keep application-specific services and other manual extensions outside
src/generated/**.
- Run the JoinedWorkz Maven plugin (
Iterate from the model
As requirements change:- update the CMN models
- regenerate
- re-run tests and the application
In the canonical module, src/generated/** is replaceable output. The manual application shell is src/main/java/org/joinedworkz/examples/SpringBootExampleApplication.java; manual configuration is in src/main/resources/application.properties. Your own manual services likewise belong outside the generated tree.
9. Summary
The example-spring-boot project demonstrates how the SpringBoot facility can:
- read a domain model (
Customer) - read an API model that uses entity-specific CRUD method types
- generate:
- Spring MVC controllers
- data access services
- mappers between entities and DTOs
- generate and compile the intended main-source CRUD API structure
By following the same pattern in your own project you can:
- model entities and APIs in CMN
- let JoinedWorkz generate the Spring Boot plumbing
- focus your manual code on business logic instead of boilerplate
This summary does not imply that the generated integration test or the runtime CRUD flow has been verified.
