Apex

Apexの@futureで書く非同期処理の制約と使い分け

同期処理は呼び出した側の処理が終わるまで画面を止めます。@futureを付けると、処理を後回しにして呼び出し元をすぐ返せます。書き方と、はまりやすい制約を説明します。

2023.02.13

同期処理と非同期処理の違い

Apexのメソッドは、何もしなければ同期で動きます。呼び出した側は、そのメソッドの処理が終わるまで次に進めません。重い処理を同期のまま呼ぶと、画面やAPIの応答がその分だけ遅くなります。

@futureを付けたメソッドは非同期になります。呼び出した瞬間にキューへ積まれ、呼び出し元はすぐに処理を続けられます。実際の処理は、システムのリソースが空いたタイミングで実行されます。いつ実行されるかは保証されません。

@futureの書き方

メソッド宣言の直前に@futureを付けるだけです。ただし、戻り値と引数には制約があります。

public with sharing class AccountBulkCreator {
    public static void createAccounts(List<String> names) {
        List<Account> accounts = new List<Account>();
        for (String name : names) {
            accounts.add(new Account(Name = name));
        }
        insert accounts;
    }

    @future
    public static void createAccountsAsync(List<String> names) {
        createAccounts(names);
    }
}

戻り値はvoidだけです。引数にはプリミティブ型かそのコレクションしか使えません。

同期版のcreateAccountsを呼ぶと、呼び出し側はinsertが終わるまで待たされます。createAccountsAsyncを呼んだ場合は、呼び出し側はすぐ戻り、Accountの作成は後で行われます。画面やAPIのレスポンスを速くしたいときに使う効果は、ここで出ます。

Visualforceページのボタン。FutureAnnotationNotExist Click!! と書かれている
同期版を試す画面です。このボタンから、@futureを付けていないメソッドを呼びます。
作成された取引先の項目表示。取引先名はFutureNotExistTest
同期版の結果です。ボタンを押した処理の中で、取引先がそのまま作られています。
実行ログ。USER_DEBUGにFutureNotExist Method called with: 1 and 1 と出ている
同期版のデバッグログです。呼び出しと同じトランザクションの中で出力されます。
Visualforceページのボタン。FutureAnnotationExist Click!! と書かれている
非同期版を試す画面です。押した瞬間に画面は戻り、処理はあとで実行されます。
作成された取引先の項目表示。取引先名はFutureExistTest
非同期版でも同じ取引先ができます。違うのは、作られるタイミングだけです。
実行ログ。USER_DEBUGにFutureExist Method called with: 2 and 2 と出ている
非同期版のログです。タイムスタンプが呼び出しより後になっている点を見てください。

⚠️ DMLはループの中で呼びません。上のコードは、Accountのリストをループの外で1回だけinsertしています。名前の件数が増えても、DMLの発行回数は変わりません。

sObjectを引数にできない理由

@futureの引数は、プリミティブ型・プリミティブ型の配列・プリミティブ型のコレクションに限られます。sObjectそのものは渡せません。Accountのリストを直接渡すことはできず、List<String>やList<Id>にする必要があります。

理由は、キューに積まれてから実際に実行されるまでの間に、渡したレコードの内容が変わっている可能性があるためです。公式ドキュメントは、IDだけを渡して、実行時にあらためてクエリで取得することをすすめています。

public with sharing class AccountFollowUpAsync {
    @future
    public static void notify(List<Id> accountIds) {
        List<Account> accounts = [
            SELECT Id, Name
            FROM Account
            WHERE Id IN :accountIds
        ];
        for (Account acc : accounts) {
            System.debug('非同期処理: ' + acc.Name);
        }
    }
}

トリガはレコードではなくIDのリストを渡します。実行時にSOQLで最新の値を取り直します。

trigger AccountTrigger on Account (after insert) {
    List<Id> newIds = new List<Id>();
    for (Account acc : Trigger.new) {
        newIds.add(acc.Id);
    }
    AccountFollowUpAsync.notify(newIds);
}

トリガはレコードごとに呼ばず、IDをためてから1回だけ呼びます。

トリガの中でレコード1件ごとにnotifyを呼ぶと、一括登録の件数が増えたときに次の上限へ当たります。Trigger.newをループしてIDだけをためて、最後に1回で呼び出します。

呼び出し回数の上限

呼び出し元のコンテキスト1トランザクションで呼べる回数
同期Apex(画面・API・トリガなど)50回
Queueable Apex50回
別の@futureメソッド・バッチApexの中0回(呼び出せません)

@futureメソッドから、別の@futureメソッドは呼べません。バッチApexの中から呼ぶこともできません。

24時間あたりの上限もあります。公式ドキュメントには「250,000 or the number of applicable user licenses in your org multiplied by 200, whichever is greater」とあり、バッチApex・@future・Queueable・スケジュールApexの実行回数を合算した、組織全体のローリング制限です。

外部サービスを呼ぶ場合

@futureメソッドの中からHTTPコールアウトをする場合は、callout=trueを付けます。付け忘れると、実行時に例外になります。

public with sharing class ExternalNotifier {
    @future(callout=true)
    public static void notifyExternalService(List<Id> accountIds) {
        HttpRequest req = new HttpRequest();
        req.setEndpoint('callout:External_Service/notify');
        req.setMethod('POST');
        new Http().send(req);
    }
}

外部サービスを呼び出すfutureメソッドには、callout=trueを付けます。

エンドポイントは指定ログイン情報(Named Credential)を指すのが安全です。URLやトークンをコードへ直接書きません。

Queueableとの使い分け

観点@futureQueueable
引数プリミティブ型とそのコレクションだけsObjectやカスタムApex型も渡せます
ジョブの状況取得できませんSystem.enqueueJobの戻り値でジョブIDを取得し、追跡できます
連鎖実行できません実行中のジョブから次のジョブを開始できます
ヒープサイズなどの上限非同期Apexの上限(同期Apexより緩和)非同期Apexの上限(同期Apexより緩和)

公式ドキュメントは「Salesforce recommends that you use Queueable Apex instead of Apex future methods」と明記しており、新規に書く非同期処理は、@futureではなくQueueableを第一候補にするという立場です。既存の@futureをそのまま置き換える判断は、影響範囲を確認してから進めます。

ここで間違えやすい

間違い何が起きるか
@futureメソッドの引数にsObjectを渡すコンパイルエラーになります
トリガでレコードごとに@futureを呼ぶ51件以上の一括登録で、1トランザクションの上限(50回)を超えます
@futureメソッドから別の@futureメソッドを呼ぶ実行時エラーになります
コールアウトのある@futureにcallout=trueを付け忘れる実行時に例外になります

確認した環境

  • 2026年9月 / Salesforce Summer '26(APIバージョン 67.0)時点の公式ドキュメントで、@futureの制約と呼び出し回数の上限、Queueableとの比較を確認しています

まとめ

  • @futureを付けると、呼び出し元はすぐ戻り、処理は後で実行されます。実行タイミングは保証されません
  • 戻り値はvoidのみ、引数はプリミティブ型とそのコレクションだけです。sObjectは渡せません
  • トリガから呼ぶときは、IDをためて1回で呼び出します。レコードごとに呼ぶと呼び出し回数の上限に当たります
  • 1トランザクションで呼べる回数は同期・Queueableで50回、@futureやバッチApexの中では0回です
  • コールアウトをする@futureにはcallout=trueが必要です。新規の非同期処理はQueueableが公式の推奨です

Salesforceの導入・運用についてご相談ください

導入前の検討から、お使いの環境の改修・運用、AIとの連携まで承ります。状況を伺ったうえで、進め方をご提案します。