LearnFrenzy
Sign in Take a free test

DML and Transactions · Lesson 21 of 41

Database Methods and Partial Success in Apex

5 min read Updated Nov 8, 2026 Saurabh Samir

A plain insert is all-or-nothing: if one record in 200 fails a validation rule, all 200 are rolled back and an exception is thrown. That's right for a single business operation, but wrong for a data load where you want the 199 good records saved and the bad one reported. The Database class methods give you that choice.

In this lesson

  • DML statements vs Database methods
  • allOrNone = false and SaveResult
  • Reading errors per record
  • DMLOptions
  • Saving in user mode

DML statement vs Database method

insert records;Database.insert(records, false);
One record failseverything rolls back, exception thrownthe others are saved
Errorsa DmlException to catcha result object per record
Use forone logical operation that must fully succeedimports, integrations, batch jobs

With allOrNone = true (the default if you omit it), Database methods behave like the statements.

Partial success with SaveResult

List<Contact> rows = new List<Contact>{
    new Contact(LastName = 'Verma', Email = 'asha@acme.example'),
    new Contact(FirstName = 'Missing'),                                  // no LastName
    new Contact(LastName = 'Kumar', Email = 'not an email'),             // invalid email
    new Contact(LastName = 'Rao')
};

List<Database.SaveResult> results = Database.insert(rows, false);

Integer ok = 0;
for (Integer i = 0; i < results.size(); i++) {
    Database.SaveResult sr = results[i];
    if (sr.isSuccess()) {
        ok++;
    } else {
        for (Database.Error err : sr.getErrors()) {
            System.debug('Row ' + i + ' failed: ' + err.getStatusCode() + ' – ' + err.getMessage() + ' ' + err.getFields());
        }
    }
}
System.debug(ok + ' of ' + rows.size() + ' saved');
System.debug('In the database: ' + [SELECT COUNT() FROM Contact]);

Debug log

Row 1 failed: REQUIRED_FIELD_MISSING – Required fields are missing: [LastName] (LastName)
Row 2 failed: INVALID_EMAIL_ADDRESS – Email: invalid email address: not an email (Email)
2 of 4 saved
In the database: 2

Results come back in the same order as the input list, so results[i] belongs to rows[i]. That's how you report which source row failed – essential for imports, where users need to fix and resend specific lines.

A reusable pattern: collect failures

// DmlResultReport.cls
public with sharing class DmlResultReport {
    public Integer succeeded = 0;
    public List<String> failures = new List<String>();

    public DmlResultReport(List<SObject> records, List<Database.SaveResult> results, Schema.SObjectField labelField) {
        for (Integer i = 0; i < results.size(); i++) {
            if (results[i].isSuccess()) {
                succeeded++;
                continue;
            }
            List<String> messages = new List<String>();
            for (Database.Error e : results[i].getErrors()) messages.add(e.getMessage());
            failures.add(records[i].get(labelField) + ': ' + String.join(messages, '; '));
        }
    }
}
List<Account> load = new List<Account>{
    new Account(Name = 'Acme Corp', Website = 'acme.example'),
    new Account(Website = 'no-name.example'),                    // Name is required
    new Account(Name = 'Globex', Website = 'globex.example')
};
DmlResultReport report = new DmlResultReport(load, Database.insert(load, false), Account.Website);
System.debug('Saved: ' + report.succeeded);
for (String f : report.failures) System.debug('Failed: ' + f);

Debug log

Saved: 2
Failed: no-name.example: Required fields are missing: [Name]

DMLOptions

Database.DMLOptions changes how a save behaves – truncating long text instead of failing, running assignment rules, or allowing saves that duplicate rules would block:

Database.DMLOptions opts = new Database.DMLOptions();
opts.allowFieldTruncation = true;                    // cut long text to fit instead of failing
opts.optAllOrNone = false;

Account longName = new Account(Name = 'X'.repeat(300));
Database.SaveResult sr = Database.insert(longName, opts);
System.debug('Saved with truncation: ' + sr.isSuccess());
System.debug('Stored length: ' + [SELECT Name FROM Account WHERE Id = :sr.getId()].Name.length());

// other common options
// opts.assignmentRuleHeader.useDefaultRule = true;            // Leads and Cases
// opts.duplicateRuleHeader.allowSave = true;                  // bypass "alert" duplicate rules
// opts.emailHeader.triggerUserEmail = true;                   // send standard emails (e.g. case comments)

Debug log

Saved with truncation: true
Stored length: 255

Saving in user mode

Apex runs in system mode by default: object and field permissions aren't checked. When code acts on behalf of a user – an LWC controller, a REST endpoint – you usually want the user's permissions enforced. Add as user to a DML statement, or pass AccessLevel.USER_MODE to a Database method:

insert as user new Account(Name = 'Saved with the user\'s permissions');

List<Database.SaveResult> res = Database.insert(
    new List<Account>{ new Account(Name = 'Also user mode') }, false, AccessLevel.USER_MODE);
System.debug('User-mode insert ok: ' + res[0].isSuccess());
System.debug('Accounts: ' + [SELECT COUNT() FROM Account WITH USER_MODE]);

Debug log

User-mode insert ok: true
Accounts: 2

If the user lacks create access to the object or edit access to a field you set, a user-mode save fails with a clear error instead of silently writing data they shouldn't be able to write. The security lesson covers this in depth.

Practice

Try it yourself

  1. A Queueable updates 5,000 Opportunities and must not stop because a handful fail validation rules. Which call do you use, and what do you do with failures?

    Show answer

    Database.update(opps, false) (in chunks within limits), then loop over the SaveResults and log failures – for example to a custom log object or a Platform Event – with the record Id and error messages.

  2. When should you keep the default all-or-nothing behaviour?

    Show answer

    When the records form one business operation – an order and its lines, a case and its first comment. Saving half of it would leave inconsistent data.

Quick check

What does the second parameter of Database.insert control?

allOrNone – false allows partial success.

How do you match a SaveResult to its record?

By index – results are in the same order as the input list.

How do you insert while enforcing the user's permissions?

insert as user …, or Database.insert(records, AccessLevel.USER_MODE).

Summary

  • Use Database methods with allOrNone=false for imports and integrations.
  • Read SaveResult/UpsertResult/DeleteResult by index and report each failure.
  • DMLOptions controls truncation, assignment rules and duplicate handling.
  • Use as user or AccessLevel.USER_MODE when acting for a user.

Finished this lesson?

Mark it complete to track your progress, then carry on with the next lesson.

Next lesson

Search LearnFrenzy

Esc

Quick links