Salesforce Winter '27: 9 Developer Features That Actually Matter

Salesforce Winter 27 Updates

Salesforce Winter ’27 ships API version 68.0. The release notes were published on 19 August 2026, sandbox preview began in late August 2026, and production instances upgrade across weekends in early September and October 2026 depending on your instance. (Coverage dates these weekends inconsistently because Salesforce Trust maintenance windows run from Friday evening into Saturday. Look up your own instance on Salesforce Trust rather than trusting any blog’s calendar, this one included.)

Reading the Winter ’27 release notes end to end is not the same as knowing what to do. The notes tell you a limit moved from 6 MB to 25 MB. They do not tell you which of your batch jobs were shrunk to a scope size of 50 three years ago to work around that limit, or whether it is safe to put them back. They tell you FORMULA() exists in SOQL. They do not tell you that it is Beta, sandbox-only, and supports exactly two arithmetic operators.

This article covers the nine Winter ’27 developer features with real consequences for how you build and maintain Salesforce applications, with the availability status, the constraints, and the parts that will bite you.

Quick Summary: Salesforce Winter ’27 Developer Features

#FeatureStatus in Winter '27Requires
1Higher Apex heap limits (6→10 MB sync, 12→25 MB async)GA, automaticNothing. Applies on org upgrade
2FORMULA() in SOQL WHEREBetaAPI 68.0+, sandbox / Dev Edition / scratch org only
3Complex template expressions in LWCStatus changed in Winter '27, verify in your orgComponent apiVersion 66.0 or later
4Salesforce development plugin for Claude CodeAvailable (tooling, not a platform feature)Claude Code, Node.js LTS, Salesforce CLI, Python 3.8+
5/services/data/latest/ REST endpointGAREST API
6Elastic limits extended to Batch ApexBetaOrg setting in Apex Settings
7Real HTTP callouts in Apex integration testsDeveloper PreviewScratch orgs only, ApexIntegrationTests feature
8Apex Symbol APIBetaAPI 68.0+, Author Apex and View Setup permissions
9Recompile only invalid Apex classes and triggersGAAPI 68.0+, Author Apex permission

1. Higher Apex Heap Limits

What changed in Apex heap limits in Salesforce Winter ’27?


Winter ’27 raises the Apex heap limit from 6 MB to 10 MB for synchronous transactions and from 12 MB to 25 MB for asynchronous transactions.
 The change is generally available, applies automatically when your org upgrades, and requires no configuration or opt-in.

The asymmetry matters more than the numbers. No other per-transaction governor limit moved with it. SOQL queries are still 100 synchronous and 200 asynchronous. DML statements are still 150. CPU time is still 10 seconds synchronous and 60 seconds asynchronous. Total query rows is still 50,000. Only the memory ceiling changed.

What this actually changes in your codebase

Most mature orgs contain a handful of what you might call limit settlements: a batch scope reduced from 200 to 50, a Map split into two passes, a callout response truncated before deserialization. Each was a fix made under time pressure, and each permanently costs throughput.

Winter ’27 makes some of those settlements reversible. Not all of them. The diagnostic question is whether the original workaround was protecting heap and only heap. If it was also protecting CPU time, query rows, or DML count, the constraint has not moved and reverting will simply relocate the failure.

Before you touch anything, measure. Both of these are existing Limits methods, not new in Winter ’27:

// Run in anonymous Apex in production AND in each sandbox you deploy from.
System.debug('Heap ceiling: ' + Limits.getLimitHeapSize());
System.debug('Heap used: '    + Limits.getHeapSize());

If the two orgs report different ceilings, you have a deployment hazard (see below).

The second thing worth changing is how guard clauses are written.

Before, a threshold hardcoded against a ceiling that no longer exists:

public void execute(Database.BatchableContext bc, List<Order__c> scope) {
    for (Order__c o : scope) {
        if (Limits.getHeapSize() > 9000000) { // 75% of the old 12 MB async cap
            System.enqueueJob(new OrderSyncQueueable(remaining(scope, o)));
            return;
        }
        accumulate(o);
    }
}

After, derived at runtime so it follows the org:

public void execute(Database.BatchableContext bc, List<Order__c> scope) {
    Long threshold = (Long)(Limits.getLimitHeapSize() * 0.75);
    for (Order__c o : scope) {
        if (Limits.getHeapSize() > threshold) {
            System.enqueueJob(new OrderSyncQueueable(remaining(scope, o)));
            return;
        }
        accumulate(o);
    }
}

Grep your codebase for literal 6000000 and 12000000 values. Those are now wrong in every upgraded org, and they will stay wrong the next time the limit moves.

The deployment trap developers will hit

A Winter ’27 sandbox enforces 10 MB and 25 MB. A production org that has not yet reached its upgrade weekend still enforces 6 MB and 12 MB. Code written and tested against the higher ceiling compiles, passes tests, and deploys cleanly, then throws System.LimitException: Apex heap size too large in production on the first real payload.

Salesforce shipped a control for exactly this. In Setup → Apex Settings, non-production orgs (sandboxes, scratch orgs, Developer Edition) have a checkbox labelled Enforce the Summer ’26 Apex heap limit. Enabling it keeps that org on the old 6 MB and 12 MB ceilings so anything built there is safe to deploy into an org that has not upgraded. Salesforce’s Apex product team has described the setting as a transitional mechanism, and it becomes unnecessary once every org in your deployment chain has upgraded.

