function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
David_David_ 

Best Practices for resolving "System.FinalException: Record is read-only"

Hey there, 

the System.FinalException: Record is read-only error is a well known error when talking about After Insert Triggers. I was wondering what's the most common workaround to resolve it.

Example scenario:

Let's say we want to write an After Insert Trigger that...

1) ... sets a field value in a newly created Lead record
2) ... creates a related record (e.g. Task) and therefore requires the Record Id of the new Lead (this part of the logic actually forces us to go for an After Trigger).

This code apparently will give us the notorious error message:
trigger leadTestTrigger on Lead (after insert) {
    for (Lead myLead : Trigger.new) {
        // We're inserting a value into a field of the initial record
        myLead.AnyField = 'Any Value';
        // Create a task related to the Lead
        Task myTask     = new Task();
        myTask.Subject  = 'Anything';
        myTask.WhoId    = myLead.Id;
        myTask.Priority = 'Normal';
        myTask.Status   = 'Not Started';
        insert myTask;       
    }
}

What's the most used technique to get out of this dilemma? How should the above code be amended?

Best,
David
Best Answer chosen by David_
NehaDabas16NehaDabas16
Hi David,

For the above stated scenario, you will have to use both, before and after triggers.
1) ... sets a field value in a newly created Lead record ---- Before Insert
2) ... creates a related record (e.g. Task) and therefore requires the Record Id of the new Lead (this part of the logic actually forces us to go for an After Trigger). -- After Insert

Use below snippet for your reference:
//Trigger
trigger leadTestTrigger on Lead (before insert,after insert) {

    if(Trigger.isBefore){
		LeadTriggerHelper.updateLeads(Trigger.New);
	}
	if(Trigger.isAfter){
		LeadTriggerHelper.createTasks(Trigger.New);
	}
}
// TRigger helper class. 
// It is again a best practice to put all your trigger logic in a separate helper class. This helps in code readability and when trigger has many scenarios, it is easier to scale.
public class LeadTriggerHelper{
	public static void updateLeads(List<Lead> leadsLst){
		for (Lead myLead : leadsLst) {
			// We're inserting a value into a field of the initial record
			myLead.AnyField = 'Any Value';
		}
	}
	
	public static void createTasks(List<Lead> leadsLst){
		List<Task> tasksLst = new List<Task>(); // Always use collections and never make DML statements in loops
		for (Lead myLead : leadsLst) {
			// Create a task related to the Lead
			Task myTask     = new Task();
			myTask.Subject  = 'Anything';
			myTask.WhoId    = myLead.Id;
			myTask.Priority = 'Normal';
			myTask.Status   = 'Not Started';
			tasksLst.add(myTask);   
		}
		
		if(tasksLst.size() > 0 ){
			insert tasksLst;
		}
	}
}

Please like and mark as resolved, if this helps.

All Answers

NehaDabas16NehaDabas16
Hi David,

For the above stated scenario, you will have to use both, before and after triggers.
1) ... sets a field value in a newly created Lead record ---- Before Insert
2) ... creates a related record (e.g. Task) and therefore requires the Record Id of the new Lead (this part of the logic actually forces us to go for an After Trigger). -- After Insert

Use below snippet for your reference:
//Trigger
trigger leadTestTrigger on Lead (before insert,after insert) {

    if(Trigger.isBefore){
		LeadTriggerHelper.updateLeads(Trigger.New);
	}
	if(Trigger.isAfter){
		LeadTriggerHelper.createTasks(Trigger.New);
	}
}
// TRigger helper class. 
// It is again a best practice to put all your trigger logic in a separate helper class. This helps in code readability and when trigger has many scenarios, it is easier to scale.
public class LeadTriggerHelper{
	public static void updateLeads(List<Lead> leadsLst){
		for (Lead myLead : leadsLst) {
			// We're inserting a value into a field of the initial record
			myLead.AnyField = 'Any Value';
		}
	}
	
	public static void createTasks(List<Lead> leadsLst){
		List<Task> tasksLst = new List<Task>(); // Always use collections and never make DML statements in loops
		for (Lead myLead : leadsLst) {
			// Create a task related to the Lead
			Task myTask     = new Task();
			myTask.Subject  = 'Anything';
			myTask.WhoId    = myLead.Id;
			myTask.Priority = 'Normal';
			myTask.Status   = 'Not Started';
			tasksLst.add(myTask);   
		}
		
		if(tasksLst.size() > 0 ){
			insert tasksLst;
		}
	}
}

Please like and mark as resolved, if this helps.
This was selected as the best answer
Ajay K DubediAjay K Dubedi
Hi David,
Try this code:
 
trigger leadTestTrigger on Lead (after insert) {
    List<Lead> udatedListLead = new List<Lead>();
    List<Task> listTask = new List<Task>();
    for (Lead myLead : Trigger.new) {
        // We're inserting a value into a field of the initial record
        Lead leadObj = new Lead();
        leadObj.Id = myLead.Id;
        leadObj.Title = 'Any Value';
        udatedListLead.add(leadObj);
        
        // Create a task related to the Lead
        Task myTask     = new Task();
        myTask.Subject  = 'Anything';
        myTask.WhoId    = myLead.Id;
        myTask.Priority = 'Normal';
        myTask.Status   = 'Not Started';
        listTask.add(myTask); 
    }
    update udatedListLead;
    insert listTask;
    system.debug('udatedListLead-----' + udatedListLead);
    system.debug('listTask-----' + listTask);
}



I hope you find the above solution helpful. If it does, please mark as Best Answer to help others too.

Thanks,
Ajay Dubedi