4

This is a canonical question and answer developed by the community to help address common questions. If you've been directed here, or your question has been closed as a duplicate, please look through the resources here and use them to shape more specific questions. To browse all canonical questions and answers, navigate to the canonical-qa tag.

This question and answer is owned by the community! If you can improve it and make it more useful, you're welcome to edit it.


Variants

When trying to use an InvocableMethod in a Flow, I receive the following error:

The number of results does not match the number of interviews that were executed in a single bulk execution request.

What's going on, and how do I fix this?


When I try to assign the return value from my Apex action to a variable, I cannot assign it to a Collection. How do I fix this?


Example Codes

@InvocableMethod
public static List<Account> getLeadConvertedAccount(List<Lead> leads) {
  List<Id> accountIds = new List<Id>();
  for(Lead record: leads) {
    accountIds.add(record.ConvertedAccountId);
  }
  List<Account> accounts = [SELECT Name FROM Account WHERE Id IN :accountIds];
  return accounts;
}

@InvocableMethod 
public static List<String> splitTextByLine(List<String> source) {
  return source[0].split('\n');
}

@InvocableMethod
public static List<Account> processAccountData(List<String> data) {
  List<Account> results = new List<Account>();
  for(String accountJson: data) {
    if(String.isNotBlank(accountJson)) {
      results.add((Account)JSON.deserialize(accountJson, Account.class));
    }
  }
  return results;
}

1 Answer 1

5

In Salesforce, flows are bulkified. The documentation explains it best, so I'll copy it here:

When multiple interviews for the same flow run in one transaction, each interview runs until it reaches a bulkifiable element. Salesforce takes all the interviews that stopped at the same element and intelligently executes those operations together. If other interviews are at a different element, Salesforce then intelligently executes those operations together. Salesforce repeats this process until all the interviews finish.

For clarity, a Flow Interview is a running instance of a flow.

In regards to InvocableMethod calls, this is why there is a restriction on the types of data you can use. The flow runtime will wait until all interviews are at the same element, then call the Apex code with the parameters being all the inputs from all the interviews, and the output will be returned, in order, to the interview the input came from.

In order to facilitate this behavior in Apex, an extra List is wrapped around the input parameter and the return value. That means if you want to return a single value, like Id, you must put it in a List, and if you want to return a List<Id>, you need to return a List<List<Id>>.

In order to make sure that your code will behave correctly, you must make certain that, if you return a List, it has exactly the same number of elements as the input parameter, and in the same order as they were received. If you don't, you may experience errors or even erroneous logic, such as the wrong data reaching the wrong interview.

Fixed Examples

In the first example, the order of the output values may not match the expected size, or may return the accounts in the wrong order, meaning that you can either receive an exception, or some interviews might get the wrong value.

@InvocableMethod
public static List<Account> getLeadConvertedAccount(List<Lead> leads) {
  List<Id> accountIds = new List<Id>();
  for(Lead record: leads) {
    accountIds.add(record.ConvertedAccountId);
  }
  Map<Id, Account> accountsById = new Map<Id, Account>([
    SELECT Name FROM Account WHERE Id IN :accountIds
  ]);
  List<Account> accounts = new List<Account>();
  for(Lead record: leads) {
    accounts.add(accountsById.get(record.ConvertedAccountId);
  }
  return accounts;
}

In the second example, the action is not properly bulkified, and so will get an exception if it executes in bulk. The code should always be bulkified, even if only used in a screen flow. A future developer might want to use it in a different context, and it won't correctly if it runs in a bulkified context.

@InvocableMethod 
public static List<List<String>> splitTextByLine(List<String> source) {
  List<List<String>> results = new List<List<String>>();
  for(String value: source) {
    results.add(value?.split('\n'));
  }
}

In the third example, the original code failed to add a value in certain contexts, so it would "randomly" fail. Always ensure that the number of inputs and outputs match, even if it's just to return a null value.

@InvocableMethod
public static List<Account> processAccountData(List<String> data) {
  List<Account> results = new List<Account>();
  for(String accountJson: data) {
    // Never fail to add a value for each input
    if(String.isNotBlank(accountJson)) {
      results.add((Account)JSON.deserialize(accountJson, Account.class));
    } else {
      results.add(null);
    }
  }
  return results;
}
5
  • 1
    Very nice. The only thing I would add is an explanation as to what an "interview" is, since the term isn't exactly an obvious choice (at least to me). Yeah, canonical, I should add an explanation myself ;)
    – Phil W
    Aug 10 at 20:40
  • Done. Hopefully in an acceptable manner.
    – Phil W
    Aug 10 at 20:45
  • @PhilW That's a sensible edit. Thanks!
    – sfdcfox
    Aug 11 at 0:05
  • 1
    superb - I might change/narrow the title of the OP as there are more things to cover under a topic like "How do I make my InvocableMethod Apex work properly with Flows?" - e.g. exception handling, callouts, ... Also - maybe hammer home the point that even when using invocable apex for a screen flow, one should bulkify it as shown and not use the requests[0] hack
    – cropredy
    Aug 11 at 0:07
  • @cropredy Thanks for the feedback. I'll make some edits to it.
    – sfdcfox
    Aug 11 at 0:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.