import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.Map; import java.util.Objects; import java.util.TreeMap; @SuppressWarnings("unchecked") public class Main { public static void main(String[] args) throws JsonProcessingException { //Serializing a TreeMap with String keys and String values TreeMap<String, String> mappings = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); mappings.put("Test3", "3"); mappings.put("test1", "1"); mappings.put("Test2", "2"); ObjectMapper objectMapper = new ObjectMapper(); String json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(mappings); System.out.println("mappings: \n" + json); // Deserializing to a generic type without TypeReference (wrong approach). // // First of all, a small side note on deserializing with an interface. When you don't use a specific implementation // but an interface when deserializing Maps, the default implementation used by Jackson is a HashMap. This is because // an interface is not a concrete class, while Jackson needs an actual Map implementation to deserialize the Json // values somewhere. This is what was happening to you, judging from the initial problem described in the comments: // // > "I originally had this deserializing to a field which was Map<String, String> but when I checked if it was a // > TreeMap using mappings instanceof TreeMap it returned false. It returned true when I did mappings instanceof HashMap." // // Now, back to the TypeReference approach. In this case, even without using a TypeReference instance, the // deserialization happens successfully because json values are read as String, which is coincidentally the same // type of your Map's values. However, in general, when deserializing generic types you should subclass the actual // type representing your data. This is because the argument type tells Jackson how to unmarshall those values. Map<String, String> mappingsAsHashMap = objectMapper.readValue(json, Map.class); System.out.println(mappingsAsHashMap instanceof TreeMap<String, String>); //Prints false because, by default, it was deserialized with a HashMap Map<String, String> mappinggsAsTreeMap = objectMapper.readValue(json, TreeMap.class); System.out.println(mappinggsAsTreeMap instanceof TreeMap<String, String>); //Prints true because you specified the actual implementation //Works because the values have been read as a String Map<String, String> mappingsWithStringValues = objectMapper.readValue(json, TreeMap.class); String s = mappingsWithStringValues.get("test1"); System.out.println("length of s :" + s.length()); //Fails because tries to treat the values as Integer while the actual instances are String Map<String, Integer> mappingsWithIntValues = objectMapper.readValue(json, TreeMap.class); try { Integer i = mappingsWithIntValues.get("test1"); System.out.println(i.intValue()); } catch (ClassCastException ex){ System.out.println("Attempting to read a String as an Integer"); } //Serializing a TreeMap with String keys and a custom Bean TreeMap<String, MyBean> mappingsBean = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); mappingsBean.put("Test3", new MyBean(3, "MyBean3")); mappingsBean.put("test1", new MyBean(1, "MyBean1")); mappingsBean.put("Test2", new MyBean(2, "MyBean2")); String jsonBean = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(mappingsBean); System.out.println("\n" + "mappingsBean:\n" + jsonBean); // Deserializing the mappingsBean json without a TypeReference (wrong approach). // // Like so, Jackson doesn't know to which data type those inner values ({"id" : 2,"name" : "MyBean2"}) correspond to, // therefore that sequence of key-value pairs is read as a LinkedHashMap, and even worse, it is added as such within // your Map despite being a TreeMap<String, MyBean>! In fact, if you attempt to read or perform any operation on // its values, you will raise a ClassCastException as the type expected for your values is MyBean and not LinkedHashMap. mappingsBean = objectMapper.readValue(jsonBean, TreeMap.class); try { MyBean myBean = mappingsBean.get("test1"); } catch (ClassCastException ex) { System.out.println("Attempting to read a LinkedHashMap as a MyBean"); } try { System.out.println(mappingsBean.get("test1").getId()); } catch (ClassCastException ex) { System.out.println("Attempting to perform a MyBean's operation on a LinkedHashMap"); } // Deserializing the mappingsBean json with a TypeReference (right approach). // // With this approach, Jackson knows via the argument type of the TypeReference, that those inner values // ({"id" : 2,"name" : "MyBean2"}) correspond to a MyBean that needs to be reconstructed. mappingsBean = objectMapper.readValue(jsonBean, new TypeReference<TreeMap<String, MyBean>>() { }); System.out.println("\n" + mappingsBean); //Proper deserialization of your original Map while maintaining the case insensitive order. // // If you attempt to simply read the initial json with a TypeReference that subclasses a TreeMap<String, String> // you'll find out that it won't maintain your custom ordering, since the default ordering is case-sensitive. // This is noticeable when looking at the print of mappingsBean, it follows the default case-sensitive ordering. // Therefore, if you want to read your json as a TreeMap and maintain your custom case insensitive ordering, you // need to define a temporary TreeMap where to read the json, and a result TreeMap initialized with the custom // comparator and all the key-value pairs from the temporary TreeMap. //Wrong ordering mappings System.out.println("\nWrong Ordering mappings:\n" + objectMapper.readValue(json, new TypeReference<TreeMap<String, String>>() { })); //Right ordering mappings TreeMap<String, String> mappingsTemp = objectMapper.readValue(json, new TypeReference<TreeMap<String, String>>() { }); TreeMap<String, String> mappingsRes = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); mappingsRes.putAll(mappingsTemp); System.out.println("\nRight Ordering mappings:\n" + mappingsRes); } static class MyBean { private int id; private String name; public MyBean(){} public MyBean(int id, String name) { this.id = id; this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; MyBean myBean = (MyBean) o; return id == myBean.id && Objects.equals(name, myBean.name); } @Override public int hashCode() { return Objects.hash(id, name); } @Override public String toString() { return "MyBean{" + "id=" + id + ", name='" + name + '\'' + '}'; } } }