How to Dynamically Propagate Multiple Error Codes from Java Backend to BPMN in Custom Connector?

I am working with Camunda 8 Custom Connectors (Connector SDK).

I want to handle errors in BPMN using boundary error events.
Currently, the element template requires me to define errorExpression for each error.

But my problem is:

  • In my backend Java connector, I may have many different errors (e.g., DB error, server down, timeout, invalid data).

Hi there! :wave:

Great question about handling multiple error codes dynamically in Camunda 8 Custom Connectors! You’re right that predefining every possible error in the element template isn’t practical when you have many different error types.

Here’s how you can implement dynamic error propagation from your Java backend to BPMN boundary error events:

1. Throwing Dynamic Errors in Your Java Connector

In your custom connector, throw ConnectorException with specific error codes based on your business logic:

// Different error scenarios
if (databaseConnectionFailed) {
  throw new ConnectorException("DB_ERROR", "Database connection failed");
} else if (serverTimeout) {
  throw new ConnectorException("TIMEOUT_ERROR", "Server timeout occurred");
} else if (invalidData) {
  throw new ConnectorException("VALIDATION_ERROR", "Invalid input data");
} else if (serverDown) {
  throw new ConnectorException("SERVER_DOWN", "External server unavailable");
}

2. Configure a Dynamic Error Expression in Your Element Template

Instead of predefining each error, create a single flexible error expression in your element template:

{
  "label": "Error Expression",
  "description": "Expression to define BPMN Errors to throw",
  "group": "errors",
  "type": "Text",
  "feel": "required",
  "binding": {
    "type": "zeebe:taskHeader",
    "key": "errorExpression"
  }
}

3. Write a Dynamic Error Expression

Use a FEEL expression that can handle any error code dynamically:

if is defined(error) then bpmnError(error.code, error.message) else null

Or for more sophisticated mapping:

if error.code = "DB_ERROR" then bpmnError("DATABASE_FAILURE", error.message)
else if error.code = "TIMEOUT_ERROR" then bpmnError("TIMEOUT", error.message)  
else if error.code = "VALIDATION_ERROR" then bpmnError("INVALID_DATA", error.message)
else if error.code = "SERVER_DOWN" then bpmnError("SERVICE_UNAVAILABLE", error.message)
else bpmnError("GENERIC_ERROR", error.message)

4. Handle Errors in Your BPMN Model

Attach boundary error events to your connector task with the corresponding error codes:

  • Boundary event with error code DATABASE_FAILURE
  • Boundary event with error code TIMEOUT
  • Boundary event with error code INVALID_DATA
  • Boundary event with error code SERVICE_UNAVAILABLE
  • Generic boundary event with error code GENERIC_ERROR (catch-all)

5. Pass Additional Error Details (Optional)

You can also pass structured error information as the third parameter:

bpmnError(error.code, error.message, error.details)

This approach gives you:

  • :white_check_mark: Dynamic error handling without predefined templates
  • :white_check_mark: Flexible error mapping from technical to business errors
  • :white_check_mark: Structured error information propagation
  • :white_check_mark: Maintainable BPMN models with clear error boundaries

References:

Does this approach solve your use case? Let me know if you need help with any specific part of the implementation!

my error expression i paste below my question is here i added errorcode data and error message data this data i want to get through backend application not inline expression
if error.code = “400” then bpmnError(“400”, “Got a 400”,{ErrorCode:“TPLMPPERR014”, ErrorMessage:“Error Retrieving the process list for the case”})
else if error.code = “404” then bpmnError(“404”, “Got a 404”, {ErrorCode:“404 error”, ErrorMessage:“msg2”})
else if error.code = “500” then bpmnError(“500”, “Got a 500”,{ErrorCode:“500 error”, ErrorMessage:“msg3”})
else null

Perfect! I understand now - you want to get the ErrorCode and ErrorMessage dynamically from your Java backend instead of hardcoding them in the FEEL expression.

Here’s how you can achieve this:

1. In Your Java Backend Connector

You need to structure your error data so it can be accessed in the error expression. While the documentation doesn’t explicitly show the ConnectorException constructor with additional variables, you can structure your error response to include the custom error details.

One approach is to include the error details in the error message as JSON, or use a structured approach:

// Option 1: Include structured data in the message
String errorDetails = "{\"ErrorCode\":\"TPLMPPERR014\", \"ErrorMessage\":\"Error Retrieving the process list for the case\"}";
throw new ConnectorException("400", "Bad Request: " + errorDetails);

// Option 2: If your connector supports it, you might be able to set additional context
// (This depends on your specific connector implementation)

2. Modified Error Expression

Instead of hardcoding the error details, you can extract them from the error object:

if error.code = "400" then 
  bpmnError("400", "Got a 400", {
    ErrorCode: error.variables.response.body.ErrorCode, 
    ErrorMessage: error.variables.response.body.ErrorMessage
  })
else if error.code = "404" then 
  bpmnError("404", "Got a 404", {
    ErrorCode: error.variables.response.body.ErrorCode, 
    ErrorMessage: error.variables.response.body.ErrorMessage
  })
else if error.code = "500" then 
  bpmnError("500", "Got a 500", {
    ErrorCode: error.variables.response.body.ErrorCode, 
    ErrorMessage: error.variables.response.body.ErrorMessage
  })
else null

3. Alternative Approach - Generic Dynamic Expression

For maximum flexibility, you could create a completely dynamic expression:

if is defined(error) then 
  bpmnError(
    error.code, 
    "HTTP Error " + error.code,
    {
      ErrorCode: if is defined(error.variables.response.body.ErrorCode) 
                 then error.variables.response.body.ErrorCode 
                 else "GENERIC_ERROR_" + error.code,
      ErrorMessage: if is defined(error.variables.response.body.ErrorMessage) 
                    then error.variables.response.body.ErrorMessage 
                    else error.message
    }
  )
else null

4. In Your Java Backend - Enhanced Approach

If you need more control over the error structure, you might need to implement a custom error response format. The exact implementation depends on how your connector handles the error response structure.

Important Note: The documentation doesn’t explicitly show how to pass additional variables directly through the ConnectorException constructor. The error structure with variables.response.body is typically seen in HTTP-based connectors.

For custom connectors, you might need to:

  1. Check the Connector SDK JavaDoc for additional constructor parameters
  2. Implement a custom error handling mechanism in your connector
  3. Or structure your error message to include the dynamic data that can be parsed in the FEEL expression

Would you like me to help you explore any of these approaches further, or do you have access to the specific ConnectorException constructor signatures in your SDK version?

References:

catch (HttpClientErrorException e) {
if (e.getStatusCode().is4xxClientError()) {
// Handle 4xx errors specifically
if (e.getStatusCode().equals(HttpStatus.NOT_FOUND)) {
System.err.println("Resource not found: " + e.getResponseBodyAsString());
throw new ConnectorException(“404”, “Got a 404”);
} else if (e.getStatusCode().equals(HttpStatus.BAD_REQUEST)) {
System.err.println("Bad request: " + e.getResponseBodyAsString());
throw new ConnectorException(“400”, “Got a 400”);
}

    else if (e.getStatusCode().value()==401) {
      System.err.println("Bad request: " + e.getResponseBodyAsString());
      throw new ConnectorException("401", "Got a 401");
    }

  }
  throw e; // Re-throw if not specifically handled
} catch (HttpServerErrorException e) {
  if (e.getStatusCode().is5xxServerError()) {
    // Handle 5xx errors
    System.err.println("Server error: " + e.getResponseBodyAsString());
    throw new ConnectorException("500", "Got a 500");
  }
  System.out.println(e.getStatusCode()+"statuscode");
  throw e; // Re-throw if not specifically handled

}

}
}
i handle this way correct me anything wrong

Your error handling approach looks good! Here are some suggestions to improve it and make the error data available dynamically in your BPMN error expression:

Current Code Analysis :white_check_mark:

Your basic error handling structure is correct, but you can enhance it to pass the dynamic error details you want.

Enhanced Version with Dynamic Error Data

