Camunda REST
Experimental module
The experimental module is fully working and tested, but requires additional usage validation and analysis.
Therefore, the API may receive minor changes before full readiness.
Camunda 7 is deprecated
CamundaRestModule is marked @Deprecated because Camunda 7 has reached end of life.
The module still works and is still shipped, but no new capabilities are planned for it.
For new services consider Camunda 8 or the Operaton engine, a community fork of Camunda 7.
The module exposes the Camunda 7 REST API of a Kora application:
it deploys the standard CamundaRestResources JAX-RS resources on a RESTEasy deployment and serves them through a separate Undertow HTTP server.
It is used together with the Camunda 7 BPMN module: the BPMN engine executes processes, while this module gives HTTP access to Camunda 7 operations —
starting process instances, querying tasks and deployments, correlating messages, and everything else the engine REST API offers.
The module can also serve the OpenAPI description of the REST API together with the Swagger UI and Scalar pages.
Requests to the REST API have their own settings for CORS, logging, metrics, tracing, and graceful server shutdown.
Dependency¶
Dependency build.gradle:
Module:
Dependency build.gradle.kts:
Module:
Requires the Camunda 7 BPMN module.
The Camunda engine itself is a compileOnly dependency of this module, so the engine reaches the classpath only through camunda-engine-bpmn, which also creates the ProcessEngine this module serves.
CamundaRestUndertowModule extends CamundaRestModule: the base module contributes the configuration, the telemetry factory, and the default JAX-RS application, while the Undertow module adds the HTTP handler and the server itself.
Applications connect CamundaRestUndertowModule — connecting only CamundaRestModule leaves the REST API without a transport.
HTTP server¶
The module starts a separate, independent Undertow HTTP server dedicated to the Camunda 7 REST API.
It listens on its own port (default: 8081) and is completely isolated from the main HTTP server module:
it has its own CORS filter, its own telemetry, and its own graceful shutdown.
The Camunda REST API and the application's own controllers therefore run on different ports and do not share request handling or configuration.
The server is disabled by default and is started by camunda.rest.enabled = true.
The REST handler itself is always built during graph initialization — enabled only decides whether the HTTP listener is opened.
If the configured port is already taken, startup fails with Camunda HTTP Server (Undertow) failed to start, cause port '<port>' is already in use.
The ProcessEngine that serves these requests is provided by the Camunda 7 BPMN module.
There is no dependency between the two modules inside the Kora container: the BPMN module registers the engine it built in the static ProcessEngines registry of Camunda,
and this module publishes a KoraProcessEngineProvider through the org.camunda.bpm.engine.rest.spi.ProcessEngineProvider service loader, which resolves the default engine from that registry.
This module only exposes the engine over HTTP under the configured path (default: /engine-rest).
Requests are dispatched onto virtual threads, so a Camunda REST call blocking on the database does not occupy an Undertow I/O thread.
On shutdown, the server stops accepting new requests and waits up to shutdownWait (default: 30s)
for in-flight requests to complete before it terminates.
Configuration¶
Example of the complete configuration described by the CamundaRestConfig interface:
camunda {
rest {
enabled = false //(1)!
path = "/engine-rest" //(2)!
port = 8081 //(3)!
shutdownWait = "30s" //(4)!
openapi {
enabled = false //(5)!
files = [ "openapi.json" ] //(6)!
path = "/openapi" //(7)!
cache = "GZIP" //(8)!
swaggerui {
enabled = false //(9)!
path = "/swagger-ui" //(10)!
withCredentials = true //(11)!
cache = "GZIP" //(12)!
options { //(13)!
layout = "StandaloneLayout"
validatorUrl = "null"
defaultModelsExpandDepth = "0"
deepLinking = "true"
persistAuthorization = "true"
displayOperationId = "true"
filter = "true"
}
}
scalar {
enabled = false //(14)!
path = "/scalar" //(15)!
cache = "GZIP" //(16)!
}
}
cors {
enabled = false //(17)!
allowOrigin = "*" //(18)!
allowHeaders = [ "*" ] //(19)!
allowMethods = [ "GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD" ] //(20)!
allowCredentials = true //(21)!
exposeHeaders = [ "*" ] //(22)!
maxAge = "1h" //(23)!
}
telemetry {
logging {
enabled = false //(24)!
stacktrace = true //(25)!
mask = "***" //(26)!
maskQueries = [ ] //(27)!
maskHeaders = [ "authorization", "cookie", "set-cookie" ] //(28)!
pathFull = false //(29)!
}
metrics {
enabled = false //(30)!
slo = [ 1, 10, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 30000, 60000, 90000 ] //(31)!
tags = { //(32)!
"key1" = "value1"
"key2" = "value2"
}
}
tracing {
enabled = true //(33)!
attributes = { //(34)!
"key1" = "value1"
"key2" = "value2"
}
}
}
}
}
- Starts the separate HTTP server with the
Camunda 7 REST API(default:false). - Path prefix of the
Camunda 7 REST API(default:/engine-rest). - Port of the separate
UndertowHTTP server serving theREST API(default:8081). - Maximum time to wait for HTTP server graceful shutdown (default:
30s). - Enables serving
OpenAPIfiles (default:false). - List of
OpenAPIfiles in application resources (default:[ "openapi.json" ]), see OpenAPI. The default value is the specification bundled with thecamunda-engine-rest-openapidependency. - Path where
OpenAPIfiles are available (default:/openapi). With one file it is exactly this path; with several files it becomes a prefix of the/openapi/{file}form. - Response caching mode for
OpenAPIfiles:NONE,GZIP, orFULL(default:GZIP), see Caching. - Enables the
Swagger UIpage (default:false). - Path where the
Swagger UIpage is available (default:/swagger-ui). - Sends browser credentials (cookies,
Authorizationheader) with requests issued fromSwagger UI(default:true). - Response caching mode for the
Swagger UIpage:NONE,GZIP, orFULL(default:GZIP). Swagger UIinitialization options, see Swagger UI options (default: the seven values shown above).- Enables the
Scalarpage (default:false). - Path where the
Scalarpage is available (default:/scalar). - Response caching mode for the
Scalarpage:NONE,GZIP, orFULL(default:GZIP). - Enables the
CORSfilter (default:false). - Allowed origin for
CORS(default not specified, optional). When not specified, the filter echoes the requestOriginheader, and falls back to*when the request has none. - Allowed headers for
CORSrequests (default:[ "*" ]). - Allowed HTTP methods for
CORSrequests (default:[ "GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD" ]). - Whether credentials are allowed in
CORSrequests (default:true). - Headers exposed to the client in a
CORSresponse (default:[ "*" ]). - Maximum caching time for
CORSpreflight requests (default:1h). - Enables module logging (default:
false). - Enables call stack logging on exception (default:
true). - Mask used to hide specified headers and request parameters (default:
***). - List of request parameters to hide (default:
[]). - List of request or response headers to hide (default:
[ "authorization", "cookie", "set-cookie" ]). - Whether to log the full request path instead of the route template; when not specified, the template is used except at
TRACE, where the full path is used (default not specified, optional). - Enables module metrics (default:
false). - Configures SLO for metrics (default:
io.koraframework.telemetry.common.TelemetryConfig.MetricsConfig#DEFAULT_SLO). - Configures metric tags (default:
{}). - Enables module tracing (default:
true). - Configures tracing attributes (default:
{}).
camunda:
rest:
enabled: false #(1)!
path: "/engine-rest" #(2)!
port: 8081 #(3)!
shutdownWait: "30s" #(4)!
openapi:
enabled: false #(5)!
files: [ "openapi.json" ] #(6)!
path: "/openapi" #(7)!
cache: "GZIP" #(8)!
swaggerui:
enabled: false #(9)!
path: "/swagger-ui" #(10)!
withCredentials: true #(11)!
cache: "GZIP" #(12)!
options: #(13)!
layout: "StandaloneLayout"
validatorUrl: "null"
defaultModelsExpandDepth: "0"
deepLinking: "true"
persistAuthorization: "true"
displayOperationId: "true"
filter: "true"
scalar:
enabled: false #(14)!
path: "/scalar" #(15)!
cache: "GZIP" #(16)!
cors:
enabled: false #(17)!
allowOrigin: "*" #(18)!
allowHeaders: [ "*" ] #(19)!
allowMethods: [ "GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD" ] #(20)!
allowCredentials: true #(21)!
exposeHeaders: [ "*" ] #(22)!
maxAge: "1h" #(23)!
telemetry:
logging:
enabled: false #(24)!
stacktrace: true #(25)!
mask: "***" #(26)!
maskQueries: [ ] #(27)!
maskHeaders: [ "authorization", "cookie", "set-cookie" ] #(28)!
pathFull: false #(29)!
metrics:
enabled: false #(30)!
slo: [ 1, 10, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 30000, 60000, 90000 ] #(31)!
tags: #(32)!
key1: value1
key2: value2
tracing:
enabled: true #(33)!
attributes: #(34)!
key1: value1
key2: value2
- Starts the separate HTTP server with the
Camunda 7 REST API(default:false). - Path prefix of the
Camunda 7 REST API(default:/engine-rest). - Port of the separate
UndertowHTTP server serving theREST API(default:8081). - Maximum time to wait for HTTP server graceful shutdown (default:
30s). - Enables serving
OpenAPIfiles (default:false). - List of
OpenAPIfiles in application resources (default:[ "openapi.json" ]), see OpenAPI. The default value is the specification bundled with thecamunda-engine-rest-openapidependency. - Path where
OpenAPIfiles are available (default:/openapi). With one file it is exactly this path; with several files it becomes a prefix of the/openapi/{file}form. - Response caching mode for
OpenAPIfiles:NONE,GZIP, orFULL(default:GZIP), see Caching. - Enables the
Swagger UIpage (default:false). - Path where the
Swagger UIpage is available (default:/swagger-ui). - Sends browser credentials (cookies,
Authorizationheader) with requests issued fromSwagger UI(default:true). - Response caching mode for the
Swagger UIpage:NONE,GZIP, orFULL(default:GZIP). Swagger UIinitialization options, see Swagger UI options (default: the seven values shown above).- Enables the
Scalarpage (default:false). - Path where the
Scalarpage is available (default:/scalar). - Response caching mode for the
Scalarpage:NONE,GZIP, orFULL(default:GZIP). - Enables the
CORSfilter (default:false). - Allowed origin for
CORS(default not specified, optional). When not specified, the filter echoes the requestOriginheader, and falls back to*when the request has none. - Allowed headers for
CORSrequests (default:[ "*" ]). - Allowed HTTP methods for
CORSrequests (default:[ "GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD" ]). - Whether credentials are allowed in
CORSrequests (default:true). - Headers exposed to the client in a
CORSresponse (default:[ "*" ]). - Maximum caching time for
CORSpreflight requests (default:1h). - Enables module logging (default:
false). - Enables call stack logging on exception (default:
true). - Mask used to hide specified headers and request parameters (default:
***). - List of request parameters to hide (default:
[]). - List of request or response headers to hide (default:
[ "authorization", "cookie", "set-cookie" ]). - Whether to log the full request path instead of the route template; when not specified, the template is used except at
TRACE, where the full path is used (default not specified, optional). - Enables module metrics (default:
false). - Configures SLO for metrics (default:
io.koraframework.telemetry.common.TelemetryConfig.MetricsConfig#DEFAULT_SLO). - Configures metric tags (default:
{}). - Enables module tracing (default:
true). - Configures tracing attributes (default:
{}).
The listing above shows every available option; in practice you enable only what you need.
A typical setup exposes the REST API on a custom port together with the OpenAPI description, Swagger UI, and request logging:
cache values are matched against the enum constants exactly, so they must be written in upper case.
OpenAPI¶
Besides the REST API itself, the separate server can serve the API's OpenAPI description together with
the Swagger UI and Scalar pages.
All three are disabled by default and are enabled independently through the openapi configuration section,
and all three are served by the same handlers the OpenAPI management module uses, so their behavior matches that module.
When enabled, the pages are available on the REST server port at the configured paths:
| Page | Configuration flag | Default path |
|---|---|---|
| OpenAPI spec | openapi.enabled |
/openapi |
| Swagger UI | openapi.swaggerui.enabled |
/swagger-ui |
| Scalar | openapi.scalar.enabled |
/scalar |
For example, with port = 8090 and openapi.enabled = true the specification is served at http://localhost:8090/openapi,
and Swagger UI (when enabled) at http://localhost:8090/swagger-ui.
Everything outside these paths and outside the REST API path prefix answers with 404.
By default the module serves the OpenAPI specification bundled with the
camunda-engine-rest-openapi dependency, which is why files already has a working default.
Before the file is sent, the module rewrites the port 8080 written in it to the configured port, and — when path differs from /engine-rest — rewrites the engine-rest prefix as well,
so the served OpenAPI always matches the live REST API address.
To serve a custom specification instead, point openapi.files at one or more files in resources:
Each path is resolved as a classpath resource, first as written and then with a leading / prepended, so openapi/my.json and /openapi/my.json address the same resource.
A .json file is served as text/json, any other extension as text/x-yaml.
When more than one file is configured, the public name of a file in the URL is derived from the file name: directories are stripped and a trailing .json, .yml, or .yaml extension is removed.
So with files = [ "openapi/engine.json", "openapi/custom.json" ] the specifications are available at /openapi/engine and /openapi/custom, while /openapi itself answers with 404.
CORS¶
The REST server has its own CORS filter, disabled by default and enabled through cors.enabled.
When enabled, the filter wraps the whole server — both the REST API and the OpenAPI pages — and adds the Access-Control-* headers to every response, without short-circuiting preflight requests itself.
When cors.allowOrigin is not set, the filter reflects the request Origin header back in the response,
falling back to * when the request carries no Origin header.
The remaining cors.* options control the allowed headers and methods, whether credentials are allowed,
the headers exposed to the client, and the preflight cache duration.
A header that a resource has already set itself is never overwritten, and Access-Control-Expose-Headers is omitted entirely when exposeHeaders is empty.
Telemetry¶
Requests handled by the REST server are covered by the standard Kora telemetry signals — logging,
metrics, and tracing — configured under the telemetry section.
Logging and metrics are disabled by default, tracing is enabled by default; when all three are off, the module installs a no-op telemetry and adds no overhead per request.
Logging is written to the io.koraframework.http.server.common.HttpServer logger and produces a CamundaRest received request event before the call
and a CamundaRest succeed response (or CamundaRest errored response on failure) event after it.
The operation field carries the method with the route template, resultCode, statusCode, and processingTime are attached to the response event, and query parameters and headers are added at the DEBUG level.
Headers listed in maskHeaders and query parameters listed in maskQueries are replaced with the mask value.
The logged operation uses the route template by default and the full path when pathFull = true or the logger level is TRACE.
Tracing creates a <METHOD> <route template> SERVER span per request with the http.request.method, url.scheme, server.address, url.path, http.route, http.response.status_code, and http.response.result_code attributes.
The incoming W3C trace context is propagated into the span and the resulting context is written back into the response headers.
Module metrics are described in the Metrics Reference section.
The default telemetry can be overridden by registering your own DefaultCamundaRestLoggerFactory or DefaultCamundaRestMetricsFactory subclass as a @Component;
the whole CamundaRestTelemetryFactory is provided via @DefaultComponent and can be replaced as well.
Route templates¶
Metrics, tracing, and logging all identify a request by its route template rather than by its full path, so /engine-rest/process-instance/{id} stays a single time series no matter how many process instances exist.
The module cannot ask RESTEasy for the matched template, so it carries a built-in table of every Camunda 7 REST API route, prefixed with the configured path, and matches the request against it.
A request that matches nothing in that table — an unknown endpoint, or a custom JAX-RS resource added through Applications — is still served, but it produces no log record and no span,
and its duration metric is recorded with the http.route tag set to UNKNOWN_ROUTE.
Applications¶
The module already registers a default @Tag(CamundaRest.class) jakarta.ws.rs.core.Application that exposes the standard
Camunda 7 REST API resources (CamundaRestResources) together with a ResteasyJackson2Provider for JSON serialization.
To add custom JAX-RS resources, register your own jakarta.ws.rs.core.Application component marked with the @Tag(CamundaRest.class) tag.
All such applications are collected and merged with the default one — their getClasses(), getSingletons(), and getProperties() are combined —
so custom resources are served on the same REST server alongside the standard Camunda endpoints, under the same path prefix.
Custom resources are not part of the built-in route table, so they are reported as UNKNOWN_ROUTE in metrics and are not logged or traced, see Route templates.