The class below has a method that runs on an After Update Trigger for a custom object. It attempts to automatically Reject the approval process when a picklist field (Work_Sub_Status__c) on its Target Object gets a certain value ("Cancelled"). It compiles just fine, but when I test it, it throws an INVALID_CROSS_REFERENCE_KEY error when calling the Approval.process() method. I worked down a list of possible reasons why this could be:

  1. Was I using the wrong Id type for setWorkItemId()? No, the Id is definitely that of a ProcessInstanceWorkItem, and when I test and look at the item in my org, it is an open, actionable work item that I can choose to Approve or Reject manually.

  2. When I make the query for a ProcessInstanceWorkItem, am I retrieving the wrong one? No - when testing, there is only one ProcessInstanceWorkItem to query for on the record's ProcessInstance, and as above, it is actionable. There is no way that the query could get the wrong item.

  3. Is there some other field that is the root cause of this issue? This doesn't seem possible, as the Id in setWorkitemId() is the only Id field that I am providing the Approval.process() method.

I have tried tons of different methods to fix this, mostly from online recommendations. At this point I would be extremely surprised if you could dig up another StackOverflow or Salesforce Developer Forum post that is suggesting something that I have not already tried (and that did not work).

The code: (please ignore the sloppy queries and printing -- most of this was for debugging, and I will clean it up once it works)

public class CreditApprovalAfterUpdateHandler {
    public static void run(Map<Id,Credit_Approval__c> caMap){
        if (caMap.isEmpty()) return;

        ProcessInstance pi;
        ProcessInstanceWorkItem piWorkItem;

        List<Approval.ProcessWorkitemRequest> pWorkItemRequestList = new List<Approval.ProcessWorkitemRequest>();

        for (Id caId : caMap.keyset()){
            if (caMap.get(caId).Work_Sub_Status__c == 'Cancelled'){
                pi = [SELECT Id, Status FROM ProcessInstance WHERE TargetObjectId = :caId AND Status = 'Pending' LIMIT 1];
                System.debug('Process Instance: ' + pi);
                List<ProcessInstanceWorkItem> lol = [SELECT Id FROM ProcessInstanceWOrkItem WHERE ProcessInstanceId = :pi.Id];

                piWorkItem = [SELECT Id, ProcessInstanceId, OriginalActorId, ActorId, IsDeleted, CreatedDate, CreatedById, SystemModstamp
                                                FROM ProcessInstanceWorkItem WHERE ProcessInstanceId = :pi.Id ORDER BY SystemModStamp DESC LIMIT 1];
                if (piWorkItem != null){
                    Approval.ProcessWorkitemRequest pWorkItemRequest = new Approval.ProcessWorkitemRequest();
                    //pWorkItemRequest.setComments('This record was cancelled, so the approval process was recalled.');
                    //pWorkItemRequest.setNextApproverIds(new Id[]{UserInfo.getUserId()});
        Approval.ProcessResult[] resultList = Approval.process(pWorkItemRequestList);
        for (Approval.ProcessResult result : resultList){
            System.debug('Success: ' + result.isSuccess());
            System.debug('Status afterward: ' + result.getInstanceStatus());

    private static void printList(List<ProcessInstanceWorkItem> input){
        for (integer i = 0; i < input.size(); i++){
            System.debug('ProcessInstanceWorkItem ' + i + ': ' + input[i]);

And the error message:

CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY, CreditApprovalTrigger: execution of AfterUpdate caused by: System.DmlException: Process failed. First exception on row 0; first error: INVALID_CROSS_REFERENCE_KEY, invalid cross reference id: []

  • Did you check all the parameters needed in order to enter an Approval Process?. In most cases you have a logic to allow a record to enter an Approval Process. If you try to execute an approval process but the parameters are not met, you can get this error. So try to check that the record you trying to use programmatically is meeting all values needed for that particular approval process. Commented Jan 17, 2020 at 20:24
  • In this case, the record is already in an active approval process, so it was submitted successfully. What I'm trying to do is just Reject whatever the active approval step is, but for whatever reason I can't do that.
    – apollo 15
    Commented Jan 17, 2020 at 21:07
  • Indeed... you're executing a "Reject'... Did you execute that code with the Admin profile? Commented Jan 17, 2020 at 21:17
  • Yes, so far I've only tested this as a sys admin.
    – apollo 15
    Commented Jan 17, 2020 at 21:28
  • Can you try a couple of things? Check before entering the for loop that the caMap is not empty. Just add an if statement above your for loop, something like 'if(!caMap.isEmpty()) { ..... Try also to see if you have access to the records you are in theory executing on this method, from the UI... Commented Jan 17, 2020 at 23:13

2 Answers 2


The actual reason is you are trying to process a single record through an approval process multiple times. This happens when you have two different approvers at a single stage.

For Example, You have an approval process that has approver A and approver B at step 2. Now you require to reject or approve the approval process through APEX, so you run a loop over ProcessInstance and find the respective ProcessInstanceWorkitem and reject all the ProcessInstanceWorkitem, but the catch here is there will be two ProcessInstanceWorkitem for single ProcessInstance as there are two approvers at that stage. Salesforce creates one ProcessInstanceWorkitem for each approver if there are multiple approvers in a single step.

Solution: Pick any single ProcessInstanceWorkitem for a Process Instance and approve/reject it on the bases of your need and leave all the remaining. The approval process will work automatically.

You can refer to bellow code-

     Set<Id> pIds = (new Map<Id, ProcessInstance>([SELECT Id,Status,TargetObjectId 
                                                              FROM ProcessInstance 
                                                              where Status='Pending' 
                                                              AND TargetObjectId in : targetObjectIdSet])).keySet();
    for(ProcessInstanceWorkitem processInstanceWorkItems:  [SELECT Id,ProcessInstanceId FROM ProcessInstanceWorkitem WHERE ProcessInstanceId in :pIds]){
                    ProcessInstanceIdToProcessInstanceWorkitemMap.put(processInstanceWorkItems.ProcessInstanceId, processInstanceWorkItems.Id);
                for (Id pInstanceWorkitemsId: ProcessInstanceIdToProcessInstanceWorkitemMap.values()){
                    Approval.ProcessWorkitemRequest singleApprovalProcess = new Approval.ProcessWorkitemRequest();
                    singleApprovalProcess.setComments('Reason here.');
                    singleApprovalProcess.setNextApproverIds(new Id[] {UserInfo.getUserId()});
                    Approval.ProcessResult result = Approval.process(singleApprovalProcess); 
  • Thanks for the clear explanation! I've marked your answer as the solution.
    – apollo 15
    Commented Jun 19, 2020 at 18:47

Solved. This error was being caused by the approval process setting a value on Rejection that was invalid for this scenario. This value activated a Process which in turn activated a validation rule.

The weird thing is that the error message "INVALID_CROSS_REFERENCE_KEY" provided zero help in discovering this issue, and led me on a search in a totally wrong direction. I suppose this is a lesson that errors aren't always as they appear in Salesforce.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .