15

Here's the problem: when an org has a trigger that calls an @Future method, any batch Apex that fires that trigger will fail, since you cannot call an @Future method from an @Future method OR from batch Apex. If you do, the following error results:

Type: System.DmlException Message: Update failed. First exception on row 0 with id 00Q3000000y3sIZEAY; first error: CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY, methodNameHere: execution of AfterUpdate caused by: System.AsyncException: Future method cannot be called from a future or batch method

So, if you have a managed package on the AppExchange that includes batch Apex that, for example, updates contacts periodically, and this package is installed into an org that has @Future calls made from inside a contact trigger, your batch Apex will fail to execute.

Has anyone worked through this issue or perhaps does anyone have any suggestions around what options might be available for this scenario?

Thanks in advance!

1
  • Wow, that's a doozy. Have never considered this scenario before but can't think of any way to prepare for other people's code in this manner.
    – Matt Lacey
    Commented Aug 11, 2012 at 2:03

3 Answers 3

7

Since it turns out System.AsyncException is uncatchable (just tested it) there isn't much you can do to either predict when this is going to happen or resolve it outside of editing the conflicting code.

In short: there's not really a solution other than requiring customers to fix their code to not use @future form non-async safe contexts. And yes, I realize how well that response goes over, I've had to give it more than once.

3
  • The thing that really gets me about this is that you can find blog recommendations about how to make callouts from within triggers by using Future methods! (cheenath.com/?tutorial/sfdc/sample1/index.html) I'd say that if batch Apex is effectively broken by using Future from a trigger, then Future from a trigger should be disallowed. But thanks for the uncomfortable news.
    – Adam
    Commented Aug 11, 2012 at 2:53
  • 2
    Well, what you really should be doing is checking (System.isBatch() || System.isFuture() || (Limits.getLimitFutureCalls() - Limits.getFutureCalls() == 0) If that returns true then queue your work up for later or run it synchronously because @future calls aren't safe in that context. Sadly that's a rather complex and not particularly well known thing to do. Commented Aug 11, 2012 at 3:29
  • 1
    As I mentioned to E.J. Wilburn, I can't do this since calling System.isBatch() from within my batch Apex that's located in a managed package is redundant. I absolutely know System.isBatch() == true. It's the other triggers that I'm not in control of that are the issue, again unless there's some magic answer that I haven't discovered yet. But my tests confirmed what you claimed before I posted. I just wanted to see if anyone held out hope for a workaround.
    – Adam
    Commented Aug 11, 2012 at 18:21
5

You can use the System.isBatch() and System.isFuture() methods to determine if your code is currently executing in an asynchronous context and then function differently rather than trying to catch an async exception.

Any code that initiates async calls (customer org or managed package) should be performing these checks so that code doesn't break down the line when new code is added that also executes async calls.

See the documentation for this here: http://www.salesforce.com/us/developer/docs/apexcode/index.htm

5
  • That's true, but the problem is that I'm not in control of the code that's causing this issue. Someone, somewhere, installs a managed package that has a batch apex process within it. If the org installing it has a related trigger that fires a future method, it's trouble.
    – Adam
    Commented Aug 11, 2012 at 18:16
  • Both the managed package and the customer code should be performing these checks and behaving differently if they're called during an async operation. I'll edit the answer to clarify that. Commented Aug 11, 2012 at 19:12
  • Good clarification - thanks. And hopefully this problem will not occur with another managed package that's not considering async issues, since the only solution is to uninstall the other package.
    – Adam
    Commented Aug 13, 2012 at 1:49
  • 3
    There is no way around this. If a trigger isn't making that check before calling the @future method, it's a poorly written trigger. Not that it helps you, but that's the case. You'd really have to provide guidance to the developers of that trigger on how to fix it.
    – hemmeter
    Commented Aug 14, 2012 at 18:57
  • 1
    I too faced the same problem in one of our customer org where there is a trigger on Campaign object from another managed package. That trigger invokes a future method on their package and when the batch job from our package create Campaign record, we see the above issue. Like most of the people suggested here, it is the responsibility of the other package developer to do the System.isFuture and System.isBatch checks in the trigger before calling a future method. This is the advised solution by the Salesforce dev team too. Commented Nov 28, 2016 at 15:13
2

There isn't a way around this, but with the new Queueable interface, you have a solution that you can ask people to implement in their orgs if they are getting this when they use your package.

The Queueable inteface is able to be called safely from a Batch and is in many ways superior to @future calls.

  • It's async, like @future
  • It's deterministic - i.e. it's a fifo structure, so you rely on order of execution
  • You can pass parameters of any type to it.
  • You can chain jobs!

You must log in to answer this question.

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