How to retrieve UserTasks with process instance variables using springboot?

I am using latest springboot apis

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

And I am trying to retrieve all tasks for a specific candidate group as follows:

SearchResponse<UserTask> response = camundaClient
				.newUserTaskSearchRequest().filter(filter -> filter
					.candidateGroup("myCandidateGroup1").state(UserTaskState.CREATED))
				.send().join();

I have couple of questions:

1- How to adjust above code to make the filter on a list of candidateGroups ?
I mean retrieve all tasks for myCandidateGroup1 or myCandidateGroup2
2- How to retrieve the list of UserTask and get process instance variables in the result?

Hi! Great questions about UserTask searching with the Camunda 8 Spring Boot client. Let me address both of your questions:

Current Limitations with Java Client

Unfortunately, the current Java client’s newUserTaskSearchRequest() API doesn’t fully support:

  1. Filtering by multiple candidate groups in a single request
  2. Filtering by process instance variables

These features are available in the Tasklist REST API v1 but haven’t been fully implemented in the Java client yet, especially for the version you’re using (8.9.0-SNAPSHOT).

Solutions for Your Questions

1. Multiple Candidate Groups

Current Java Client Approach (Limited):
You would need to make separate requests for each candidate group and merge the results client-side:

// This requires multiple calls and client-side merging
List<UserTask> allTasks = new ArrayList<>();

// Call for first group
SearchResponse<UserTask> response1 = camundaClient
    .newUserTaskSearchRequest()
    .filter(filter -> filter
        .candidateGroup("myCandidateGroup1")
        .state(UserTaskState.CREATED))
    .send().join();
allTasks.addAll(response1.items());

// Call for second group  
SearchResponse<UserTask> response2 = camundaClient
    .newUserTaskSearchRequest()
    .filter(filter -> filter
        .candidateGroup("myCandidateGroup2")
        .state(UserTaskState.CREATED))
    .send().join();
allTasks.addAll(response2.items());

2. Process Instance Variables

The Java client doesn’t currently support filtering by process instance variables in the UserTask search.

Recommended Solution: Use Tasklist REST API v1

For both requirements, I recommend calling the Tasklist REST API v1 directly from your Spring Boot application:

@Service
public class TaskService {
    
    @Autowired
    private WebClient webClient;
    
    public List<UserTask> searchTasksWithVariables(List<String> candidateGroups, 
                                                  Map<String, String> variables) {
        
        // Build the request body
        Map<String, Object> requestBody = new HashMap<>();
        requestBody.put("candidateGroups", candidateGroups);
        requestBody.put("state", "CREATED");
        requestBody.put("pageSize", 50);
        
        // Add variable filters
        if (variables != null && !variables.isEmpty()) {
            List<Map<String, String>> taskVariables = variables.entrySet().stream()
                .map(entry -> Map.of(
                    "name", entry.getKey(),
                    "value", "\"" + entry.getValue() + "\"", // Note: values need to be JSON-escaped
                    "operator", "eq"
                ))
                .collect(Collectors.toList());
            requestBody.put("taskVariables", taskVariables);
        }
        
        // Make the REST call
        return webClient.post()
            .uri("/v1/tasks/search")
            .contentType(MediaType.APPLICATION_JSON)
            .bodyValue(requestBody)
            .retrieve()
            .bodyToMono(TaskSearchResponse.class)
            .map(response -> response.getItems())
            .block();
    }
}

Usage example:

List<String> groups = Arrays.asList("myCandidateGroup1", "myCandidateGroup2");
Map<String, String> variables = Map.of(
    "customerId", "12345",
    "priority", "HIGH"
);

List<UserTask> tasks = taskService.searchTasksWithVariables(groups, variables);

Configuration

You’ll need to configure a WebClient for Tasklist API:

@Configuration
public class TasklistConfig {
    
