ImmutableEnumChecker
Enums should always be immutable

Severity
WARNING

Alternate names: Immutable

The problem

All fields in your enum class should be final and either be primitive or refer to deeply immutable objects.

Static state is dangerous to begin with, but much worse for enums. We all think of enum values as constants – and even refer to them as “enum constants” – and would be very surprised if any of their state ever changed, or was not thread-safe.

To make enums immutable, ensure:

TIP: annotating the declaration of the enum class with @Immutable is unnecessary – Error Prone assumes enums are immutable by default.

Example:

import com.google.errorprone.annotations.Immutable;

@Immutable
class Foo {
  final int id;
  public Foo(int id) {
    this.id = id;
  }
}
// The declaration doesn't need to be annotated with @Immutable.
enum E {
  A("A", ImmutableList.of(new Foo(1), new Foo(2))),
  B("B", ImmutableList.of(new Foo(3)));

  // All fields are final, and deeply immutable:
  // Error Prone knows String is immutable.
  private final String name;
  // Error Prone knows ImmutableList<T> is an immutable collection of some
  // objects, and it recognizes the @Immutable annotation on the declaration of
  // Foo, so it can safely determine that this ImmutableList is deeply
  // immutable.
  private final ImmutableList<Foo> foos;

  private E(String name, ImmutableList<Foo> foos) {
    this.name = name;
    this.foos = foos;
  }

  public ImmutableList<Foo> foos() {
    return foos;
  }

  public String name() {
    return foos;
  }

}

TIP: Instead of creating an enum with functional interface fields (Predicate, Function, etc.), declare abstract methods that are overridden by each constant. For example, do this:

enum Types {
  STRING {
    @Override public boolean hasCompatibleType(Object o) {
      return o instanceof String;
    }
  },
  NUMBER {
    @Override public boolean hasCompatibleType(Object o) {
      return o instanceof Number;
    }
  },
  // ...

  public abstract boolean hasCompatibleType(Object o);
}

… not this:

enum Types {
  STRING(o -> o instanceof String),
  NUMBER(o -> o instanceof Number),
  // ...

  final Predicate<Object> hasCompatibleType;

  Types(Predicate<Object> hasCompatibleType) {
    this.hasCompatibleType = hasCompatibleType;
  }
}

This has several advantages on top of sidestepping this checker, e.g. not tying you to a particular functional interface type – your callers should e.g. use STRING::hasCompatibleType instead of STRING.hasCompatibleType which only works for one interface type.

Suppression

Suppress false positives by adding the suppression annotation @SuppressWarnings("ImmutableEnumChecker") to the enclosing element.