@future method calls another @future method
ID |
apex.correctness.future_calls_future |
Severity |
high |
Remediation Complexity |
medium |
Remediation Risk |
medium |
Remediation Effort |
medium |
Resource |
Api Usage |
Language |
Apex |
Description
Reports a @future method whose body calls another @future method declared in the same class, plainly or through a qualified reference. Only same-class calls are reported: OpenGrep does not resolve a callee across files, so a @future method calling a @future method declared elsewhere is not detected.
Rationale
A @future method already runs in an asynchronous transaction, and the platform refuses to start another one from inside it. The call throws AsyncException - "Future method cannot be called from a future or batch method" - at runtime, not at compile time, so the class deploys and the failure appears only when the asynchronous job actually runs. That is the worst place for it: nothing is on screen, the user has already been told the operation succeeded, and the only trace is a failed job in the Apex Jobs list or an email nobody is watching. Whatever the second method was to do is silently never done.
The following code illustrates the pattern detected by this rule:
public class InvoiceDispatcher {
@future
public static void enqueue(Id invoiceId) {
System.debug(LoggingLevel.INFO, invoiceId);
}
@Future
public static void archive(Id invoiceId) {
System.debug(LoggingLevel.INFO, invoiceId);
}
// FLAGGED: @future method calls another @future method
@future
public static void dispatch(Id invoiceId) {
enqueue(invoiceId);
}
// FLAGGED: @future method calls another @future method
@future(callout=true)
public static void send(Id invoiceId) {
InvoiceDispatcher.archive(invoiceId);
}
@future
public static void stamp(Id invoiceId) {
String label = describe(invoiceId);
System.debug(LoggingLevel.INFO, label);
}
public static String describe(Id invoiceId) {
return 'invoice-' + invoiceId;
}
public static void dispatchAll(Set<Id> invoiceIds) {
for (Id i : invoiceIds) {
System.debug(LoggingLevel.FINE, i);
}
enqueue(null);
}
}
Remediation
Do the second method’s work inline in the first, or move both steps into a Queueable and chain them with System.enqueueJob, which is the supported way to run one asynchronous step after another. Where the second step is a callout, @future(callout=true) on the single method does what the chain was trying to achieve.