Straight answer: Apex runs in system mode by default, which ignores the running user’s object, field and record permissions. WITH USER_MODE on a SOQL query and AccessLevel.USER_MODE on a DML call make the platform enforce those permissions for you, throwing QueryException or DmlException when the user lacks access. This replaces the hand-rolled isAccessible() checks and Security.stripInaccessible calls that most orgs still carry.
What system mode actually skips
In system mode Apex ignores object permissions (CRUD), field-level security (FLS), and, unless the class is declared with sharing, record-level sharing. Three separate layers, and they fail independently, which is why partial fixes are so common.
| Layer | Controlled by | Enforced in system mode? |
|---|---|---|
| Object permissions | Profile / permission set | No |
| Field-level security | Profile / permission set | No |
| Record access | OWD, roles, sharing rules | Only with with sharing |
The distinction that matters: with sharing has never enforced CRUD or FLS. A class marked with sharing that writes to a field the user cannot see does so happily. Many teams believe they are covered because every class carries the keyword.
USER_MODE on queries
List<Account> accounts = [
SELECT Id, Name, AnnualRevenue
FROM Account
WHERE Industry = 'Manufacturing'
WITH USER_MODE
];
That single clause enforces object permissions, field-level security and sharing together. If the running user cannot read AnnualRevenue, the query throws rather than returning the field — which is the important behavioural choice. stripInaccessible silently removes the field; WITH USER_MODE refuses to proceed.
Both are defensible. Stripping is right for a read-only display that should degrade gracefully. Throwing is right when continuing without the field would produce a wrong answer. Pick deliberately rather than by habit.
The counterpart, WITH SYSTEM_MODE, exists so you can be explicit when you genuinely want system-mode access — an aggregate that must count rows the user cannot see, for instance. Writing it out makes the decision reviewable instead of implied by absence.
USER_MODE on DML
Database.insert(accounts, AccessLevel.USER_MODE);
Database.update(accounts, AccessLevel.USER_MODE);
Database.delete(accounts, AccessLevel.USER_MODE);
The bare insert accounts; statement has no user-mode form, so every write you want enforced has to move to the Database method. That is the main migration cost in an existing codebase, and it is mechanical rather than difficult.
The three-argument form combines partial success with access level, which is the shape most bulk code wants:
List<Database.SaveResult> results =
Database.insert(accounts, false, AccessLevel.USER_MODE);
for (Integer i = 0; i < results.size(); i++) {
if (!results[i].isSuccess()) {
for (Database.Error e : results[i].getErrors()) {
System.debug(accounts[i].Name + ': ' + e.getMessage());
}
}
}
Where this changes behaviour, and will break things
Turning on enforcement in a codebase that never had it is a behaviour change, and it surfaces access gaps that were previously invisible. Expect three categories.
Integration users with thin permission sets
An integration user who was never granted a field now fails a write that used to succeed. This is the system telling you the truth, but it is a production incident if you discover it at deploy time. Audit which users run the code before you enforce it.
Code that queries fields it does not display
A query selecting twenty fields to pass two into a calculation now fails for any user missing any of the twenty. Trim the SELECT list to what you use: good practice regardless, and here it is load-bearing.
Triggers running as whoever saved the record
A trigger enforcing user mode runs with the permissions of whoever fired it, which is often a low-privilege user updating a record through a screen flow. Automation that must complete regardless of who triggered it belongs in explicit system mode, and now you have to say so.
Migrating an existing codebase
Do not flip everything at once. The order I would use:
- Start with new code, and make user mode the default in review.
- Convert Apex exposed to external callers next,
@AuraEnabledmethods and Apex REST endpoints, because that is where an access bug is most exposed. - Convert batch and scheduled jobs last, and mark most of them
WITH SYSTEM_MODEexplicitly. Background work usually should run in system mode. The value is in stating it. - Delete the
isAccessible()scaffolding as you go, rather than leaving both mechanisms in place.
Salesforce Code Analyzer’s security rules flag CRUD and FLS violations, so wiring it into CI gives you a shrinking list to work through rather than a guess about coverage.
Frequently asked
Does WITH USER_MODE also enforce sharing?
Yes: it covers object permissions, field-level security and record sharing together, which is what makes it more than a shorthand for the old checks. It does not, however, change the class’s declared sharing behaviour for other queries in the same class.
Do I still need “with sharing” on the class?
Keep it. It sets the default for anything in the class not explicitly marked, and leaving classes with no sharing declaration means the behaviour depends on the caller, which is a bad thing to depend on.
What is the difference from Security.stripInaccessible?
stripInaccessible removes fields the user cannot access and lets execution continue. WITH USER_MODE throws. Use stripping when a partial result is genuinely usable, and user mode when it is not.
Does user mode slow queries down?
Enforcement adds work, but in practice the cost is not what determines whether a query is fast. Selectivity is. If a query is slow with user mode, it was slow without it.
How do I test that enforcement works?
Create a user with a deliberately restricted permission set in the test, wrap the call in System.runAs, and assert that the expected exception is thrown. A test that only runs as the admin proves nothing about enforcement.
Reference: Enforce User Mode for Database Operations, Filter SOQL Queries Using WITH USER_MODE and Using the with sharing, without sharing, and inherited sharing Keywords, Salesforce Apex Developer Guide.