Table of Contents
ToggleDatabase.SaveResult Class in Salesforce: Complete Guide with Apex Examples
When you’re working with Salesforce Apex development, handling DML operations effectively is crucial for building robust, error-resilient applications. The Database.SaveResult class is your secret weapon for managing insert and update operations with precision and control. This comprehensive post will teach you everything about the Database.SaveResult class in Salesforce, complete with practical Apex code examples you can use immediately.
What is Database.SaveResult Class in Salesforce?
The Database.SaveResult class in Salesforce is a built-in Apex class that captures the results of DML operations performed using Database methods. Unlike standard DML statements that throw exceptions on failure, Database methods return SaveResult objects that allow you to handle successes and failures individually within the same transaction.
Think of Database.SaveResult as a detailed report card for each record you’re trying to save. It tells you:
- Whether the operation succeeded or failed
- The ID of the successfully saved record
- Detailed error information for failed records
- Which fields caused the failure
This granular control is essential for building enterprise-grade Salesforce applications that need sophisticated error handling and partial success capabilities.
Why Use Database.SaveResult Instead of Standard DML?
Before diving into the technical details, let’s understand when and why you should use Database.SaveResult:
Standard DML vs Database Methods
Standard DML Statements (insert, update, delete):
List<Account> accounts = new List<Account>{
new Account(Name = 'Valid Account'),
new Account(Name = '') // Missing required field
};
insert accounts; // Throws exception, ENTIRE operation fails
When any record fails, the entire transaction rolls back. Not a single record gets saved, even if 99 out of 100 records are valid.
Database Methods with SaveResult:
List<Account> accounts = new List<Account>{
new Account(Name = 'Valid Account'),
new Account(Name = '') // Missing required field
};
Database.SaveResult[] results = Database.insert(accounts, false);
// Valid records save, invalid records return errors
With Database methods, you can process valid records while capturing detailed information about failed ones. This is the power of Database.SaveResult.
Understanding Database.SaveResult Return Values
When you execute a Database insert or update operation, you receive an array of Database.SaveResult objects. The order of SaveResult objects corresponds exactly to the order of records in your input list.
List<Contact> contacts = new List<Contact>{
new Contact(FirstName = 'John', LastName = 'Doe'), // Index 0
new Contact(FirstName = 'Jane', LastName = 'Smith'), // Index 1
new Contact(FirstName = 'Bob') // Index 2 - Missing LastName
};
Database.SaveResult[] saveResults = Database.insert(contacts, false);
// saveResults[0] corresponds to John Doe
// saveResults[1] corresponds to Jane Smith
// saveResults[2] corresponds to Bob (will have errors)
This one-to-one correspondence is crucial for tracking which specific records succeeded or failed in bulk operations.
Key Methods of Database.SaveResult Class
The Database.SaveResult class provides three essential methods for handling operation results:
1. isSuccess() Method
Returns a Boolean indicating whether the DML operation succeeded for this specific record.
Syntax: Boolean isSuccess()
Example:
Database.SaveResult result = Database.insert(newAccount, false);
if (result.isSuccess()) {
System.debug('Record saved successfully with ID: ' + result.getId());
} else {
System.debug('Record save failed');
} 2. getId() Method
Returns the Salesforce ID (15 or 18 character) of the successfully saved record. Returns null if the operation failed.
Syntax: Id getId()
Example:
List<Account> accounts = new List<Account>{
new Account(Name = 'Tech Corp'),
new Account(Name = 'Innovation Inc')
};
Database.SaveResult[] results = Database.insert(accounts, false);
List<Id> successfulIds = new List<Id>();
for (Database.SaveResult result : results) {
if (result.isSuccess()) {
successfulIds.add(result. getId());
System.debug('Successfully created Account ID: ' + result.getId());
}
}
System.debug('Total successful inserts: ' + successfulIds.size());
3. getErrors() Method
Returns an array of Database.Error objects containing detailed information about why the operation failed. Returns an empty array if the operation succeeded.
Syntax: Database.Error[] getErrors()
Example:
Database.SaveResult result = Database.insert( invalidAccount, false);
if (!result.isSuccess()) {
for (Database.Error error : result.getErrors()) {
System.debug('Error Status Code: ' + error.getStatusCode());
System.debug('Error Message: ' + error.getMessage());
System.debug('Fields Affected: ' + error.getFields());
}
} Database.Error Class Methods
When a Database.SaveResult indicates failure, you access the Database.Error class to get specific details:
Database.Error Key Methods
getMessage(): Returns the error message string
getStatusCode(): Returns the error status code enum (REQUIRED_FIELD_MISSING, DUPLICATE_VALUE, etc.)
getFields(): Returns an array of field API names that caused the error
Comprehensive Apex Examples
Let’s explore practical examples that demonstrate real-world usage of Database.SaveResult class in Salesforce.
For teams looking beyond traditional data movement tools and exploring real-time unification and activation of enterprise data, Exploring Salesforce Data Cloud: A Comprehensive Guide is a valuable resource. Salesforce Data Cloud enables organizations to ingest, harmonize, and unify data from CRM systems, external apps, legacy sources, and streaming platforms into a single trusted profile that’s ready for analytics, automation, and AI-driven insights. This guide breaks down how Data Cloud works, its core capabilities, and practical ways it supports real-time customer engagement and operational analytics — making it a must-read for anyone architecting a future-ready Salesforce data strategy.
Example 1: Basic Insert with Error Handling
public class AccountCreationService {
public static void createAccounts(List<String> accountNames) {
List<Account> accountsToInsert = new List<Account>();
// Prepare account records
for (String name : accountNames) {
accountsToInsert.add(new Account(Name = name));
}
// Insert with allOrNone = false to allow partial success
Database.SaveResult[] saveResults = Database.insert( accountsToInsert, false);
// Process results
Integer successCount = 0;
Integer failureCount = 0;
for (Integer i = 0; i < saveResults.size(); i++) {
Database.SaveResult sr = saveResults[i];
if (sr.isSuccess()) {
successCount++;
System.debug('Successfully inserted Account: ' +
accountsToInsert[i].Name +
' with ID: ' + sr.getId());
} else {
failureCount++;
System.debug('Failed to insert Account: ' + accountsToInsert[i].Name);
// Log detailed error information
for (Database.Error error : sr.getErrors()) {
System.debug('Error Code: ' + error.getStatusCode());
System.debug('Error Message: ' + error.getMessage());
System.debug('Affected Fields: ' + error.getFields());
}
}
}
System.debug('Summary - Success: ' + successCount + ', Failed: ' + failureCount);
}
}
Usage:
List<String> accountNames = new List<String>{'Valid Corp', '', 'Another Company'};
AccountCreationService. createAccounts(accountNames);
Example 2: Bulk Update with Retry Logic
public class OpportunityUpdateService {
public static void updateOpportunityStages(List< Opportunity> oppsToUpdate) {
Database.SaveResult[] updateResults = Database.update(oppsToUpdate, false);
List<Opportunity> failedOpportunities = new List<Opportunity>();
Map<Id, String> errorMessages = new Map<Id, String>();
for (Integer i = 0; i < updateResults.size(); i++) {
Database.SaveResult sr = updateResults[i];
Opportunity opp = oppsToUpdate[i];
if (sr.isSuccess()) {
System.debug('Opportunity updated: ' + sr.getId());
} else {
// Collect failed records
failedOpportunities.add(opp);
// Build comprehensive error message
String errorMsg = '';
for (Database.Error error : sr.getErrors()) {
errorMsg += error.getMessage() + '; ';
}
errorMessages.put(opp.Id, errorMsg);
}
}
// Handle failed records
if (!failedOpportunities.isEmpty( )) {
handleFailedUpdates( failedOpportunities, errorMessages);
}
}
private static void handleFailedUpdates(List< Opportunity> failedOpps,
Map<Id, String> errors) {
// Create error log records or send notifications
List<Error_Log__c> errorLogs = new List<Error_Log__c>();
for (Opportunity opp : failedOpps) {
errorLogs.add(new Error_Log__c(
Record_Id__c = opp.Id,
Record_Name__c = opp.Name,
Error_Message__c = errors.get(opp.Id),
Operation_Type__c = 'Update',
Timestamp__c = DateTime.now()
));
}
if (!errorLogs.isEmpty()) {
insert errorLogs;
}
}
}
Example 3: Contact Import with Validation
public class ContactImportService {
public class ImportResult {
public Integer successCount = 0;
public Integer failureCount = 0;
public List<String> errorMessages = new List<String>();
public List<Id> createdContactIds = new List<Id>();
}
public static ImportResult importContacts(List<Contact> contactsToImport) {
ImportResult result = new ImportResult();
// Perform insert operation
Database.SaveResult[] saveResults = Database.insert( contactsToImport, false);
// Process each result
for (Integer i = 0; i < saveResults.size(); i++) {
Database.SaveResult sr = saveResults[i];
Contact contact = contactsToImport[i];
if (sr.isSuccess()) {
result.successCount++;
result.createdContactIds.add( sr.getId());
} else {
result.failureCount++;
// Build detailed error message
String errorDetail = 'Contact: ' + contact.FirstName + ' ' +
contact.LastName + ' - Errors: ';
for (Database.Error error : sr.getErrors()) {
errorDetail += '[' + error.getStatusCode() + '] ' +
error.getMessage() + ' (Fields: ' +
error.getFields() + '); ';
}
result.errorMessages.add( errorDetail);
}
}
return result;
}
}
Usage:
List<Contact> contacts = new List<Contact>{
new Contact(FirstName = 'John', LastName = 'Doe', Email = '[email protected]'),
new Contact(FirstName = 'Jane', LastName = 'Smith'), // No email
new Contact(LastName = 'Brown') // Missing FirstName
};
ContactImportService. ImportResult result =
ContactImportService. importContacts(contacts);
System.debug('Successful imports: ' + result.successCount);
System.debug('Failed imports: ' + result.failureCount);
System.debug('Created IDs: ' + result.createdContactIds);
for (String errorMsg : result.errorMessages) {
System.debug('Error: ' + errorMsg);
} Example 4: Complex Batch Processing with SaveResult
public class AccountBatchProcessor implements Database.Batchable<sObject> {
public Database.QueryLocator start(Database. BatchableContext bc) {
return Database.getQueryLocator(
'SELECT Id, Name, AnnualRevenue FROM Account WHERE Industry = \'Technology\''
);
}
public void execute(Database. BatchableContext bc, List<Account> scope) {
List<Account> accountsToUpdate = new List<Account>();
// Business logic: categorize accounts based on revenue
for (Account acc : scope) {
if (acc.AnnualRevenue != null) {
if (acc.AnnualRevenue > 1000000) {
acc.Customer_Tier__c = 'Enterprise';
} else if (acc.AnnualRevenue > 100000) {
acc.Customer_Tier__c = 'Mid-Market';
} else {
acc.Customer_Tier__c = 'Small Business';
}
accountsToUpdate.add(acc);
}
}
// Update with error handling
if (!accountsToUpdate.isEmpty()) {
Database.SaveResult[] updateResults = Database.update( accountsToUpdate, false);
// Log errors to custom object
List<Batch_Error_Log__c> errorLogs = new List<Batch_Error_Log__c>();
for (Integer i = 0; i < updateResults.size(); i++) {
Database.SaveResult sr = updateResults[i];
if (!sr.isSuccess()) {
Account failedAccount = accountsToUpdate[i];
for (Database.Error error : sr.getErrors()) {
errorLogs.add(new Batch_Error_Log__c(
Batch_Job_Id__c = bc.getJobId(),
Record_Id__c = failedAccount.Id,
Record_Name__c = failedAccount.Name,
Error_Code__c = String.valueOf(error. getStatusCode()),
Error_Message__c = error.getMessage(),
Affected_Fields__c = String.join(error.getFields(), ', ')
));
}
}
}
if (!errorLogs.isEmpty()) {
Database.insert(errorLogs, false); // Best effort insert of error logs
}
}
}
public void finish(Database. BatchableContext bc) {
// Send completion notification
AsyncApexJob job = [SELECT Id, Status, NumberOfErrors,
JobItemsProcessed, TotalJobItems
FROM AsyncApexJob
WHERE Id = :bc.getJobId()];
System.debug('Batch job completed. Status: ' + job.Status);
}
} Example 5: Upsert with SaveResult (UpsertResult)
While similar to SaveResult, the Database.UpsertResult class handles upsert operations:
public class LeadUpsertService {
public static void upsertLeads(List<Lead> leadsToUpsert) {
// Upsert using Email as external ID
Database.UpsertResult[] upsertResults =
Database.upsert(leadsToUpsert, Lead.Email, false);
Integer insertedCount = 0;
Integer updatedCount = 0;
Integer failedCount = 0;
for (Integer i = 0; i < upsertResults.size(); i++) {
Database.UpsertResult ur = upsertResults[i];
Lead lead = leadsToUpsert[i];
if (ur.isSuccess()) {
if (ur.isCreated()) {
insertedCount++;
System.debug('Lead inserted: ' + ur.getId());
} else {
updatedCount++;
System.debug('Lead updated: ' + ur.getId());
}
} else {
failedCount++;
System.debug('Lead upsert failed for: ' + lead.Email);
for (Database.Error error : ur.getErrors()) {
System.debug('Error: ' + error.getMessage());
}
}
}
System.debug('Upsert Summary - Inserted: ' + insertedCount +
', Updated: ' + updatedCount +
', Failed: ' + failedCount);
}
}
The allOrNone Parameter Explained
A critical aspect of using Database methods is understanding the allOrNone parameter:
When moving Salesforce data — especially across environments, integrations, or through custom automation — handling date and time fields correctly is critical to preserving data accuracy. The Understanding Apex Date Class: How to Work with Dates in Salesforce? guide breaks down how Salesforce’s Apex Date, DateTime, and Time classes work, how to perform common calculations, and how to avoid timezone or formatting pitfalls during imports and exports. Incorporating this logic into your data transformation or middleware processes helps ensure dates like contract start/end, event timestamps, and history tracking fields stay consistent and meaningful throughout the data movement process.
allOrNone = true (default):
- Behaves like standard DML
- All records must succeed or entire operation fails
- Transaction rolls back on any error
- Exception is thrown
allOrNone = false:
- Allows partial success
- Valid records are saved
- Failed records return errors in SaveResult
- No exception thrown
// Example comparing both approaches
List<Account> accounts = new List<Account>{
new Account(Name = 'Valid Account 1'),
new Account(), // Missing required Name field
new Account(Name = 'Valid Account 2')
};
// Approach 1: allOrNone = true (default)
try {
Database.SaveResult[] results1 = Database.insert(accounts, true);
// This line won't execute because exception is thrown
} catch (DmlException e) {
System.debug('All records failed: ' + e.getMessage());
// Result: ZERO records inserted
}
// Approach 2: allOrNone = false
Database.SaveResult[] results2 = Database.insert(accounts, false);
// Result: 2 valid accounts inserted, 1 failed
// No exception thrown - you handle errors via SaveResult
for (Database.SaveResult sr : results2) {
if (sr.isSuccess()) {
System.debug('Saved: ' + sr.getId());
} else {
System.debug('Failed with errors');
}
}
Common Error Status Codes
Understanding common error codes helps you build better error handling:
public class ErrorCodeHandler {
public static void handleSpecificErrors(Database. SaveResult[] results) {
for (Database.SaveResult sr : results) {
if (!sr.isSuccess()) {
for (Database.Error error : sr.getErrors()) {
switch on error.getStatusCode() {
when REQUIRED_FIELD_MISSING {
System.debug('Missing required field: ' + error.getFields());
// Handle missing field scenario
}
when DUPLICATE_VALUE {
System.debug('Duplicate record detected');
// Handle duplicate scenario
}
when FIELD_CUSTOM_VALIDATION_ EXCEPTION {
System.debug('Custom validation failed: ' + error.getMessage());
// Handle validation rule failure
}
when INVALID_EMAIL_ADDRESS {
System.debug('Invalid email format');
// Handle email validation
}
when UNABLE_TO_LOCK_ROW {
System.debug('Record locked by another process');
// Implement retry logic
}
when else {
System.debug('Unhandled error: ' + error.getStatusCode() +
' - ' + error.getMessage());
}
}
}
}
}
}
}
Best Practices for Database.SaveResult
If you want a broader view of the tools available beyond Salesforce’s native options — especially cloud-based or advanced integration solutions — explore The Best Ways to Move Salesforce Data in 2026. This guide breaks down not only traditional tools like Salesforce Data Loader but also modern approaches including cloud-based ETL platforms, connectors, and integration services that help you migrate, sync, or automate data flows at scale. Whether you’re handling large enterprise migrations or building ongoing integrations, understanding the expanding ecosystem of data-movement technologies will help you choose the right approach for your 2026 roadmap.
1. Always Check isSuccess() Before Accessing getId()
// WRONG - This can cause NullPointerException
for (Database.SaveResult sr : results) {
Id recordId = sr.getId(); // Could be null if failed
processRecord(recordId);
}
// CORRECT
for (Database.SaveResult sr : results) {
if (sr.isSuccess()) {
Id recordId = sr.getId(); // Safe to use
processRecord(recordId);
}
}
2. Log Detailed Error Information
public static void logErrors(Database.SaveResult[ ] results, List<sObject> records) {
for (Integer i = 0; i < results.size(); i++) {
if (!results[i].isSuccess()) {
String errorLog = 'Record: ' + records[i] + '\n';
for (Database.Error error : results[i].getErrors()) {
errorLog += 'Status Code: ' + error.getStatusCode() + '\n';
errorLog += 'Message: ' + error.getMessage() + '\n';
errorLog += 'Fields: ' + error.getFields() + '\n';
}
System.debug(LoggingLevel. ERROR, errorLog);
// Also consider inserting to custom error log object
}
}
}
3. Maintain Record-to-Result Mapping
public class RecordProcessor {
public static Map<Id, Database.SaveResult> getResultMap(
List<Account> accounts,
Database.SaveResult[] results) {
Map<Id, Database.SaveResult> resultMap = new Map<Id, Database.SaveResult>();
for (Integer i = 0; i < results.size(); i++) {
if (results[i].isSuccess()) {
resultMap.put(results[i]. getId(), results[i]);
} else if (accounts[i].Id != null) {
// For updates, map using existing Id
resultMap.put(accounts[i].Id, results[i]);
}
}
return resultMap;
}
}
4. Implement Retry Logic for Transient Errors
public class RetryableProcessor {
private static final Integer MAX_RETRIES = 3;
public static void insertWithRetry(List<Account> accounts) {
List<Account> accountsToInsert = accounts.clone();
Integer retryCount = 0;
while (retryCount < MAX_RETRIES && !accountsToInsert.isEmpty()) {
Database.SaveResult[] results = Database.insert( accountsToInsert, false);
List<Account> failedAccounts = new List<Account>();
for (Integer i = 0; i < results.size(); i++) {
if (!results[i].isSuccess()) {
Boolean shouldRetry = false;
for (Database.Error error : results[i].getErrors()) {
// Retry only for specific error codes
if (error.getStatusCode() == StatusCode.UNABLE_TO_LOCK_ROW ||
error.getStatusCode() == StatusCode.QUERY_TIMEOUT) {
shouldRetry = true;
break;
}
}
if (shouldRetry) {
failedAccounts.add( accountsToInsert[i]);
}
}
}
accountsToInsert = failedAccounts;
retryCount++;
if (!accountsToInsert.isEmpty() && retryCount < MAX_RETRIES) {
// Wait before retrying (in real scenarios, consider async retry)
System.debug('Retrying ' + accountsToInsert.size() + ' records. Attempt: ' + retryCount);
}
}
if (!accountsToInsert.isEmpty()) {
System.debug('Failed to insert ' + accountsToInsert.size() +
' records after ' + MAX_RETRIES + ' attempts');
}
}
}
5. Create Reusable Error Handling Utilities
public class SaveResultHelper { public class ProcessResult { public List<Id> successIds = new List<Id>(); public Map<Integer, String> failureMap = new Map<Integer, String>(); public Integer successCount = 0; public Integer failureCount = 0; } public static ProcessResult processResults(Database.SaveResult[] results) { ProcessResult processResult = new ProcessResult(); for (Integer i = 0; i < results.size(); i++) { Database.SaveResult sr = results[i]; if (sr.isSuccess()) { processResult.successIds.add( sr.getId()); processResult.successCount++; } else { String errorMessage = buildErrorMessage(sr. getErrors()); processResult.failureMap.put( i, errorMessage); processResult.failureCount++; } } return processResult; } private static String buildErrorMessage(Database. Error[] errors) { List<String> errorMessages = new List<String>(); for (Database.Error error : errors) { errorMessages.add( error.getStatusCode() + ': ' + error.getMessage() + ' [' + String.join(error.getFields(), ', ') + ']' ); } return String.join(errorMessages, '; '); } public static void logResults(ProcessResult processResult, String operationType) { System.debug('===== ' + operationType + ' Operation Results ====='); System.debug('Success Count: ' + processResult.successCount); System.debug('Failure Count: ' + processResult.failureCount); System.debug('Successful IDs: ' + processResult.successIds); if (!processResult.failureMap. isEmpty()) { System.debug('Failed Records:'); for (Integer index : processResult.failureMap. keySet()) { System.debug(' Record ' + index + ': ' + processResult.failureMap.get( index)); } } } }
Usage:
List<Account> accounts = createTestAccounts();
Database.SaveResult[] results = Database.insert(accounts, false);
SaveResultHelper.ProcessResult processResult =
SaveResultHelper. processResults(results);
SaveResultHelper.logResults( processResult, 'Insert'); Real-World Use Cases
Use Case 1: Data Migration with Error Reporting
public class DataMigrationService {
public static void migrateAccountData(List< Account> legacyAccounts) {
Database.SaveResult[] results = Database.insert( legacyAccounts, false);
// Create detailed migration report
List<Migration_Report__c> reports = new List<Migration_Report__c>();
for (Integer i = 0; i < results.size(); i++) {
Migration_Report__c report = new Migration_Report__c();
report.Source_Record_Id__c = legacyAccounts[i].External_Id_ _c;
report.Migration_Date__c = Date.today();
if (results[i].isSuccess()) {
report.Status__c = 'Success';
report.Target_Record_Id__c = results[i].getId();
} else {
report.Status__c = 'Failed';
report.Error_Details__c = getErrorDetails(results[i]. getErrors());
}
reports.add(report);
}
insert reports;
}
private static String getErrorDetails(Database. Error[] errors) {
String details = '';
for (Database.Error error : errors) {
details += error.getMessage() + ' ';
}
return details.abbreviate(255);
}
}
Use Case 2: Integration Error Handling
public class ExternalSystemIntegration {
public static void syncContactsFromExternalSystem (List<Contact> externalContacts) {
Database.SaveResult[] results = Database.upsert(
externalContacts,
Contact.External_System_Id__c,
false
);
List<Integration_Log__c> logs = new List<Integration_Log__c>();
for (Integer i = 0; i < results.size(); i++) {
Integration_Log__c log = new Integration_Log__c();
log.External_Id__c = externalContacts[i].External_ System_Id__c;
log.Sync_Timestamp__c = DateTime.now();
if (results[i].isSuccess()) {
log.Status__c = 'Synced';
log.Salesforce_Record_Id__c = results[i].getId();
} else {
log.Status__c = 'Failed';
log.Error_Message__c = formatErrorsForLog(results[i]. getErrors());
log.Requires_Manual_Review__c = true;
}
logs.add(log);
}
Database.insert(logs, false);
}
private static String formatErrorsForLog(Database. Error[] errors) {
List<String> errorStrings = new List<String>();
for (Database.Error error : errors) {
errorStrings.add(
'[' + error.getStatusCode() + '] ' + error.getMessage()
);
}
return String.join(errorStrings, ' | ').left(32768);
}
} Testing Database.SaveResult
Proper testing is essential when working with SaveResult:
@isTest
private class AccountServiceTest {
@isTest
static void testSuccessfulInsert() {
List<Account> accounts = new List<Account>{
new Account(Name = 'Test Account 1'),
new Account(Name = 'Test Account 2')
};
Test.startTest();
Database.SaveResult[] results = Database.insert(accounts, false);
Test.stopTest();
// Assertions
System.assertEquals(2, results.size(), 'Should have 2 results');
for (Database.SaveResult sr : results) {
System.assert(sr.isSuccess(), 'Insert should succeed');
System.assertNotEquals(null, sr.getId(), 'Should have valid Id');
}
// Verify records were created
List<Account> insertedAccounts = [SELECT Id, Name FROM Account];
System.assertEquals(2, insertedAccounts.size(), 'Should have 2 accounts');
}
@isTest
static void testPartialFailure() {
List<Account> accounts = new List<Account>{
new Account(Name = 'Valid Account'),
new Account() // Missing required Name
};
Test.startTest();
Database.SaveResult[] results = Database.insert(accounts, false);
Test.stopTest();
// Verify first record succeeded
System.assert(results[0]. isSuccess(), 'First account should succeed');
System.assertNotEquals(null, results[0].getId(), 'Should have valid Id');
// Verify second record failed
System.assert(!results[1]. isSuccess(), 'Second account should fail');
System.assertEquals(null, results[1].getId(), 'Failed record has no Id');
// Verify error details
Database.Error[] errors = results[1].getErrors();
System.assert(errors.size() > 0, 'Should have error details');
System.assertEquals(
StatusCode.REQUIRED_FIELD_ MISSING,
errors[0].getStatusCode(),
'Should be required field error'
);
}
@isTest
static void testBulkOperation() {
List<Account> accounts = new List<Account>();
// Create 200 test accounts
for (Integer i = 0; i < 200; i++) {
accounts.add(new Account(Name = 'Bulk Account ' + i));
}
Test.startTest();
Database.SaveResult[] results = Database.insert(accounts, false);
Test.stopTest();
// Verify all succeeded
Integer successCount = 0;
for (Database.SaveResult sr : results) {
if (sr.isSuccess()) {
successCount++;
}
}
System.assertEquals(200, successCount, 'All 200 accounts should succeed');
}
}
Performance Considerations
Bulk Processing Best Practices
// INEFFICIENT - Multiple DML operations
public static void inefficientUpdate(List< Contact> contacts) {
for (Contact c : contacts) {
try {
update c; // Separate DML for each record - BAD!
} catch (DmlException e) {
System.debug('Error: ' + e.getMessage());
}
}
}
// EFFICIENT - Single bulk DML operation
public static void efficientUpdate(List<Contact> contacts) {
Database.SaveResult[] results = Database.update(contacts, false);
for (Integer i = 0; i < results.size(); i++) {
if (!results[i].isSuccess()) {
System.debug('Error updating contact: ' + contacts[i].Id);
for (Database.Error error : results[i].getErrors()) {
System.debug('Error: ' + error.getMessage());
}
}
}
}
Governor Limit Management
public class LimitAwareProcessor {
public static void processBatchWithLimits(List< Account> accounts) {
// Check DML limits before processing
Integer dmlLimit = Limits.getLimitDmlStatements() ;
Integer dmlUsed = Limits.getDmlStatements();
if (dmlUsed >= dmlLimit) {
System.debug('DML limit reached. Cannot process batch.');
return;
}
// Check row limits
Integer rowLimit = Limits.getLimitDmlRows();
Integer rowsUsed = Limits.getDmlRows();
Integer rowsAvailable = rowLimit - rowsUsed;
if (accounts.size() > rowsAvailable) {
System.debug('Warning: Batch size exceeds available DML rows');
// Consider splitting into smaller batches
}
Database.SaveResult[] results = Database.insert(accounts, false);
// Process results
for (Database.SaveResult sr : results) {
if (!sr.isSuccess()) {
// Handle errors
}
}
}
}
Common Pitfalls and How to Avoid Them
Pitfall 1: Not Checking isSuccess() Before Using getId()
// WRONG
Database.SaveResult[] results = Database.insert(records, false);
for (Database.SaveResult sr : results) {
myMap.put(sr.getId(), someValue); // NullPointerException if failed!
}
// CORRECT
Database.SaveResult[] results = Database.insert(records, false);
for (Database.SaveResult sr : results) {
if (sr.isSuccess()) {
myMap.put(sr.getId(), someValue);
}
}
Pitfall 2: Ignoring Error Details
// INSUFFICIENT - Only checking success
if (!sr.isSuccess()) {
System.debug('Failed'); // Not helpful!
}
// BETTER - Logging detailed errors
if (!sr.isSuccess()) {
for (Database.Error error : sr.getErrors()) {
System.debug('Error Code: ' + error.getStatusCode());
System.debug('Message: ' + error.getMessage());
System.debug('Fields: ' + error.getFields());
}
} Pitfall 3: Not Handling Partial Success Properly
// WRONG - Assuming all-or-nothing
Database.SaveResult[] results = Database.insert(accounts, false);
// Continuing without checking which records actually saved
// CORRECT - Track successes and failures separately
Database.SaveResult[] results = Database.insert(accounts, false);
List<Id> successIds = new List<Id>();
List<Account> failedAccounts = new List<Account>();
for (Integer i = 0; i < results.size(); i++) {
if (results[i].isSuccess()) {
successIds.add(results[i]. getId());
} else {
failedAccounts.add(accounts[i] );
}
}
Master Salesforce Development with Comprehensive Training
Understanding Database.SaveResult class is fundamental to professional Apex development, but it’s just one piece of building robust Salesforce applications. To truly excel as a Salesforce developer, you need comprehensive training that covers DML operations, triggers, asynchronous processing, testing, and integration patterns.
Ready to take your Salesforce development skills to the next level? The Salesforce Certified Platform Developer I (LWC & Aura) Course provides in-depth coverage of Apex fundamentals including Database methods, SaveResult handling, error management, and all other essential developer competencies. This comprehensive course includes hands-on projects, real-world scenarios, and complete certification exam preparation to help you become a certified Salesforce Platform Developer I. Enroll today and master the skills that top Salesforce developers rely on daily!
Comparison: Database Methods vs Standard DML
Here’s a quick reference table:
| Feature | Standard DML | Database Methods |
|---|---|---|
| Partial Success | No | Yes (with allOrNone=false) |
| Exception Handling | Throws exception on failure | Returns result objects |
| Detailed Error Info | Limited (via exception) | Comprehensive (via SaveResult) |
| Performance | Slightly faster | Negligible difference |
| Code Complexity | Simpler | More verbose |
| Use Case | Simple operations where all must succeed | Complex operations needing granular control |
Database.SaveResult Quick Reference
// Basic syntax
Database.SaveResult result = Database.insert(record, allOrNone);
Database.SaveResult[] results = Database.insert(records, allOrNone);
// Check success
if (result.isSuccess()) { }
// Get ID of saved record
Id recordId = result.getId();
// Get errors
Database.Error[] errors = result.getErrors();
for (Database.Error error : errors) {
String message = error.getMessage();
StatusCode code = error.getStatusCode();
List<String> fields = error.getFields();
}
// Common status codes
StatusCode.REQUIRED_FIELD_ MISSING
StatusCode.DUPLICATE_VALUE
StatusCode.FIELD_CUSTOM_ VALIDATION_EXCEPTION
StatusCode.INVALID_EMAIL_ ADDRESS
StatusCode.UNABLE_TO_LOCK_ROW
StatusCode.FIELD_INTEGRITY_ EXCEPTION Conclusion
The Database.SaveResult class in Salesforce is an essential tool for professional Apex developers who need sophisticated error handling and partial success capabilities. By mastering SaveResult, you can build more resilient applications that gracefully handle failures, provide detailed error reporting, and maintain data integrity even in complex bulk operations.
Remember these key takeaways:
- Always check isSuccess() before accessing getId()
- Use allOrNone=false when you need partial success capability
- Log detailed error information using getErrors()
- Maintain the correspondence between input records and SaveResult objects
- Implement retry logic for transient errors
- Create reusable error handling utilities for consistency
- Test both success and failure scenarios thoroughly
The Database.SaveResult class transforms error handling from a reactive process into a proactive strategy, giving you complete control over how your application responds to data operation outcomes. Master this class, and you’ll write more robust, maintainable, and professional Salesforce code.
Advance Your Salesforce Development Career
Want to master Apex development and earn your Platform Developer I certification? Join the comprehensive Salesforce Certified Platform Developer I (LWC & Aura) Course Learn Database methods, triggers, asynchronous Apex, testing, Lightning Web Components, and everything you need to become a certified Salesforce developer. Start building production-ready applications today!
Frequently Asked Questions
Q: What’s the difference between Database.SaveResult and Database.UpsertResult? SaveResult is returned by insert and update operations, while UpsertResult (which extends SaveResult) is returned by upsert operations. UpsertResult includes an additional isCreated() method to determine if a record was inserted (true) or updated (false).
Q: When should I use allOrNone=false vs allOrNone=true? Use allOrNone=false when you want to process valid records even if some fail (data imports, integrations). Use allOrNone=true when all records must succeed or the entire operation should fail (financial transactions, critical data updates).
Q: Can I use Database.SaveResult with custom objects? Yes! Database.SaveResult works with all standard and custom objects. The class is generic and handles any sObject type.
Q: How do I handle multiple errors for a single record? A single record can have multiple errors (e.g., multiple field validation failures). Use getErrors() which returns an array, then iterate through all errors for comprehensive error handling.
Q: Does using Database methods impact performance compared to standard DML? The performance difference is negligible. Use Database methods when you need the additional control they provide, not for performance reasons.
Q: How can I retry failed records from a SaveResult? Extract failed records by checking isSuccess() for each result, collect those records in a new list, and attempt to insert/update them again. Implement maximum retry limits to avoid infinite loops.
Q: Can I use SaveResult in triggers? Yes, but be careful with recursive DML operations. If your trigger performs additional DML using Database methods, ensure you have proper recursion prevention logic.
Q: What happens to related records when an insert fails? When allOrNone=false, only the specific record that failed is not inserted. Other records in the batch are processed independently. However, any relationships to the failed record won’t be established.
Q: How do I test Database.SaveResult error scenarios? Create test data that intentionally violates validation rules, required fields, or other constraints. Use @isTest methods to verify that your code correctly handles the SaveResult errors.





