Andy in the Cloud

From BBC Basic to Force.com and beyond…

Calling Flow from Apex

65 Comments

Since Summer’14 it has been possible to invoke a Flow from Apex, through the oddly named Interview system class method start. This blog talks about using this as a means to provide an extensibility mechanism, as an alternative option to asking an Apex developer to implement an Apex interface you’ve exposed. Clicks not code plugins!

Prior to Summer’14 it was only possible to embed a Flow in a Visualforce page, using the the flow:interview component, as described here. But this of course required a UI and hence user interaction to drive it. What if you wanted to allow admins to extend or customise the flow of some Apex logic using Flow from a none UI context?

Enter trigger ready-flows, first introduced as part of the Summer’14 pilot for running Flow from Workflow, which itself is still in Pilot, thought the ability to create trigger ready-flows is now generally available, yipee! Here’s what the docs have to say about them…

You can also build trigger-ready flows. A trigger-ready flow is a flow that can be launched without user interaction, such as from a flow trigger workflow action or the Apex interview.start method. Because trigger-ready flows must be able to run in bulk and without user interaction, they can’t contain steps, screens, choices, or dynamic choices in the active flow version—or the latest flow version, if there’s no active version.

This blog will present the following three examples of calling trigger-ready flows from Apex.

  • Hello World, returning a text value set in a Flow
  • Calc, shows passing in values to a Flow and returning the result of some processing.
  • Record Updater, shows passing in some SObject records to the Flow for processing and returning them.

Hello World Example

Here is a simple example that invokes a Flow that just returns a string value defined within the Flow.

ReturnHelloWorldFlow

ReturnHelloWorldAssignment

The following Apex code calls the above Flow and outputs to the Debug log.

// Call the Flow
Map<String, Object> params = new Map<String, Object>();
Flow.Interview.ReturnHelloWorld helloWorldFlow = new Flow.Interview.ReturnHelloWorld(params);
helloWorldFlow.start();

// Obtain the results
String returnValue = (String) helloWorldFlow.getVariableValue('ReturnValue');
System.debug('Flow returned ' + returnValue);

This outputs the following to the Debug log…

11:18:02.684 (684085568)|USER_DEBUG|[13]|DEBUG|Flow returned Hello from the Flow World!

Calc Example

This example passes in a value, which is manipulated by the Flow and returned.

CalcFlow CalcFlowAssignment

The following code invokes this Flow, by passing the X and Y values in through the Map.

// Call the Flow
Map<String, Object> params = new Map<String, Object>();
params.put('X', 10);
params.put('Y', 5);
Flow.Interview.Calc calcFlow = new Flow.Interview.Calc(params);
calcFlow.start();

// Obtain the results
Double returnValue = (Double) calcFlow.getVariableValue('ReturnValue');
System.debug('Flow returned ' + returnValue);

This outputs the following to the Debug log…

12:09:55.190 (190275787)|USER_DEBUG|[24]|DEBUG|Flow returned 15.0

Record Updater Example

With the new SObject and SObject Collection types in Summer’14 we can also pass in SObject’s. For example to apply some custom defaulting logic before the records are processed and inserted by the Apex logic. The following simple Flow loops over the records passed in and sets the Description field.

RecordUpdaterFlowRecordUpdaterAssignment

This code constructs a list of Accounts, passes them to the Flow and retrieves the updated list after.

// List of records
List<Account> accounts = new List<Account>{
	new Account(Name = 'Account A'),
	new Account(Name = 'Account B') };

// Call the Flow
Map<String, Object> params = new Map<String, Object>();
params.put('Accounts', accounts);
Flow.Interview.RecordUpdater recordUpdaterFlow = new Flow.Interview.RecordUpdater(params);
recordUpdaterFlow.start();

// Obtain results
List<Account> updatedAccounts =
	(List<Account>) recordUpdaterFlow.getVariableValue('Accounts');
for(Account account : updatedAccounts)
	System.debug(account.Name + ' ' + account.Description);

The follow debug update shows the field values set by the Apex code and those by the Flow…

13:10:31.060 (60546122)|USER_DEBUG|[39]|DEBUG|Account A Description set by Flow
13:10:31.060 (60588163)|USER_DEBUG|[39]|DEBUG|Account B Description set by Flow

Summary

Calling Flows from Apex is quite powerful to provide more complex extensibility back into the hands of admins vs developers. Though depending on your type of solution is somewhat hampered by the lack of the ability to dynamically create a subscriber configured Flow. As such for now is really only useful for none packaged Apex code deployments in production environment, where the referenced Flow’s can be edited (managed packaged Flows cannot be edited).

Despite this, for custom Apex code deployments to production it is quite powerful as it allows tweaks to the behaviour of solutions to be made without needing a developer to go through Sandbox and redeployment etc. Of course your also putting a lot of confidence in the person defining the Flows as well, so use this combo wisely with your customers!

Upvote: I’ve raised an Idea here to allow Apex to Dynamically create Flow Interview instances, with this in place ISV and packaged applications can make more use of this facility to provide clicks not code extensibility to their packaged application logic.

Update: Blog post Flow Factory presents a workaround to the dynamic calling problem.

65 thoughts on “Calling Flow from Apex

  1. This is good example .I had one for how to use this flows to connect to external system.Would like to know your feedback on this http://cloudyworlds.blogspot.in/2014/09/invoking-apex-callouts-through-visual.html

    • Thats looks great, i love the Apex Plugin feature of Flow, sadly, though understandably enough for now, ‘trigger ready-flows’ cannot contain Apex Plugin Elements. So effectively you cannot call a Flow from Apex, that then calls back out to Apex via an Apex Plugin. It’s a bit of strange use case i admit, hopefully they will add it in the future, it seems a shame to block Flows using Flow Plugins.

  2. Hi Andy,

    This was a helpful article for me as I was trying to use flows in VF Page and there are some choices on the Flow, and trying to select the Choice via code as I have two choices on the screen “Yes” and “No” but could not succeeded, can you please suggest that Is this possible to select Choice of a screen via code?

  3. It is a really cool idea, and I could see how there could be some powerful use cases, but I am not 100% on board with giving so much power over to the declarative side of things where testing is very lacking and sometimes actions aren’t 100% thought out. I am not at all against giving admins more control, I just don’t like the idea that an untrained individual can make changes like this directly in prod (assuming a trained admin would know better and make changes in a sandbox first for testing). This could instantly break some functionality and the process for reverting it would be all manual at that point. Still, it is probably more a matter of training than anything else.

    Good read!

    • Yeah totally agree and its a balance Salesforce are going to have to address, the more and more they head into Declarative things like Lightning Process builder. Maybe an idea to have LIghtning Test Harness? 😉

  4. I recently added code like this to my instance. At first, I was just returning a page reference by the flow url. Then I changed calling the Flow from apex directly using the the same method as this post.

    You might want to be aware of this known issue.
    https://success.salesforce.com/issues_view?id=a1p300000008XAoAAM

    This was preventing me from deploying the code to production because of “Internal Error” in one test class. The workaround does not seem to make any sense, the flow in question does not even have any task assignments, but for some reason disabling the “Enable User Control over Task Assignment Notifications” allowed me to deploy without error.

  5. This works really well as an alternative to creating a rules engine in apex.

  6. Great post Andy!! You’ve single-handedly made me start dabbling in this at work!

  7. Pingback: Stumbled on another AWESOME Apex blog! - Salesforce coding lessons for the 99%

  8. Hi Andrew,

    I’m trying to pass list of records from Apex into a Flow using @Invocable
    method. However In Flow I could see Output as only the 1st record from
    list. Is there a way wherein we can display all the records?

    Basically in Flow I have a page where a User can select Multiple Check
    boxes for Example lets take Months ( January to December). Once we select
    the Months from the Page, the selection goes to Apex Class where I have
    @Invocable method and then it passes back the Ids of the Months I selected
    in the Flow. In Flow I have SObject variable which I associate as Output to
    display the List, but I end up getting only the 1st record from the List
    and not the whole List. Is there a way where in I can display the whole
    List.

    Thanks,
    Anup

  9. This is some great information. I’ve built a trigger which instantiates the flow, passes in the parameters, then call the start method for the flow instance.

    I modify data to execute the trigger and I don’t receive any error. I open the debug log and find the parameters passed in are correct and the flow start method enters and exits without any issue. The problem is that the flow doesn’t appear to be running. I’m not seeing the record update.

    Is there anything that needs to be added besides the flow start method? I see you used the getVariableValue method as well, but I’m assuming that’s if you want to manipulate the data further. Our flow runs a fast update at the end, so no need to run any DML or further manipulation through code.

    My other big question, which I’m trying to test, is how do triggered flows work with governor limits? From what I’m seeing our current demo is not bulkified; we trigger the flow within a for loop and the flow runs a fast lookup (I would assume is running SOQL) and runs a fast update (I would assume runs a DML). Would we get errors and need to bulkify by adjusting the parameters the flow is looking for?

    By the way – thanks for being a great resource to the Salesforce community!

    • Thank you for your kind words, i really enjoy contributing to the community. Regarding debugging Flow, take a look at this and see if this helps, http://docs.releasenotes.salesforce.com/he-il/spring14/release-notes/rn_forcecom_process_debug_logs_flow_action.htm. Regarding bulkification yes, you do need to consider this, in my tests in the past i can see that fast lookup and fast update are akin to SOQL and DML, so for sure do not put Flow calls in a loop. You should be able to leverage Flow Collection variables to pass in lists and then ensure your Flow is bulkified, though i have not personally tried this. Hope this helps! Let me know how you get on.

      • Thanks for the link. What I’m finding is it’s nearly impossible to retrieve data from a parent record then iterate through the child records and update a field with the retrieved data because flow doesn’t have any functionality similar to maps. It seems Salesforce would need to support a collection that has a key for apex triggered flows to be a practical solution for update parent or child records with data from the related record. Still a really cool and powerful concept.

        I ended up playing around with the limits as well. I built a lookup within a loop within a lookup within a loop (intended to break it) and at first it worked – I updated about 250 records this way, which I thought was odd. When I scaled up to update 4000 record I hit the limit and it rolled everything back.

        Thanks again for the help.

      • Welcome

  10. Is there any way to call a flow in a scheduled Apex class? If it is possible, and you are nice enough to explain how, can you please include the whole class declaration?

    I am a noob to APEX and am trying to get the flow.interview to be called through a schedulable apex class. The ultimate goal is to send emails to users who haven’t logged the last 90 days and warn them they might lose their license. I have the two flows working, I just need to call them both once a night.

  11. Nope. But if there is such an element, that would be great. I went looking for a scheduling element and didn’t find anything. The only thing close is the wait element. If you could point me in the right direction either way, I’d be grateful.

  12. The wait element is more for time based actions within the flow. It doesn’t help trigger the flow itself. If only SF had the possibility to fire off workflow on a login. Then my problem would be solved. If you can help me with the Apex class on “Schedulable”, I’d really appreciate it.

  13. Ok. I have this now and SF swallows it.

    global class ScheduledLicenseExpirationReminder implements Schedulable
    {
    public Flow.Interview.Remind_Users_About_Expiring_Licenses userReminder {get; set;}

    global void execute(SchedulableContext SC) {

    userReminder.start();

    }
    }

    What do you think?

    Scott

    • Try making it “public final”, Saleforce maybe loosing the variable value and giving you a null pointer exception, which should show under Apex Jobs page under Setup btw. Otherwise trying getting hold of the flow interview instance in the execute method instead of depending on it being setup outside the class.

  14. SF doesn’t let me move the flow interview instance into the execute method. It gives me an error about looking for ; and found {

  15. Any chance you could give me an example where you call the flow as schedulable AND you don’t pass anything into the flow? I just need to be able to call my flow on a Monthly basis via scheduled APEX. I don’t need to pass anything to the flow as the flow is just doing some maintenance items for me.

    Here is my attempt:
    global class scheduled_BadgeReset implements Schedulable {
    global void execute(SchedulableContext SC) {
    Flow.Interview.Reset_Badge_Count_Each_Month resetBadge = new Flow.Interview.Reset_Badge_Count_Each_Month();
    resetBadge.start();
    }
    }

  16. Pingback: Tip# 6 : Calling a flow from APEX | CRM Crazy - Salesforce

  17. Pingback: Supercharging Salesforce Report Subscriptions with Apex and Flow | Andy in the Cloud

  18. Have you had this error occur? Constructor not defined: [Flow.Interview.Update_Account_Team_with_DSM_when_no_AC].<Constructor>(List<Account>)

    I followed the steps described to initiate the flow. I am wondering if I am missing something in my scheduled apex code that was not shown in your example.

    • Can you maybe share a Gist of your code? Also double check your flow name.

      • global class scheduleUpdateAccountTeamwithDSM implements Schedulable{

        global void execute(SchedulableContext ctx)
        {
        startFlow();
        }
        public void startFlow()
        {
        // get accounts with no AC
        List params = new List([Select Id from Account where Id NOT IN (select AccountId From AccountTeamMember WHERE TeamMemberRole = ‘Advertising Consultant – ATC’)]);
        // pass list to flow
        Flow.Interview.Update_Account_Team_with_DSM_when_no_AC_headless uatDSM = new Flow.Interview.Update_Account_Team_with_DSM_when_no_AC_headless();
        system.debug(‘FLOW STARTING:Update_Account_Team_with_DSM_when_no_AC_headless’);
        uatDSM.start();
        }
        }

        Constructor not defined: [Flow.Interview.Update_Account_Team_with_DSM_when_no_AC_headless].

      • You need to pass parameter into the constructor. Check the apex developers guide or my sample code it passes a map of strong and object in as a parameter

      • ignore the pass list to flow comment… we have not gotten that far…

      • Not sure what you mean?

  19. Pingback: Dynamically Creating Flows in Apex | Andy in the Cloud

  20. Pingback: Introducing the Flow Factory | Andy in the Cloud

  21. Can we invoke a flow from @InvocableMethod

    • I have not tried it and don’t know if any restrictions so try it out. Let me know. Also interested in your use case? Since Flow can call another Flow natively already? Conditional execution I am guessing?

  22. Hi, Andy, I have a method that invokes a flow that updates records. Now we are trying to create unit tests around this but cannot assert the values set by the flow later in the unit test. Have you done anything like this? Is it possible to assert object values after updated by a flow in a unit test?

  23. Hi Andy,

    Great post! But I have a question. What if you have a flow that does not need inputs or parameters? I basically have a flow that just needs to be started from apex. Here is my code, but I get the error Constructor not defined: [Flow.Interview.Update_Amount_3].()

    Flow.Interview.Update_Amount_3 f = new Flow.Interview.Update_Amount_3();
    f.start();

  24. Hi Andy,

    Sorry to bother you but I am so stuck and I hope that this is something where you can get me on the right track in a glance, with your expertise.

    I have tried to follow your example in a sandbox with a very (very) simple flow
    . For some reason I keep hitting this error: “CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY”.

    This implies No Permissions but I am a (regular) System Administrator in a Sandbox where I have created many Flows. These all run fine via Process Builder, Visual Force Pages or (Flow) Screens.

    My trigger is:
    – – – – – – – – – –
    trigger MyTriggerQuote on Quote (before delete, before insert, before update, after delete, after insert, after update) {

    if (Trigger.isAfter) {
    if (Trigger.isInsert || Trigger.isUpdate) {

    // Call the Flow
    Map params = new Map();
    for (Quote r :[SELECT Id FROM Quote
    WHERE Id IN :Trigger.old]) {

    params.put(‘inp_Record_Id’, r.Id);

    Flow.Interview.My_A_TEST InstOfFlow = new flow.Interview.My_A_TEST (params);
    InstOfFlow.start();

    }

    }
    }

    }
    – – – – – – – – – –

    The (FlowApplication) error ends with:
    – – – – – – – – – –


    RECORD UPDATE: Quote_Update
    Find all Quote records where:
    Id Equals {!inp_Record_Id} (0Q01q0000004DKNCA2) // This is a valid record so the Trigger passed the correct Id/Value to the Flow !
    Update the records’ field values.
    Description = {!var_Flow_has_Run_Description} (Flow has Run)
    Result
    Failed to update records that meet the filter criteria.
    —————————————-
    Error Occurred: The flow tried to update these records: null. This error occurred: CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY: MyTriggerQuote: execution of AfterUpdate caused by: System.FlowExc
    – – – – – – – – – –

    >> SOAP API Developer Guide: https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_calls_concepts_core_data_objects.htm#
    = = = =
    CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY
    You do not have permission to create, update, or activate the specified record.
    = = = =

    I’m completely stuck.
    It has to be something simple and stupid and I’ll probably kick myself but so far I’m just pulling my hair !

    Thank you for your attention and your fantastic site.

    Hans Post

    • It looks like this is a problem with you trying to update the quote record within the Flow. Since there is already a pending update being made you get this error. My suggestion is to not do the update in the Flow. You can use SObjectCollection variables in the Flow to pass in and out the Trigger.new list of records being updated. You will need to do this in the before phase of the trigger though.

  25. Hi Andrew! Great post, we are using part of it but we are super stuck!

    We are using an apex class which will run every night looking for all opportunities that have a Line Item. This should fire a flow which will circle through the opportunity Line Items and sum up the total price only if 2 checkboxes are true. This is out code:

    global class CalculateMRR implements Schedulable {
    global void execute(SchedulableContext sc) {

    Map params = new Map();
    // List of records
    List oppsWithProducts = [SELECT Id, (SELECT Id, Vigente__c, Es_Recurrente__c, TotalPrice, UnitPrice
    FROM OpportunityLineItems
    WHERE Vigente__c = TRUE AND Es_Recurrente__c = TRUE)
    FROM Opportunity
    WHERE HasOpportunityLineItem = TRUE];

    // Call the Flow
    if (oppsWithProducts.size() > 0) {
    for (Opportunity oppWithProduct : oppsWithProducts) {
    params.put(‘curOpportunity’, oppWithProduct.Id);
    }
    Flow.Interview.MRR_Update_on_Opportunity MRR_Update_on_OpportunityFlow = new Flow.Interview.MRR_Update_on_Opportunity(params);
    MRR_Update_on_OpportunityFlow.start();
    }
    }
    }

    Our problem comes when sending the Ids to the flow, it processes fine if we send just 1 ID, but it gives us a Too many SOQL queries error if it finds more.

    Any idea how we can solve this? How can we pass the entire list of opps to the flow?

    Thank you!!

    • You need to create and sobject collection variable in your flow, make that variable an input type. Then pass your whole list “oppsWithProducts”. Then in your flow use a loop over the sobject collection and add to another number variable that is set as an output variable.

  26. Hi Andrew,
    Do mind sharing the details of your Accounts sObject collection variable in the Record Updater Example? I’m getting a data type error.
    Thanks so much!

  27. Dear Mary & Andrew,

    Actually I also ran in this problem, and it makes a lot of sense it is a collection variable, but unfortunately, this also prevents a quick workaround for flows that are triggered from Process Builder that lacks the after delete trigger… Since Proces Builder passes a single sObject instead of an array. At least I wouldn’t know of a way changing that behaviour, or is there? I guess I could loop over the records and put the interview class in the loop, but that sounds like a terrible practice… Maybe I will give it try, see where I had any limits anyway!

    I don’t know Mary if you tried a similar thing perhaps?

    Kind regards,
    Rutger

    p.s.: I know that the after delete trigger for flows is coming in Winter’21 (safe harbour et al of course), but I am trying to anticipate a bit, given that I had to refactor some processes (and rollups from you excellent packages, thanks for that!) anyway. This is also the reason I would like to have the flow execute in the context of a single record and not pass all the logic to a trigger and pass arrays to the flow, because that would mean I would have to refactor the flow again once the functionality is there…

  28. Hi Andrew,

    Thank you i am using the similar way and is there any way i can call Flow.FaultMessage in the Apex Code.
    Thanks and regards,

    • Not that i know. I would ensure before your Flow ends you copy this value to a output variable that your apex code can access.

  29. Hi Andrew,

    Question regarding the Record Updater Example:
    When your parameters are (i.e. map), how does the flow “know” to loop over the list “accounts”? I mean, is it just because you used the same name for the flow and code account list (“accounts”)?

    The reason I’m asking is that I’m currently stuck with a code-to-flow implementation. I built a trigger for the FeedComment object (chatter comment), that calls a helper class. That class invokes a flow (followed your instructions), and the flow should update the record that the chatter comment is related to (“Case” record, in my use case). In short, the status of the post’s parent case should be updated according to business logic. The logic should be in the flow.

    Since the flow needs information from both the user who posted the message and the chatter comment’s parent (case) record, I’m trying to send the flow the following map: . In my case, the Object is a new class that I created and that has 2 “fields”: “C” of type Case and “U” of type User.

    Example flow input looks like: {“test string” : [C=Case:{Id=5006C000006xd8SQAQ}, U=User:{Id=0056C000002IbfkQAC}]}

    In the flow, I created an “Apex Defined” var that gets a list of my unique object, and loops over it.
    However, seems like the flow’s loop doesn’t go over the list. Not sure what’s the reason for that.

    Thanks!

    • It does look like apex type collection is supported. So i would expect the flow loop element to work. Have you tried using the flow debug levels and checking the output in the apex debug log?

  30. How can we please lunch an Autolaunched Flow from the Apex class?

  31. At present, it looks like we can only fire 1 flow interview using the Interview.start().

    The downside here is that we cannot fire multiple flow interviews at once, rather we have to explicitly fire them for every record.

    Am I correct in thinking that Flows called from Apex will not be bulkified?

Leave a comment