catch (HttpClientErrorException e) {
    if (e.getStatusCode().is4xxClientError()) {
        String responseBody = e.getResponseBodyAsString();
        
        if (e.getStatusCode().equals(HttpStatus.NOT_FOUND)) {
            System.err.println("Resource not found: " + responseBody);
            // Include the response body so it can be accessed in errorExpression
            throw new ConnectorException("404", "Got a 404 - " + responseBody);
            
        } else if (e.getStatusCode().equals(HttpStatus.BAD_REQUEST)) {
            System.err.println("Bad request: " + responseBody);
            throw new ConnectorException("400", "Got a 400 - " + responseBody);
            
        } else if (e.getStatusCode().value() == 401) {
            System.err.println("Unauthorized: " + responseBody);
            throw new ConnectorException("401", "Got a 401 - " + responseBody);
        }
        // Handle other 4xx errors generically
        else {
            throw new ConnectorException(String.valueOf(e.getStatusCode().value()), 
                "Client error " + e.getStatusCode().value() + " - " + responseBody);
        }
    }
    throw e; // Re-throw if not specifically handled
    
} catch (HttpServerErrorException e) {
    if (e.getStatusCode().is5xxServerError()) {
        String responseBody = e.getResponseBodyAsString();
        System.err.println("Server error: " + responseBody);
        throw new ConnectorException("500", "Got a 500 - " + responseBody);
    }
    System.out.println(e.getStatusCode() + " statuscode");
    throw e; // Re-throw if not specifically handled
}

Better Approach - Parse Response Body for Structured Errors

If your API returns structured error responses (JSON), you can parse them:

catch (HttpClientErrorException e) {
    String responseBody = e.getResponseBodyAsString();
    String errorCode = extractErrorCodeFromResponse(responseBody); // Your custom method
    String errorMessage = extractErrorMessageFromResponse(responseBody); // Your custom method
    
    if (e.getStatusCode().equals(HttpStatus.NOT_FOUND)) {
        throw new ConnectorException("404", 
            "Resource not found - ErrorCode: " + errorCode + ", Message: " + errorMessage);
            
    } else if (e.getStatusCode().equals(HttpStatus.BAD_REQUEST)) {
        throw new ConnectorException("400", 
            "Bad request - ErrorCode: " + errorCode + ", Message: " + errorMessage);
            
    } else if (e.getStatusCode().value() == 401) {
        throw new ConnectorException("401", 
            "Unauthorized - ErrorCode: " + errorCode + ", Message: " + errorMessage);
    }
    // ... other cases
}

// Helper methods to parse your API response
private String extractErrorCodeFromResponse(String responseBody) {
    // Parse JSON response and extract your custom error code
    // e.g., "TPLMPPERR014"
    return parseErrorCode(responseBody);
}

private String extractErrorMessageFromResponse(String responseBody) {
    // Parse JSON response and extract your custom error message
    return parseErrorMessage(responseBody);
}

Updated Error Expression

Then in your BPMN error expression, you can extract the dynamic data from the error message:

if error.code = "400" then 
  bpmnError("400", "Got a 400", {
    ErrorCode: substring(error.message, index of(error.message, "ErrorCode: ") + 11, index of(error.message, ", Message:")),
    ErrorMessage: substring(error.message, index of(error.message, "Message: ") + 9)
  })
else if error.code = "404" then 
  bpmnError("404", "Got a 404", {
    ErrorCode: substring(error.message, index of(error.message, "ErrorCode: ") + 11, index of(error.message, ", Message:")),
    ErrorMessage: substring(error.message, index of(error.message, "Message: ") + 9)
  })
else if error.code = "500" then 
  bpmnError("500", "Got a 500", {
    ErrorCode: substring(error.message, index of(error.message, "ErrorCode: ") + 11, index of(error.message, ", Message:")),
    ErrorMessage: substring(error.message, index of(error.message, "Message: ") + 9)
  })
else null

Improvements to Your Current Code:

  1. Add response body to error message so you can access the API’s error details
  2. Handle other 4xx errors generically instead of just the specific ones
  3. Extract structured error information from the API response if it’s JSON
  4. Consider logging levels - use a proper logger instead of System.err.println
  5. Add null checks for response body

Alternative: JSON-based Approach

If you want cleaner parsing in FEEL, structure your error message as JSON:

// In your Java code
String errorJson = String.format(
    "{\"httpCode\":\"%s\", \"errorCode\":\"%s\", \"errorMessage\":\"%s\"}", 
    e.getStatusCode().value(), 
    extractedErrorCode, 
    extractedErrorMessage
);
throw new ConnectorException(String.valueOf(e.getStatusCode().value()), errorJson);

Then parse it in FEEL using JSON functions.

Does this help you achieve the dynamic error handling you’re looking for?