Hi people,
We are working on a Reltio workflow that should trigger only when a specific attribute (SCH_Organization_Classification_Level_3) of an entity is modified, the attribute is a nested attribute since we have the Organization Classification which then has 3 levels inside. However, we are facing an issue where the workflow gets triggered on any change to the entity, rather than just the relevant attribute change.
For what I originally understood in how I should implement the classes I have:
-
Validator: Ensures the Org Classification Level 3 is modified with valid transitions (e.g., from/to "Academic Health System" or "Academic Medical Center").
-
Listener: Assigns tasks to ROLE_AMC_REVIEWERS when the conditions validated above are met.
What We Have Implemented
-
We have a listener (AMCChangeAndAssignListener) that assigns tasks to a specific role when a relevant change occurs.
-
We have a validator (AMCChangeValidator) that checks whether a relevant attribute change has occurred before proceeding.
Current Behavior (Unexpected)
-
Whenever any attribute of an entity is modified, the workflow is triggered.
-
Tasks are assigned even when changes are unrelated to the expected attribute (SCH_Organization_Classification_Level_3).
What We Have Tried
-
Checking API Responses Manually:
-
Updating Listener Logic:
-
Reviewing Workflow API Documentation:
So we have the following questions:
-
How can we ensure that the workflow is only triggered when a specific attribute changes?
-
Is there a recommended way to check for an open task before creating a new one?
-
Are there any debugging tips for diagnosing workflow triggers in Reltio?
Any insights or best practices would be greatly appreciated!
Thanks in advance for any guidance!
PD: since all the changes were triggering the flow and assigning the new role, which was being done in my Listener, I tried adding all the logic there like behind but still no luck, it gets triggered every time I change any attribute and :
package com.reltio.cliniciannexus.workflow.listeners.amc;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.reltio.workflow.api.WorkflowService;
import com.reltio.workflow.api.listeners.WorkflowTaskListener;
import com.reltio.workflow.api.rest.ReltioApi;
import com.reltio.workflow.api.tasks.Task;
import com.reltio.workflow.api.tasks.TaskService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
public class AMCChangeAndAssignListener implements WorkflowTaskListener {
private static final Logger logger = LoggerFactory.getLogger(AMCChangeAndAssignListener.class);
@WorkflowService
private TaskService taskService;
@WorkflowService
private ReltioApi reltioApi;
private static final String REVIEWER_ROLE = "ROLE_AMC_REVIEWERS";
private static final String ATTRIBUTE_URI = "configuration/entityTypes/HCO/attributes/SCH_Organization_Classification/attributes/SCH_Organization_Classification_Level_3";
private static final List<String> AMC_VALUES = List.of("Academic Health System", "Academic Medical Center");
@Override
public void notify(Task task) {
logger.info("AMCChangeAndAssignListener triggered for Task ID: {}", task.getId());
// Ensure the task has associated object URIs
if (task.getObjectUris() == null || task.getObjectUris().isEmpty()) {
logger.warn("Task ID {} does not have associated object URIs. Skipping assignment.", task.getId());
return;
}
// Get the first entity URI from the task
String entityUri = task.getObjectUris().stream()
.filter(uri -> uri.startsWith("entities/"))
.findFirst()
.orElse(null);
if (entityUri == null) {
logger.warn("No entity found for Task ID {}. Skipping assignment.", task.getId());
return;
}
try {
// 1. CHECK IF RELEVANT CHANGE EXISTS
if (!isRelevantChange(entityUri, task)) {
logger.info("No relevant changes detected for entity {}. Skipping task creation.", entityUri);
return;
}
// 2. CHECK FOR EXISTING OPEN TASKS
if (hasOpenTask(entityUri, task)) {
logger.info("An open task already exists for entity {}. Skipping task creation.", entityUri);
return;
}
// 3. ASSIGN TASK TO REVIEWER ROLE
taskService.assignTask(task, REVIEWER_ROLE);
taskService.setVariable(task.getId(), "reviewAssigned", true);
logger.info("Successfully assigned Task ID {} to role: {}", task.getId(), REVIEWER_ROLE);
} catch (Exception e) {
logger.error("Error while processing Task ID {}: {}", task.getId(), e.getMessage(), e);
}
}
/**
* Check if an open task already exists for this entity.
*/
private boolean hasOpenTask(String entityUri, Task task) {
try {
String response = reltioApi.invokeApi(
task.getAccessToken(),
task.getEnvironmentUrl() + "/workflow/" + task.getTenantId() + "/tasks",
"POST",
"{ \"objectURIs\": [\"" + entityUri + "\"], \"state\": \"valid\" }"
);
JsonNode tasksNode = new ObjectMapper().readTree(response).get("data");
return tasksNode != null && !tasksNode.isEmpty();
} catch (Exception e) {
logger.error("Error checking existing tasks for entity {}: {}", entityUri, e.getMessage());
return false;
}
}
/**
* Check if the change request contains a relevant change.
*/
private boolean isRelevantChange(String entityUri, Task task) {
try {
// Fetch change requests for the entity
String changeRequestsResponse = reltioApi.invokeApi(
task.getAccessToken(),
task.getEnvironmentUrl() + "/changeRequests?filter=equals(objectURI, '" + entityUri + "')&max=10&offset=0&select=uri,updatedTime",
"GET",
null
);
JsonNode changeRequests = new ObjectMapper().readTree(changeRequestsResponse);
if (changeRequests == null || !changeRequests.isArray()) return false;
for (JsonNode changeRequest : changeRequests) {
String changeRequestUri = changeRequest.get("uri").asText();
String changeRequestDetailsResponse = reltioApi.invokeApi(
task.getAccessToken(),
task.getEnvironmentUrl() + "/" + changeRequestUri,
"GET",
null
);
JsonNode changeDetails = new ObjectMapper().readTree(changeRequestDetailsResponse).get("changes");
if (changeDetails != null && changeDetails.isObject()) {
for (JsonNode entityChange : changeDetails.get(entityUri)) {
String attributePath = entityChange.get("attributePath").asText();
String newValue = entityChange.at("/newValue/value").asText();
if (ATTRIBUTE_URI.equals(attributePath) && AMC_VALUES.contains(newValue)) {
logger.info("Relevant change detected: attributePath={}, newValue={}", attributePath, newValue);
return true;
}
}
}
}
} catch (Exception e) {
logger.error("Error processing entity URI: {}", entityUri, e);
}
return false;
}
}
Here is how the workflow runs for any change I do on any attribute from the entity

------------------------------
Luis B.
------------------------------