What to test

  • Run Limits.getLimitHeapSize() in every org in your pipeline and record the values.
  • Enable Enforce the Summer ’26 Apex heap limit in preview sandboxes until production has upgraded.
  • Test any reverted workaround at realistic volume. A test class that passes with 20 records proves nothing about a limit measured in megabytes.
  • Watch CPU time when you increase batch scope. Heap and CPU usually fail together, and only one of them moved.

When not to use the extra headroom

Do not treat 25 MB as license to hold entire datasets in memory. Heap is per transaction, not per job. A batch job that processes 500,000 records across 2,500 chunks still needs each chunk to be bounded. Using the new ceiling as a substitute for bulkification produces code that works today and fails the next time record sizes grow.

2. FORMULA() in SOQL

How does FORMULA() work in SOQL?


FORMULA()
 lets you evaluate an arithmetic expression across fields directly inside a SOQL WHERE clause, so you can filter on a computed value without creating a formula field or post-filtering in Apex.
 In Winter ’27 it is a Beta feature, available only in sandboxes, Developer Edition orgs, and scratch orgs on API version 68.0 or later. It is not available in production.

The syntax:

WHERE FORMULA('<expression>') <operator> <literal>

The problem it solves

Filtering on a derived value has always meant choosing between three bad options: add a formula field you only need for one query, duplicate the rule in Apex and query a wider candidate set, or maintain both. For ISVs the problem is worse, because you cannot add fields to a subscriber’s schema.

Before, the rule lives in Apex after a wider query:

List<Order__c> allOrders = [
    SELECT Id, Name, Revenue__c, Cost__c
    FROM Order__c
    WHERE Status__c = 'Shipped'
];

List<Order__c> highProfit = new List<Order__c>();
for (Order__c o : allOrders) {
    if (o.Revenue__c != null && o.Cost__c != null
        && (o.Revenue__c - o.Cost__c) > 250) {
        highProfit.add(o);
    }
}

After, the rule lives in the query:

SELECT Id, Name, Revenue__c, Cost__c
FROM Order__c
WHERE Status__c = 'Shipped'
AND FORMULA('Revenue__c - Cost__c') > 250

Date arithmetic works the same way. To find orders that shipped more than three days after the order date:

SELECT Id, Name, OrderDate__c, ShipDate__c
FROM Order__c
WHERE FORMULA('ShipDate__c - OrderDate__c') > 3

And it composes with ordinary field filters:

SELECT Id, Name, Revenue__c
FROM Order__c
WHERE Revenue__c > 600
AND FORMULA('ShipDate__c - OrderDate__c') <= 2

Limitations you need to know before designing around it

  • Supported in the WHERE clause only, not HAVING.
  • The expression supports addition and subtraction (+ and -) only. No multiplication, division, or function calls. A margin percentage filter is not expressible.
  • Expressions can evaluate to DOUBLEINTEGERDATETIMEDATE, and CURRENCY. In practice INTEGER behaves like DOUBLE and DATE behaves like DATETIME.
  • Beta status means it is governed by Salesforce’s Beta Services Terms and carries no guarantee of reaching GA in any particular timeframe, or at all.

When to still prefer a formula field or Apex

Use a formula field when the same derived value is needed in reports, list views, validation rules, or Flow, or when the calculation involves multiplication, division, or conditional logic. Use Apex filtering when the rule genuinely needs branching or lookups that arithmetic cannot express. Reach for FORMULA() when the rule is query-only, simple additive or subtractive arithmetic, and you would otherwise be adding schema you do not want.

The realistic Winter ’27 posture: prototype with it in a scratch org, keep the equivalent Apex or formula field in the production path, and revisit when Salesforce announces GA.

3. Complex Template Expressions in LWC


What are complex template expressions in LWC?

 

Complex template expressions let a Lightning Web Component evaluate a broad subset of JavaScript directly inside template curly braces, instead of requiring a getter in the JavaScript class for every derived display value. The capability arrived in Spring ’26 (API version 66.0) as a beta feature. Its status was reported to change in Winter ’27, and this is the one item on this list where you should verify against the release-specific documentation for your own org before relying on it: the general Lightning Web Components Developer Guide page still carries the beta notice and an explicit instruction not to use the feature in production. Treat the status as org-specific and version-specific until you have confirmed it.

Enablement is per component, not global. Set apiVersion to 66.0 or later in the component’s .js-meta.xml:

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>68.0</apiVersion>
    <isExposed>true</isExposed>
</LightningComponentBundle>

Before and after

 

Before, boilerplate getters:

import { LightningElement, api } from 'lwc';

export default class ContactCard extends LightningElement {
    @api contact;

    get fullName() {
        return `${this.contact.FirstName} ${this.contact.LastName}`;
    }
    get statusLabel() {
        return this.contact.IsActive ? 'Active' : 'Inactive';
    }
    get formattedRevenue() {
        return `$${this.contact.Revenue.toFixed(2)}`;
    }
}

After:

<template>
    <div>{`${contact.FirstName} ${contact.LastName}`}</div>
    <div>{contact.IsActive ? 'Active' : 'Inactive'}</div>
    <div>{`$${contact.Revenue.toFixed(2)}`}</div>
</template>

Three getters removed from the class.

The case that actually matters: iterations

The genuinely awkward pre-existing pattern was formatting inside for:each. A getter has no way of knowing which item it is being called for, so developers had to map over their data in JavaScript first and attach display values to each record before binding it.

Template expressions remove that mapping step. You bind the raw data and compute in the template:

