System.FinalException: Record is read-only: Trigger.updateCompetitors: line 24

  • I am writing a trigger to get all the Opportunity competitors into a field on Opportunity, however, getting an error while testing it.

    Error: updateCompetitors: execution of AfterUpdate caused by: System.FinalException: Record is read-only: Trigger.updateCompetitors: line 24

    Can you please check the below code and suggest me what's wrong with this?

    trigger updateCompetitors on Opportunity(after insert, after update){
       List<Opportunity> OppIds = new List<Opportunity>();
        for (Opportunity opp:{
     List<OpportunityCompetitor> oppComps = new List<OpportunityCompetitor>();
     oppComps= [select id, CompetitorName, Strengths, Weaknesses from OpportunityCompetitor where OpportunityId in: Trigger.newMap.keySet()];
     List<Opportunity> OppstoUpdate = new List<Opportunity>();
     for (Opportunity opp: OppIds){
        String allcomps = null;
         if( oppComps.size()>0 ) {
          for (integer i = 0; i < oppComps.size(); i++){
             allcomps = allcomps + oppComps[i].CompetitorName+',';
       opp.OpportunityCompetitors__c = allcomps;
      update OppstoUpdate;

    I think this is because you're updating the same records in an after update trigger. Any reason why this can't be done in the before insert context ?

    SOQL is the reason why it cant be done in before as Bob suggests in his answer

  • This is because you are in an after insert/update trigger and the records are read only in that context as they have been written, but not committed, to the database.

    Unfortunately your trigger is relying on the ids of the records, which means you won't be able to use before insert as the ids won't be populated at that time (as the records haven't been written to the database at that point so while you can write to them, database generated fields aren't populated).

    In this instance you'll need to clone the record (or query anew via SOQL) and make changes to the new copy of the record, then execute the update against the new copies.

    This is exactly what I needed; thanks!

  • You cannot update fields on triggered records when you are in an after trigger

    Imagine you have updated the opportunity ,again your record will invoke the trigger since an update happened and there will be infinite recurssion loop that will happen for your case.

    Use a static boolean approach to stop the recurssion if you are using the above trigger

  • I have recently come across the same issue and was able to resolve it by having Two - Part context variables.

    First one just to initiate the set or Add all the Opp ids into the set Second one to execute the Database update of the changes you intend.

    While the first context variable of is being executed the Opportunity record will be saved which gives the required time lapse for the trigger to start the Second Context variable of

    trigger mynewtesttrigger on Opportunity (after insert, after update) {
    if (trigger.isAfter){
    if (trigger.isUpdate || trigger.isInsert){
    //Create set of Opportunity Ids based on updated Opportunity/s
    Set <String> OppIds = new Set <String>();
    for (Opportunity opp :{
    // First Initiation of is Completed and the Opportunity record is saved and no longer in Read Only mode.
    //Compare against reference data
        Map <String, OpportunityCompetitor>matchingoppcomps = new Map <String, OpportunityCompetitor> ();
    for (OpportunityCompetitor oppcomp : [Select Id, CompetitorName, Strengths, Weaknesses From OpportunityCompetitor Where OpportunityId IN :OppIds]) 
    **// Here you need to put your logic to add all the competitors to the String** 
    matchingoppcomps.put(oppcomp.CompetitorName, oppcomp); 
    for(Opportunity theopp : {
    if (matchingoppcomps.get(theopp.ID)!=null){
    //Found the data. Perform the update.
    theopp.OpportunityCompetitors__c = matchingoppcomps.get(theopp.ID).Id; 

License under CC-BY-SA with attribution

Content dated before 7/24/2021 11:53 AM