What is an sObject?
An sObject is Apex's generic representation of any Salesforce record — standard objects like Account, Contact, and Opportunity, or custom objects like Invoice__c, all inherit from the base sObject type. You can declare fields dynamically with new Account(Name = 'Acme Corp', Industry = 'Technology') and every sObject automatically carries system fields like Id, CreatedDate, and LastModifiedDate once it's been inserted, with Id remaining null until the DML operation completes.
Cricket analogy: A player registration form used by the ICC works the same for a batter, bowler, or all-rounder, one generic template with a name and role, the same way sObject is the generic base type underlying Account, Contact, and every custom object.
// Creating and inserting a new sObject record
Account newAcct = new Account(
Name = 'Acme Corp',
Industry = 'Technology',
AnnualRevenue = 5000000
);
insert newAcct;
System.debug('New Account Id: ' + newAcct.Id); // populated after insert
// Generic sObject variable — useful for dynamic code
SObject genericRecord = newAcct;
String acctName = (String) genericRecord.get('Name');
// Custom object example
Invoice__c inv = new Invoice__c(
Account__c = newAcct.Id,
Amount__c = 1500.00,
Status__c = 'Draft'
);
insert inv;DML: insert, update, upsert, delete, undelete
Data Manipulation Language (DML) statements are how Apex persists changes: insert creates new records, update modifies existing ones (requires a populated Id), upsert does either based on Id or an External ID field, delete moves records to the Recycle Bin, and undelete restores them within 15 days. Every DML operation is bulk-capable — insert accountList; works identically whether the list has 1 record or 200, which is exactly why the bulk patterns from Lists and Maps matter so much in real trigger code.
Cricket analogy: A team's official squad announcement (insert) differs from a mid-series replacement announcement (update) and a final squad-list correction that adds or replaces a name based on jersey number (upsert), each a distinct roster operation.
upsert is especially valuable for data integration scenarios where an external system feeds records into Salesforce repeatedly — you mark a custom field as an External ID (e.g. SAP_Id__c) and call upsert accountList SAP_Id__c;, letting Salesforce decide per-record whether to insert or update based on whether that External ID value already exists. Without upsert, integration code would need to query first to check existence, adding an extra SOQL query per sync run.
Cricket analogy: A stadium's ticketing system reconciling a season-ticket holder's renewal either creates a fresh membership or updates the existing one based on a unique member number, exactly like upsert keyed on an External ID.
Database.insert(), Database.update(), and Database.upsert() are the 'partial success' variants of DML. Unlike the plain insert/update statements, which throw an exception and roll back the whole batch on any single record failure, the Database.* methods accept a Boolean allOrNothing parameter — passing false lets valid records succeed while failed ones are reported individually via a returned Database.SaveResult[] array.
List<Account> accountsToInsert = new List<Account>{
new Account(Name = 'Globex'),
new Account(Name = null) // will fail: Name is required
};
Database.SaveResult[] results = Database.insert(accountsToInsert, false);
for (Integer i = 0; i < results.size(); i++) {
if (!results[i].isSuccess()) {
for (Database.Error err : results[i].getErrors()) {
System.debug('Row ' + i + ' failed: ' + err.getMessage());
}
}
}
// Upsert against an External Id field for integration sync
List<Account> externalAccounts = new List<Account>{
new Account(SAP_Id__c = 'SAP-001', Name = 'Acme via SAP')
};
upsert externalAccounts SAP_Id__c;A plain update statement on a list throws a DmlException and rolls back the entire operation if even one record among hundreds fails validation. In trigger and batch code where partial success is acceptable, use Database.update(records, false) and inspect the SaveResult array instead of letting one bad record block 199 good ones.
Trigger context and sObject state
Inside a trigger, Trigger.new holds the list of sObjects in their new state (insert/update contexts) and Trigger.old holds the previous state (update/delete contexts), both accessible alongside Trigger.newMap and Trigger.oldMap for Id-keyed lookups. A common pattern is comparing Trigger.new and Trigger.old field-by-field to detect actual changes, since an update DML call fires even if no field values actually changed, and you generally don't want side effects re-running unnecessarily.
Cricket analogy: Comparing a player's fitness report before and after a training camp (Trigger.old vs Trigger.new) tells the coach exactly what changed, rather than assuming every report update means something meaningful shifted.
- sObject is the generic base type underlying every standard object (Account, Contact) and custom object (Invoice__c).
- insert creates records, update modifies existing ones by Id, upsert creates-or-updates based on Id or an External ID field, and delete/undelete manage the Recycle Bin lifecycle.
- All DML statements are inherently bulk-capable, operating on a List of up to 10,000 records in a single call (subject to the 150 DML statement limit).
- Database.insert/update/upsert with allOrNothing = false enable partial success, returning a SaveResult[] to inspect per-record errors.
- A plain insert/update statement throws and rolls back the entire operation if any single record fails.
- Trigger.new/old and their Map variants (newMap/oldMap) give access to a record's before/after state inside triggers.
- Comparing Trigger.old and Trigger.new field values prevents automation logic from re-firing on no-op updates.
Practice what you learned
1. What is the base type that both standard objects like Account and custom objects like Invoice__c inherit from in Apex?
2. Which DML operation would you use to sync records from an external system, creating new records or updating existing ones based on a custom External ID field, without a separate existence-check query?
3. What does calling Database.insert(recordList, false) allow that a plain insert recordList; statement does not?
4. Inside an update trigger, why would you compare Trigger.new and Trigger.old field values before running side-effect logic?
5. What happens to a plain insert statement operating on a List<Account> if one record out of 200 fails validation?
Was this page helpful?
You May Also Like
Conditionals and Loops in Apex
Learn how Apex controls program flow with if/else branching, switch statements, and for/while loops, including the governor limits that shape how you write them.
Lists, Sets, and Maps
Master Apex's three core collection types — ordered Lists, unique Sets, and key-value Maps — and when to use each for bulk-safe, governor-limit-friendly code.
SOQL Queries
Learn Salesforce Object Query Language (SOQL) syntax for retrieving records, including relationship queries, aggregate functions, and governor-limit-aware query design.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics