Hello! I’d be happy to help you create a Custom Connector and Element Template for Camunda 8. Let me provide you with a comprehensive end-to-end example covering all your requirements.
1. Custom Connector Implementation
Project Setup
First, use the connector-template-outbound as a starting point and add the required dependency to your pom.xml
:
<dependency>
<groupId>io.camunda.connector</groupId>
<artifactId>connector-core</artifactId>
<version>${version.connectors}</version>
</dependency>
Request Class
Create a request class to handle input data:
package io.camunda.connector.example;
public class MyConnectorRequest {
private String message;
private String endpoint;
private Authentication authentication;
// getters and setters
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
public String getEndpoint() { return endpoint; }
public void setEndpoint(String endpoint) { this.endpoint = endpoint; }
public Authentication getAuthentication() { return authentication; }
public void setAuthentication(Authentication authentication) { this.authentication = authentication; }
}
class Authentication {
private String token;
private String user;
// getters and setters
public String getToken() { return token; }
public void setToken(String token) { this.token = token; }
public String getUser() { return user; }
public void setUser(String user) { this.user = user; }
}
Connector Implementation
Create the main connector function:
package io.camunda.connector.example;
import io.camunda.connector.api.annotation.OutboundConnector;
import io.camunda.connector.api.outbound.OutboundConnectorContext;
import io.camunda.connector.api.outbound.OutboundConnectorFunction;
@OutboundConnector(
name = "MY_CUSTOM_CONNECTOR",
inputVariables = {"message", "endpoint", "authentication"},
type = "io.camunda:my-custom-connector:1"
)
public class MyCustomConnectorFunction implements OutboundConnectorFunction {
@Override
public Object execute(OutboundConnectorContext context) throws Exception {
// Deserialize input variables into your request class
MyConnectorRequest request = context.bindVariables(MyConnectorRequest.class);
// Execute your business logic
return executeConnector(request);
}
private MyConnectorResult executeConnector(MyConnectorRequest request) {
try {
// Your custom logic here
String result = processMessage(request.getMessage(), request.getEndpoint());
MyConnectorResult response = new MyConnectorResult();
response.setResult(result);
response.setStatus("SUCCESS");
return response;
} catch (Exception e) {
// Handle errors - these can be caught by errorExpression
throw new RuntimeException("CONNECTOR_ERROR: " + e.getMessage(), e);
}
}
private String processMessage(String message, String endpoint) {
// Implement your connector logic
return "Processed: " + message + " via " + endpoint;
}
}
class MyConnectorResult {
private String result;
private String status;
// getters and setters
public String getResult() { return result; }
public void setResult(String result) { this.result = result; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
}
Register the Connector
Create the SPI file at src/main/resources/META-INF/services/io.camunda.connector.api.outbound.OutboundConnectorFunction
:
io.camunda.connector.example.MyCustomConnectorFunction
2. JSON Element Template
Create element-templates/my-custom-connector.json
:
{
"$schema": "https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json",
"name": "My Custom Connector",
"id": "io.camunda.examples.MyCustomConnector",
"description": "A custom connector example with error handling",
"appliesTo": ["bpmn:ServiceTask"],
"elementType": {
"value": "bpmn:ServiceTask"
},
"groups": [
{
"id": "definition",
"label": "Task definition",
"openByDefault": true
},
{
"id": "input",
"label": "Input Configuration"
},
{
"id": "authentication",
"label": "Authentication"
},
{
"id": "output",
"label": "Output Mapping"
},
{
"id": "errors",
"label": "Error Handling"
}
],
"properties": [
{
"type": "Hidden",
"value": "io.camunda:my-custom-connector:1",
"group": "definition",
"binding": {
"type": "zeebe:taskDefinition",
"property": "type"
}
},
{
"label": "Message",
"description": "The message to process",
"type": "String",
"group": "input",
"binding": {
"type": "zeebe:input",
"name": "message"
},
"constraints": {
"notEmpty": true
}
},
{
"label": "Endpoint URL",
"description": "The endpoint to send the message to",
"type": "String",
"group": "input",
"binding": {
"type": "zeebe:input",
"name": "endpoint"
},
"constraints": {
"notEmpty": true,
"pattern": {
"value": "^https?://.*",
"message": "Must be a valid HTTP(S) URL"
}
}
},
{
"label": "Authentication Token",
"description": "Bearer token for authentication",
"type": "String",
"group": "authentication",
"binding": {
"type": "zeebe:input",
"name": "authentication.token"
},
"constraints": {
"notEmpty": true
}
},
{
"label": "Username",
"description": "Username for authentication",
"type": "String",
"group": "authentication",
"optional": true,
"binding": {
"type": "zeebe:input",
"name": "authentication.user"
}
},
{
"label": "Result Variable",
"description": "Name of variable to store the response in",
"type": "String",
"group": "output",
"value": "connectorResult",
"binding": {
"type": "zeebe:taskHeader",
"key": "resultVariable"
}
},
{
"label": "Error Expression",
"description": "Expression to handle connector errors",
"group": "errors",
"type": "Text",
"feel": "required",
"binding": {
"type": "zeebe:taskHeader",
"key": "errorExpression"
},
"value": "if contains(error.message, \"CONNECTOR_ERROR\") then bpmnError(\"CUSTOM_CONNECTOR_ERROR\", error.message) else null"
}
]
}
3. Error Handling
The errorExpression
in the template above handles errors dynamically. You can customize it further:
if contains(error.message, "TIMEOUT") then bpmnError("TIMEOUT_ERROR", "Service timeout occurred")
else if contains(error.message, "AUTH") then bpmnError("AUTH_ERROR", "Authentication failed")
else if contains(error.message, "CONNECTOR_ERROR") then bpmnError("CUSTOM_ERROR", error.message)
else bpmnError("GENERIC_ERROR", "Unexpected error occurred")
4. Usage in Camunda Modeler
- Deploy the template: Place your
my-custom-connector.json
file in the element-templates
folder of your Camunda Modeler installation
- Create a Service Task: In your BPMN diagram, add a Service Task
- Apply the template: Select the Service Task, go to the properties panel, and choose “My Custom Connector” from the template dropdown
- Configure properties: Fill in the required fields (message, endpoint, authentication token)
- Add error handling: Attach a boundary error event to your Service Task and set the error code to match your
errorExpression
(e.g., “CUSTOM_CONNECTOR_ERROR”)
5. Build and Deploy
- Build: Package your connector as a fat JAR using Maven:
mvn clean package
- Deploy:
- For Docker: Mount your JAR into the connectors runtime container
- For Spring Boot: Run as a standalone application with the
spring-boot-starter-camunda-connectors
dependency
References
This example provides a complete working setup that you can customize for your specific use case. Let me know if you need clarification on any part!