Close

Type Annotations Use Cases And Processing

[Updated: Feb 18, 2018, Created: Jan 24, 2016]

Just like other existing annotations, type annotations can be processed (as described in Java annotation processor tutorial) for the following purposes:

  • Doing enhanced compile time checking or enforcing rules and issue errors/warnings.
  • We can generate Java source code that can effect the annotated code in some ways.
  • Use reflection to customize the runtime behavior based on type annotations.

Other than that our code can be more expressive by placing type annotation at more places as they show the related intents.

Type Annotation to enhance type checking during compile time

With type annotations we can enhance Java strong typing at more places, which can help to reduce the runtime errors, unwanted mutations, concurrency errors etc.

There's no build in type annotations provided in Java SE but we can create them ourselves per our need and then write a custom Annotation Processor and plug in to the compiler to verify annotated code, enforcing rules by generating compiler errors/warnings when code does not meet certain requirements.

Followings are the use cases to enhance Java checking and strong typing using type annotations. These use cases are not necessarily be implemented by some framework but just to have an idea where type annotation can be of great value.

Qualify Types:

Consider the following example:

String str = getValue(args);
System.out.println(str.trim());

The above code has a potential of having null pointer exception at the trim() call. But if we create a type annotation say @NotNull, the related processor during compile time can check and enforce getValue method not to return null values.

@NotNull String str = getValue(args);
System.out.println(str.trim());

Other scenarios to qualify types:

@Encrypted String str;
@Format(theFormatterConstant) String str;
@Localized String str;
List<@ReadOnly T> list;
Store<@NotNull Product> product;
Store<@Prod(Type.Grocery) Product> product;
void showResources(@Authenticated User user);
@SwingElementOrientation int orientation;
@Positive int i;
@CreditCard string cardNumber;
Date date = (@Readonly Date) object;
Date date = (@NotNull Date) object;
Extending class

We saw in above use cases that the annotation processor can check the compatibility between the annotated type and the provided value source type. With 'extends' or 'implements' clauses, we don't have a value source type. We can think of two possibilities: either our annotation processor will be doing some compatibility checking between super type and sub type or it's just a forcing of a proper type within the subclass scope.

For the first case assume we want to extend a class which supposedly has some additional characteristics (other than it's main responsibility) e.g. immutability or thread safety or validation of some sort etc. Taking advantage of type annotation, we can create an annotation and processor checker which will confirm the characteristics requirements of the super class during every compilation cycle.

public class MyClass extends @ThreadSafe OtherClass{.....}

Other use case as mentioned before: we can also enforce proper type while extending a class. For example:

public class MyClass<T> extends @NonEmpty AbstractList<T>{ .... }

In above example our type annotation processor will simply be checking that MyClass is not having empty list internally or wherever it is referenced.

Implementing an Interface

Just like above example, we might want to use type annotation while implementing interfaces to define the characteristics we are expecting in the interfaces. Another use case is to enforce the proper type for the implementing class.

public class MyClass<T> implements @NonEmpty List<@ReadOnly T>{ .... }
Exceptions:

Exceptions can be annotated to ensure that they adhere to certain criteria, for example:

catch (@Critical Exception e) {
 ...
}

The catch block is responsible to meet the 'Critical' criteria i.e. if it's compatible with the throws clause.

Processing Type Annotations

We can process type annotations using Java annotation processor . For static type checking we have to additionally analysis the source code. It's a good idea to extend a third party framework. For example Checker Framework has already implemented number of type annotations to enhance Java Strong typing. Their framework is extensible so we can write our own processor (aka checkers). The Checker framework uses Sun Tree API to access program's AST (Abstract syntax tree)

Reflection enhancement For type annotations

We can access type annotations using existing methods and new reflection API in Java 8. Followings are the new methods and uses of the java.lang.Class.


Type annotation in extends clause
public class MyClass extends @MyAnnotation OtherClass { ...... }

AnnotatedType annotatedSuperclass = MyClass.class.getAnnotatedSuperclass();//new 1.8 method
System.out.println(Arrays.toString(annotatedSuperclass.getAnnotations()));

check out a example here.


Type annotation in implements clause
public class MyClass implements  @MyAnnotation Runnable { ...... }

for (AnnotatedType annotatedType : MyClass.class.getAnnotatedInterfaces()) {//new 1.8 method
System.out.println(Arrays.toString(annotatedType.getAnnotations()));
}

check out a example here.


Type annotation with generic type element
public class MyClass{
 List<@MyAnnotation String> list;
    public static void main(String[] args) throws NoSuchFieldException {
        Field field = MyClass.class.getDeclaredField("list");
        AnnotatedType type = field.getAnnotatedType();//new 1.8 method
        if(type instanceof AnnotatedParameterizedType){//new 1.8 interface
            AnnotatedParameterizedType ptype = (AnnotatedParameterizedType) type;
            for (AnnotatedType annotatedType : ptype.getAnnotatedActualTypeArguments()) {
                System.out.println(Arrays.toString(annotatedType.getAnnotations()));
            }
        }
    }
}

Type annotation in array components
public class MyClass{
    String[] [] @MyAnnotation [] strings;

    public static void main(String[] args) throws NoSuchFieldException {
        Field field = MyClass.class.getDeclaredField("strings");
        AnnotatedType type = field.getAnnotatedType();//new 1.8 method
        if (type instanceof AnnotatedArrayType) {//new 1.8 interface
            AnnotatedArrayType atype = (AnnotatedArrayType) type;
            System.out.println(Arrays.toString(atype.getAnnotations()));//empty array
            System.out.println(Arrays.toString(atype.getAnnotatedGenericComponentType()
                    .getAnnotations()));//this works
        }
    }
}

See more examples here.

See Also