Salesforce Apex Governor Limits Per Transaction (2026)

Type: ERP Integration System: Salesforce Platform (API v63.0) Confidence: 0.95 Sources: 7 Verified: 2026-03-02 Freshness: evolving

TL;DR

System Profile

This card covers Salesforce Apex governor limits as enforced by the Salesforce multitenant platform. These limits apply to all Apex code execution -- triggers, classes, Visualforce controllers, Lightning components, @future methods, Batch Apex, Queueable Apex, and Scheduled Apex. Limits apply uniformly across Enterprise, Unlimited, Performance, and Developer editions; edition differences affect org-level API call quotas (covered separately) but not per-transaction governor limits.

PropertyValue
VendorSalesforce
SystemSalesforce Platform (Spring '26, API v63.0)
API SurfaceApex Runtime (triggers, classes, VF, Lightning, async Apex)
Current API Versionv63.0 (Spring '26)
Editions CoveredEnterprise, Unlimited, Performance, Developer
DeploymentCloud
API DocsExecution Governors and Limits
StatusGA

Rate Limits & Quotas

Per-Transaction Governor Limits (Core)

Limit TypeSynchronousAsynchronousNotes
SOQL queries100200Includes queries from triggers, flows, process builders in same context
Records retrieved by SOQL50,00050,000Use LIMIT clause or queryMore/Database.getQueryLocator for larger sets
SOSL queries2020Each query returns max 2,000 records
Records retrieved by SOSL2,0002,000Per single SOSL query
DML statements150150Each insert/update/delete/undelete counts as 1, regardless of record count
Records processed by DML10,00010,000Total across all DML statements in the transaction
CPU time10,000 ms60,000 msExceeded = System.LimitException, transaction aborted
Heap size6 MB12 MBCollections, deserialized JSON, SOQL results all consume heap
HTTP callouts100100Includes REST, SOAP, and any external HTTP request
Callout cumulative timeout120 seconds120 secondsTotal across all callouts in the transaction
Default callout timeout10 seconds10 secondsConfigurable per request up to 120 seconds
Future method calls (@future)500 (batch/future) / 1 (queueable)Cannot call @future from @future or batch
Queueable jobs (System.enqueueJob)501Queueable can chain to 1 more queueable
sendEmail invocations1010Per Messaging.sendEmail call
Push notification method calls1010Each call can send up to 2,000 notifications
Maximum execution time10 minutes10 minutesHard timeout regardless of CPU usage
Trigger recursion depth1616Maximum nested trigger invocations

Certified Managed Package Limits

Certified managed packages (AppExchange) receive their own separate governor limit pools in addition to the org's native code limits. [src1]

Limit TypeAdditional Allocation (per package)
SOQL queries+100 (sync) / +100 (async)
DML statements+150
Callouts+100
sendEmail+10
Database.getQueryLocator records+10,000
SOSL queries+20

Org-Level Async Apex Limits (24-hour rolling)

Limit TypeValueNotes
Asynchronous Apex executions250,000 or (user licenses x 200), whichever is greaterShared across batch, queueable, scheduled, future
Concurrent batch jobs (queued + active)5Additional jobs queue until slot opens
Scheduled Apex classes100 (Developer: 5)
Synchronous concurrent transactions (long-running)10Transactions running >5 seconds
Batch Apex Database.QueryLocator records50,000,000Per batch job
Batch size (Database.executeBatch)200 records (default)Configurable 1-2,000

Org-Level API Request Limits (24-hour rolling)

These are NOT governor limits -- they are org-level quotas on API calls. Included because agents frequently confuse them. [src3]

EditionBase API Calls/24hPer User LicensePer Platform LicenseSandbox
Developer15,000------
Enterprise100,000+1,000+1,0005,000,000
Unlimited100,000+5,000+5,0005,000,000
Performance100,000+5,000+5,0005,000,000

Formula: base + (user_licenses x per_license) + purchased_add_ons

Constraints

Integration Pattern Decision Tree

START -- Developer hitting or worried about governor limits
|-- Which limit is being hit?
|   |-- SOQL queries (100 sync / 200 async)
|   |   |-- SOQL inside a loop?
|   |   |   |-- YES -> Bulkify: query ONCE before loop, use Map<Id, SObject> for lookups
|   |   |   +-- NO -> Check for cascading triggers consuming SOQL budget
|   |   +-- Need >50,000 rows?
|   |       |-- YES -> Use Database.getQueryLocator in Batch Apex (50M record limit)
|   |       +-- NO -> Add LIMIT clause, use selective filters (indexed fields)
|   |-- DML statements (150)
|   |   |-- DML inside a loop?
|   |   |   |-- YES -> Collect records in a List, single DML outside loop
|   |   |   +-- NO -> Check for process builders/flows adding DML statements
|   |   +-- Hitting 10,000 DML rows?
|   |       +-- Move to Batch Apex (processes records in chunks of 200)
|   |-- CPU time (10,000 ms sync)
|   |   |-- Complex logic in trigger?
|   |   |   |-- YES -> Move heavy processing to @future or Queueable
|   |   |   +-- NO -> Profile with Limits.getCpuTime() to find hotspot
|   |   +-- Need >10s processing?
|   |       +-- Use Batch Apex (60,000 ms per execute()) or Platform Events
|   |-- Heap size (6 MB sync)
|   |   |-- Large SOQL result set?
|   |   |   |-- YES -> Use FOR loop on SOQL (streaming), or reduce fields in SELECT
|   |   |   +-- NO -> Check for large String/JSON operations
|   |   +-- Need >6 MB?
|   |       +-- Move to async context (12 MB) or Batch Apex
|   +-- Callouts (100 per transaction)
|       |-- Callouts in a trigger?
|       |   |-- YES -> MUST use @future or Queueable (triggers block callouts)
|       |   +-- NO -> Batch external calls, use Composite API to reduce count
|       +-- Need >100 callouts?
|           +-- Use Batch Apex with Database.AllowsCallouts
+-- General approach
    |-- < 200 records -> Synchronous trigger/class (standard limits)
    |-- 200-10,000 records -> Queueable Apex (async limits, chainable)
    |-- > 10,000 records -> Batch Apex (50M record ceiling)
    +-- Real-time notifications -> Platform Events (separate transaction context)

Quick Reference

OperationSync LimitAsync LimitHow to Check Remaining
SOQL queries issued100200Limits.getQueries() / Limits.getLimitQueries()
SOQL rows retrieved50,00050,000Limits.getQueryRows() / Limits.getLimitQueryRows()
DML statements issued150150Limits.getDmlStatements() / Limits.getLimitDmlStatements()
DML rows processed10,00010,000Limits.getDmlRows() / Limits.getLimitDmlRows()
CPU time consumed10,000 ms60,000 msLimits.getCpuTime() / Limits.getLimitCpuTime()
Heap size used6 MB12 MBLimits.getHeapSize() / Limits.getLimitHeapSize()
Callouts made100100Limits.getCallouts() / Limits.getLimitCallouts()
Future calls made500-1Limits.getFutureCalls() / Limits.getLimitFutureCalls()
Queueable jobs added501Limits.getQueueableJobs() / Limits.getLimitQueueableJobs()
SOSL queries issued2020Limits.getSoslQueries() / Limits.getLimitSoslQueries()
Email invocations1010Limits.getEmailInvocations() / Limits.getLimitEmailInvocations()

Code Examples

Apex: Checking Governor Limit Consumption at Runtime

// Input:  Running Apex code that needs to monitor limit consumption
// Output: Debug log with current consumption vs limits

public class GovernorLimitChecker {
    public static void logLimits(String context) {
        System.debug('=== Governor Limits [' + context + '] ===');
        System.debug('SOQL queries: ' + Limits.getQueries() + ' / ' + Limits.getLimitQueries());
        System.debug('SOQL rows: ' + Limits.getQueryRows() + ' / ' + Limits.getLimitQueryRows());
        System.debug('DML statements: ' + Limits.getDmlStatements() + ' / ' + Limits.getLimitDmlStatements());
        System.debug('DML rows: ' + Limits.getDmlRows() + ' / ' + Limits.getLimitDmlRows());
        System.debug('CPU time: ' + Limits.getCpuTime() + ' ms / ' + Limits.getLimitCpuTime() + ' ms');
        System.debug('Heap size: ' + Limits.getHeapSize() + ' / ' + Limits.getLimitHeapSize());
        System.debug('Callouts: ' + Limits.getCallouts() + ' / ' + Limits.getLimitCallouts());
        System.debug('Future calls: ' + Limits.getFutureCalls() + ' / ' + Limits.getLimitFutureCalls());
    }

    public static Boolean canAffordQuery(Integer needed) {
        return (Limits.getLimitQueries() - Limits.getQueries()) >= needed;
    }

    public static Boolean canAffordDml(Integer needed) {
        return (Limits.getLimitDmlStatements() - Limits.getDmlStatements()) >= needed;
    }
}

Apex: Bulkified Trigger Pattern (Avoids SOQL/DML in Loops)

// Input:  Trigger on Account -- up to 200 records per batch
// Output: Related Contacts updated without hitting governor limits

trigger AccountTrigger on Account (after update) {
    Set<Id> accountIds = new Set<Id>();
    for (Account acc : Trigger.new) {
        Account oldAcc = Trigger.oldMap.get(acc.Id);
        if (acc.BillingCity != oldAcc.BillingCity) {
            accountIds.add(acc.Id);
        }
    }

    if (accountIds.isEmpty()) return;

    // 1 SOQL query regardless of batch size (uses 1 of 100)
    List<Contact> contactsToUpdate = [
        SELECT Id, MailingCity, AccountId
        FROM Contact
        WHERE AccountId IN :accountIds
    ];

    for (Contact c : contactsToUpdate) {
        c.MailingCity = Trigger.newMap.get(c.AccountId).BillingCity;
    }

    // 1 DML statement regardless of record count (uses 1 of 150)
    if (!contactsToUpdate.isEmpty()) {
        update contactsToUpdate;
    }
}

cURL: Check Org-Level API Limits

# Input:  Valid access token and instance URL
# Output: JSON with current API usage and remaining limits

# Check org-level API usage via REST API
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \
  "$INSTANCE_URL/services/data/v63.0/limits" | jq '.DailyApiRequests'

# Expected output:
# { "Max": 100000, "Remaining": 98500 }

# Check all org limits (async Apex, bulk API, etc.)
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \
  "$INSTANCE_URL/services/data/v63.0/limits" | jq '.'

Anti-Patterns

Wrong: SOQL Query Inside a Loop

// BAD -- consumes 1 SOQL per record. 200 records = 200 SOQL = LimitException
for (Account acc : Trigger.new) {
    List<Contact> contacts = [SELECT Id FROM Contact WHERE AccountId = :acc.Id];
    // Process contacts...
}

Correct: Single Query with Collection Filter

// GOOD -- 1 SOQL regardless of batch size
Map<Id, List<Contact>> contactsByAccount = new Map<Id, List<Contact>>();
for (Contact c : [SELECT Id, AccountId FROM Contact WHERE AccountId IN :Trigger.newMap.keySet()]) {
    if (!contactsByAccount.containsKey(c.AccountId)) {
        contactsByAccount.put(c.AccountId, new List<Contact>());
    }
    contactsByAccount.get(c.AccountId).add(c);
}
for (Account acc : Trigger.new) {
    List<Contact> contacts = contactsByAccount.get(acc.Id);
    // Process contacts...
}

Wrong: DML Inside a Loop

// BAD -- 200 records = 200 DML statements. Exceeds 150 DML limit.
for (Contact c : contactsToUpdate) {
    c.MailingCity = 'New York';
    update c; // 1 DML per iteration
}

Correct: Collect and Batch DML

// GOOD -- 1 DML statement regardless of collection size
List<Contact> toUpdate = new List<Contact>();
for (Contact c : contactsToUpdate) {
    c.MailingCity = 'New York';
    toUpdate.add(c);
}
update toUpdate; // 1 DML, even for 10,000 records

Wrong: Synchronous Callouts in Triggers

// BAD -- callouts are blocked in trigger context; throws CalloutException
trigger OrderTrigger on Order (after insert) {
    for (Order o : Trigger.new) {
        Http h = new Http();
        HttpRequest req = new HttpRequest();
        req.setEndpoint('https://erp.example.com/api/orders');
        req.setMethod('POST');
        HttpResponse res = h.send(req); // CalloutException!
    }
}

Correct: Delegate Callouts to @future or Queueable

trigger OrderTrigger on Order (after insert) {
    Set<Id> orderIds = new Set<Id>();
    for (Order o : Trigger.new) {
        orderIds.add(o.Id);
    }
    OrderCalloutService.syncToErp(orderIds);
}

public class OrderCalloutService {
    @future(callout=true)
    public static void syncToErp(Set<Id> orderIds) {
        List<Order> orders = [SELECT Id, OrderNumber, TotalAmount FROM Order WHERE Id IN :orderIds];
        Http h = new Http();
        HttpRequest req = new HttpRequest();
        req.setEndpoint('https://erp.example.com/api/orders');
        req.setMethod('POST');
        req.setBody(JSON.serialize(orders));
        req.setTimeout(30000);
        HttpResponse res = h.send(req);
    }
}

Common Pitfalls

Diagnostic Commands

// Check current governor limit consumption in Execute Anonymous
System.debug('SOQL: ' + Limits.getQueries() + '/' + Limits.getLimitQueries());
System.debug('SOQL Rows: ' + Limits.getQueryRows() + '/' + Limits.getLimitQueryRows());
System.debug('DML: ' + Limits.getDmlStatements() + '/' + Limits.getLimitDmlStatements());
System.debug('DML Rows: ' + Limits.getDmlRows() + '/' + Limits.getLimitDmlRows());
System.debug('CPU: ' + Limits.getCpuTime() + 'ms/' + Limits.getLimitCpuTime() + 'ms');
System.debug('Heap: ' + Limits.getHeapSize() + '/' + Limits.getLimitHeapSize());
System.debug('Callouts: ' + Limits.getCallouts() + '/' + Limits.getLimitCallouts());
System.debug('Future: ' + Limits.getFutureCalls() + '/' + Limits.getLimitFutureCalls());
System.debug('Queueable: ' + Limits.getQueueableJobs() + '/' + Limits.getLimitQueueableJobs());
# Query Apex execution logs for limit violations (via Tooling API)
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \
  "$INSTANCE_URL/services/data/v63.0/tooling/query/?q=SELECT+Id,Operation,Status,LogLength+FROM+ApexLog+WHERE+Status='Error'+ORDER+BY+SystemModstamp+DESC+LIMIT+10"

Version History & Compatibility

API VersionReleaseStatusGovernor Limit ChangesNotes
v63.0Spring '26 (Feb 2026)CurrentNo governor limit changesNamed Query API GA
v62.0Winter '26 (Oct 2025)SupportedNo governor limit changes--
v61.0Summer '25 (Jun 2025)SupportedNo governor limit changes--
v60.0Spring '25 (Feb 2025)SupportedNo governor limit changes--
v59.0Winter '25 (Oct 2024)SupportedNo governor limit changes--
v58.0Spring '24 (Feb 2024)SupportedCPU time measurement methodology updatedLast significant limit-related change

When to Use / When Not to Use

Use WhenDon't Use WhenUse Instead
Writing Apex triggers, classes, or controllersNeed org-level API call quotas per editionSalesforce API request limits reference
Debugging LimitException errors in productionNeed Bulk API batch size and file limitsSalesforce Bulk API 2.0 reference
Designing trigger architecture for high-volume objectsNeed authentication flow guidanceSalesforce auth flows reference
Planning async processing strategy (batch vs queueable)Need NetSuite or SAP transaction limitsSystem-specific governor limits card
Reviewing AppExchange package limit isolationNeed to understand Flow-specific limits (not Apex)Salesforce Flow limits reference

Important Caveats

Related Units