3

When using a custom Iterable/Iterator class with Database.Batchable, if the Iterator throws an exception it seems impossible to catch the exception.

Is there any way to catch the exceptions thrown in the following example code?

public class BatchableWithTryCatch implements Database.Batchable<String> {
    public Iterable<String> start(Database.BatchableContext bc) {
        System.debug(
            'youll see this message in the same log with the exception'
        );

        try {
            return new ErrorIterator();
        } catch (Exception e) {
            System.debug(
                'this doesnt catch anything because there is no exception ' +
                'thrown by the constructor'
            );
            return null;
        }
    }

    public void execute(Database.BatchableContext bc, List<String> scope) {
        System.debug(
            'execute() doesnt get called because of exception in hasNext()'
        );
    }

    public void finish(Database.BatchableContext bc) {
        System.debug(
            'finish() doesnt get called because of exception in hasNext()'
        );
    }

    public class ErrorIterator implements Iterable<String>, Iterator<String> {
        public Iterator<String> iterator() {
            return this;
        }

        public Boolean hasNext() {
            throw new StringException(
                'Cant catch exceptions from hasNext()'
            );
            return true;
        }

        public String next() {
            throw new StringException(
                'Cant catch exceptions from next()'
            );
            return 'foo';
        }
    }
}

This is what the log looks like for the first 'Batch Apex' execution:

36.0 APEX_CODE,DEBUG;APEX_PROFILING,NONE;CALLOUT,NONE;DB,NONE;SYSTEM,NONE;VALIDATION,NONE;VISUALFORCE,NONE;WORKFLOW,NONE
18:29:46.0 (436218)|EXECUTION_STARTED
18:29:46.0 (475757)|CODE_UNIT_STARTED|[EXTERNAL]|01p36000006TMzQ|BatchableWithTryCatch
18:29:46.0 (17094809)|USER_DEBUG|[3]|DEBUG|youll see this message in the same log with the exception
18:29:46.0 (18531282)|FATAL_ERROR|System.StringException: Cant catch exceptions from hasNext()

Class.BatchableWithTryCatch.ErrorIterator.hasNext: line 36, column 1

It does make sense that the exceptions thrown by the Iterator class are not caught by the try/catch block in the start() method, because those methods are called by Salesforce after calling the start() method in order to set up the scope for the execute() method.

However, it is a major disadvantage not being able to catch these exceptions, so I am hoping that I am just unaware of some kind of existing error handling mechanism!

How can I catch exceptions thrown from a custom Iterable/Iterator class when used with Database.Batchable?

4

Basically, problem is that next method is not called in execute method of batch.

Lets make next assumptions -- if we have some custom iterator like this:

global class CustomIterable implements Iterator<Object>{ 
    integer counter;
    public CustomIterable(){ 
        counter = 0;
    }   

    public boolean hasNext(){ 
        return true;//or false
    }

    public Object next(){ 
        return <some Object>;
    }
}

, and batch like this:

public class CustomBatch implements Database.Batchable<object>{

    public Iterable<object> start(Database.batchableContext info){ 
        return new  CustomIterable();
    } 
    public void execute(Database.batchableContext info, List<Account> scope){ 
        //do something iterable
    }
    global void finish(Database.batchableContext info){ 
        //do something as result
    } 

}

Then, pseudocode that do real job in salesforce, would look like next:

class Database{
    public static executeBatch(Database.Batchable<object> batch_instance){
        Database.batchableContext info = buildContext();//some internal method
        Iterable<sObject> custom_iterator = batch_instance.start(info);
        while(custom_iterator.hasNext()){
            batch_instance.execute(info, custom_iterator.next());
        }
        batch_instance.finish(info);
    }
}

Of course, real code is more complex to handle limits, Those approach was described here and/or here So, basically, what you can do - you can iterate on some custom class objects, like:

class SingleBatchWrapper{..}

, which would contain list of actual records, that you really need to process, and some extra field, like error, timestamp etc, so:

class SingleBatchWrapper{
    List<Object> RealData;
    Boolean IsSuccessful;
    String ErrorMessage;
}

Then, in case of building your custom iterator, you can have it like:

global class CustomIterable implements Iterator<SingleBatchWrapper>{ 
    integer counter;
    SingleBatchWrapper NextBatch;
    public CustomIterable(){ 
        counter = 0;
    }   

    public boolean hasNext(){ 
        // set Next batch.
        try{
           NextBatch = buildNextBatch();
        }catch(Exception ex){
           NextBatch = buildFailedBatch(ex);
        }
        return true;//or false if there are no more records
    }

    public Object next(){ 
        return NextBatch;
    }
}

So, in that case, you can check your SingleBatchWrapper data in your execute method like:

public class CustomBatch implements Database.Batchable<SingleBatchWrapper>{
    //some extra methods
    public void execute(Database.batchableContext info, List<SingleBatchWrapper> scope){ 
        for(SingleBatchWrapper wrapper : scope){
            if (wrapper.isSuccessful){
            //catch good things
           }else{
           //catch everything else
           } 
        }
    }
}

So, as a result, you would move your exceptions thrown inside execute method, and would be able to process it there with more clean way -- for example, stop batch or notify user about issues in the finish method by using Database.stateful interface if needed.

P.S. Code above are pseudocode, that describes approach, not actual solution and was written in notepad, so it may have typos

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.