diff --git a/README.md b/README.md
index ffbc688..f9ff59c 100644
--- a/README.md
+++ b/README.md
@@ -1,19 +1,18 @@
# ZenBPM Java Client
-> Spring Boot starter and core REST/gRPC client to interact with the ZenBPM process engine. Includes auto-configuration, OpenAPI-generated REST client, optional gRPC job workers, logging and OpenTelemetry hooks.
+> Spring Boot starter for ZenBPM, plus a core artifact containing the generated REST APIs and gRPC stubs. Spring auto-configuration, `@JobWorker`, logging, and OpenTelemetry hooks are provided by the starter; gRPC workers also require a `ManagedChannel` provider.
## Features
-* Project uses JDK 8+ and Spring Boot 2.7+ to support legacy systems, but can be easily adjusted to the newest JDK and Spring versions (see pom.xml).
* Spring Boot auto-configuration (drop-in starter)
-* REST client (`ApiClient` + typed APIs generated from OpenAPI)
-* gRPC job workers via `@JobWorker` and ZenbpmJobWorkerManager
+* REST client (`ApiClient` plus typed APIs generated from OpenAPI)
+* gRPC job workers via `@JobWorker` and `ZenbpmJobWorkerManager`
* OpenTelemetry interceptors for REST and spans for gRPC
* Configurable HTTP/gRPC logging
## Build this project
-This repo uses [mise ](https://mise.jdx.dev/) (`mise.toml`) to pin the local Java and Maven versions used by CI.
+This repo uses [mise](https://mise.jdx.dev/) (`mise.toml`) to pin the local Java and Maven versions used by CI.
First-time setup:
@@ -33,7 +32,7 @@ This runs `mvn -B clean package` with pinned Temurin 17 and Maven, without requi
For release validation, set the Maven artifact version from a release tag before building:
```bash
-RELEASE_TAG=v1.4.0 mise run set-release-version
+RELEASE_TAG=v1.5.0 mise run set-release-version
mise run verify-release
```
@@ -41,7 +40,7 @@ The release workflow downloads backend OpenAPI/proto sources before running thes
## Releasing
-Releases are triggered by the ZenBPM release orchestrator with `workflow_dispatch` input `version` set to the backend release tag, for example `v1.4.0`.
+Releases are triggered by the ZenBPM release orchestrator with `workflow_dispatch` input `version` set to the backend release tag, for example `v1.5.0`.
The workflow downloads `openapi/api.yaml` and `pkg/zenclient/proto/zenbpm.proto` from the matching `pbinitiative/zenbpm` tag, sets Maven artifact versions from that tag without the `v` prefix, commits the generated release inputs, tags this repository, creates a GitHub Release, and publishes artifacts to Maven Central under `org.pbinitiative.zenbpm`.
@@ -49,136 +48,183 @@ Required repository secrets: `APP_ID_ZENBPM_RELEASE`, `APP_PRIVATE_KEY_ZENBPM_RE
## Getting started
-Add the starter to your application and the core client as needed.
+The Java client uses Maven group and Java package prefix `org.pbinitiative.zenbpm`.
+
+### Compatibility
+
+The ZenBPM Java client has been tested on:
+
+- Java 17 and Spring Boot 3
+- Java 8 and Spring Boot 2.7
+
+The examples below target Java 17 and Spring Boot 3 and intentionally use modern Java syntax.
+
+### Dependencies
+
+For a minimal Spring Boot application, use the standard Boot starter together with the ZenBPM starter and a gRPC channel provider:
-Maven:
```xml
-
- org.pbinitiative.zenbpm
- zenbpm-spring-boot-starter
- ${project.version}
-
-
- org.pbinitiative.zenbpm
- zenbpm-client-core
- ${project.version}
-
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.5.16
+
+
+
+
+ 17
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+ org.pbinitiative.zenbpm
+ zenbpm-spring-boot-starter
+ 1.5.0
+
+
+ io.grpc
+ grpc-netty-shaded
+ 1.78.0
+
+
```
-Configure connection settings in application.yml
+Do **not** directly declare `org.pbinitiative.zenbpm:zenbpm-client-core` in a Spring application: `zenbpm-spring-boot-starter` brings `zenbpm-client-core` transitively. Client 1.5.0 supplies the gRPC APIs and stubs but no `ManagedChannel` provider, so `grpc-netty-shaded` is required when using `@JobWorker`; it is not an optional worker transport.
-values shown in `zenbpm` section are defaults.
+A non-Spring application may instead declare `org.pbinitiative.zenbpm:zenbpm-client-core:1.5.0` to use the generated REST APIs or gRPC stubs, but it must construct and configure the clients itself. gRPC use still requires a channel provider. `@JobWorker` and its auto-configuration are provided by the Spring Boot starter.
+
+### Configuration
+
+This tested `application.yml` uses kebab-case Spring properties and environment overrides for the engine endpoints:
-`logging` section configures logging for rest and grpc clients separately.
- - `DEBUG` levels expose headers of calls and responses.
- - `TRACE` level exposes full request and response bodies. Never use this in production!
```yaml
zenbpm:
- restUrl: http://localhost:8080/v1
- restLoggingEnabled: true
- grpcHost: localhost
- grpcPort: 9090
- grpcPlaintext: true
- grpcLoggingEnabled: true
- jobWorkerEnabled: true
-
-otel.sdk.disabled: true
-
-logging:
- level:
- root: INFO
- org.pbinitiative.zenbpm.rest: TRACE
- org.pbinitiative.zenbpm.grpc: DEBUG
-
+ rest-url: ${ZENBPM_REST_URL:http://localhost:8080/v1}
+ rest-logging-enabled: false
+ grpc-host: ${ZENBPM_GRPC_HOST:localhost}
+ grpc-port: ${ZENBPM_GRPC_PORT:9090}
+ # Local development only. Use gRPC TLS and an HTTPS REST URL in other environments.
+ grpc-plaintext: true
+ grpc-logging-enabled: false
+ job-worker-enabled: true
+
+otel:
+ sdk:
+ disabled: true
```
-## Working examples
+This sample explicitly disables client logging. Set both logging flags to `false` in production, especially when process variables may contain sensitive data. If REST logging is enabled, OkHttp `BASIC` logging at DEBUG emits request/response lines, not headers or bodies. At TRACE, REST `BODY` logging can expose full HTTP data, and gRPC logging can expose job variables and results.
+
+### REST: deploy and start
+
+Place the example classes in the same package as, or a child package of, your `@SpringBootApplication` class so Spring discovers them. Replace the example `package` declaration with your application's package when needed.
-### 1) Use REST APIs
-Inject the provided ZenbpmClientService to obtain the ApiClient, then create a typed API.
+`ZenbpmClientService` supplies the configured `ApiClient`. Deploy a `java.io.File` with `ProcessDefinitionApi`, then start with a business key and variables in `CreateProcessInstanceRequest`:
```java
-import org.springframework.stereotype.Service;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.pbinitiative.zenbpm.rest.ZenbpmClientService;
-import org.pbinitiative.zenbpm.client.ApiException;
+package com.example.zenbpmverification;
+
+import java.io.File;
+import java.util.Map;
+
import org.pbinitiative.zenbpm.client.ApiClient;
+import org.pbinitiative.zenbpm.client.ApiException;
import org.pbinitiative.zenbpm.client.api.ProcessDefinitionApi;
import org.pbinitiative.zenbpm.client.api.ProcessInstanceApi;
import org.pbinitiative.zenbpm.client.api.dto.CreateProcessInstanceRequest;
-
-import java.util.HashMap;
-import java.util.Map;
+import org.pbinitiative.zenbpm.client.api.dto.ProcessInstance;
+import org.pbinitiative.zenbpm.rest.ZenbpmClientService;
+import org.springframework.stereotype.Service;
@Service
-public class MyService {
- @Autowired
- private ZenbpmClientService zenbpm;
+public class ZenbpmRestService {
+
+ private final ApiClient apiClient;
+ private final ProcessDefinitionApi processDefinitionApi;
+ private final ProcessInstanceApi processInstanceApi;
+
+ public ZenbpmRestService(ZenbpmClientService zenbpmClientService) {
+ this.apiClient = zenbpmClientService.getApiClient();
+ this.processDefinitionApi = new ProcessDefinitionApi(apiClient);
+ this.processInstanceApi = new ProcessInstanceApi(apiClient);
+ }
+
+ public long deploy(File bpmnFile) throws ApiException {
+ return processDefinitionApi.createProcessDefinition(bpmnFile).getProcessDefinitionKey();
+ }
+
+ public ProcessInstance start(
+ long processDefinitionKey,
+ String businessKey,
+ Map variables) throws ApiException {
+ CreateProcessInstanceRequest request = new CreateProcessInstanceRequest()
+ .processDefinitionKey(processDefinitionKey)
+ .businessKey(businessKey)
+ .variables(variables);
+
+ return processInstanceApi.createProcessInstance(request);
+ }
+
+ public ProcessInstance get(long processInstanceKey) throws ApiException {
+ return processInstanceApi.getProcessInstance(processInstanceKey);
+ }
+}
+```
- public Long deployExampleProcess() throws ApiException {
- ApiClient apiClient = zenbpm.getApiClient();
- ProcessDefinitionApi defApi = new ProcessDefinitionApi(apiClient);
+### gRPC worker
- // Example: create a process definition from a BPMN string (adjust to your endpoint contract)
- String bpmnXml = "...";
- Long definitionKey = defApi.createProcessDefinition(bpmnXml).getProcessDefinitionKey();
- return definitionKey;
- }
+The worker must be a discovered Spring bean. This example validates that `email` is a nonblank `String` and returns a mock completion:
- public void startMyProcess() throws ApiException {
- ApiClient apiClient = zenbpm.getApiClient();
- ProcessInstanceApi piApi = new ProcessInstanceApi(apiClient);
+```java
+package com.example.zenbpmverification;
- Map vars = new HashMap<>();
- vars.put("orderId", 12345L);
+import java.util.Map;
- CreateProcessInstanceRequest req = new CreateProcessInstanceRequest()
- .processDefinitionKey(123456L)
- .variables(vars);
+import org.pbinitiative.zenbpm.grpc.JobContext;
+import org.pbinitiative.zenbpm.grpc.JobWorker;
+import org.springframework.stereotype.Component;
+
+@Component
+public class EmailWorker {
- piApi.createProcessInstance(req);
- }
+ /** Returns a mock confirmation; this worker does not send an actual email. */
+ @JobWorker("send-email")
+ public Map sendEmail(JobContext context) {
+ Object emailValue = context.getVariables().get("email");
+ if (!(emailValue instanceof String email) || email.isBlank()) {
+ throw new IllegalArgumentException("Job variable 'email' must be a nonblank String");
+ }
+
+ return Map.of(
+ "emailSent", true,
+ "confirmation", "Mock email confirmation for " + email);
+ }
}
```
-Notes:
-- Available typed APIs include ProcessDefinitionApi, ProcessInstanceApi, JobApi, MessageApi, etc. Construct them with the provided ApiClient.
-- Methods and DTOs come from the generated package `org.pbinitiative.zenbpm.client.api` and `org.pbinitiative.zenbpm.client.api.dto`.
+An exception from the method fails the job. The returned map is serialized as job-completion output variables. The worker manager connects only when `zenbpm.job-worker-enabled` is true **and** at least one annotated worker has been discovered.
-### 2) Register a gRPC job worker
-Create a Spring bean with a method annotated by `@JobWorker`. Accepted method signatures:
-- no parameters
-- one parameter of type `org.pbinitiative.zenbpm.proto.Zenbpm.WaitingJob`
-- one parameter of type `org.pbinitiative.zenbpm.grpc.JobContext`
-- one parameter of type `Map`
+### Tested flow
-Return value can be any object and will be serialized as variables for job completion. Throwing an exception fails the job.
+The BPMN service task's job type must match the annotation. Declare `xmlns:zenbpm="http://zenbpm.pbinitiative.org/1.0"` on the BPMN definitions and place the task definition and output mappings inside the service task's extension elements:
-```java
-import org.springframework.stereotype.Component;
-import org.pbinitiative.zenbpm.grpc.JobWorker;
-import org.pbinitiative.zenbpm.grpc.JobContext;
-import java.util.Map;
-import java.util.HashMap;
-
-@Component
-public class EmailWorker {
- @JobWorker("send-email")
- public Map handleJob(JobContext ctx) {
- Map vars = ctx.getVariables();
- String to = (String) vars.get("email");
-
- // send email ...
-
- Map result = new HashMap<>();
- result.put("success", true);
- result.put("message", "Email to " + to + " mocked successfully");
- return result;
- }
-}
+```xml
+
+
+
+
+
+
+
+
+
```
-The gRPC worker manager connects on application start if `zenbpm.jobWorkerEnabled` is true.
+Deploy the BPMN `File` and start the instance through `ZenbpmRestService`, passing `Map.of("email", "customer@example.com")` and a business key. The `EmailWorker` then receives and completes the `send-email` job over gRPC; the mappings copy its `emailSent` and `confirmation` outputs into process variables.
---