<template>
    <template for:each={orders} for:item="order">
        <li key={order.Id}>
            {order.Name}: {`$${(order.Revenue - order.Cost).toFixed(2)}`}
            <span>{order.Status === 'Shipped' ? '✓' : '…'}</span>
        </li>
    </template>
</template>

Supported expression types

Per the Lightning Web Components Developer Guide, the supported subset is wide: literals (string, numeric, boolean, null), template literals including tagged templates, member expressions, ternary operators, logical && and ||, unary operators (!~typeofvoid), all binary arithmetic, relational and bitwise operators, function calls, optional call expressions, array and object expressions, optional chaining and nullish coalescing, computed properties via bracket notation, arrow functions (including assignment inside them), and use within iterators and directives such as if:true.

Constraints that will trip you up

Templates are still parsed as HTML. The < character in a text node looks like the start of a tag:

<!-- Breaks: parser sees an opening tag -->
<div>{age < 18}</div>

<!-- Works: invert the comparison -->
<div>{18 > age}</div>

<!-- Works in attributes when quoted -->
<div data-minor="{age < 18}"></div>

Complex expressions in attributes must be surrounded by double quotes, or the LWC compiler throws an error.

When getters are still the right answer

Move logic back into JavaScript when the expression is used in more than one place, when it needs a unit test of its own, when it exceeds roughly one readable line, or when it performs work you would not want re-evaluated on every render. Salesforce’s own guidance suggests putting formatting functions in a shared API module component and importing them, rather than inlining increasingly baroque expressions. A template with three nested ternaries and a reduce() is harder to maintain than the getter it replaced, and it is invisible to your JavaScript test suite.

4. Salesforce Development Plugin for Claude Code

What does the Salesforce Claude Code plugin do?


The salesforce-development plugin is Salesforce’s first official plugin for Claude Code, Anthropic’s command-line coding agent. It bundles Salesforce development skills, specialised agents, three MCP servers, hooks, and commands into a single install so the agent operates with real project and org context instead of generic training knowledge.
 It was announced on the Salesforce Developers blog on 19 August 2026.

Worth stating plainly: this is ecosystem tooling that landed alongside Winter ’27, not a platform feature delivered by API 68.0.

What is in it

  • Roughly 40 skills drawn from the open-source Salesforce skills library, covering core Salesforce development. Data 360, Commerce, and mobile skills are deliberately excluded.
  • Specialised agentssalesforce-dev auto-detects DX projects and routes requests through skills, then Salesforce CLI, then Salesforce APIs, in that priority order. architecture-review is read-only and grades a project against the Well-Architected pillars. Agentforce Development Lifecycle agents handle agent authoring and testing.
  • Three MCP serverssalesforce-api-context (API and metadata guidance), salesforce-metadata-experts (metadata type knowledge), and salesforce-lsp, which hosts local Apex and SOQL language servers and exposes diagnostics, completions, and code actions as MCP tools. That last one matters most: it gives the agent real-time Apex syntax validation from the terminal, comparable to what Salesforce Extensions for VS Code provides in the editor.
  • Hooks: DX project detection on startup or directory change, Agent Script validation on .agent edits, and deployment result validation after deploy commands.
  • Commands including /setup/discovery/org/login, and /logout.

Setup

Prerequisites are Claude Code, Node.js LTS (v22 or v24 at time of writing), Salesforce CLI, and Python 3.8 or above (currently needed by internal hooks; Salesforce has indicated this dependency may be removed later).

sf org login web --alias my-org --set-default

Then, inside Claude Code:

/plugin marketplace add forcedotcom/sf-skills
/plugin install salesforce-development@salesforce
/salesforce-development:setup

Install commands have varied across third-party coverage of this plugin. Use the ones in the official Salesforce Developers documentation and the plugin repository.

Documented example prompts include generating an Apex service class for a stated business purpose, generating a custom object with named fields, deploying a project to an org, and writing and running a test class for an existing Apex class.

The pairing worth noticing is with the Apex Symbol API covered in section 8. An agent that can query compiler-verified type information is materially less likely to invent a method signature that does not exist.

Code review and security considerations

The plugin can deploy metadata to a connected org. That is the whole point, and it is also the risk. Treat agent output as an untrusted pull request:

  • Authenticate the agent to a scratch org or developer sandbox, never a production org.
  • Every generated Apex class goes through normal code review. Generated tests in particular tend to assert that code ran, not that it produced correct results.
  • Check with sharing / without sharing declarations and user-mode versus system-mode database operations explicitly. Note that in API version 67.0 and later, Apex database operations default to user mode.
  • Do not let generated code bypass your static analysis, PMD ruleset, or security review gates.
  • Assume anything in your prompt context, including sample records, may leave your environment. Do not paste production data.

5. Simplified REST API Version Management


What changed in Salesforce REST API version management?


Winter ’27 supports latest in place of a version number in REST API URIs. /services/data/latest/<resource> automatically resolves to the most recently released API version.
 This is documented in the REST API Developer Guide for version 68.0.

Before:

https://<MyDomainName>.my.salesforce.com/services/data/v67.0/sobjects/Account

After:

https://<MyDomainName>.my.salesforce.com/services/data/latest/sobjects/Account

You can enumerate available versions through the Versions resource.

Who this helps, and who it will hurt

For exploration, scripting, local tooling, and sandbox testing this removes real friction. Every seasonal release currently forces a decision: sweep version numbers across dozens of scripts, or drift onto an increasingly old version.

