codelessgenie blog

Why HashMap<? super ArrayList> Doesn't Accept new Object() in put() Method? Java Generics Explained

Java Generics, introduced in Java 5, revolutionized type safety by enabling compile-time checks for collections and other generic classes. However, generics—especially wildcards with extends and super—can be notoriously confusing. A common head-scratcher is: Why can’t I put new Object() into a HashMap<? super ArrayList>?

At first glance, <? super ArrayList> seems to include Object (since Object is a supertype of ArrayList). So why does the compiler reject put(new Object())? In this blog, we’ll demystify Java generics, wildcards, and the super bound to answer this question. By the end, you’ll understand the logic behind this restriction and avoid similar pitfalls in your code.

2026-01

Table of Contents#

  1. Understanding Java Generics Basics
  2. Wildcards in Generics: extends vs. super
  3. Deep Dive: <? super T> (Lower Bounded Wildcards)
  4. HashMap’s put() Method and Generic Types
  5. Why new Object() Fails in HashMap<? super ArrayList>.put()
  6. Practical Examples and Common Pitfalls
  7. Conclusion
  8. References

1. Understanding Java Generics Basics#

Before diving into wildcards, let’s recap core generics concepts:

What Are Generics?#

Generics allow classes, interfaces, and methods to operate with type parameters (e.g., List<String>, HashMap<K, V>). They enforce type safety by ensuring a collection holds only one type of object, eliminating runtime ClassCastException errors.

Raw Types vs. Parameterized Types#

  • A raw type (e.g., List) ignores generics, allowing any object type (unsafe).
  • A parameterized type (e.g., List<String>) specifies the type of objects it contains (safe).

2. Wildcards in Generics: extends vs. super#

Wildcards (?) represent unknown types in generics. They enable flexibility when working with generic classes but come with rules for type safety. There are two key wildcard bounds:

Upper Bounded Wildcards: <? extends T>#

Represents an unknown type that is a subtype of T (or T itself). For example:

List<? extends Number> numbers; // Can hold Integer, Double, Number, etc.

Rule: Use extends for producers (collections you read from). You can retrieve elements as T but cannot add elements (except null), because the actual type could be a subtype of T (e.g., List<Integer>), and adding a Double would break type safety.

Lower Bounded Wildcards: <? super T>#

Represents an unknown type that is a supertype of T (or T itself). For example:

List<? super Integer> integers; // Can hold Integer, Number, Object, etc.

Rule: Use super for consumers (collections you write to). You can add elements of type T (or its subtypes), but retrieving elements returns Object (since the actual type could be a supertype of T).

The PECS Mnemonic#

To remember when to use extends vs. super, use PECS:

  • Producer: Extends (read from)
  • Consumer: Super (write to)

3. Deep Dive: <? super T> (Lower Bounded Wildcards)#

For <? super T>, the unknown type is a supertype of T. For example, <? super ArrayList> could represent:

  • ArrayList (itself),
  • List (supertype of ArrayList),
  • Collection (supertype of List),
  • Object (supertype of all objects).

But how does this affect what we can add to a collection?

4. HashMap’s put() Method and Generic Types#

HashMap is a parameterized class with two type parameters: K (key) and V (value). Its put() method adds a key-value pair:

V put(K key, V value); // Adds key-value pair to the map

When we write HashMap<? super ArrayList>, we’re using a wildcard for one of its type parameters (either K or V). For clarity, let’s assume we’re focusing on the value type (e.g., HashMap<String, ? super ArrayList>), where the value is bounded by <? super ArrayList>.

5. Why new Object() Fails in HashMap<? super ArrayList>.put()#

Let’s formalize the problem: Suppose we have a HashMap where the value type is <? super ArrayList>:

HashMap<String, ? super ArrayList> listMap = new HashMap<>();

We try to add an Object as a value:

listMap.put("key", new Object()); // Compile error! Why?

The Root Cause: Type Safety#

<? super ArrayList> means the value type is an unknown supertype of ArrayList (let’s call this unknown type X). X could be:

  • ArrayList,
  • List,
  • Collection,
  • Object.

For put() to be safe, the value we add must be compatible with any possible X.

Why new Object() Isn’t Compatible#

Object is a supertype of ArrayList, but X might not be Object. For example:

  • If X is ArrayList (a supertype of ArrayList), the map expects ArrayList values. Adding Object would violate type safety (an Object is not an ArrayList).
  • If X is List, the map expects List values. Object is not a List, so adding it would fail.

What Can We Add?#

With <? super T>, you can safely add instances of T or its subtypes. For <? super ArrayList>, this includes:

  • ArrayList (itself),
  • Any subclass of ArrayList (e.g., CustomArrayList extends ArrayList).

These are guaranteed to be subtypes of any possible X (since X is a supertype of ArrayList). For example:

listMap.put("key", new ArrayList<>()); // OK: ArrayList is a subtype of X
listMap.put("key", new CustomArrayList()); // OK: Subtype of ArrayList (and thus X)

6. Practical Examples and Common Pitfalls#

Example 1: List<? super ArrayList>#

Let’s test with a List first (simpler than HashMap):

List<? super ArrayList> list = new ArrayList<>();
 
// Allowed: Add ArrayList or its subtypes
list.add(new ArrayList<>()); // OK
list.add(new CustomArrayList()); // OK (if CustomArrayList extends ArrayList)
 
// Rejected: Add supertypes of ArrayList
list.add(new Object()); // Compile error!
list.add(new List() {}); // Compile error! (List is a supertype of ArrayList)

Example 2: HashMap<String, ? super ArrayList>#

Now apply this to HashMap, where the value type is <? super ArrayList>:

HashMap<String, ? super ArrayList> map = new HashMap<>();
 
// Allowed: Add ArrayList or subtypes as values
map.put("a", new ArrayList<>()); // OK
map.put("b", new CustomArrayList()); // OK
 
// Rejected: Add Object (supertype of ArrayList)
map.put("c", new Object()); // Compile error!

Common Pitfall: Confusing super with "Any Supertype"#

Developers often assume <? super T> allows any supertype of T, but this is incorrect. super enforces that added elements are subtypes of the unknown X (the supertype of T). Since X could be T itself, only T and its subtypes are safe to add.

7. Conclusion#

The key takeaway is understanding the super wildcard rule:

  • <? super T> represents an unknown supertype of T.
  • You can add T or its subtypes to a consumer collection (PECS: Consumer Super).
  • You cannot add supertypes of T (e.g., Object for <? super ArrayList>), because the actual type X might be a subtype of that supertype, breaking type safety.

Thus, HashMap<? super ArrayList> rejects new Object() in put() because Object is a supertype of ArrayList, and the compiler cannot guarantee it matches the unknown supertype X.

8. References#

By mastering wildcards and PECS, you’ll write safer, more flexible generic code. Remember: extends for reading, super for writing! 🚀