15

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!?

6
  • is there any way I can see how your map is formed?
    – Rao
    Commented Feb 1, 2017 at 19:30
  • @Rao, there's quite a bit of code involved because I'm pulling data from a Currency Exchange API. I think the issue is the context. This same function works as expected when running in a VF page or aynon apex. However, I'm getting this bug when running in a batch context. The Map is initialized in the Batch Start method. I'm going to do some more digging and see if I can replicate with a simpler use case
    – NSjonas
    Commented Feb 1, 2017 at 19:35
  • Maybe enum is being treated as Object instead. Can you try as passing the key: CurrencyType.GBP ...........also trim the input parameter to make sure that there is no trailing space
    – Ayub
    Commented Feb 1, 2017 at 19:36
  • @AyubAnsari I assume the op would have done the same, the class would not save unless its a CurrencyType member
    – Rao
    Commented Feb 1, 2017 at 19:39
  • 1
    @Rao See above... I was able to confirm that the hashcodes between two Identical enums did not match!!! this seems like a platform bug to me
    – NSjonas
    Commented Feb 1, 2017 at 19:57

1 Answer 1

18

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);
6
  • So finally- In short, Enums cannot be used as keys in when using Database.Stateful.....gonna remember that:)
    – Ayub
    Commented Feb 2, 2017 at 18:15
  • I faced a similar issue with Map(string,object) passing b/w start and execute methods. I was trying to pass an object as a value with a string as a key and it somehow did not like it. So, to make it work, i had to pass the object as String, passing all the fields from that object concatenating them. Maps & Batchables have to be used with caution.
    – SF Learner
    Commented Feb 2, 2017 at 22:51
  • @ShaliniMendu very odd... I think it might have something to do with the fact that the state must be serialized to pass between executions. Some objects are not serializable.
    – NSjonas
    Commented Feb 2, 2017 at 23:07
  • @NSjonas, true! The issue is when you code, you expect these things to work seamlessly and it takes an extra amount of time/analysis/effort to fix these issues.
    – SF Learner
    Commented Feb 3, 2017 at 4:28
  • 1
    I just spent 3 hours of my life discovering the same thing as I refused to consider this as a possibility until exhaustive debugging of all other options. Wouldn't you know that the answer was on SFSE. +1 for both question and answer
    – cropredy
    Commented Feb 13, 2020 at 23:30

You must log in to answer this question.

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