For production integrations, latest is a liability rather than a convenience. API versioning exists precisely so that Salesforce can change response shapes and default behaviours between versions without breaking existing callers. An endpoint that silently changes version on your upgrade weekend converts a controlled migration into an unannounced one, and the failure mode is quiet: a field that changed serialisation, a default that flipped, a response your parser handles slightly wrong.

The Winter ’27 default behaviour change for Apex database operations is a good illustration of why version pinning matters. In API version 67.0 and later, Apex database operations run in user mode by default, where 66.0 and earlier defaulted to system mode. Version numbers carry semantics, not just recency.

Recommendation: pin explicit versions in production integrations. Use latest in exploratory tooling, sandbox scripts, and CI environments where a break is visible immediately.

The version retirement context integration teams should track

Separately from this feature, SOAP, REST, and Bulk API versions 31.0 through 40.0 are scheduled for deprecation in the Summer ’27 release and retirement in Summer ’28, at which point REST returns 410: GONE. Versions 21.0 through 30.0 were already retired in Summer ’25. Winter ’27 also adds compiler warnings for Apex saved at API versions 9.0 through 19.0. If your integration inventory contains anything in these ranges, that is a higher-priority item than adopting latest.

6. Elastic Limits for Batch Apex (Beta)

How does Elastic Limits affect Batch Apex?


Elastic limits let an org keep enqueuing asynchronous Apex jobs beyond its standard rolling 24-hour limit, up to a higher elastic limit, with throttled processing instead of outright failure. In Winter ’27 the Beta extends this from Queueable Apex and future methods to Batch jobs.

Before Winter ’27, elastic limits applied only to Queueable Apex and future methods in production and demo orgs. Batch Apex and scheduled jobs remained hard-capped at the rolling 24-hour asynchronous job limit, which meant an org in an overage situation saw batch jobs fail outright.

How the limit is calculated

DailyAsyncApexElasticExecutions = DailyAsyncApexExecutions
                                + min(licensed DailyAsyncApexExecutions, 2,000,000)

The licensed rolling 24-hour asynchronous job limit is defined as 250,000 jobs or 200 times the number of applicable user licenses, whichever is greater. The additional capacity beyond your standard rolling 24-hour limit is capped at either your org’s licensed asynchronous Apex job limit or 2 million jobs, whichever is lower.

So an org with a 250,000 rolling limit and a 250,000 licensed limit gets an elastic limit of 500,000 (250,000 + min(250,000, 2,000,000)). An org with a 12 million licensed limit gets an elastic limit of 14 million (12,000,000 + min(12,000,000, 2,000,000)), because the second term is capped at 2 million.

One correction worth flagging if you have read about this feature elsewhere: earlier documentation capped the additional capacity at 10 million jobs. Salesforce revised that figure down as part of the Winter ’27 release notes update published the week of 24 August 2026. The 10 million number is retired.

If an org reaches both the rolling 24-hour limit and the elastic limit, exceptions are thrown for jobs enqueued beyond the elastic limit. Elastic limits raise the ceiling; they do not remove it.

Throttling behaviour, which is the part that changes your architecture

When executions over the rolling 24-hour window exceed the daily limit, Salesforce processes additional enqueued jobs at a throttled rate of one concurrent job per asynchronous Apex type. For Batch specifically, the system throttles the processing rate of in-flight Batch jobs and limits new Batch jobs to one active job at a time. Normal concurrency resumes only once executions in the trailing 24 hours fall back below the daily limit.

Read that carefully before treating this as free capacity. Jobs complete rather than fail, but they complete slowly and serially. A nightly window that assumed four concurrent batch jobs will not fit into the same window under throttling. If you have SLAs expressed in wall-clock time, throttled success can be operationally indistinguishable from failure.

Enabling and monitoring

Enable in Setup → Apex Settings → Use elastic limits for asynchronous Apex jobs (Beta).

The Apex Jobs page in Setup shows a banner with jobs processed in the trailing 24 hours against both limits, plus whether processing is currently throttled. Programmatically:

Map<String, System.OrgLimit> limitsMap = OrgLimits.getMap();
System.OrgLimit dailyLimit   = limitsMap.get('DailyAsyncApexExecutions');
System.OrgLimit elasticLimit = limitsMap.get('DailyAsyncApexElasticExecutions');

System.debug('Enqueued (24h): '  + dailyLimit.getValue());
System.debug('Daily ceiling: '   + dailyLimit.getLimit());
System.debug('Elastic ceiling: ' + elasticLimit.getLimit());

Two gotchas: if the setting is not enabled, OrgLimits.getMap() does not return a DailyAsyncApexElasticExecutions key at all, so null-check before dereferencing. And getValue() returns jobs enqueued in the last 24 hours, not executed. Throttling triggers on executions, so an alert built naively on getValue() fires early.

To rehearse throttling without a production-scale workload, Winter ’27 adds asyncApexExecutionsOverride to the ApexSettings metadata type (API version 68.0 and later, non-production orgs only). It sets an override below the licensed rolling 24-hour limit, which is the intended way to test elastic behaviour without generating hundreds of thousands of jobs.

When not to enable it

Elastic limits are a resilience mechanism for orgs that occasionally spike past their asynchronous ceiling, not a capacity plan. If your org routinely exceeds its rolling 24-hour limit, reduce job volume (batch more work per Queueable, consolidate triggers that each enqueue their own job, use Platform Events for fan-out) rather than running permanently throttled. The Beta caveat applies: Salesforce guarantees no GA timeframe.

7. Real HTTP Callouts in Apex Integration Tests (Developer Preview)


What changed in Apex integration testing in Winter ’27?


