skip to content

Why couldn't the diamond operator originally be used with anonymous classes, and what changed in Java 9?

level: seniorimportance: nice to knowfreq 30%

answer

  1. Anonymous class = implicit named subclass needing a concrete type arg
  2. Pre-9 ban: inference could yield a non-denotable (unnameable) type
  3. Non-denotable = intersection types, wildcard capture (CAP#1)
  4. Java 9 / JEP 213: allowed when inferred type is denotable
  5. Still an error if inference yields a non-denotable type

basics

~20 s

In Java 7-8 you couldn't write the diamond <> when creating an anonymous class, because the compiler couldn't always name the inferred type for the hidden subclass it generates. Java 9 relaxed this: it now allows <> with anonymous classes whenever the inferred type can be expressed.

solid answer

~50 s

An anonymous class creates an implicit subclass at the `new` site. With generics, the compiler infers the type argument and would use it as the type of that synthetic subclass. In Java 7-8 the diamond was simply disallowed for anonymous classes: the inferred type could be a non-denotable type (one with no name in Java's syntax, such as an intersection or a captured wildcard type), and the spec couldn't always represent it, so the language conservatively rejected all anonymous-class diamonds. You had to spell the type argument out: `new Comparator<String>() { ... }`. Java 9 (JEP 213) refined this: the diamond is now permitted with anonymous classes whenever the inferred type argument is denotable — i.e., can be written as a normal type. If inference would produce a non-denotable type, it remains an error and you must specify the argument explicitly. So in practice `new ArrayList<>() { ... }` and `new Comparator<>() { ... }` now compile.

code

java · 12 lines
java
// Java 7-8: ERROR — diamond not allowed with anonymous class
// Comparator<String> c1 = new Comparator<>() { ... };

// Java 7-8: you had to spell out the type argument
Comparator<String> c2 = new Comparator<String>() {
    public int compare(String a, String b) { return a.length() - b.length(); }
};

// Java 9+: diamond now works because String is a denotable type
Comparator<String> c3 = new Comparator<>() {
    public int compare(String a, String b) { return a.length() - b.length(); }
};

go deeper

for a junior

Aware that older Java rejected new Foo<>(){...} and modern Java accepts it; not expected to explain why.

for a middle

Knows Java 9 enabled the diamond on anonymous classes and that you previously had to spell the type out.

for a senior

Explains the non-denotable-type reason for the original ban and that Java 9 allows it only when the inferred type is denotable.

for a principal

Connects this to the gap between the compiler's internal type system and Java's surface syntax, and can reason about when inference yields non-denotable types.

## Background: what an anonymous class is An **anonymous class** is a class with no name, declared and instantiated in one expression, that implicitly extends a class or implements an interface: ```java Comparator<String> c = new Comparator<String>() { public int compare(String a, String b) { return a.length() - b.length(); } }; ``` Under the hood the compiler generates a hidden subclass (e.g. `Outer$1`) and creates an instance of it. With generics, that synthetic subclass needs a concrete type argument — here `String`. ## Why the diamond was banned (Java 7-8) When you use the diamond, the compiler **infers** the type argument. For an anonymous class, the inferred type becomes the type argument of the generated subclass. The problem: inference can produce a **non-denotable type** — a type that exists in the compiler's type system but has **no way to be written in Java source**. Examples of non-denotable types are: - **Intersection types** like `Serializable & Comparable<...>` produced by combining bounds. - **Capture types** from wildcard capture (the compiler's internal `CAP#1` placeholders). If inference for the anonymous-class diamond produced such a type, the compiler would need to give the synthetic subclass a type it literally cannot name. Rather than handle this partially, Java 7 took the **conservative** route and forbade the diamond with anonymous classes entirely. So this was a compile error in Java 7-8: ```java Comparator<String> c = new Comparator<>() { ... }; // error before Java 9 ``` and you were forced to repeat the type: ```java Comparator<String> c = new Comparator<String>() { ... }; ``` ## What Java 9 changed (JEP 213, 'Milling Project Coin') Java 9 refined the rule instead of removing it. The diamond is now allowed with anonymous classes **when the inferred type argument is denotable** — i.e., can be written as an ordinary type name. The compiler does the inference; if the result is a normal type, it accepts the diamond: ```java // Java 9+ — compiles, infers String Comparator<String> c = new Comparator<>() { public int compare(String a, String b) { return a.length() - b.length(); } }; ``` If inference would yield a non-denotable type (an intersection or capture), the diamond is **still** rejected and you must specify the argument explicitly. So Java 9 didn't make it unconditional — it allowed the common, safe cases and kept the error only for the genuinely unrepresentable ones. ## Why it matters This is mostly an ergonomics fix: with Java 8 lambdas you'd usually avoid anonymous classes anyway, but for cases that *must* be anonymous classes (e.g. you need fields, multiple methods, or you're subclassing a class not a functional interface) the diamond now removes the repeated type argument. Understanding the 'denotable type' restriction also illuminates a deeper concept: the compiler's internal types are richer than what Java syntax can express, which is exactly why some inference results can't be used in every position. ## Key takeaways - Anonymous class = implicit named subclass needing a concrete type argument. - Java 7-8 banned the diamond there because inference could yield a non-denotable (unnameable) type. - Java 9 allows it when the inferred type is denotable; otherwise it's still an error and you specify the type.

  • What is a 'non-denotable type' and how does it relate to this restriction?
    A type the compiler can represent internally but that has no Java source syntax to name it — e.g. an intersection type or a wildcard capture (CAP#1). The pre-9 ban existed because anonymous-class diamond inference could produce one; Java 9 allows the diamond only when the inferred type is denotable.
  • Can you use the diamond with a lambda?
    No — a lambda is not a class instantiation with type arguments, so there's nothing to put a diamond on. The diamond applies to constructor calls (new), including anonymous-class creation since Java 9.

saying these in an interview costs you the question

  • Saying Java 9 made the anonymous-class diamond unconditionally legal (it's only when the type is denotable)
  • Thinking the original ban was arbitrary rather than about non-denotable types
  • Confusing anonymous classes with lambdas (lambdas can't use a diamond — they're not class instantiations)
  • Believing the diamond changes which subclass is generated at runtime

context