Sunday, July 6, 2025

Savepoint & Rollback in Apex

In Salesforce Apex, Savepoint and Rollback work like an “undo” feature for database changes during a transaction. They help prevent unwanted data from being saved if something goes wrong in your code execution.

What is a Savepoint?

A Savepoint marks a specific stage in your transaction. Everything done before the savepoint is considered safe. If an error occurs after the savepoint, you can roll back to that point.

Savepoint sp = Database.setSavepoint();

What is a Rollback?

Rollback undoes all database changes made after a savepoint. It does not affect records inserted, updated, or deleted before the savepoint.

Database.rollback(sp);


Example 1: Using Savepoint and Rollback

Account acc1 = new Account(Name='First Account');

insert acc1;


// Set savepoint

Savepoint sp = Database.setSavepoint();


Account acc2 = new Account(Name='Second Account');

insert acc2;


// Simulate an error

if (true) {

    Database.rollback(sp);

}

Result:

  • acc1 is saved.
  • acc2 is rolled back.
  • No exception is thrown.


Example 2: Without Savepoint and Rollback

Account acc1 = new Account(Name='First Account');

insert acc1;


Account acc2 = new Account(Name='Second Account');

insert acc2;


// Simulate an error

if (true) {

    throw new CustomException('Something went wrong!');

}

Result:

  • Entire transaction fails.
  • Neither acc1 nor acc2 is saved.
  • A custom exception is thrown.


Why This Matters

Without Savepoints, any error rolls back all changes in the transaction. Even valid data that was saved before the error is lost.
With Savepoints, you can protect the valid part of the transaction and only undo the part that failed.


Real-World Use Case

Suppose you are creating three related records in one transaction:

  • An Account
  • A Contact
  • An Opportunity

If the Opportunity creation fails, you can use a savepoint before inserting it and roll back just that part — keeping the Account and Contact saved.


When to Use Savepoint and Rollback

  • When you want to protect only part of a transaction.
  • When you expect possible failures in specific DML operations.
  • When you want more control over error handling and data safety.


Limitations of Savepoint and Rollback in Apex

1. Rolling Back Invalidates Later Savepoints

If you create multiple savepoints and roll back to an earlier one, any savepoints created after that become invalid. Using those invalidated savepoints will cause a runtime error.

Example:

Savepoint sp1 = Database.setSavepoint(); // First savepoint


Account acc1 = new Account(Name = 'First');

insert acc1;


Savepoint sp2 = Database.setSavepoint(); // Second savepoint


Account acc2 = new Account(Name = 'Second');

insert acc2;


// Roll back to first savepoint

Database.rollback(sp1);


// Now sp2 is invalid. Using it will cause a runtime error

Database.rollback(sp2); // This line will throw an error


2. Savepoints Do Not Work Across Trigger Invocations

Each time a trigger is executed, it runs in a separate context. If you try to use a savepoint created in one trigger execution inside another, it will not work — even if stored in a static variable.

Step-by-Step Example:

Apex Class:

public class SavepointHolder {

    public static Savepoint sp;

}

Trigger Code:

trigger AccountTrigger on Account (before insert) {

    if (Trigger.isBefore) {

        if (SavepointHolder.sp == null) {

            // First trigger execution: set savepoint

            SavepointHolder.sp = Database.setSavepoint();

            System.debug('Savepoint set in first trigger run.');

        } else {

            // Second trigger execution: try to use old savepoint

            Database.rollback(SavepointHolder.sp); // This will throw an error

            System.debug('Trying to use savepoint from earlier run.');

        }

    }

}

Result:

  • First trigger run: savepoint is set.
  • Second trigger run: trying to use the same savepoint causes a runtime error.


3. Savepoints Count Against DML Limits

Salesforce enforces a limit of 150 DML operations per transaction. Every time you create a savepoint, it counts as one DML operation.

Example:

for (Integer i = 0; i < 150; i++) {

    Savepoint sp = Database.setSavepoint(); // Each counts as DML

}

Result:

  • DML limit is reached just by setting savepoints.
  • No room left for actual insert, update, or delete operations.
  • Transaction fails if more DML is attempted.

Saturday, June 21, 2025

Understanding WITH SECURITY_ENFORCED in Salesforce Apex

When writing Apex code in Salesforce, it’s important to protect data by checking user permissions like Field-Level Security (FLS) and Object-Level Security (OLS). One way to do this is by using WITH SECURITY_ENFORCED in your SOQL queries.

This keyword tells Salesforce to check if the current user has access to the fields and objects in the query. If the user doesn’t have access, the query fails and throws an error. This helps avoid showing data that the user shouldn’t see.

Account[] accts = [SELECT Name, AnnualRevenue FROM Account WITH SECURITY_ENFORCED];

In this example, Salesforce checks if the user can access the Name and AnnualRevenue fields. If not, it will stop the query and show an error.

Salesforce Recommendation: Use USER_MODE Instead

Salesforce recommends using WITH USER_MODE instead of WITH SECURITY_ENFORCED because it has fewer limitations and works better in more situations.

With USER_MODE, your Apex code runs as if it were the user, automatically respecting all their permissions—without needing extra checks.

For more details on user mode please refer blog https://www.sfdc-lightning.com/2025/06/user-mode-in-apex-new-way-to-respect.html

Important Notes:

Don’t use WITH SECURITY_ENFORCED and USER_MODE together in the same query.

If you switch to USER_MODE, remove any WITH SECURITY_ENFORCED clauses—they’re not needed.

Using both together will cause errors.

Limitations of WITH SECURITY_ENFORCED:

  • Works only with SOQL

WITH SECURITY_ENFORCED works only in SOQL queries. It cannot be used with SOSL or DML.

Valid Example:

Account[] accs = [SELECT Name FROM Account WITH SECURITY_ENFORCED];

  • Does not support relationship fields

You cannot use relationship fields like Owner.Name in the query.

Invalid Example:

[SELECT Name, Owner.Name FROM Account WITH SECURITY_ENFORCED];

  • Cannot be used in dynamic SOQL

WITH SECURITY_ENFORCED does not work in queries written as strings and executed at runtime.

Invalid Example:

String soql = 'SELECT Name FROM Account WITH SECURITY_ENFORCED';

Database.query(soql); // This will fail

  • Exceptions cannot be caught

If the user doesn’t have access to a field or object, the query throws an exception that cannot be handled with try-catch.

Invalid Example:

try {

    [SELECT Name, AnnualRevenue FROM Account WITH SECURITY_ENFORCED];

} catch (Exception e) {

    // This will not catch the error

}

  • No partial access allowed

If the user lacks access to even one field, the entire query fails. It does not return partial results.

Example:

[SELECT Name, AnnualRevenue FROM Account WITH SECURITY_ENFORCED];

Friday, June 20, 2025

User Mode in Apex – The New Way to Respect Permissions

 In Salesforce, Apex code runs in system mode by default. That means it can access all data, even if the current user doesn’t have permission. This can lead to security issues if you're not careful.

To fix this, Salesforce introduced User Mode – a cleaner and safer way to run SOQL and DML operations as the user.

What is User Mode?

User Mode lets your code automatically respect:

  • Field-level security (FLS)
  • Object-level security (CRUD)

This means your queries and operations behave just like the user is running them.

How to Use User Mode

You can use User Mode in these operations:

Database.query(new Query("SELECT Name FROM Account").setUserMode(UserMode.CURRENT));


Database.getQueryLocator(new Query("SELECT Name FROM Account").setUserMode(UserMode.CURRENT));

You can also use it with:

  • Database.countQuery()
  • Database.insert(), update(), delete() – by setting user mode through Database.DMLOptions


Benefits

  • No need to manually check FLS or CRUD
  • Prevents accidental access to restricted data
  • Works across async processes and batch jobs
  • Safer and more user-friendly than WITH SECURITY_ENFORCED

Why It Matters

User Mode is the recommended approach by Salesforce. It makes your Apex code more secure, easier to maintain, and aligned with user permissions—automatically.

Here are real-life examples of using User Mode in Apex code to make sure your logic respects user permissions (FLS & CRUD):

Example 1: Querying Accounts Securely in a Controller

Without User Mode (runs in system mode – not secure):

List<Account> accounts = [SELECT Id, Name, Industry FROM Account];


With User Mode (recommended):

Query query = new Query('SELECT Id, Name, Industry FROM Account')

    .setUserMode(UserMode.CURRENT);


List<Account> accounts = Database.query(query);


Now the query will return only fields the current user is allowed to access.

Example 2: Batch Apex – Get Records with Respect to User Access

Batch class using User Mode in start method:

global class AccountBatch implements Database.Batchable<SObject> {

    global Database.QueryLocator start(Database.BatchableContext BC) {

        Query query = new Query('SELECT Id, Name FROM Account')

            .setUserMode(UserMode.CURRENT);

        return Database.getQueryLocator(query);

    }


    global void execute(Database.BatchableContext BC, List<SObject> scope) {

        // Process records here

    }


    global void finish(Database.BatchableContext BC) {}

}


Example 3: Secure DML Operation (Insert)

Without User Mode (may insert data user isn't allowed to):

insert new Account(Name='Test');

With User Mode:

Account acct = new Account(Name='Test');


Database.DMLOptions dmlOpts = new Database.DMLOptions();

dmlOpts.setUserMode(UserMode.CURRENT);


Database.insert(acct, dmlOpts);


If the user doesn’t have create access to the Account object, this insert will fail safely.

Example 4: Reporting Component – Avoid Showing Restricted Fields

Say you’re building a custom report viewer in LWC or Aura and calling Apex:

Query query = new Query('SELECT Name, AnnualRevenue, Industry FROM Account')

    .setUserMode(UserMode.CURRENT);


List<Account> result = Database.query(query);


If the user can't see AnnualRevenue, it will automatically be excluded from results — no error, no extra code!