Pages

Showing posts with label Salesforce Summer '26 release. Show all posts
Showing posts with label Salesforce Summer '26 release. Show all posts

Wednesday, August 5, 2026

Apex Changes in Summer '26: The Biggest Security Shift in Year

 A practical guide for Salesforce architects, developers, and admins preparing for API version 67.0

If you've been putting off reading the Summer '26 release notes because "it's probably just the usual stuff," this is the release to make time for. Summer '26 ships API version 67.0, and buried inside that version bump is what Salesforce itself is calling one of the most significant shifts to the Apex security model in years.

This isn't a UI refresh or a nice-to-have feature. It's a change to how your Apex code behaves at a fundamental level — and if you're not ready for it, upgrading your API version could silently change what data your users can see, or break your deployment outright.

Let's walk through exactly what's changing, why it matters, and how to prepare your orgs (and your clients') before this hits production.

Quick facts: Summer '26 release timeline

  • Sandbox preview: May 8–9, 2026
  • Production rollout weekends: May 15, June 5, and June 12–13, 2026 (varies by instance — check your Maintenance Calendar)
  • New API version: 67.0
  • Biggest theme for developers: Secure-by-default Apex

The three changes that define this release

Three related changes land together at API version 67.0. Individually, each one is significant. Together, they represent a full reversal of Apex's historical security posture — from "open by default, lock it down if you remember" to "locked down by default, open it up if you mean to."



1. Database operations now default to user mode

Before 67.0, SOQL, SOSL, DML, and Database class methods ran in system mode by default. That meant your queries and DML statements quietly ignored the running user's field-level security (FLS), object permissions, and sharing rules — unless you went out of your way to enforce them.

From API 67.0 onward, that default flips. The same operations now run in user mode, meaning they automatically respect whatever the logged-in user is allowed to see and do.

This is good news for security, but it's a real behavior change for existing code. Any class that assumed implicit "God mode" access — pulling back every field and every record regardless of who's running it — will now filter results based on the actual user's permissions.

2. with sharing is the new default for Apex classes

Previously, an Apex class with no sharing keyword defaulted to without sharing in most contexts, silently bypassing org-wide defaults, sharing rules, and role hierarchy.

At API 67.0, a class with no explicit declaration now defaults to with sharing. If you genuinely need a class to ignore sharing rules going forward, you now have to say so explicitly with without sharing — which is exactly the kind of intentional, auditable decision this change is designed to encourage.

3. WITH SECURITY_ENFORCED is gone — replaced by WITH USER_MODE

This one isn't a behavior change, it's a hard compile error. Any SOQL query still using the WITH SECURITY_ENFORCED clause will fail to compile once a class is on API 67.0.

The replacement, WITH USER_MODE, isn't just a rename with better branding. It's meaningfully more thorough:

  • It checks field-level security across the entire query, including the WHERE clause — not just the SELECT list.
  • It correctly handles polymorphic fields like Owner or Task.WhatId.
  • It reports every inaccessible field it finds, not just the first one it hits, via QueryException.getInaccessibleFields().
apex
// This fails to compile at API 67.0:
// "WITH SECURITY_ENFORCED is no longer supported, use WITH USER_MODE instead."
List<Account> accts = [SELECT Id FROM Account WITH SECURITY_ENFORCED LIMIT 1];

// The replacement:
try {
    List<Account> accts = [
        SELECT Id, Name, AnnualRevenue
        FROM Account
        WITH USER_MODE
        LIMIT 1
    ];
} catch (QueryException e) {
    Map<String, Set<String>> blockedFields = e.getInaccessibleFields();
    // Handle or surface the blocked fields gracefully
}

If a query genuinely needs elevated access, be explicit about it with WITH SYSTEM_MODE rather than relying on an implicit default.

The one deliberate exception: triggers

Apex triggers are carved out of this change entirely. Triggers always run in system mode, and that isn't changing — you can't declare a sharing or access mode on a trigger itself. What is affected is the handler class your trigger delegates to. If your trigger-handler pattern relies on a handler class with no explicit sharing declaration, that handler now defaults to with sharing at 67.0. Audit those handler classes specifically.

Why this matters more than a typical governor-limit tweak

Here's the part that should get every architect's attention: your existing test coverage was written against the old defaults. A test class that passes cleanly on API 66.0 can pass just as cleanly on 67.0 and still hide a real production problem, because the assertions were never written to check field- or record-level visibility for a restricted user.

Before bumping any class to API 67.0, it's worth adding test scenarios that specifically run as a constrained user (System.runAs()) and assert on what that user can and cannot see. This is especially important for managed packages, where you don't get a second chance once it's in a subscriber org.

The diagram above sums up the shift at a glance: three quiet defaults that used to favor access are now three defaults that favor restriction — and each one has an explicit escape hatch if you truly need the old behavior.

Your Apex security migration checklist

  1. Search your codebase for WITH SECURITY_ENFORCED. Every instance needs to become WITH USER_MODE before you touch API 67.0.
  2. Audit classes with no sharing declaration, especially trigger handlers. Decide deliberately: should this be with sharing (the new default, no change needed) or does it genuinely need without sharing?
  3. Review Database class calls (Database.query, Database.insert, etc.) for explicit AccessLevel parameters, and add them where system-mode access is actually required.
  4. Add restricted-user test scenarios. Don't just re-run your existing suite — write new assertions that run as a limited-permission user and check data visibility.
  5. Stage the API version bump. Don't flip every class to 67.0 in one deployment. Move incrementally, class by class or module by module, and test as you go.
  6. Pay special attention to managed packages. Once a package version is released with these defaults, there's no quiet fix later — get this right before you ship.

Beyond security: other Apex updates worth knowing

The security overhaul is the headline, but a handful of other Apex changes in Summer '26 are genuinely useful for day-to-day development.

Multiline strings and String.template()

Building JSON payloads or email bodies with a chain of + '\n' + concatenations has always been painful. Triple single-quotes now give you real multiline string literals, and String.template() adds named placeholder interpolation:

apex
String payload = '''
{
    "Account": "${accountName}",
    "Last Updated": "${date}"
}'''.template(new Map<String, Object>{
    'accountName' => 'My Account',
    'date' => Datetime.newInstance(2026, 6, 15, 8, 0, 0)
});

Two small gotchas worth remembering: the newline right after the opening ''' is trimmed automatically, and String.template() renders Datetime values in GMT using yyyy-MM-dd HH:mm:ss — not the running user's local time zone — so format it yourself if you need something else.

Elastic limits for async jobs (Beta)

Orgs that regularly hit their daily asynchronous job ceiling get some breathing room. Queueable and @future jobs can now be enqueued up to twice the licensed daily limit, with anything above the standard threshold processed at a throttled rate instead of failing outright.

The architectural catch: throttling is quiet. A hard limit fails loudly and shows up in your monitoring immediately; a throttled org just gets slower, which can go unnoticed if your alerting only watches for errors. If you plan to lean on this buffer, track the new DailyAsyncApexElasticExecutions and DailyAsyncApexProcessed entries in System.OrgLimits.getMap() and build monitoring around queue depth and latency, not just failure counts.

No-argument constructors required for invocable action parameters

Any custom Apex class used as an input parameter for an invocable action now needs a visible no-argument constructor (public, or global if it's in a managed package). This applies from API 67.0 onward — a good one to check if you're building custom actions for Flow or Agentforce.

@IntegrationTest for real Agentforce and Data 360 callouts (Developer Preview)

Standard Apex unit tests mock callouts and roll back data, which makes it impossible to properly assert on live Agentforce or Data 360 behavior. The new @IntegrationTest annotation allows real, uncommitted-by-default callouts with a controlled way to commit test data mid-transaction using IntegrationTest.commitTestOnly(), plus a @TearDown method for cleanup. Currently scratch-org only, and worth watching as it matures.

The bigger picture

Summer '26 makes one thing clear: Salesforce is done treating security as something you opt into. Between the Apex defaults, the retirement of the OAuth username-password flow, changes to SOAP login(), and the mandatory SAML framework migration, this release is pushing every org toward explicit, auditable access decisions instead of implicit trust.

For architects, that's a genuinely good direction — but it means this is not a release to skip past on your way to the Agentforce headlines. Block time this sprint to search your codebase for WITH SECURITY_ENFORCED, audit your sharing declarations, and get ahead of the API version bump before it becomes a scramble.


Sources: Salesforce Developers Blog — "The Salesforce Developer's Guide to the Summer '26 Release"; Salesforce Architecture Blog — "Summer '26 Release Architect Highlights"; official Summer '26 Release Notes (help.salesforce.com).