Hard Delete
Permanently delete records from the database, bypassing the Recycle Bin.
Example
List<Account> obsoleteAccounts = [SELECT Id FROM Account WHERE CreatedDate < LAST_N_YEARS:5];
List<Lead> obsoleteLeads = [SELECT Id FROM Lead WHERE CreatedDate < LAST_N_YEARS:5];
new DML()
.toHardDelete(obsoleteAccounts)
.toHardDelete(obsoleteLeads)
.commitWork();WARNING
Hard delete is irreversible. Records are deleted and then removed from the Recycle Bin (Database.emptyRecycleBin), so they cannot be restored with toUndelete/undelete or recovered from the Recycle Bin.
toHardDelete
Register records for hard deletion. The actual DML is executed when commitWork() is called — records are deleted (Database.delete) and then permanently removed from the Recycle Bin (Database.emptyRecycleBin).
Signature
Commitable toHardDelete(Id recordId);
Commitable toHardDelete(SObject record);
Commitable toHardDelete(Iterable<Id> recordIds);
Commitable toHardDelete(List<SObject> records);Records registered with toHardDelete must have an Id by the time the operation executes — otherwise a DmlException with the message Only existing records can be registered as deleted. is thrown when commitWork() runs. A record inserted earlier in the same unit of work receives its Id from the insert, so it can be registered for hard delete in the same commit. Operations that executed earlier in the same commitWork() are not rolled back when the exception is thrown — use commitTransaction() when the whole unit of work must be atomic (see Deferred Validation).
INFO
There is no hardDeleteImmediately variant — hard delete is deferred-only and always executes as part of commitWork().
TIP
- Results: Hard deletes appear in the same result buckets as regular deletes —
result.deletes()andresult.deletesOf(SObjectType)withDML.OperationType.DELETE_DML. There is no separate hard-delete bucket. See Result. - Bucketing: Hard-delete registrations are kept separate from
toDeleteregistrations of the same SObjectType — they never combine into one DML statement. - Governor limits: The
Database.emptyRecycleBincall is an additional DML operation counted against governor limits.
Single Record
Signature
Commitable toHardDelete(SObject record);Standard DML
Account account = [SELECT Id FROM Account LIMIT 1];
delete account;
Database.emptyRecycleBin(account);DML Lib
Account account = [SELECT Id FROM Account LIMIT 1];
new DML()
.toHardDelete(account)
.commitWork();By Record ID
Hard delete using a record ID directly.
Signature
Commitable toHardDelete(Id recordId);Standard DML
Id accountId = '001xx000003DGbYAAW';
delete [SELECT Id FROM Account WHERE Id = :accountId];
Database.emptyRecycleBin(new List<Id>{ accountId });DML Lib
Id accountId = '001xx000003DGbYAAW';
new DML()
.toHardDelete(accountId)
.commitWork();Multiple Records
Signature
Commitable toHardDelete(List<SObject> records);
Commitable toHardDelete(Iterable<Id> recordIds);Standard DML
List<Account> accounts = [SELECT Id FROM Account WHERE Name LIKE 'Test%'];
delete accounts;
Database.emptyRecycleBin(accounts);DML Lib
List<Account> accounts = [SELECT Id FROM Account WHERE Name LIKE 'Test%'];
new DML()
.toHardDelete(accounts)
.commitWork();By Record IDs
Hard delete using a collection of record IDs.
Signature
Commitable toHardDelete(Iterable<Id> recordIds);Standard DML
Set<Id> accountIds = new Set<Id>{ accountId1, accountId2, accountId3 };
delete [SELECT Id FROM Account WHERE Id IN :accountIds];
Database.emptyRecycleBin(new List<Id>(accountIds));DML Lib
Set<Id> accountIds = new Set<Id>{ accountId1, accountId2, accountId3 };
new DML()
.toHardDelete(accountIds)
.commitWork();Mocking
There are no dedicated hard-delete mock methods — DML.mock(identifier).allDeletes(), .deletesFor(SObjectType), .exceptionOnDeletes(), and .exceptionOnDeletesFor(SObjectType) cover toHardDelete as well, and results land in the delete buckets. Mocked commits execute zero DML. See Delete Mocking.