    @Bean
    public WebClient tasklistWebClient() {
        return WebClient.builder()
            .baseUrl("https://your-tasklist-url") // Your Tasklist endpoint
            .defaultHeader("Authorization", "Bearer " + getAccessToken())
            .build();
    }
}

Important Notes

  • Multiple candidate groups: Supported in Tasklist REST API v1 since Camunda 8.5
  • Variable filtering: Only works with small/truncated variables (large variables are not supported)
  • API Evolution: The Java client APIs are evolving in Camunda 8.8+ with method name changes (newUserTaskSearchRequest), but full feature parity is still in development

References

This approach gives you the full filtering capabilities you need while we wait for the Java client to catch up with these features!

@zee-bot is it possible to retrieve all variables when using Tasklist REST API v1 ?

Great follow-up question! No, you cannot retrieve all variables automatically in the TaskSearchResponse. Here’s how variable retrieval works with Tasklist REST API v1:

Variable Retrieval Options

1. Include Specific Variables in Search Response

You can only get variables that you explicitly specify in the search request using the includeVariables field:

Map<String, Object> requestBody = new HashMap<>();
requestBody.put("candidateGroups", Arrays.asList("group1", "group2"));
requestBody.put("state", "CREATED");

// Specify which variables to include in the response
List<Map<String, Object>> includeVariables = Arrays.asList(
    Map.of("name", "customerId"),
    Map.of("name", "priority"),
    Map.of("name", "amount")
);
requestBody.put("includeVariables", includeVariables);

The response will then include these variables in the variables field of each task.

2. Get All Variables for Specific Tasks (Recommended Approach)

For retrieving all variables of specific tasks, use the separate endpoint:

@Service
public class TaskService {
    
    public List<TaskWithAllVariables> getTasksWithAllVariables(
            List<String> candidateGroups) {
        
        // Step 1: Search for tasks
        List<UserTask> tasks = searchTasks(candidateGroups);
        
        // Step 2: Get all variables for each task
        return tasks.stream()
            .map(task -> {
                List<Variable> variables = getAllVariablesForTask(task.getId());
                return new TaskWithAllVariables(task, variables);
            })
            .collect(Collectors.toList());
    }
    
    private List<Variable> getAllVariablesForTask(String taskId) {
        // Call POST /v1/tasks/{taskId}/variables/search with empty body
        return webClient.post()
            .uri("/v1/tasks/{taskId}/variables/search", taskId)
            .contentType(MediaType.APPLICATION_JSON)
            .bodyValue(Map.of()) // Empty body returns all variables
            .retrieve()
            .bodyToMono(VariablesSearchResponse.class)
            .map(response -> response.getItems())
            .block();
    }
}

3. Complete Example with All Variables

Here’s a complete working example:

@Service
public class EnhancedTaskService {
    
    @Autowired
    private WebClient tasklistWebClient;
    
    public List<TaskWithVariables> searchTasksWithAllVariables(
            List<String> candidateGroups, 
            Map<String, String> filterVariables) {
        
        // Step 1: Search tasks with variable filters
        Map<String, Object> searchRequest = buildSearchRequest(candidateGroups, filterVariables);
        
        TaskSearchResponse searchResponse = tasklistWebClient.post()
            .uri("/v1/tasks/search")
            .contentType(MediaType.APPLICATION_JSON)
            .bodyValue(searchRequest)
            .retrieve()
            .bodyToMono(TaskSearchResponse.class)
            .block();
        
        // Step 2: Get all variables for each found task
        return searchResponse.getItems().stream()
            .map(task -> {
                List<Variable> allVariables = getAllTaskVariables(task.getId());
                return new TaskWithVariables(task, allVariables);
            })
            .collect(Collectors.toList());
    }
    
    private List<Variable> getAllTaskVariables(String taskId) {
        VariablesSearchResponse response = tasklistWebClient.post()
            .uri("/v1/tasks/{taskId}/variables/search", taskId)
            .contentType(MediaType.APPLICATION_JSON)
            .bodyValue(Map.of()) // Empty request = all variables
            .retrieve()
            .bodyToMono(VariablesSearchResponse.class)
            .block();
        
        return response.getItems();
    }
    
    private Map<String, Object> buildSearchRequest(List<String> candidateGroups, 
                                                  Map<String, String> filterVariables) {
        Map<String, Object> request = new HashMap<>();
        request.put("candidateGroups", candidateGroups);
        request.put("state", "CREATED");
        request.put("pageSize", 50);
        
        // Add variable filters (for filtering, not retrieval)
        if (filterVariables != null && !filterVariables.isEmpty()) {
            List<Map<String, String>> taskVariables = filterVariables.entrySet().stream()
                .map(entry -> Map.of(
                    "name", entry.getKey(),
                    "value", "\"" + entry.getValue() + "\"",
                    "operator", "eq"
                ))
                .collect(Collectors.toList());
            request.put("taskVariables", taskVariables);
        }
        
        return request;
    }
}

// Helper classes
public class TaskWithVariables {
    private UserTask task;
    private List<Variable> variables;
    
    // constructors, getters, setters
}

Important Considerations

Performance Impact

  • Multiple API calls: Getting all variables requires one additional API call per task
  • Consider pagination: If you have many tasks, implement proper pagination
  • Batch processing: Consider processing tasks in batches to avoid overwhelming the API

Variable Size Limitations

  • Large variables: Variables with large content may be truncated in responses
  • Use includeVariables: You can specify alwaysReturnFullValue: true for specific variables if needed

Alternative Approach for Better Performance

If you only need specific variables frequently, consider including them directly in the search:

// Include commonly needed variables in the search response
List<Map<String, Object>> includeVariables = Arrays.asList(
    Map.of("name", "customerId", "alwaysReturnFullValue", true),
    Map.of("name", "priority", "alwaysReturnFullValue", true),
    Map.of("name", "processData", "alwaysReturnFullValue", false) // Allow truncation
);
requestBody.put("includeVariables", includeVariables);

References

So the pattern is: search for tasks first, then fetch all variables per task using the dedicated variables endpoint!

Hi @devmsaleh,

Filtering by multiple candidate groups should be
possible as follows using advanced filter with in operator

    .candidateGroup(group -> 
        group.in(Arrays.asList("groupA", "groupB", "groupC"))
    );

The Orchestration Cluster API still doesn’t support returning variables when querying tasks.
Currently, the only way to include variables in a single API call is by using the Tasklist API.

Excellent addition! Thank you for sharing that approach. You’re absolutely right about the in operator for multiple candidate groups.

Updated Solution Summary

Based on the community input, here are your options:

Option 1: Java Client with in Operator (Recommended for Multiple Groups)

UserTaskFilter filter = new UserTaskFilterImpl()
    .candidateGroup(group -> 
        group.in(Arrays.asList("myCandidateGroup1", "myCandidateGroup2"))
    )
    .state(UserTaskState.CREATED);

SearchResponse<UserTask> response = camundaClient
    .newUserTaskSearchRequest()
    .filter(filter)
    .send().join();

This is much cleaner than making multiple API calls and merging results!

Option 2: Tasklist REST API v1 (For Variables + Multiple Groups)

As confirmed by the community member, the Orchestration Cluster API doesn’t support returning variables when querying tasks. So for your second requirement (process instance variables), you’ll still need the Tasklist REST API approach I outlined earlier.

Final Recommendation

For your specific use case:

  1. Multiple candidate groups only: Use the Java client with in operator (Option 1)
  2. Multiple candidate groups + process instance variables: Use Tasklist REST API v1 (Option 2)

The Java client approach is definitely preferable when you don’t need variable data, as it keeps everything in the same API and is more type-safe.

Thanks to the community member for the clarification on the in operator - that’s a much better solution for the multiple candidate groups requirement!

References