Getting annotation value of an enum constant

In this post we will see how to extract the annotation value, (i.e. `”choc”` in `@SerializedName(“choc”)`) given an annotated enum constant. This technique is useful when we generate strings for unit tests in the context of Json deserialization. Furthermore, we will discuss some features of enums and annotations, and their connections with the reflection API.

Let’s consider an example with `SerializedName`, an annotation from Gson, the popular Json library. The annotation can be used this way:

enum Flavor {
    @SerializedName("van")
    VANILLA,
    @SerializedName("choc")
    CHOCOLATE
}

This tells Gson to convert the Json string `”van”` to the enum constant `VANILLA`. Now we want to do the opposite, i.e. write code to go from `VANILLA` to `”van”`. Let’s jump right into it:

String getSerializedName(Enum<?> e) {
    try {
        Field f = e.getClass().getField(e.name());
        SerializedName a = f.getAnnotation(SerializedName.class);
        return a == null ? null : a.value();
    } catch (NoSuchFieldException ignored) {
        return null;
    }
}

We pass in an `Enum<?>` instead of a `Flavor` because we want our code to be as general as possible. For each enum, in fact, a class is generated, its enum type, which always extends `Enum<?>`. This class, in particular, contains the method `name()` which returns the name of the enum constant as defined in the source code. (The enum type actually extends `Enum`, but that is another topic)

Now that we know that the enum type is just a class, it is easy to retrieve the corresponding field through the reflection API: `getClass()`, `getField()`, and `getAnnotation()` are exactly what we are looking for. Note that `getField()` could in principle throw an exception (not in our case though) so we have to wrap it in a try-catch block.

Finally we return the value of the annotation if it is present. Alternatevely we could have made the check with `Field.isAnnotationPresent()`, without changing the logic. The “method” `value()` is what is actually called annotation type element and it is specified in the annotation type declaration, i.e. the interface-like declaration of `SerializedName`. This element has the special convention that, if it is the only element present in the annotation, it can be omitted. For example

@SerializedName(value = "van")

is equivalent to

@SerializedName("van")

To wrap up let’s compare enums and annotations: both introduced in Java 5, enum types relate to classes and annotation type relate to interfaces. Without the word “type” they might mean the feature in the Java language or a specific instance of their type, i.e. an enum constant or a particular annotation on some field, method, class, etc. In the package `java.lang` there is a class called `Enum` (not to be confused with the legacy `Enumeration`) and an interface called `Annotation`. They are super types respectively of all enum types and all annotation types.