Enums as Map keys don’t work in Batchable

I’m running into some weird behavior when using an Enum as a Key in a map. This same code works fine in other contexts, but not when in batch.

I have an Enum to represent currency types:

   public enum CurrencyType {
       USD, AUD, GBP, CAD
   }    

In an exchange client, I have a Map<CurrencyType, Decimal>. However, when I try to get a value using an Enum, it’s returning null.

  private Map<CurrencyType, Decimal> exchangeRates;

   /**
     * Returns the exchange rate for any given currency vs the base
     * @param  currencyType The currency to get the exchange rate for
     * @return              The Exchange Rate for given currency
     */
  public Decimal getExchangeRate(CurrencyType currencyType){
        System.debug(currencyType);  //GBP
        System.debug(exchangeRates); //{USD=1.0, AUD=1.3184, CAD=1.3065, GBP=0.79164}
        System.debug(exchangeRates.containsKey(currencyType)); //false

        for(CurrencyType ct : exchangeRates.keySet()){
            System.debug(ct); //when == GBP
            system.debug(ct == currencyType); //true
            System.debug(ct.hashCode()); //127990640
            System.debug(currencyType.hashCode()); //608648681
        }

        return exchangeRates.get(currencyType); //returns null
   }

UPDATE:

Here is the Raw Debug Statements so you can see everything is populated properly:

12:50:53.0 (19696843)|USER_DEBUG|[55]|DEBUG|~Getting Exchange Rates~
12:50:53.0 (19711834)|USER_DEBUG|[56]|DEBUG|GBP
12:50:53.0 (19752692)|USER_DEBUG|[57]|DEBUG|{USD=1.0, AUD=1.3184, CAD=1.3065, GBP=0.79164}
12:50:53.0 (19786841)|USER_DEBUG|[58]|DEBUG|false
12:50:53.0 (19805826)|USER_DEBUG|[59]|DEBUG|608648681
12:50:53.0 (19815542)|USER_DEBUG|[60]|DEBUG|~Testing vs Keyset~
12:50:53.0 (19879783)|USER_DEBUG|[62]|DEBUG|USD
12:50:53.0 (19912155)|USER_DEBUG|[63]|DEBUG|false
12:50:53.0 (19930846)|USER_DEBUG|[64]|DEBUG|70283834
12:50:53.0 (19945440)|USER_DEBUG|[65]|DEBUG|608648681
12:50:53.0 (19973023)|USER_DEBUG|[62]|DEBUG|AUD
12:50:53.0 (20006415)|USER_DEBUG|[63]|DEBUG|false
12:50:53.0 (20026344)|USER_DEBUG|[64]|DEBUG|1746277182
12:50:53.0 (20041304)|USER_DEBUG|[65]|DEBUG|608648681
12:50:53.0 (20068693)|USER_DEBUG|[62]|DEBUG|CAD
12:50:53.0 (20097827)|USER_DEBUG|[63]|DEBUG|false
12:50:53.0 (20116138)|USER_DEBUG|[64]|DEBUG|76325736
12:50:53.0 (20130603)|USER_DEBUG|[65]|DEBUG|608648681
12:50:53.0 (20157100)|USER_DEBUG|[62]|DEBUG|GBP
12:50:53.0 (20186003)|USER_DEBUG|[63]|DEBUG|true
12:50:53.0 (20204017)|USER_DEBUG|[64]|DEBUG|127990640
12:50:53.0 (20218331)|USER_DEBUG|[65]|DEBUG|608648681

As you can see in the last two lines, the enum equals true when testing directly against the keyset(). However containsKey() returns false (USER_DEBUG|[58]);

Weird thing is… This definitely seems to work in other contexts. I know I could easily work around this by using enum.name() as the key instead of the enum itself, but I really want know whats going on here.

UPDATE 2:

I added hashCode() details and confirmed that they are different! So basically it’s possible for two enums of the same value to generate different Hash Codes!?

Answer

I think I’ve figured it out.

In java, the hashCode for an enum is not consistent across different executions.

My Map is being initialized in the Batchable start() (with Database.Stateful). Then I try to access the map in Batchable execute().

Behind the scenes, salesforce must be running start() & execute() in separate java executions. This means that when the hashcode is evaluated in the start() method it is not the same as when it is evaluated in the execute()

In short, Enums cannot be used as keys in when using Database.Stateful.

UPDATE:

I did some testing and it only seems to be an issue when you use a query locator and only fails on the second iterator. I’ve replicated it with the following code:

global class BatchEnumTesting implements Database.Batchable<SObject>, Database.Stateful {

    public Enum Foo{
        A, B, C
    }


    private Integer i;
    private Map<Foo, String> enumMap;

    global BatchEnumTesting() {}

    global Database.QueryLocator start(Database.BatchableContext BC) {
        i = 1;

        enumMap = new Map<Foo, String>();
        enumMap.put(Foo.A,'Testing1');
        enumMap.put(Foo.B,'Testing2');
        enumMap.put(Foo.C,'Testing3');

        for(Foo e : Foo.values()){
            System.debug('Key: ' + e.Name()  + ' | ' + e.hashCode());
        }

        return Database.getQueryLocator('SELECT Id FROM Account Limit 2');
    }

    global void execute(Database.BatchableContext BC, List<SObject> scope) {
        System.debug(i++);

        for(Foo e : enumMap.keySet()){
            System.debug('Key: ' + e.Name());
            if(e == Foo.A){
                System.debug('Hash Compare: ' + e.hashCode() + ' | ' + Foo.A.hashCode());
                System.debug('Map Match : ' + enumMap.containsKey(Foo.A));
            }else if(e == Foo.B){
                System.debug('Hash Compare: ' + e.hashCode() + ' | ' + Foo.B.hashCode());
                System.debug('Map Match : ' + enumMap.containsKey(Foo.B));
            }if(e == Foo.C){
                System.debug('Hash Compare: ' + e.hashCode() + ' | ' + Foo.C.hashCode());
                System.debug('Map Match : ' + enumMap.containsKey(Foo.C));
            }
        }
    }
}

global void finish(Database.BatchableContext BC) {}

Run with a batch size of 1:

Database.executeBatch(new BatchEnumTesting(), 1);

Attribution
Source : Link , Question Author : NSjonas , Answer Author : Community

Leave a Comment