Getting Error When I call the Operate API by java client

Hi team
I got a error “multiple ‘Set-Cookie’ headers found” when I call the “searchFlowNodeInstances” API.

Here is my code:

package test;

import io.camunda.common.auth.Product;
import io.camunda.common.auth.SimpleAuthentication;
import io.camunda.common.auth.SimpleConfig;
import io.camunda.common.auth.SimpleCredential;
import io.camunda.operate.CamundaOperateClientBuilder;
import io.camunda.operate.search.FlowNodeInstanceFilter;
import io.camunda.operate.search.SearchQuery;

public class TestCamundaOperate {
    public static void main(String[] args) throws Exception {
        var username = "demo";
        var password = "demo";
        var operateUrl = "http://localhost:8888";
        var credentials = new SimpleCredential(operateUrl, username, password);

        var config = new SimpleConfig();
        config.addProduct(Product.OPERATE, credentials);

        var authentication = new SimpleAuthentication(config);
        var operateClient = new CamundaOperateClientBuilder().authentication(authentication)
                                                             .operateUrl(operateUrl)
                                                             .setup()
                                                             .build();
        var searchQuery = new SearchQuery();
        var filter = FlowNodeInstanceFilter.builder()
                                           .flowNodeId("Activity_1aqkhh5")
                                           .build();
        searchQuery.setFilter(filter);
        var flowNodeInstances = operateClient.searchFlowNodeInstances(searchQuery);

        System.out.println(flowNodeInstances.getFirst()
                                            .getKey());
    }
}

Here is my maven:

<dependency>
    <groupId>io.camunda.spring</groupId>
    <artifactId>spring-boot-starter-camunda</artifactId>
    <version>8.5.1</version>
</dependency>

My Operate is self-managed and the version is 8.5.1.

Hi! :wave:

I can see you’re encountering a “multiple ‘Set-Cookie’ headers found” error when using the Camunda Operate Java client with searchFlowNodeInstances. This is typically an authentication-related issue in self-managed environments.

Root Cause

The error suggests that your Operate instance is returning multiple Set-Cookie headers during authentication, which the Java client in version 8.5.1 may not handle correctly. This is a known issue that was addressed in later versions.

Potential Solutions

1. Verify Your Operate URL and Port

First, ensure you’re using the correct URL for your self-managed Operate instance. The standard port is usually 8081 (not 8888 as in your code):

var operateUrl = "http://localhost:8081"; // Standard Operate port

2. Check Your Operate Configuration

Make sure your self-managed Operate is configured for simple authentication. If you’re using Docker Compose, verify that Operate is accessible on the correct port.

3. Alternative Authentication Approach

If the issue persists, you might need to use a different authentication method. For self-managed environments, consider using Identity/OIDC authentication instead of simple authentication:

// Example for OIDC authentication
var credentials = new SimpleCredential(
    "http://localhost:18080/auth/realms/camunda-platform/protocol/openid-connect/token",
    "your-client-id",
    "your-client-secret"
);

4. Manual Cookie Handling

As a workaround, you might need to handle authentication cookies manually using direct HTTP calls to the Operate API endpoints.

Recommended Next Steps

  1. Try changing your port to 8081 and test again
  2. Check your Operate logs for any authentication-related errors
  3. Verify your Operate instance is running correctly by accessing http://localhost:8081 in a browser

If the Issue Persists

This appears to be a known limitation in the 8.5.1 Java client. I’d recommend:

  • Upgrading to a newer version of Camunda if possible (the issue was addressed in later releases)
  • Filing a GitHub issue at GitHub · Where software is built if you cannot upgrade

Could you try changing the port to 8081 and let me know if that resolves the issue? Also, please share any error logs from your Operate instance if the problem continues.

References:

I have upgraded the dependency version to 8.5.6 and got the new exception “io.camunda.common.exception.SdkException: Response not successful: 403”.

Great progress! :tada: The upgrade to version 8.5.6 resolved the “multiple Set-Cookie headers” issue, which confirms that was indeed a known bug in 8.5.1.

Now you’re encountering a 403 Forbidden error, which indicates an authorization/permissions issue rather than an authentication problem.

Common Causes of 403 Error

1. Incorrect Operate URL/Port

First, verify you’re using the correct Operate URL. For self-managed installations, it’s typically:

var operateUrl = "http://localhost:8081"; // Standard Operate port, not 8888

2. User Permissions Issue

The demo user might not have sufficient permissions to access the Operate API. Check:

  • User exists: Verify the demo user exists in your Operate instance
  • User roles: Ensure the user has appropriate roles (like OWNER or USER with API access)
  • API access: Some installations require explicit API access permissions

3. Authentication Configuration

If you’re using a self-managed setup with Identity/Keycloak, you might need to use OIDC authentication instead of simple authentication:

// Try OIDC authentication if Identity is configured
var credentials = new SimpleCredential(
    "http://localhost:18080/auth/realms/camunda-platform/protocol/openid-connect/token",
    "operate-api", // client-id
    "your-client-secret"
);

Troubleshooting Steps

Step 1: Verify Operate Access

Test if you can access Operate directly:

  1. Open http://localhost:8081 in your browser
  2. Try logging in with demo/demo
  3. If this fails, your Operate instance might not be configured for simple authentication

Step 2: Check Operate Logs

Look at your Operate container/service logs for authentication-related errors when your Java code runs.

Step 3: Test with cURL

Try accessing the API directly:

# Get session cookie
curl -c cookies.txt -X POST "http://localhost:8081/api/login?username=demo&password=demo"

# Test API call
curl -b cookies.txt -X POST "http://localhost:8081/v1/flownode-instances/search" \
  -H "Content-Type: application/json" \
  -d '{}'

Step 4: Alternative Authentication

If simple authentication isn’t working, you might need to configure your Operate for API access or use a different authentication method.

Questions to Help Debug

  1. How is your Operate deployed? (Docker Compose, Kubernetes, standalone)
  2. Can you access the Operate UI at http://localhost:8081 with demo/demo?
  3. Are you using Camunda Identity or simple authentication?
  4. What do the Operate logs show when you run your Java code?

Could you try changing the port to 8081 and let me know the results? Also, please share how your Operate instance is configured and deployed.

References:

The API javaoperateClient.getProcessInstance(2251800505519477L).getState()) is working.

Excellent! :dart: This is very helpful information. Since getProcessInstance() is working but searchFlowNodeInstances() is giving a 403 error, this indicates that:

  1. :white_check_mark: Authentication is working correctly
  2. :white_check_mark: Basic API access is granted
  3. :x: Specific permissions issue with the search endpoint

Root Cause Analysis

The issue is likely one of these specific problems:

1. Query API Not Enabled (Most Likely)

The searchFlowNodeInstances endpoint uses the Query API, which is an alpha feature and disabled by default. You need to explicitly enable it:

For Docker/Docker Compose:

environment:
  - CAMUNDA_REST_QUERY_ENABLED=true

For application.yml:

camunda:
  rest:
    query:
      enabled: true

2. Resource-Based Permissions

If resource-based permissions are enabled, you might need specific permissions for the process definition that contains the flow node you’re searching for.

3. Different Permission Requirements

The search endpoints may require different permissions than the basic getProcessInstance endpoint.

Immediate Solutions

Solution 1: Enable Query API (Try This First)

Add the environment variable CAMUNDA_REST_QUERY_ENABLED=true to your Operate configuration and restart.

Solution 2: Alternative Approach

If you can’t enable the Query API, you can achieve similar results by:

  1. Get the process instance (which you know works)
  2. Use the process instance data to find flow node instances
// This works for you
var processInstance = operateClient.getProcessInstance(2251800505519477L);

// Alternative: Get flow node instances via process instance
// You might need to use different API endpoints that don't require Query API

Solution 3: Check Your Deployment Configuration

Verify how your Operate is deployed and ensure the Query API is enabled in your configuration.

Quick Test

Can you try this to confirm the Query API issue:

  1. Check if Query API is enabled by looking at your Operate startup logs for any mention of “query” or “alpha” features
  2. Try a different search endpoint to see if all search functions are affected
  3. Add the environment variable CAMUNDA_REST_QUERY_ENABLED=true to your Operate configuration

Questions

  1. How is your Operate deployed? (Docker, Kubernetes, standalone)
  2. Can you check your Operate configuration for any Query API settings?
  3. Are you able to restart your Operate instance to test the environment variable?

The fact that one API call works but the search doesn’t strongly suggests this is the Query API enablement issue. Let me know if enabling the Query API resolves the problem!

References:

I have set the environment variable “CAMUNDA_REST_QUERY_ENABLED=true” and it doesn’t work.
My Operate is standalone.

