Camunda 8.9 APIs & Tools migration guide
Migrate your API integrations, SDKs, and generated clients to Camunda 8.9.
About this guide
This guide details the API and SDK changes introduced in Camunda 8.9 that require customer action.
Details are provided for each integration type, including what changed, why, and what action you must take.
| Integration type | Description |
|---|---|
| Official SDK users | Java client, TypeScript SDK, Python SDK, C# SDK. |
| Generated-client users | Clients generated from the Camunda OpenAPI specification. |
| Custom integrations | Custom code that calls the Camunda REST API directly. |
For a full list of changes, see the 8.9 release announcements and release notes.
Upgrade steps
Complete the following steps in this guide:
- Upgrade to the latest official Camunda SDK versions.
- If you generate clients from OpenAPI, regenerate them from the 8.9 specification.
- Re-run compilation/type checks and address any errors.
- Review and apply fixes for the breaking changes, deprecations, and supported environment changes below.
API and SDK changes to migrate before Camunda 8.10
If you did not already migrate to the following APIs and SDKs during your 8.8 upgrade, Camunda recommends you perform these migrations before you upgrade to 8.9, as this must be performed before 8.10.
If you already performed these migrations during your 8.8 upgrade, proceed to Camunda 8.9 breaking changes, deprecations, and supported environment changes.
| 8.9 status | Component/Use | Migrate to | Migrate by |
|---|---|---|---|
| Deprecated | V1 component APIs | Orchestration Cluster API | Before Camunda 8.10 |
| Deprecated | ZeebeClient | Camunda Java Client | Before Camunda 8.10 |
| Deprecated | Spring Zeebe SDK | Camunda Spring Boot Starter | Before Camunda 8.10 |
| Deprecated | Zeebe Process Test (ZPT) | Camunda Process Test (CPT) | Before Camunda 8.10 |
| Deprecated | Job-based user tasks | Camunda user tasks | Before Camunda 8.10 |
Learn more about API changes in the blog post Upcoming API Changes in Camunda 8: A Unified and Streamlined Experience.
Camunda 8.9 breaking changes, deprecations, and supported environment changes
Review the actions required for the following 8.9 changes:
| Type | Change |
|---|---|
| Breaking change | Bug fix: FormResult.schema type corrected from object to string |
| Breaking change | Document API response schemas now have explicit required and nullable annotations |
| Breaking change | MCP Client and MCP Remote Client connectors |
| Breaking change | OpenAPI enum extensions |
| Breaking change | OpenAPI type-safety enhancements |
| Breaking change | Resource deletion endpoint now returns a response body |
| Breaking change | Search filter validation errors now return structured error collections |
| Breaking change | Spring Boot 4.0 default for Camunda Spring Boot Starter |
| Breaking change | Type-safe pagination model in the Camunda Java client |
| Breaking change | versionTag returns null instead of empty string when absent |
| Deprecated | Deprecated: enum literals in Orchestration Cluster API v2 |
Breaking changes
Review actions required for the following breaking changes:
Bug fix: FormResult.schema type corrected from object to string
Change
The schema property in FormResult was incorrectly specified as type: object in the OpenAPI contract. The server has always returned it as a JSON string. The specification is now corrected.
Why
This is a bug fix. The original specification was inaccurate and caused incorrect typing in generated clients.
Impact
This impacts the Java client as io.camunda.client.api.search.response.Form::getSchema() now returns String instead of Object.
Action
- Official SDK users
- Generated-client users
- Custom integrations
Update to the latest SDK version. If you are a Java client user, update any calls to Form::getSchema() that cast or process the return value as Object — it is now String.
Regenerate your client. If your generated code relied on the incorrect object typing for FormResult.schema, update it to handle string.
No change needed if your code was already handling the actual string response from the server.
Document API response schemas now have explicit required and nullable annotations
Change
The OpenAPI specification now uses distinct schemas for document request and response payloads, and adds explicit required / nullable annotations to document response types.
Why
A shared DocumentMetadata schema was used for both creating and reading documents. Because response fields like customProperties are always populated by the server but optional in requests, a single schema could not accurately express both contracts. This caused incorrect required/optional behavior in generated clients.
Affected schemas
| Schema | Change |
|---|---|
DocumentMetadata | Now request-only. Removed required: [customProperties] — customProperties is now optional in requests. |
DocumentMetadataResponse (new) | Response schema with required fields: fileName, expiresAt, size, contentType, customProperties, processDefinitionId, processInstanceKey. expiresAt, processDefinitionId, and processInstanceKey are nullable. |
DocumentReference | metadata now references DocumentMetadataResponse. Added required: camunda.document.type, storeId, documentId, contentHash, metadata. contentHash is now nullable. |
DocumentLink | url and expiresAt are now explicitly required. |
UserTaskResult.candidateGroups | Now marked as required in the response schema. |
UserTaskProperties.candidateGroups | Now marked as required in the response schema. |
Impact
The Java client is impacted as DocumentMetadataImpl (both io.camunda.client and the deprecated io.camunda.zeebe.client) now uses DocumentMetadataResponse instead of DocumentMetadata internally.
Action
- Official SDK users
- Generated-client users
- Custom integrations
Update to the latest SDK version. The updated response models are included automatically. Re-compile your application to verify.
- Regenerate your client from the 8.9 OpenAPI specification.
- Update any code that references
DocumentMetadatain response handling — the response type is nowDocumentMetadataResponse. - Review nullable annotations:
DocumentReference.contentHash,DocumentMetadataResponse.expiresAt,.processDefinitionId, and.processInstanceKeycan benull. - Code that reads
candidateGroupsfrom user task or job responses can now rely on the field being present without null checks.
No request-side changes are needed. Response fields listed above are now guaranteed to be present (though some may be null). If your code reads document metadata responses and checks for customProperties or candidateGroups presence, those fields are now always included.
MCP Client and MCP Remote Client connectors
Change
Breaking changes were introduced in alpha 2 to the element templates and runtime configuration of the MCP Client.
Why
This improves the stability and configuration model of the MCP connectors.
Action
Update both the MCP Client and MCP Remote Client connectors to use element template version 1. See the MCP documentation for details.
OpenAPI enum extensions
Change
New enum literals were added to support expanded 8.9 functionality.
Why
These additions enable new features such as decision instance deletion and user task authorization.
Enum members added
| Enum | New value |
|---|---|
BatchOperationTypeEnum / BatchOperationTypeFilterProperty | DELETE_DECISION_INSTANCE |
ResourceTypeEnum | USER_TASK |
PermissionTypeEnum | COMPLETE |
Action
- Official SDK users
- Generated-client users
- Custom integrations
Update to the latest SDK version for full enum support. Re-compile your application — the compiler will signal any exhaustive match issues.
- Regenerate your client from the 8.9 OpenAPI specification.
- Add fallback/default handling in enum parsing and deserialization.
- Ensure exhaustive
switchor pattern matches include adefaultbranch.
Review all code paths that handle these enum values. Add handling for the new values and ensure you have a fallback for unknown values in switch/if-else chains.
In Java, the compiler does not signal incomplete enum handling at compile time. Search your codebase for references to BatchOperationTypeEnum, ResourceTypeEnum, and PermissionTypeEnum and verify coverage manually.
OpenAPI type-safety enhancements
Change
Several request properties in the OpenAPI contract now use stronger domain types instead of plain string, and one schema type was renamed. This completes the type-safety work that began in 8.8.
Why
This increases compile-time safety and helps prevent semantic substitution errors — for example, accidentally passing a tenantId where a documentId is expected. Compilers can now reason about semantic correctness in addition to structural correctness for these fields.
Affected fields and types
| Field | Old type | New type |
|---|---|---|
CreateDeploymentData.body.tenantId | string | TenantId |
CreateDocumentData.query.documentId | string | DocumentId |
SearchCorrelatedMessageSubscriptionsData.body.filter.processDefinitionKey.$eq | string | ProcessDefinitionKey |
CorrelatedMessageSubscriptionFilter.processDefinitionKey | string | ProcessDefinitionKeyFilterProperty | undefined |
CorrelatedMessageSubscriptionSearchQuery.filter.processDefinitionKey.$eq | string | ProcessDefinitionKey |
Schema rename
| Old name | New name |
|---|---|
ProcessInstanceIncidentSearchQuery | IncidentSearchQuery |
Example — message subscription filter payload:
{
"processDefinitionKey": "2251799813685251"
}
{
"processDefinitionKey": { "$eq": "2251799813685251" }
}
Action
- Official SDK users
- Generated-client users
- Custom integrations
Update to the latest SDK version. The wire-type of these fields does not change, so most SDK users will not need code changes. Re-compile your application to verify.
- Regenerate your client from the 8.9 OpenAPI specification.
- Update type imports and references — in particular, rename
ProcessInstanceIncidentSearchQuerytoIncidentSearchQuery. - Update request payload construction for
processDefinitionKeyfields to use the new filter property type.
- Update request payload construction for
processDefinitionKeyto use the filter object format. - Update any references to
ProcessInstanceIncidentSearchQueryin your code.
Resource deletion endpoint now returns a response body
Change
The resource deletion endpoint POST /resources/{resourceKey}/deletion now returns a response body instead of an empty response.
Why
This provides explicit deletion feedback, making client-side confirmation, auditing, and follow-up workflow logic more reliable.
Action
- Official SDK users
- Generated-client users
- Custom integrations
Update to the latest SDK version. The updated response model is included automatically.
Regenerate your client from the 8.9 OpenAPI specification. Update any code that previously expected an empty 204 response to handle the new response body.
Update your HTTP client code to parse the new JSON response body from the deletion endpoint, rather than treating it as a 204 No Content.
Spring Boot 4.0 default for Camunda Spring Boot Starter
Change
Starting with 8.9.0, the default Camunda Spring Boot Starter (camunda-spring-boot-starter) is bundled with and requires Spring Boot 4.0.x. A dedicated camunda-spring-boot-3-starter module is available for applications that are not yet ready to upgrade.
Action
- Migrate your application to Spring Boot 4.0.x and continue using
camunda-spring-boot-starter. - If you cannot migrate yet, switch your dependency to
camunda-spring-boot-3-starter, which is bundled with Spring Boot 3.5.x. Note that OSS support for Spring Boot 3.5.x ends in June 2026, so plan your migration accordingly. - See the Spring Boot support timeline for details.
- See the dedicated Spring Boot 3 and 4 modules documentation for more information.
versionTag returns null instead of empty string when absent
Change
API response fields for versionTag now return null instead of an empty string "" when no version tag is set.
Why
This properly signals absence instead of leaking an internal empty-string default. It aligns versionTag with how other optional fields like businessId are handled, simplifying absence-detection logic.
Action
- Official SDK users
- Generated-client users
- Custom integrations
Update to the latest SDK version. Review any code that checks for an empty string ("") to detect a missing version tag, and update it to check for null.
Regenerate your client to pick up the updated nullable annotation. Update absence-detection logic from empty-string checks to null checks.
Update your response-handling code:
if (versionTag != null && !versionTag.isEmpty()) {
// version tag is present
}
if (versionTag != null) {
// version tag is present
}
Search filter validation errors now return structured error collections
Change
REST API search endpoints now collect all filter validation errors and return them together in a single 400 Bad Request response. Previously, only the first conversion error was returned.
Why
This is a bug fix that improves error handling consistency across the REST API. Collecting all validation errors in a single response makes debugging easier.
Impact
Search filter validation error responses now contain a list of all validation issues instead of stopping at the first error. The error detail format has changed:
| Aspect | Before | After | Breaking? |
|---|---|---|---|
| HTTP status code | 400 | 400 | No |
ProblemDetail title | "Bad Request" | "INVALID_ARGUMENT" | Yes |
ProblemDetail detail | "Failed to parse date-time: [invalid]" | "The provided evaluationDate 'invalid' cannot be parsed as a date according to RFC 3339, section 5.6." | Yes |
| Error collection | Fails on first error | Collects all validation errors | Yes (response may contain more errors) |
Affected search endpoints include all endpoints that accept advanced search filters with key fields (such as processInstanceKey, processDefinitionKey, scopeKey) or date fields (such as startDate, endDate, creationDate).
Who is affected?
- Customers parsing error response bodies (specifically
titleordetailfields) for validation errors → affected. - Customers only checking HTTP status codes → not affected.
- Customers sending valid requests → not affected (happy path is unchanged).
Action
If your code parses error response bodies from search endpoints for specific validation error messages, update it to handle:
- The
titlefield value changed from"Bad Request"to"INVALID_ARGUMENT". - The
detailfield now contains more descriptive, structured messages. - A collection of validation errors in the response body (instead of a single error message).
Type-safe pagination model in the Camunda Java client
Change
The Camunda Java client now uses type-safe pagination interfaces (AnyPage, OffsetPage, CursorForwardPage, CursorBackwardPage) instead of the previous SearchRequestPage class. Each search or statistics endpoint exposes only the pagination methods it actually supports.
Direction methods on AnyPage now return style-specific interfaces: from() returns OffsetPage, after() returns CursorForwardPage, and before() returns CursorBackwardPage. This prevents mixing incompatible pagination styles at compile time.
Why
The previous API allowed mixing incompatible pagination styles (for example, .page(p -> p.from(10).after("cursor"))), which always resulted in a 400 Bad Request at runtime. This change surfaces that restriction at compile time. The pattern mirrors the existing sort polymorphism design (TypedSortableRequest).
Impact
This change is not binary-compatible. Code compiled against the previous API will fail at runtime without recompilation, because the method signature changed from page(Consumer<SearchRequestPage>) to page(Consumer<AnyPage>). All users must recompile their applications.
Additionally, TypedSearchRequest now has 4 generic type parameters (previously 3) and TypedPageableRequest now has 2 (previously 1), which is a source-breaking change for custom implementations of these interfaces.
Migration reference
| Before (8.8) | After (8.9) |
|---|---|
import ...search.request.SearchRequestPage | import ...search.page.AnyPage |
import ...search.request.SearchRequestOffsetPage | import ...search.page.OffsetPage |
Consumer<SearchRequestPage> | Consumer<AnyPage> |
Consumer<SearchRequestOffsetPage> | Consumer<OffsetPage> |
SearchRequestBuilders.searchRequestPage(fn) | SearchRequestBuilders.anyPage(fn) (old method deprecated) |
implements TypedSearchRequest<F, S, Self> | implements TypedSearchRequest<F, S, AnyPage, Self> |
implements TypedPageableRequest<Self> | implements TypedPageableRequest<AnyPage, Self> |
SearchRequestPage r = p.from(10) | OffsetPage r = p.from(10) |
SearchRequestPage r = p.after("c") | CursorForwardPage r = p.after("c") |
Action
Update to the latest Java client version and recompile your application. If you use inline lambdas with valid pagination patterns (for example, .page(p -> p.from(5).limit(10))), your source code does not require changes — but recompilation is mandatory.
If you have explicit references to SearchRequestPage, replace them with AnyPage. If you store the return value of direction methods (for example, SearchRequestPage r = p.from(10)), update the variable type to OffsetPage, CursorForwardPage, or CursorBackwardPage as appropriate.
This change is specific to the Camunda Java client. Generated clients and custom REST API integrations are not affected.
Deprecations
Review the actions required for the following deprecations:
Deprecated: enum literals in Orchestration Cluster API v2
The following enum literals are now marked as deprecated:
UNSPECIFIEDinDecisionDefinitionTypeEnumUNKNOWNinDecisionInstanceStateFilterPropertyUNKNOWNinDecisionInstanceStateEnum
These values were reintroduced to preserve backward compatibility but are planned for removal in a future release. Removal will be signaled as a breaking change at that time.
Action
Avoid using these values in new integrations. If your code references them, plan to remove these references before the 8.10 release.
Next steps
Once you have completed the upgrade steps in this guide, you should:
- Re-compile and run your test suite against the 8.9 API.
- Review 8.9 release announcements for additional context on each change.