Apex integration tests can now make real HTTP callouts to any authorised endpoint, including External Services configured through Named Credentials, without an HttpCalloutMock.
 In Summer ’26 (API version 67.0), integration tests existed but callouts were limited to Agentforce and Data 360 services; everything else still required a mock. Winter ’27 removes that restriction and adds a @BeforeClass annotation for test data shared across methods in a class.

This remains a Developer Preview feature, available only in scratch orgs. It is not available in production orgs, sandboxes, or during metadata deployments, and Salesforce reserves the right to change or deprecate it without notice.

The problem it solves

Mock-based tests verify that your code handles a response you wrote yourself. That is useful for branch coverage and useless for catching the failure that actually happens in production: the vendor changed a field from a string to an object, added a nullable property, started returning 204 instead of 200, or tightened an auth requirement. Mocks drift from reality silently, and the drift is invisible until deployment.

Integration tests trade Apex’s automatic rollback for the ability to hit the real endpoint. That trade is the whole design. Because data commits, you own the cleanup.

How it works

Enable the feature in your scratch org definition file:

{
  "orgName": "Company",
  "edition": "Developer",
  "features": ["ApexIntegrationTests"]
}

Then write the class with @IntegrationTest on the class and its test methods:

@IntegrationTest
public with sharing class OrderApiIntegrationTest {

    @BeforeClass
    static void setup() {
        Account a = new Account(Name = 'Integration Test Account');
        insert as user a;
        // Platform auto-commits data set up in @BeforeClass.
        // No explicit commit call is needed here.
    }

    @IntegrationTest
    public static void testRealEndpointReturnsExpectedShape() {
        Account a = [SELECT Id FROM Account
            WHERE Name = 'Integration Test Account'
            WITH USER_MODE];

        Http h = new Http();
        HttpRequest req = new HttpRequest();
        req.setEndpoint('callout:Order_Service/v1/orders');
        req.setMethod('GET');
        HttpResponse res = h.send(req);

        Assert.areEqual(200, res.getStatusCode());
        Map<String, Object> body =
            (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
        Assert.isTrue(body.containsKey('orders'), 'Contract changed: orders key missing');
    }

    @TearDown
    public static void tearDown() {
        delete as user [SELECT Id FROM Account WHERE Name = 'Integration Test Account'];
    }
}

Data set up inside @BeforeClass commits automatically. You do not need to call a manual commit method there. Use @TearDown to clean up committed data after the test class finishes.

Three pieces of machinery matter:

  • @BeforeClass (new in Winter ’27) sets up data shared across methods in the class, and the platform commits that data for you. This matters more now that setup is no longer rolled back for free.
  • IntegrationTest.commitTestOnly() is for committing data created inside a test method itself, mid-transaction, so it is visible to other threads and services. It also resets the uncommitted-work checkpoint so subsequent callouts do not fail. It can only be called from an @IntegrationTest method, and it is unnecessary for data created in @BeforeClass.
  • @TearDown marks a static cleanup method that runs after the test regardless of pass, fail, or exception. The teardown transaction auto-commits. Delete child records before parents to avoid foreign key errors.

Constraints, from the official documentation

ComparisonUnit Tests (@IsTest)Integration Tests (@IntegrationTest)
Transaction behaviourAuto-rollbackData commits; use @TearDown
Data visibilityTest data silo by defaultSeeAllData=true by default
Code coverageCounts toward deployment requirementsDoes not count
Metadata deploymentsIncluded in RunAllTestsExcluded
ExecutionSync or asyncAsync only, 1 concurrent execution per org
Maximum runtimeStandard limits10 minutes

Additional restrictions: no @TestVisible access from @IntegrationTest classes, you cannot mix @IntegrationTest and @IsTest on the same class, integration tests cannot be called from @IsTest methods, asynchronous Apex governor limits apply, and integration tests share the 24-hour asynchronous test run limit with unit tests and flow tests.

When not to use it

Do not migrate your existing test suite. Integration tests do not count toward the 75% code coverage requirement, so they complement unit tests rather than replacing them. Keep isolated business logic, trigger behaviour, and branch coverage in @IsTest classes. Reserve integration tests for contract verification against real services, behaviour that depends on committed data (field history tracking is the canonical example, since rollback means history records are never created in unit tests), and end-to-end paths worth the runtime cost.

The one-concurrent-test-per-org limit is an architectural constraint on your CI design. A suite of twenty integration tests runs serially in one scratch org. Prefer short focused tests over monolithic ones, create your own setup data rather than reusing org data to avoid row lock contention, and budget against the 10-minute ceiling.

8. Apex Symbol API (Beta)

 

What is the Apex Symbol API?

 

The Apex Symbol API is a Tooling API REST resource that returns compiler-grade metadata for built-in, custom, packaged, and dynamic Apex types, including classes, interfaces, enums, methods, and triggers. It is Beta, available in API version 68.0 and later, and requires the Author Apex org permission plus the View Setup user permission.

GET /services/data/v68.0/tooling/symbols/?category=builtin&namespace=System&name=ApexPages

Query parameters:

ParameterRequiredValues
categoryYes builtin (standard types such as System, Database, Messaging), database (custom and packaged types), dynamic
namespaceNo Filters to a namespace. Pass an empty string (namespace=) for the local org namespace only
nameNo Filters to a specific type name. Combines with namespace

The response returns a typeStubs array. Each stub carries namenamespacePrefixkind (CLASSINTERFACEENUMTRIGGER), modifiersannotationssuperClassinterfacesfieldspropertiesmethodsinnerTypestriggerOperationstriggerObjectTypedocumentation, and compileError.

Why this exists and what it replaces

Before Winter ’27, tool builders assembled Apex type information from the Tooling API completions resource and the SymbolTable object, usually with an Apex grammar parser bolted on to fill the gaps. The result was incomplete, and it drifted every time the language changed.

The structural improvement worth noticing is type references. Types are returned as nested objects with namespacePrefixname, and typeParameters rather than as strings, so List<Account> arrives already decomposed instead of as internal encoding a client has to parse. Inherited members carry a definingType reference, so a tool can tell where a method was actually declared.

Practical use cases

Salesforce documents these: code completion with full generic type support, displaying official documentation for built-in types, displaying ApexDoc for custom types, showing constructor signatures with parameter types and modifiers, identifying trigger operations such as before insert without parsing source, and grounding AI agents that generate Apex.

That last one is the reason this API and the Claude Code plugin from section 4 belong in the same release. An agent that queries compiler-verified type information does not need to guess whether Messaging.SingleEmailMessage.setTargetObjectId() exists or what it accepts. If you are building internal AI tooling against your org, this closes the grounding gap that produces plausible-looking code referencing methods that were never there.

Limitations

  • One request per org at a time. The API enforces a concurrency limit; a second concurrent request for the same org fails. Do not parallelise calls, and build retry handling into any tool that polls it.
  • In API version 68.0, ApexDoc comments from managed packages are not returned. Salesforce says packaged ApexDoc for global identifiers is planned for a later version. If you build documentation tooling for an ISV package, this is a real gap today.
  • Beta terms apply, with no guaranteed GA date.
  • The permission requirement (Author Apex plus View Setup) means this is a developer-tooling API, not something to expose to end users or general integration credentials.

9. Recompile Only Invalid Apex Classes and Triggers

What changed?


Winter ’27 adds the apexCompileResults Tooling API resource, which returns compilation results for only those Apex classes and triggers with validation errors, instead of forcing a full-org recompile to find out what is broken.
 It is generally available in API version 68.0 and later and requires the Author Apex org permission.

POST /services/data/v68.0/tooling/apexCompileResults/

The request body must be an empty JSON object ({}). Specifying any fields returns an error. The request is synchronous.

The problem

Invalid Apex accumulates invisibly. A field gets renamed, a managed package upgrades, an API version is retired, and a class that nobody has opened in two years quietly flips to IsValid = false. You typically discover it at the worst moment, during a deployment, because compile-on-deploy recompiles the whole org and surfaces every latent error at once in a release window.

Historically the diagnostic tool was the blunt one: recompile all classes and read the wreckage.

The response

{
  "status": "PARTIAL_FAILURE",
  "results": [
    {
      "name": "MyInvalidClass",
      "namespace": "MyNamespace",
      "success": false,
      "problems": [
        { "line": 14, "column": 9, "message": "Variable does not exist: var1" }
      ],
      "warnings": [
        { "line": 0, "column": 0,
          "message": "Apex API version 18.0 is scheduled for retirement. Update to the latest API version to avoid compile failures." }
      ]
    }
  ]
}

status is either OK (all invalid classes compiled successfully, or none needed recompilation) or PARTIAL_FAILURE. The results array contains only failures; classes that compile cleanly are omitted, and warnings are returned only for classes that also have errors.

Where developers will get this wrong

Read this carefully, because it is the least intuitive part of the feature: neither the API nor the Setup buttons update the IsValid field, even on success. Salesforce’s release notes state it plainly. Successful compilation through the Compile only invalid classes and Compile only invalid triggers buttons, or through the /apexCompileResults endpoint, does not update the corresponding isValid field on the affected Apex classes or triggers. The value stays false.

This makes the feature purely diagnostic. It tells you what is broken and why, but it does not flip a status flag anywhere, in the API or in Setup. If your monitoring or CI pipeline was planning to check IsValid after calling this endpoint to confirm a fix landed, that check will never pass. Treat a clean apexCompileResults response (status: "OK" with an empty results array) as your source of truth that compilation succeeded, not the IsValid field on the class or trigger record.

Where this belongs in your process

The useful pattern is monitoring, not remediation. Run it on a schedule from CI, or as a pre-deployment gate, and treat a PARTIAL_FAILURE as a signal to fix before the release window rather than during it.

It also pairs directly with a Winter ’27 change that will generate noise for older orgs: compiler warnings now appear for Apex saved at API versions 9.0 through 19.0. Those warnings surface through this resource, which makes it a practical way to inventory legacy Apex ahead of retirement deadlines. Note the earlier caveat, though: warnings are returned only for classes that also have compilation errors, so this is not a complete warning inventory on its own.

Additional Winter ’27 Developer Changes Worth Watching

  • SOAP login() now requires the Use Any API Auth user permission. This is a live requirement, not a scheduled retirement. Audit integration users before your upgrade weekend.
  • Namespace shadowing conflicts prevented in dynamic managed Apex SOQL queries.
  • Test Discovery API gains a testLevel query parameter.
  • API Catalog centralises APIs and MCP servers, including MuleSoft, Heroku, and Apex APIs, for use in Agentforce agents and Flows.

Practical Impact for Salesforce Developers

Apex development. Heap is the only Winter ’27 item that alters production behaviour with no opt-in. Because no other governor limit moved, the practical effect is that heap stops being the first constraint you hit and CPU time frequently becomes the new one.

LWC development. Template expressions shift the boundary between markup and class. Used well they delete a category of single-use getter; used badly they move untestable logic into HTML. Adopt per component and agree a team convention for how much expression complexity belongs in a template.

SOQL and query design. FORMULA() points toward filtering at the query layer rather than fetching wide and discarding. Beta status and the two-operator restriction make it a direction to prototype, not a pattern to standardise on this release.

Async processing. Elastic limits change the Batch failure mode from exception to degraded throughput. That is better, but it moves the monitoring problem: you now detect throttling rather than failures, and your scheduling assumptions must survive serialised execution.

API and integration maintenance. latest is useful in tooling and dangerous in production. The real integration work this cycle is auditing SOAP login() permissions and inventorying anything on API 31.0 through 40.0.

Testing. Winter ’27 splits Apex testing into two tiers. Unit tests keep coverage and deployment gating; integration tests get real callouts, committed data, and the cost of owning your own teardown. Deciding which tier each test belongs in is a new design question your team did not have last release.

AI-assisted development. The Claude Code plugin, the LSP-backed MCP server, and the Apex Symbol API together shift AI from guessing at your org to querying it. The review discipline does not change.

Deployment. Release readiness is now more org-state dependent: which heap ceiling each org enforces, which Beta settings are on, which component API versions are set. apexCompileResults gives you a way to check one of those states before the release window rather than during it.


What Developers Should Test Before Production

  1. Record Limits.getLimitHeapSize() in production and in every sandbox and scratch org you deploy from. Reconcile any differences.
  2. Enable Enforce the Summer ’26 Apex heap limit in preview sandboxes until production has upgraded.
  3. Search the codebase for hardcoded heap thresholds (600000012000000) and replace them with Limits.getLimitHeapSize().
  4. Re-test any batch job whose scope size was reduced as a heap workaround, at production-realistic record sizes, watching CPU time as well as heap.
  5. Audit integration users for the Use Any API Auth permission before your upgrade weekend.
  6. Inventory integrations running API versions 31.0 through 40.0 and plan migration ahead of Summer ’27.
  7. If enabling elastic limits, use asyncApexExecutionsOverride in a sandbox to rehearse throttled behaviour and confirm your batch windows still hold.
  8. Verify the exact status of complex template expressions in your org’s release documentation before setting any production component to rely on them.
  9. Confirm that no production integration has been pointed at /services/data/latest/.
  10. Regression test Apex on API 67.0 and later for user-mode database default behaviour if you have not already.
  11. Call POST /tooling/apexCompileResults/ against production and each sandbox to inventory invalid Apex before a release window, not during one.
  12. If trialling integration tests, verify every @IntegrationTest class has a @TearDown that deletes child records before parents, and confirm your CI accounts for one concurrent test per org.
  13. Confirm code coverage still comes from @IsTest classes. Integration tests do not count toward the 75% deployment requirement.

Winter ’27: What Is GA vs Beta?

FeatureStatusWhere it works
Increased Apex heap limits (10 MB / 25 MB)GA, automaticAll orgs on upgrade
REST API /services/data/latest/GAAll orgs supporting REST API
apexCompileResults (recompile only invalid Apex)GATooling API 68.0+, Author Apex permission
FORMULA() in SOQL WHEREBeta Sandbox, Developer Edition, scratch orgs on API 68.0+. Not production
Elastic limits for Batch ApexBetaOpt-in via Apex Settings
Apex Symbol APIBeta Tooling API 68.0+, Author Apex and View Setup permissions
Real HTTP callouts in Apex integration testsDeveloper Preview Scratch orgs only. Not production, sandboxes, or deployments
Complex template expressions in LWCStatus changed in Winter '27; verify in your org's docs Per component, apiVersion 66.0+
Salesforce development plugin for Claude Code Available (external tooling, open source)Local dev environment

Developer Preview carries a stricter caveat than Beta. Salesforce states that all commands, parameters, and features in a developer preview are subject to change or deprecation at any time, with or without notice, and should not be implemented in a production package.

Salesforce’s standard disclaimer applies to every Beta item: features noted as beta, pilot, or developer preview carry no guarantee of reaching general availability in any particular timeframe, or at all. Base purchasing and architectural commitments only on generally available functionality.

Salesforce Winter '27 Developer Feature Comparison

FeatureDeveloper ImpactStatusBest Use CaseKey Consideration
Higher Apex heap limits Removes heap as the binding constraint on data-heavy transactions; lets you revert scope-size workaroundsGA, automatic Batch jobs and integrations deserialising large JSON or building large in-memory maps Sandbox and production enforce different ceilings until both upgrade; CPU time did not move
FORMULA() in SOQL Moves computed filters from Apex loops into the query; eliminates query-only formula fieldsBeta, API 68.0+, non-production only ISV packages that cannot modify subscriber schema; simple additive or date-difference filters WHERE only; + and - only; no production availability
Complex template expressions (LWC) Deletes single-use getters; enables per-item formatting inside for:each without pre-mapping dataVerify status for your org Display formatting and simple conditional rendering in iterations Logic in templates is invisible to JS unit tests; < breaks HTML parsing in text nodes
Claude Code plugin Gives the coding agent real project, org, and Apex language-server context instead of generic knowledgeAvailable, open source Scaffolding, test generation, metadata deployment in scratch orgs Can deploy to a connected org; authenticate to sandbox only and review all output
REST API latest Removes per-release URI maintenance in scripts and toolingGA Exploration, sandbox scripts, CI Never pin production integrations to latest; version numbers carry behavioural semantics
Elastic limits for Batch Converts async overage from hard failure to throttled processingBeta, opt-in Orgs with occasional spikes past the rolling 24-hour async limit Throttling drops Batch to one active job; monitor executions, not enqueues
Real HTTP callouts in integration tests Catches contract drift that mocks structurally cannot catch Developer Preview, scratch orgs only Verifying real vendor API responses and behaviour that depends on committed data No auto-rollback, no code coverage credit, 1 concurrent test per org, 10-minute cap
Apex Symbol API Gives tools and AI agents compiler-verified type metadata instead of parsed guessesBeta, API 68.0+ Building IDE completion, documentation tooling, or grounding internal AI dev tools One request per org at a time; packaged ApexDoc not returned in 68.0
Recompile only invalid Apex Turns "recompile the org and see what breaks" into a targeted, scriptable checkGA, API 68.0+ Pre-deployment gates and scheduled monitoring for latent invalid Apex Reports only. Nothing updates IsValid, not the API and not the Setup buttons
What is the new Apex heap limit in Salesforce Winter '27? +

Synchronous Apex heap increases from 6 MB to 10 MB and asynchronous Apex heap increases from 12 MB to 25 MB. The change is generally available and applies automatically when your org upgrades to Winter '27, with no setting to enable.

Which API version is Salesforce Winter '27? +

Winter '27 is API version 68.0. Features gated on FORMULA() in SOQL and the asyncApexExecutionsOverride field in ApexSettings require API version 68.0 or later.

Is FORMULA() in SOQL generally available? +

No. In Winter '27, FORMULA() is a Beta feature available only in sandboxes, Developer Edition orgs, and scratch orgs on API version 68.0 or later. It is not available in production and is governed by Salesforce's Beta Services Terms.

Which operators does FORMULA() support in SOQL? +

Addition and subtraction only. Expressions can evaluate to DOUBLE, INTEGER, DATETIME, DATE, and CURRENCY, with INTEGER behaving like DOUBLE and DATE behaving like DATETIME. It is supported in the WHERE clause but not in HAVING.

How do I stop my Winter '27 sandbox from letting me exceed production's heap limit? +

Enable the Enforce the Summer '26 Apex heap limit checkbox in Setup under Apex Settings. It is available in non-production orgs and keeps the org on the 6 MB and 12 MB ceilings so code built there is safe to deploy into an org that has not yet upgraded.

How do I enable complex template expressions in an LWC? +

Set the component's apiVersion to 66.0 or later in its .js-meta.xml file. Enablement is per component rather than org-wide. Confirm the feature's current availability status in your org's release documentation before using it in production code.

How does Elastic Limits work in Batch Apex? +

When enabled, an org can enqueue asynchronous jobs beyond its rolling 24-hour limit up to an elastic limit calculated as the daily limit plus the lesser of the licensed daily limit or 2 million. Once executions exceed the daily limit, in-flight Batch jobs are throttled and new Batch jobs are restricted to one active job at a time. Jobs beyond the elastic limit still throw exceptions.

What does the Salesforce Claude Code plugin do? +

It packages roughly 40 Salesforce development skills, specialised agents, three MCP servers, hooks, and commands into a single install for Claude Code. It requires Claude Code, Node.js LTS, Salesforce CLI, and Python 3.8 or above.

Can I use /services/data/latest/ in production integrations? +

It works, but you should not. Pinning an explicit version is what prevents a Salesforce release from silently changing response behaviour under a running integration. Use latest for exploration, sandbox scripts, and CI environments.

What changed for Apex database operation defaults? +

In API version 67.0 and later, Apex database operations run in user mode by default, enforcing the current user's permissions and field-level security. API version 66.0 and earlier default to system mode.

Can Apex tests make real HTTP callouts in Winter '27? +

Yes, in Apex integration tests, and only as a Developer Preview in scratch orgs. Winter '27 expands integration test callouts from Agentforce and Data 360 to any authorised endpoint, including External Services via Named Credentials. Integration tests are not available in production orgs, sandboxes, or metadata deployments, and they do not count toward code coverage.

Do Apex integration tests replace unit tests? +

No. Integration tests are excluded from RunAllTests during metadata deployments and do not count toward the 75% code coverage requirement, so unit tests remain mandatory. Use integration tests for real service contracts and behaviour that depends on committed data, such as field history tracking.

What is the Apex Symbol API used for? +

It returns compiler-grade metadata for Apex types over the Tooling API, so tools can offer accurate code completion, display ApexDoc and built-in type documentation, show constructor signatures, and ground AI agents that generate Apex. It is Beta, requires API version 68.0 or later plus the Author Apex and View Setup permissions, and enforces one concurrent request per org.

Does apexCompileResults actually recompile my invalid Apex? +

No. It returns compilation results for invalid classes and triggers, and it does not update the IsValid field. Neither do the "Compile only invalid classes" and "Compile only invalid triggers" buttons in Setup. Treat a clean apexCompileResults response as your confirmation that compilation succeeded, not the field.

Do I need to change anything before my Winter '27 upgrade weekend? +

Two items are genuinely time-sensitive: audit integration users for the Use Any API Auth permission, because SOAP login() now requires it, and reconcile heap ceilings between sandbox and production so nothing is deployed against a limit production does not yet enforce.

Are Winter '27 Beta features safe to build on? +

No, in the contractual sense. Salesforce states that beta, pilot, and developer preview features are not guaranteed to reach general availability in any particular timeframe, or at all. Prototype with them and keep a production-viable alternative in place.