SapotaCorp

Apex Sharing Modes: With, Without, Inherited Sharing

Every Apex class runs in one of three sharing modes. The default is the most permissive. Most production orgs have at least one class running with bypass when it should respect sharing. The fix is a one-keyword change and a code review discipline.

Apex Sharing Modes: With, Without, Inherited Sharing

Key takeaways

  • Apex runs in 3 sharing modes: with sharing (respects record access rules), without sharing (bypasses sharing entirely), inherited sharing (uses the caller's mode). The default (no keyword) is without sharing, which is the most permissive and the most common source of record-leak bugs.
  • Default to "with sharing" on every Apex class that touches user-accessible data. The keyword is one word at the class declaration; the protection is automatic. Override only when you have a documented reason — bulk admin processing, system integrations, scheduled jobs that need cross-user access.
  • Inherited sharing is the right choice for utility classes that can be called from either context. The class respects whatever sharing the caller enforces, which keeps the utility safe to use from user-facing controllers (with sharing) and from system context (without sharing) without leaking data.
  • Code review must flag every Apex class without an explicit sharing keyword. Default behaviour is dangerous; explicit declaration is documentation that the developer thought about sharing. PR review on every new Apex class is the discipline that catches the default-permissive bug before it ships.

A sales rep at a private-sharing org can see Accounts they own, plus Accounts shared with them. The org's UI respects this perfectly: list views filter to owned and shared records, related lists hide unrelated children, reports return only authorized rows. Then a custom Apex method is called from a Lightning Web Component, and suddenly the rep sees every Account in the org. The query in the controller bypassed sharing rules.

This is the most common Apex sharing bug. Apex classes do not respect sharing by default in many contexts. The fix is a keyword on the class declaration. The discipline of applying it consistently across hundreds of classes is what separates orgs that enforce sharing from orgs that have leaky controllers.

The three modes

Every Apex class is in one of three sharing modes. The mode determines whether queries and DML respect the org's sharing model for the running user.

with sharing: queries and DML enforce sharing rules. Records the user is not entitled to see are excluded from query results. DML on records the user cannot edit fails.

without sharing: queries and DML ignore sharing. All records of accessible objects are visible. DML succeeds regardless of record-level access.

inherited sharing: the class adopts the sharing mode of its caller. If called from a with sharing context, it behaves as with sharing. If called from a without sharing context, it behaves as without sharing.

public with sharing class SafeController {
  public List<Account> getAccounts() {
    return [SELECT Id, Name FROM Account];
  }
}

public without sharing class ElevatedService {
  public List<Account> getAllAccounts() {
    return [SELECT Id, Name FROM Account];
  }
}

public inherited sharing class FlexibleUtility {
  public List<Account> getAccountsBasedOnCaller() {
    return [SELECT Id, Name FROM Account];
  }
}

The three classes have identical query syntax. The behavior is dramatically different at runtime.

The default that bites

A class without a sharing keyword runs in implicit without sharing mode in many entry points. The defaults differ by context:

  • Triggers: implicit without sharing.
  • Anonymous Apex execution: implicit without sharing.
  • Web service methods: implicit without sharing.
  • Lightning controllers (with @AuraEnabled methods): implicit context, depends on caller.
  • Scheduled and batch classes: implicit without sharing.

The implication: a class declared without an explicit mode often runs without sharing enforcement. Developers familiar with with sharing as the default in some languages will be surprised by Apex behavior.

The fix: always declare the mode explicitly. Even when the intent is without sharing, write the keyword so the choice is documented.

When with sharing is the right choice

Default for almost all Apex code that handles user requests:

  • Lightning controllers and @AuraEnabled methods.
  • REST API endpoints exposed via Apex.
  • Visualforce controllers.
  • Webhook handlers that authenticate as a specific user.

The rule: if the code is running on behalf of a real user, it should respect that user's sharing rules. with sharing is the safe default.

When without sharing is justified

Specific scenarios where bypass is correct:

System-level operations. A scheduled job that processes all Cases in the org, regardless of ownership. The job runs as a service account; user-level sharing does not apply. Document the justification.

Aggregate reporting. A summary calculation that counts all records, including those the running user cannot see. The user sees the aggregate number without seeing individual records. Common pattern in dashboards.

Trigger handlers that update related records. A trigger on Account that updates related Contacts and Opportunities. The user updating the Account may not own all related records. The trigger needs to bypass sharing for the related-record updates. Often combined with CRUD/FLS enforcement to prevent over-broad access.

Customer service operations. A service rep needs to see all open cases regardless of owner. The service org has a documented policy that service reps have implicit access. The Apex controller is without sharing.

In every case, the bypass should be justified, documented, and limited in scope. without sharing on a utility class is rarely correct; the bypass should apply to specific operations, not generic helpers.

When inherited sharing works

Utility classes that are called from many contexts and should adapt to each.

public inherited sharing class ContactLookupUtility {
  public Contact findPrimary(Id accountId) {
    return [SELECT Id, Email FROM Contact
            WHERE AccountId = :accountId
            AND Is_Primary__c = TRUE LIMIT 1];
  }
}

When called from a with sharing controller, the lookup respects sharing. When called from a without sharing batch job, it bypasses sharing. The utility itself does not pick a mode.

inherited sharing is the right default for shared utilities. It signals "I adopt the caller's intent" rather than imposing a mode unilaterally.

Without explicit declaration, an inner-called Apex class would be without sharing (a common surprise). inherited sharing is the explicit fix.

Common sharing mistakes

Five patterns Sapota has seen in audits:

1. Missing keyword on controllers. Controller class declared without any mode. Implicit without sharing behavior. Users see records they should not.

Fix: add with sharing to every controller.

2. without sharing as the default. A team decided without sharing was the "safer" default because it "always works." Sharing is bypassed across the entire codebase. Audit risk.

Fix: flip the default. Remove without sharing from classes that do not need it. Add documented justification where it stays.

3. Sharing in outer class but not utility. Controller is with sharing. Controller calls a utility class with no sharing declaration. Utility runs without sharing. Sharing bypassed via the indirection.

Fix: mark utilities as inherited sharing. Or with sharing if they should always enforce.

4. Sharing on the wrong layer. Sharing applied to a service class that handles business logic, but not to the controller that calls it. Or vice versa. The mode applies on the class where the query or DML executes, not on the calling class.

Fix: apply the keyword on every class that contains SOQL or DML. Belt and suspenders is OK.

5. Sharing assumed to cover FLS and CRUD. Sharing enforces row-level access. Field-level security and object-level CRUD need separate enforcement (via WITH USER_MODE, SECURITY_ENFORCED, or stripInaccessible).

Fix: combine with sharing with explicit CRUD/FLS enforcement on queries and DML.

How to audit an existing org

The audit pattern:

  1. Find every class with SOQL or DML. Static analysis tools (PMD, Salesforce Code Analyzer) generate this list.
  2. For each class, check the sharing mode declaration. Flag classes with no explicit mode.
  3. Classify each class by purpose. Controllers and entry points should be with sharing. Utility classes should usually be inherited sharing. System-level operations may justify without sharing with documentation.
  4. Trace the call paths from with sharing entry points. Verify the chain stays sharing-enforcing through utilities and helpers.
  5. Remediate the gaps. Add explicit declarations everywhere.

A typical org has dozens of classes missing explicit declarations. The remediation is mechanical but time-consuming (every class needs review, not just the obvious ones).

What good sharing discipline looks like

A Salesforce org with healthy sharing practices:

  • Every Apex class with SOQL or DML has an explicit sharing keyword.
  • Controllers and entry-point classes are with sharing.
  • Utility classes are inherited sharing or with sharing.
  • without sharing used only with documented justification.
  • Code review checks the sharing keyword on every new class.
  • Static analysis runs in CI to flag missing declarations.

Sapota's Salesforce team holds the Platform Developer I credential and runs sharing audits on every engagement that involves an existing org. The audit finds gaps in 90% of orgs reviewed; the remediation is straightforward but rarely happens without an explicit engagement.


Auditing Apex sharing modes or preparing for AppExchange Security Review? Sapota's Salesforce team handles sharing audits and security refactor on production engagements. Get in touch ->

See our full platform services for the stack we cover.

Contact Us Now

Share Your Story

We build trust by delivering what we promise – the first time and every time!

We'd love to hear your vision. Our IT experts will reach out to you during business hours to discuss making it happen.

WHY CHOOSE US

"Collaborate, Elevate, Celebrate where Associates - Create Project Excellence"

SapotaCorp beyond the IT industry standard, we are

  • Certificated
  • Assured quality
  • Extra maintenance

Tell us about your project