I'm looking for some advice, both on my approach and how to solve my particular issue.

Use Case

When an external integration team updates all particular recordTypes. Every picklist value associated with any available picklists becomes inactive. Which means it became a manual task to update all of these values back to active. 


My solution was first to store custom Metadata. With each row containing

  • Object
  • RecordType
  • Picklists ('Field_1__c, Field_2__c, Field_3__c')

Then going forwards-

  1. Integration team call to a WebService Apex class
  2. That WebService Apex class starts a batch Job
  3. The batch Job fires an Apex Class

The first two points are self-explanatory. However the Apex class is the issue.

Originally I used a for loop on the Metadata. Processing each Object & RecordType pairing using Tooling API & a Custom Wrapper Class to GET both existing Metadata, process all picklist values to become active then PATCH the RecordType using a queueable. With this approach I got success on miniature data sets, but ultimately we were getting timeouts and record locking issues.

The next approach was using FinancialForce's Apex MetadataService (md-api) code. After days of working with this, Metadata API and bring the MetadataService class up to date with API v50.0. I got more success in our sandbox org. But now I'm getting timeout issues and I'm confident that after nearly a week of back and forth code there can be a fix that I'm unable to discover.

The Problem

When running the batch job with a near-live quantity of data. 9/18 jobs are failing due to WebService time out (120000). I'm not sure whether this is lack of optimisation from me, it being part of the parcel of a large data set, or whether it's unavoidable and we need to contact our reps at Salesforce for further help.

The below is the method that is commonly timing out. Both 'service.ReadMetadata' are in the same method doing callouts. So I'm not sure how to synchronously fix these to retain within 120s.

//List<String> recordType = List of picklist fields available in the recordType
//DescribeSObjectResult objectResult = Current object information
//Set<String> metadataFields = Set of fields to udpate listed in the custom metadata as above
//String recordTypeName = recordTypeName label

public Set<String> processPicklist(List<String> recordType, DescribeSObjectResult objectResult, 
Set<String> metadataFields, String recordTypeName ) { 
    for(String picklistValue : recordType) {
            String ctObjectRecordType = String.valueOf(''+objectResult.getLocalName()+'.'+recordTypeName+'');
            String fieldName = String.valueOf(''+objectResult.getLocalName()+'.'+picklistValue+'');
            List<MetadataService.PicklistValue> pickVals = new List<MetadataService.PicklistValue>();

// Either of these two lines are timing out when it comes to error logs
            MetadataService.CustomField  picklistFieldMdApi = (MetadataService.CustomField)   service.readMetadata('CustomField', new String[] { fieldName }).getRecords()[0];
            MetadataService.GlobalValueSet  globalPickListApi = (MetadataService.GlobalValueSet) service.readMetadata('GlobalValueSet', new String[] { picklistFieldMdApi.valueSet.valueSetName }).getRecords()[0];
            for(MetadataService.CustomValue existing : globalPickListApi.customValue){
                MetadataService.PickListValue newPickVal = new MetadataService.PickListValue();
                newPickVal.fullName = existing.fullName;
                newPickVal.label = existing.label;
                newPickVal.isActive = true;
                newPickVal.default_x = existing.default_x;

            MetadataService.Metadata[] mdRecordTypes = service.readMetadata('RecordType', new String[]{ctObjectRecordType}).getRecords();
            MetadataService.RecordType[] recordTypes = (MetadataService.RecordType[]) mdRecordTypes;} else {recordTypes = new MetadataService.RecordType[]{};
            MetadataService.RecordTypePicklistValue[] rtPickValues = new MetadataService.RecordTypePicklistValue[]{};
            MetadataService.RecordTypePicklistValue rtPickValue = new MetadataService.RecordTypePicklistValue();

            rtPickValue.picklist = picklistValue;
            rtPickValue.values = pickVals;
            for(MetadataService.RecordType RT : recordTypes){ RT.PicklistValues = rtPickValues;}
            List<MetadataService.SaveResult> results = new List<MetadataService.SaveResult>();
            results = service.updateMetadata(new MetadataService.Metadata[] { mdRecordTypes[0]  }); 

    return metadataFields;

My head is deterioriating so please. Any advice is amazingly appreciated.

1 Answer 1


Deployments are locked serially, along with any changes an administrator may be performing. As such, you can't particularly guarantee that the system will be able to complete even a single deployment of a single field within the two minute timeout. You need an asynchronous solution if you're going to stay in Apex. Technically, this is a very interesting case where Salesforce Functions (also called Salesforce Evergreen) would solve your problem, but this is only available in pilot for right now. You could also use a Queueable class to wait one minute at a time, and retry until successful.

  • I completely agree. I originally experimented with a 'retry' system that would retry until successful with a limit of 10 attempts which didn't work unfortunately. One method I thought of was to implement a 'promise' like system that would still allow me to run each record synchronously, but make the call outs asynchronous but there seems to still be no guarantee and I'm not sure I can get even more time on a shot in the dark. We might have to reach out and enquire about SF Evergreen! Dependent on any other possible solutions! Jan 19, 2021 at 6:26

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .