Access Modifiers & Sharing

By default, Apex runs in system mode: object permissions (CRUD) and field-level security (FLS) of the running user are not enforced, and record-level sharing depends on the class's sharing keyword.

12.1 Access modifiers

The defining class only Default for methods/variables; top-level classes cannot be private (except test classes)

The class, inner classes, and subclasses Only for instance methods/variables in classes that can be extended

Any Apex in the same namespace (your org's code or the same package) The usual choice

Any Apex anywhere, including other namespaces and subscriber orgs

Required for managed-package APIs, webservice methods, Apex REST classes, email handlers; cannot be undone in a released managed package

Rule of thumb: start with private, widen to public when needed, and use global only for true cross-namespace APIs.

public with sharing class AccountService {
    private static Integer callCount = 0;           // internal state
    protected virtual void log(String msg) {}       // only valid in a virtual/abstract class
    public static List<Account> getTopAccounts() { callCount++; return new List<Account>(); }
}

12.2 System mode vs user mode

By default, Apex runs in system mode: object permissions (CRUD) and field-level security (FLS) of the running user are not enforced, and record-level sharing depends on the class's sharing keyword. Developers must explicitly enforce security (§29) using user-mode queries and DML, Security.stripInaccessible, or describe checks.

12.3 Sharing keywords

Sharing keywords control record-level access (which rows the user can see and edit, based on org-wide defaults, role hierarchy, sharing rules, and manual shares).

with sharing Enforce the running user's sharing rules without sharing

Ignore sharing rules (system access to all records) inherited sharing

Use the sharing mode of the calling class; runs with sharing when it is the entry point (e.g., called directly from LWC, Visualforce, REST, or Anonymous Apex)

Behaves like the caller's mode; if it is the entry point it effectively runs without sharing — except Lightning @AuraEnabled entry points, which default to with sharing. Avoid omitting it

Sharing mode is determined by the class where a method is defined, not where it's called from (except inherited sharing).

Inner classes do not inherit the outer class's sharing setting; declare it explicitly.

Triggers always run in system context without sharing; delegate to with sharing/inherited sharing handler classes when sharing must be respected.

Sharing keywords affect only record visibility, not CRUD/FLS.

public with sharing class OpportunityController {          // UI-facing: respect sharing
    @AuraEnabled(cacheable=true)
    public static List<Opportunity> myOpenDeals() {
        return [SELECT Id, Name, Amount FROM Opportunity WHERE IsClosed = false WITH USER_MODE];
    }
}

public inherited sharing class OpportunitySelector {       // reusable: adopt caller's mode
    public List<Opportunity> byIds(Set<Id> ids) {
        return [SELECT Id, Name FROM Opportunity WHERE Id IN :ids];
    }
}

public without sharing class SharingRecalculator {         // deliberate elevation; document why
    public static void recalc(Set<Id> accountIds) { /* needs to see all records */ }
}