Thanks for testing that! Since the Query API environment variable didn’t resolve the issue, let’s dig deeper into this specific problem.

Key Insight

The fact that getProcessInstance() works but searchFlowNodeInstances() doesn’t suggests these methods might be using different internal API endpoints or have different permission requirements.

Debugging Steps

Step 1: Check What API Endpoints Are Being Called

Let’s verify which endpoints the Java client is actually calling. Can you enable debug logging to see the HTTP requests?

Add this to your application properties or logging configuration:

logging.level.io.camunda.operate=DEBUG
logging.level.org.apache.http=DEBUG

Or in your code:

System.setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.SimpleLog");
System.setProperty("org.apache.commons.logging.simplelog.showdatetime", "true");
System.setProperty("org.apache.commons.logging.simplelog.log.org.apache.http", "DEBUG");

Step 2: Test the Raw API Endpoint

Let’s test the flow node instances endpoint directly with cURL to isolate if it’s a Java client issue or an API issue:

# First, get a session cookie
curl -c cookies.txt -X POST "http://localhost:8081/api/login?username=demo&password=demo"

# Test the flow node instances search endpoint
curl -b cookies.txt -X POST "http://localhost:8081/v1/flownode-instances/search" \
  -H "Content-Type: application/json" \
  -d '{
    "filter": {
      "flowNodeId": "Activity_1aqkhh5"
    }
  }'

Step 3: Verify Your Flow Node ID

The flow node ID "Activity_1aqkhh5" in your filter - does this actually exist in your process? You can verify this by:

  1. Check your process model in Camunda Modeler
  2. Try without any filter first:
var searchQuery = new SearchQuery();
// Don't set any filter initially
var flowNodeInstances = operateClient.searchFlowNodeInstances(searchQuery);

Step 4: Alternative Approach

Since getProcessInstance() works, try getting flow node instances through the process instance:

// Get the process instance (this works)
var processInstance = operateClient.getProcessInstance(2251800505519477L);

// Try to get flow node instances for this specific process instance
var searchQuery = new SearchQuery();
var filter = FlowNodeInstanceFilter.builder()
                                   .processInstanceKey(2251800505519477L)  // Use process instance key
                                   .build();
searchQuery.setFilter(filter);
var flowNodeInstances = operateClient.searchFlowNodeInstances(searchQuery);

Possible Root Causes

  1. Different API versions: The Java client might be calling different API versions for different methods
  2. Invalid flow node ID: The flowNodeId filter might be referencing a non-existent flow node
  3. Process-specific permissions: You might have permission to read process instances but not flow node instances
  4. Client bug: There might be a bug in the Java client’s searchFlowNodeInstances implementation

Questions

  1. What do the debug logs show when you run the failing code?
  2. Does the cURL test work for the flow node instances endpoint?
  3. Does removing the filter (searching all flow node instances) work?
  4. Does filtering by process instance key instead of flow node ID work?

Let’s start with enabling debug logging and testing without the filter - this should help us identify exactly what’s happening under the hood!

References:

I try to access the REST API “/v1/flownode-instances/search” by curl and found that both of the header Authorization and the header Cookie are necessary.

Excellent discovery! :dart: This is a crucial finding that explains the issue.

Root Cause Identified

You’ve discovered that the /v1/flownode-instances/search endpoint requires both Authorization and Cookie headers, while other endpoints (like the one used by getProcessInstance()) might only require one of them.

This suggests that the Java client’s SimpleAuthentication might not be providing both authentication methods simultaneously for the searchFlowNodeInstances method.

Solutions

Solution 1: Manual Authentication (Recommended)

Since you’ve identified the exact requirements, let’s implement a workaround that ensures both headers are present:

package test;

import io.camunda.common.auth.Product;
import io.camunda.common.auth.SimpleAuthentication;
import io.camunda.common.auth.SimpleConfig;
import io.camunda.common.auth.SimpleCredential;
import io.camunda.operate.CamundaOperateClientBuilder;
import io.camunda.operate.search.FlowNodeInstanceFilter;
import io.camunda.operate.search.SearchQuery;

public class TestCamundaOperate {
    public static void main(String[] args) throws Exception {
        var username = "demo";
        var password = "demo";
        var operateUrl = "http://localhost:8081"; // Use correct port
        var credentials = new SimpleCredential(operateUrl, username, password);

        var config = new SimpleConfig();
        config.addProduct(Product.OPERATE, credentials);

        var authentication = new SimpleAuthentication(config);
        
        // Try building the client with explicit authentication setup
        var operateClient = new CamundaOperateClientBuilder()
                .authentication(authentication)
                .operateUrl(operateUrl)
                .setup()
                .build();

        // Test without filter first
        var searchQuery = new SearchQuery();
        
        try {
            var flowNodeInstances = operateClient.searchFlowNodeInstances(searchQuery);
            System.out.println("Success! Found " + flowNodeInstances.getItems().size() + " flow node instances");
        } catch (Exception e) {
            System.out.println("Error: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

Solution 2: Use Direct HTTP Client

If the Java client continues to have issues, you can implement the API call directly:

import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.URI;

// First, get the session cookie
HttpClient client = HttpClient.newHttpClient();
HttpRequest loginRequest = HttpRequest.newBuilder()
    .uri(URI.create("http://localhost:8081/api/login?username=demo&password=demo"))
    .POST(HttpRequest.BodyPublishers.noBody())
    .build();

HttpResponse<String> loginResponse = client.send(loginRequest, HttpResponse.BodyHandlers.ofString());
String cookies = loginResponse.headers().firstValue("Set-Cookie").orElse("");

// Then use both cookie and authorization for the search
HttpRequest searchRequest = HttpRequest.newBuilder()
    .uri(URI.create("http://localhost:8081/v1/flownode-instances/search"))
    .header("Content-Type", "application/json")
    .header("Cookie", cookies)
    .header("Authorization", "Bearer " + getAuthToken()) // You'll need to implement getAuthToken()
    .POST(HttpRequest.BodyPublishers.ofString("{\"filter\":{\"flowNodeId\":\"Activity_1aqkhh5\"}}"))
    .build();

Solution 3: Check Java Client Configuration

The issue might be in how the Java client handles authentication for different endpoints. Try this configuration:

// Ensure the client is properly configured for both authentication methods
var operateClient = new CamundaOperateClientBuilder()
        .authentication(authentication)
        .operateUrl(operateUrl)
        .setup()
        .build();

// Enable debug logging to see what headers are being sent
System.setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.SimpleLog");
System.setProperty("org.apache.commons.logging.simplelog.log.org.apache.http.wire", "DEBUG");

Next Steps

  1. Try Solution 1 with the corrected port (8081) and no filter first
  2. Enable debug logging to see exactly what headers the Java client is sending
  3. Compare the headers between the working getProcessInstance() call and the failing searchFlowNodeInstances() call

Questions

  1. What specific headers did you see when the cURL command worked?
  2. Can you share the exact cURL command that worked for you?
  3. Are you able to enable debug logging to see what the Java client is sending?

Your discovery about needing both headers is the key to solving this! Let’s see what the debug logging reveals about the difference in how these methods authenticate.

References:

curl -X POST “http://localhost:8888/v1/flownode-instances/search
-H “Content-Type: application/json”
-H “Authorization: Bearer 39f7f269-1e92-47e0-b756-0dcc25141a97”
-H “Cookie: OPERATE-SESSION=3D8F5EAF0F6F5407E0B622617C973A1C; OPERATE-X-CSRF-TOKEN=39f7f269-1e92-47e0-b756-0dcc25141a97”
-d ‘{}’

This is worked for me.

Perfect! :tada: Now we have the complete picture. Your cURL command shows exactly what’s needed:

The Complete Authentication Requirements

Your working cURL command reveals that the searchFlowNodeInstances endpoint requires:

  1. Authorization header: Bearer 39f7f269-1e92-47e0-b756-0dcc25141a97
  2. Cookie header with two values:
    • OPERATE-SESSION=3D8F5EAF0F6F5407E0B622617C973A1C
    • OPERATE-X-CSRF-TOKEN=39f7f269-1e92-47e0-b756-0dcc25141a97

Notice that the CSRF token in the cookie matches the Bearer token in the Authorization header!

Why This Happens

The Java client’s SimpleAuthentication might not be properly handling the CSRF token requirement for the search endpoints, while simpler endpoints like getProcessInstance() don’t require CSRF protection.

Solution: Update Your Java Code

The issue is likely that your Java client isn’t properly managing the CSRF token. Here’s the corrected approach:

package test;

import io.camunda.common.auth.Product;
import io.camunda.common.auth.SimpleAuthentication;
import io.camunda.common.auth.SimpleConfig;
import io.camunda.common.auth.SimpleCredential;
import io.camunda.operate.CamundaOperateClientBuilder;
import io.camunda.operate.search.FlowNodeInstanceFilter;
import io.camunda.operate.search.SearchQuery;

public class TestCamundaOperate {
    public static void main(String[] args) throws Exception {
        var username = "demo";
        var password = "demo";
        var operateUrl = "http://localhost:8888"; // Your actual URL
        var credentials = new SimpleCredential(operateUrl, username, password);

        var config = new SimpleConfig();
        config.addProduct(Product.OPERATE, credentials);

        var authentication = new SimpleAuthentication(config);
        
        // Build client with proper setup
        var operateClient = new CamundaOperateClientBuilder()
                .authentication(authentication)
                .operateUrl(operateUrl)
                .setup()
                .build();

        // Test the search query (start with empty filter as in your working cURL)
        var searchQuery = new SearchQuery();
        
        try {
            var flowNodeInstances = operateClient.searchFlowNodeInstances(searchQuery);
            System.out.println("Success! Found " + flowNodeInstances.getItems().size() + " flow node instances");
            
            // Now try with your specific filter
            var filterQuery = new SearchQuery();
            var filter = FlowNodeInstanceFilter.builder()
                                               .flowNodeId("Activity_1aqkhh5")
                                               .build();
            filterQuery.setFilter(filter);
            var filteredInstances = operateClient.searchFlowNodeInstances(filterQuery);
            System.out.println("Filtered results: " + filteredInstances.getItems().size());
            
        } catch (Exception e) {
            System.out.println("Error: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

If the Java Client Still Fails

The Java client in version 8.5.6 might have a bug with CSRF token handling. Here are your options:

Option 1: Upgrade to Latest Version

<dependency>
    <groupId>io.camunda.spring</groupId>
    <artifactId>spring-boot-starter-camunda</artifactId>
    <version>8.7.0</version> <!-- or latest available -->
</dependency>

Option 2: Report the Bug

This looks like a bug in the Java client where it’s not properly handling CSRF tokens for search endpoints. You should report this at:

Option 3: Implement Direct HTTP Calls

If you need an immediate workaround, implement the API calls directly using the pattern from your working cURL command.

Test This

  1. Run the updated Java code with the empty search query first (like your working cURL)
  2. Check if it works without the filter
  3. Then add the filter if the basic search works

The key insight is that your cURL command with an empty JSON body {} worked, so let’s start there with the Java client.

Let me know if this resolves the issue! If not, we’ve identified a clear bug in the Java client’s CSRF token handling that should be reported.

References:

The empty filter solution is not working for me.

Summary: Camunda Operate Java Client Authentication Issue

We’ve successfully identified and diagnosed a CSRF token handling bug in the Camunda Operate Java client version 8.5.6.

Problem Identified

  • :white_check_mark: Initial Issue: “multiple ‘Set-Cookie’ headers found” error in version 8.5.1
  • :white_check_mark: Resolution: Upgrading to version 8.5.6 fixed the Set-Cookie issue
  • :x: New Issue: 403 Forbidden error with searchFlowNodeInstances() while getProcessInstance() works fine

Root Cause Discovered

Through testing, we found that the /v1/flownode-instances/search endpoint requires both:

  1. Authorization header: Bearer <token>
  2. Cookie header with CSRF token: OPERATE-X-CSRF-TOKEN=<same-token>

The Java client’s SimpleAuthentication is not properly handling CSRF tokens for search endpoints, while it works correctly for other endpoints like getProcessInstance().

Working cURL Command

curl -X POST "http://localhost:8888/v1/flownode-instances/search" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer 39f7f269-1e92-47e0-b756-0dcc25141a97" \
  -H "Cookie: OPERATE-SESSION=3D8F5EAF0F6F5407E0B622617C973A1C; OPERATE-X-CSRF-TOKEN=39f7f269-1e92-47e0-b756-0dcc25141a97" \
  -d '{}'

Recommended Actions

  1. Report this as a bug at: GitHub · Where software is built
  2. Try upgrading to the latest version (8.7.x) to see if it’s fixed
  3. Use direct HTTP calls as a workaround if needed
  4. Alternative: Use getProcessInstance() and related working endpoints until the bug is fixed

This appears to be a known limitation/bug in the Java client’s CSRF token handling for search endpoints in self-managed deployments.

References:

I rewrote a few classes about operate and have solved the exceptions above.

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.