Coherence Key HOWTO
On occasion I am asked about best practices for creating classes to be used as keys in Coherence. This usually comes about due to unexpected behavior that can be explained by incorrect key implementations.
First and foremost, equals and hashCode need to be implemented correctly for any type used as a key. I won’t describe how to do this – instead I’ll defer to Josh Bloch who has written the definitive guide on this topic.
There is an additional requirement that needs to be addressed. All serializable (non transient) fields in the key class must be used in the equals implementation. To understand this requirement, let’s explore how Coherence works behind the scenes.
First, let’s try the following experiment:
public class Key implements Serializable { public Key(int id, String zip) { m_id = id; m_zip = zip; } //... @Override public boolean equals(Object o) { // print stack trace new Throwable("equals debug").printStackTrace(); if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Key key = (Key) o; if (m_id != key.m_id) { return false; } if (m_zip != null ? !m_zip.equals(key.m_zip) : key.m_zip != null) { return false; } return true; } @Override public int hashCode() { // print stack trace new Throwable("hashCode debug").printStackTrace(); int result = m_id; result = 31 * result + (m_zip != null ? m_zip.hashCode() : 0); return result; } private int m_id; private String m_zip; }
This key prints out stack traces in equals and hashCode. Now use this key with a HashMap:
public static void testKey(Map m) { Key key = new Key(1, "12345"); m.put(key, "value"); m.get(key); } //... testKey(new HashMap());
Output is as follows:
java.lang.Throwable: hashCode debug at oracle.coherence.idedc.Key.hashCode(Key.java:60) at java.util.HashMap.put(HashMap.java:372) at oracle.coherence.idedc.KeyTest.testKey(KeyTest.java:46) at oracle.coherence.idedc.KeyTest.testKey(KeyTest.java:52) at oracle.coherence.idedc.KeyTest.main(KeyTest.java:18) java.lang.Throwable: hashCode debug at oracle.coherence.idedc.Key.hashCode(Key.java:60) at java.util.HashMap.get(HashMap.java:300) at oracle.coherence.idedc.KeyTest.testKey(KeyTest.java:47) at oracle.coherence.idedc.KeyTest.testKey(KeyTest.java:52) at oracle.coherence.idedc.KeyTest.main(KeyTest.java:18)
Try it again with a partitioned cache this time:
testKey(CacheFactory.getCache("dist-test"));
Note the absence of stack traces this time. Does this mean Coherence is not using the key’s equals and hashCode? The short answer (for now) is yes. Here is the flow of events that occur when executing a put with a partitioned cache:
- Invoke NamedCache.put
- Key and value are serialized
- Hash is executed on serialized key to determine which partition the key belongs to
- Key and value are transferred to the storage node (likely over the network)
- Cache entry is placed into backing map in binary form

Note that objects are not deserialized before placement into the backing map – objects are stored in their serialized binary format. As a result, this means that two keys that are equal to each other in object form must be equal to each other in binary form so that the keys can be later be used to retrieve entries from the backing map. The most common way to violate this principle is to exclude non transient fields from equals. For example:
public class BrokenKey implements Serializable { public BrokenKey(int id, String zip) { m_id = id; m_zip = zip; } //... @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } BrokenKey brokenKey = (BrokenKey) o; if (m_id != brokenKey.m_id) { return false; } return true; } @Override public int hashCode() { int result = m_id; result = 31 * result; return result; } }
Note this key has two fields (id and zip) but it only uses id in the equals/hashCode implementation. I have the following method to test this key:
public static void testBrokenKey(Map m) { BrokenKey keyPut = new BrokenKey(1, "11111"); BrokenKey keyGet = new BrokenKey(1, "22222"); m.get(keyPut); m.put(keyPut, "value"); System.out.println(m.get(keyPut)); System.out.println(m.get(keyGet)); }
Output using HashMap:
value value
Output using partitioned cache:
value null
This makes sense, since keyPut and keyGet will serialize to different binaries. However, things get really interesting when combining partitioned cache with a near cache. Running the example using a near cache gives the following results:
value value
What happened? In this case, the first get resulted in a near cache miss, resulting in a read through to the backing partitioned cache. The second get resulted in a near cache hit because the object’s equals/hashCode was used (since near caches store data in object form.)
In addition to equals/hashCode, keep the following in mind:
- Keys should be immutable. Modifying a key while it is in a map generally isn’t a good idea, and it certainly won’t work in a distributed/partitioned cache.
- Key should be as small as possible. Many operations performed by Coherence assume that keys are very light weight (such as the key based listeners that are used for near cache invalidation.)
- Built in types (String, Integer, Long, etc) fit all of this criteria. If possible, consider using one of these existing classes.)

Hi Patrick,
Very interesting and clean explanation. I just need some clarifications from the above explanationa.
1,Hash is executed on serialized key to determine which partition the key belongs to
2,Key and value are transferred to the storage node (likely over the network)
3,Cache entry is placed into backing map in binary form
What/How exactly the 1st statement works ? Hash is executed on the Domain Object/Converted Binary Object ?
Key and Value are transferred to the storage nodes in Serialized form/ Binary form ?
What my understanding is When you issue a NamedCache.put() as a TCP-Extend Client the key and values gets converted to Binary and then passed to TCP-Extend and form there it goes to Sotrage nodes.
Please correct my understanding if it is wrong.
Regards
Amar
Amar
10 Jun 10 at 12:00 pm
Hi Amar,
Here is a simplified way to look at it:
First thing client does is serialize the key similar to this:
Binary bin = ExternalizableHelper.toBinary(key);
Once we have the binary, all subsequent operations by partitioned/distributed cache are performed on this binary; i.e. bin.hashCode()
In the case of Extend, the client will serialize the key and value, send it to the proxy, and the proxy then passes it along to the storage member that holds the partition for that key.
Patrick Peralta
10 Jun 10 at 5:34 pm
One more question.
How the hashCode() of the KEY is used to decide which patiotion it belongs to/it goes to ? what is the internal logic used here ?
Regards
Amar
Amar
11 Jun 10 at 4:37 am
Hi Amar,
We mod the hash code by the number of partitions, which then gives us the partition id.
Patrick Peralta
14 Jun 10 at 5:54 pm
I have a question. I have a composite cache key similar to yours. Like in your example I use only one member variable on both equals() and hashcode(). But all my puts and gets (store and lookups) set the second member variable of the key to null. So though I don’t follow the best practices the cache shouldn’t have any issues as the serialized version of the key when it was used to do the put operation would be equal to the serialized version of the key when it gets used on the get operation. Now you might ask why we have the other member variable that is not transient but is not considered on either equals or hashCode.
We construct the key with the second member variable set and first member variable set to null to force a read through to the database (read write partitioned map) as we don’t have another cache which truly has key as the second member variable.
So we have
Key k = new Key(null,”m2″);
Val v = cache.get(k); //this forces a read through and populates k
If we find it we remove it immediately and construct the right key to put hte value back
if (v != null)
cache.remove(k);
cache.put(new Key(“m1″,null), v);
We keep experiencing problems during rolling restart when it can’t find objects that were just put in the cache. Can you please tell us why this might cause problems
Kasturi
20 Jul 10 at 11:25 pm
Hi Kasturi,
It sounds like there are a lot of moving parts here. Based on the snippet you provided I don’t know why reads would return null unexpectedly after a rolling restart. I would suggest submitting a service request to Oracle support, as you would be able to upload your configuration and source code for further analysis.
Patrick Peralta
21 Jul 10 at 9:36 am
Hi Patrick
I am working over implementing key association between Cache key and command context identifier where key operation gets executed. So i have added transient contextIdentifier to my cache key. I am able to insert the data but i am getting null when i try to retrieve the data using same key.If i remove transient contextIdentifier it works fine.I have not added transient field in hashcode ,equals , readexternal,writeexternal, This should not be problem as the field is transient.
My Cache key implementation is
public class TradeCacheKey implements PortableObject,KeyAssociation{
private String tradeId;
transient private Identifier contextIdentifier;
public TradeCacheKey()
{
}
/**
* constructor to create TradecacheKey
* @param tradeId – tradeId of Trade
* @param contextIdentifier – context Identifier
*/
public TradeCacheKey(String tradeId, Identifier contextIdentifier) {
this.tradeId = tradeId;
this.contextIdentifier = contextIdentifier;
}
public TradeCacheKey(String tradeId) {
this.tradeId = tradeId;
}
@Override
public void readExternal(PofReader pofreader) throws IOException {
// TODO Auto-generated method stub
tradeId = pofreader.readString(0);
// contextIdentifier = (Identifier)pofreader.readObject(1);
}
@Override
public void writeExternal(PofWriter pofwriter) throws IOException {
// TODO Auto-generated method stub
pofwriter.writeString(0, tradeId);
//pofwriter.writeObject(1, contextIdentifier);
}
public String getTradeId()
{
return tradeId;
}
/**
*
* returns hash code for this object
*/
public int hashCode() {
int result = 1;
result = 31
* result
+ ((tradeId == null) ? 0 : tradeId
.hashCode());
return result;
}
/**
*
*/
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
TradeCacheKey other = (TradeCacheKey) obj;
if (tradeId == null) {
if (other.tradeId != null)
return false;
} else if (!tradeId.equals(other.tradeId))
return false;
return true;
}
@Override
public Object getAssociatedKey() {
return contextIdentifier;
}
}
I put the key to cache as
tradeCache.put(new TradeCacheKey(trade.getTradeId(),contextIdentifier), “TEST”);
System.out.println(” Entry inside cache is ” + tradeHistoryCache.get(new TradeCacheKey(trade.getTradeId())));
Here the second line pri nts Entry inside cache is null.
More interestingly if i get all the keys from cache and print values inside cache it gives me null
Set<Entry> entrySet = tradeCache.entrySet();
Iterator<Entry> itr = entrySet.iterator();
while(itr.hasNext()){
Entry entry = itr.next();
TradeCacheKey key = (TradeCacheKey)entry.getKey();
System.out.println(“Key is ” + key);
System.out.println(“Get value using key ” + tradeCache.get(key));
System.out.println(“Get value using new key ” + tradeCache.get(new TradeCacheKey(key.getTradeId())));
}
The output is
Key is com.csfb.fid.gtb.tradecache.TradeCacheKey@28ccdc70
Get value using key null
Get value using new key null
I dont know whether am i going wrong somewhere by not adding transient field to hashcode, equals, readexternal and writeexternal?
Anil
15 Dec 10 at 5:30 pm
This is a pretty common problem. I too had written about something similar a while ago – http://javaforu.blogspot.com/2009/12/primary-key-object-under-appreciated.html
Ashwin Jayaprakash
10 May 11 at 4:15 pm
One easy way to screw this up is to use java serialization or any serialisation that uses stream back references for reference equals objects. Because then your serialised form is dependent on whether your contents are interned. Eg a simple pair of integer (1,1) could end up having an inconsistent binary form if the 1 objects are reference equals. So, use EL or POF for keys, and no tricks.
Rjw
30 May 11 at 6:31 am
RJW: this is absolutely correct! It doesn’t happen too often but I have seen it before. It can be very confusing if you don’t understand how Java serialization works internally.
Patrick Peralta
30 May 11 at 10:01 pm
Very interesting post, how does this work in case of key affinity. As we need to de serialize the key to identify the association information.
Ashish Garg
10 Apr 12 at 10:30 am
Great question! We do deserialize on the server to identify the association information. However, in 3.7.1 we added a feature to identify partition information in the binary itself so that .NET and C++ applications no longer require a corresponding Java key on the server side in order to use affinity. See http://docs.oracle.com/cd/E24290_01/coh.371/e22839/net_intobjects.htm under section “Deferring the Key Association Check” for details.
Patrick Peralta
10 Apr 12 at 11:37 am
On the similar question related to affinity just wanted to clear one doubt. The key affinity is based on the hash of the parent key not whether the parent key is present in the cache at that time.
Just to add an example let say Key B is associated with Key A. Now let say we had a parallel cache loader which first loads the Key B. So Key will be stored in the partition based on associated key details. Also when Key A is loaded it will fall in the same partition as that of its associated key B.
Ashish Garg
24 Apr 12 at 2:23 pm
This is correct Ashish – the associated key does not need to be physically present in the cache in order for affinity to work for a given entry.
Patrick Peralta
24 Apr 12 at 3:47 pm