@future method makes a callout without callout=true
ID |
apex.correctness.future_callout_annotation |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Api Usage |
Language |
Apex |
Description
Reports a @future method whose body builds an HttpRequest or sends one, while the annotation does not carry callout=true.
Rationale
The platform decides whether an asynchronous method may open a connection from the annotation alone, before the method runs. Without callout=true the send throws CalloutException: Callout from scheduled Apex not supported - and because the method runs asynchronously, nothing surfaces at the call site: the enqueuing transaction commits happily and the failure lands in the Apex Jobs list, minutes later, for whoever thinks to look. The same code called synchronously works, so the mistake usually ships from a refactor that moved a working callout into a @future.
The following code illustrates the pattern detected by this rule:
public class BillingSync {
// FLAGGED: @future method makes a callout without callout=true
@future
public static void pushInvoice(Set<Id> invoiceIds) {
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:Billing/invoices');
req.setMethod('POST');
Http http = new Http();
HttpResponse res = http.send(req);
System.debug(res.getStatusCode());
}
// FLAGGED: @future method makes a callout without callout=true
@future(timeout=60000)
public static void pushCredit(Set<Id> creditIds) {
Http http = new Http();
http.send(new HttpRequest());
}
@future(callout=true)
public static void pushRefund(Set<Id> refundIds) {
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:Billing/refunds');
Http http = new Http();
http.send(req);
}
@future
public static void recalculate(Set<Id> invoiceIds) {
List<Invoice__c> invoices = [SELECT Id FROM Invoice__c];
update invoices;
}
}