ZOFTINO.COM android and web dev tutorials

Java Annotations

Annotations are part of program elements and provide information about the program without changing any behavior or data of the program. The information annotations provide can be used by compiler, other tools to generate resources such additional source code and configuration files and runtime environment.

Table of Contents

Uses of Annotations

Annotations are used to report compilation errors and warnings, add boilerplate code at compile time, generate resource files and to make configuration information available at runtime.

As a developer, you’ll use existing annotations provided by JDK and Java frameworks but you will rarely create custom annotations.

Custom annotations are mainly created in frameworks such as Hibernate, Spring, Dagger, etc. If you are planning to develop an enterprise specific or general framework, you may need to create your own annotations.

Annotations provided by JDK are used for reporting compile time errors and for creating custom annotations.

Annotations defined by java frameworks are used to provide configuration information or to generate boilerplate code. Frameworks provide necessary modules to process compile time and run time annotations.

Annotation Basics

Annotations can be applied to annotation, package, class, interface, enum, constructor, method, field, local variable and parameter declarations. With Java 8, there is new type of annotation called type annotations which are used for type checking.

To apply an annotation to an element, you need to write the name of the annotation starting with @ near the element to which you want to apply it. Compiler recognizes annotations when it encounters @ symbol and name and processes them.

For example, @Override annotation, compiler generates an error message if the method marked with @Override in a class is not overriding a method of its super class.

	@Override
	public void sortCoupons() { …

Annotations can have elements. When using the annotations which have attributes, you need to specify values for the attributes. If an annotation contains single element, name can be ignored.

@Ticket(name = "train", route = "long") public class Travel { ..
@Account("credit")

Example of annotation without elements

	@Deprecated
	public int getDiscount() {
		return discount;
	}

Predefined Annotations

There are built in or predefined annotations which are part of JDK.

Predefined annotations which can be used in the code are Deprecated, Override, SuppressWarnings, FunctionalInterface and SafeVarargs.

@Deprecated

Deprecated annotation can be applied to constructor, field, local variable, method, package, parameter and type. Compiler reports warning when you try to use a programming element which is marked with Deprecated annotation. This annotation is usually used to indicate that latest versions of the element exists and using this version may cause issues.

@Deprecated
public void sortCoupons() {
	....
}

@Override

Override annotation can be applied to only method. When applied to a method it tells the complier that the method is overriding. Complier will report an error if there is no method with same name and signature in the super class.

public class Product {
	public void priceProduct() {
		
	}
}
public class FashionProduct extends Product {
	@Override
	public void priceIt() {
		
	}
}

This code throws compiler error since priceIt() method to which Override annotation is applied doesn’t exist in super class, error: The method priceIt() of type FashionProduct must override or implement a supertype method

@SuppressWarnings

SuppressWarnings annotation can be applied to a type, field, method, parameter, constructor and local variable. SuppressWarnings warning tells complier not to report the named warning for the element to which it is applied.

For example, to suppress all unused warnings at class level add the SuppressWarnings annotation with unused value to the class.

@SuppressWarnings("unused")

Other values for SuppressWarnings are rawtypes and unchecked.

@FunctionalInterface

FunctionalInterface is applicable to only interface type. It is used to indicate that an interface is a functional interface. One method interface is called functional interface, please lambda expressions tutorial for more information.

Compiler will report errors if this annotation is applied to other types such as class or enum and if the interface to which it is applied doesn’t satisfy the requirements of a functional interface.

@SafeVarargs

SafeVarargs annotation is applicable to constructors and methods. It is used to indicate that the body of the method or constructor to which it is applied doesn’t perform potentially unsafe operations on varargs parameters.

The impact of using this annotation is that compilers suppresses uncheck warnings related to unsafe using of varargs, but leads to runtime exceptions if varargs are used in unsafely.

@Native

Native annotation is applicable to fields. If a field is referenced from native code, marking it with Native annotations gives tools the hints so that they can generate necessary content related to header files.

Predefined annotations which can be applied to another annotations are Inherited, Documented, Retention, Target, Native and Repeatable.

@Inherited

Annotations marked with Inherited meta-annotation are inherited to sub classes. Using the Inherited meta-annotation will have no effect when the annotations marked with Inherited are used in elements other than classes.

@Documented

Annotations marked with Documented annotation will by default be documented by javadoc or other documenting tools.

@Retention

Retention annotation is applicable to only annotation type. It specifies whether annotations are available at compile or runtime. Retention annotation takes one value. If the value is RetentionPolicy.SOURCE, annotations will be discarded by compiler. If the value RetentionPolicy.CLASS, annotations will be recorded in a class files and annotations will not be available at runtime. If the value is RetentionPolicy.RUNTIME annotations will be recorded in a class files and annotations will be available at runtime.

@Retention(RetentionPolicy.CLASS)
public @interface CouponType {

}

@Target

Target annotation is applicable to annotation type. It is used to specify the programming element type such as method, class, field, etc. that the annotation is applicable to. Only one value is allowed to be specified.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface OfferType {

}

@Repeatable

Annotations marked with Repeatable annotation can be used multiple times on the same element. When you use Repeatable annotation, basically you are creating an array of values for the annotation. To hold the array, you need to define container annotation. Container annotation can be specified using value element of Repeatable annotation.

For example, here is a container annotation CategoryHolder which holds collection of Category annotations.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface CategoryHolder {
	Category[] value();
}

Here is the repeatable annotation.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Repeatable(CategoryHolder.class)
public @interface Category {
	String value();
}

Here is how repeatable annotation is used multiple times. Each time it is used a value is passed to the annotation. Collections of these values are stored in the container annotation.

@Category("Mobiles")
@Category("Electronics")
@Category("Fashion")
public class Sale{
	...
}

Type Annotations

Before Java SE 8, annotations are only applicable to declarations such method, filed and type declarations. With Java SE 8, annotations can be applied not only to declarations but also to type use. Meaning where ever types such as class, enum, interface, etc are used, you can use annotations. The annotations which can be applied to type use are called type annotations. Type annotations help in producing code which is less error prone.

For example, you can apply type annotations to new expression which instantiates an object, type casting, implements clause and throw clause.

Java SE8 doesn’t provide type annotations, but it allows developers to write frame works to provide type annotations as pluggable modules which are used with compiler.

As an application developer, again like other annotations, you may never need to create your own type annotations. You use type annotation provided by third party like checker framework by university of Washington.

Here are the examples from the checker framework.

NonNull type annotation applied to a method parameter type. Compiler reports error if the method is called with null input string.

int isValid(@NonNull String inputStr) { ... }

Initialized annotation applied to type cast. If the object yourDate is not initialized, compiler will throw an error.

myDate = (@Initialized Date) yourDate

Here is an example of type annotation used with generics. The annotation IntRange specifies a range of values that the marks list can contain.

@NonNull List<@IntRange(from=10, to=80) Integer> marks;

Type annotation used with return type.

@MinLen(10) String[] getProductNames() { ... } 

Example of type annotation used in new expression.

Coupon c = new @Interned Coupon();

Declaring Custom Annotations

Similar to interface declaration, annotations can be declared using interface keyword but with @ symbol.

public @interface PayType {
}

Then you can specify the elements to which this interface is applicable using @Target annotation. Then specify retention policy using @Retention annotation to indicate that the annotation will be used at source level, compiler level or runtime level.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface PayType{

}

Then you can define annotation elements by specifying methods in annotation definition. If it is one element just declare value() method, otherwise use names.

String value();

Here is the complete version of annotation type declaration with two elements.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface PayType{
   String name();
   String mode();
}

Now, declaration is done. You can apply it to any type.

@PayType(name = "order", mode = "cash")
public class Account {
 ....
}

But just declaring annotation and applying it to a programming element is not enough. To have the actual impact, you need to provide behavior for the annotation in the form of compiler plug-in which is called annotation processor. Annotations processor process annotations at compile time. You can write code using reflection to read annotations and process them at run time.

Annotation Processing at Compile Time

You can create annotation processor to read annotations and produce additional java source (boilerplate code) or resources (configuration). Java compiler stores annotations data in class file for annotations with retention policy of CLASS and RUNTIME so that annotations meta-data will be available at runtime for JVM or other programs.

To process annotations at compile time, you need to create annotation processor. To do that, first create a java project in eclipse, then create annotation processor class and add processor class name to javax.annotation.processing.Processor file that needs to be stored in META-INF/services/ folder.

java annotation processing

Annotation processor class needs to extend AbstractProcessor and implement init and process methods. You need to declare supported annotations by adding annotation names to SupportedAnnotationTypes annotation applied to the processor class.

@SupportedAnnotationTypes(
		  "com.zoftino.java.annotations.PayType")
public class PayTypeAnnotationProcessor extends AbstractProcessor {

	private Messager messager;

	@Override
	public synchronized void init(ProcessingEnvironment env){
		super.init(env);
		messager = env.getMessager();
		messager.printMessage(Diagnostic.Kind.WARNING, 
				"init in this");
	}

	@Override
	public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) { 

		for (TypeElement annotation : annotations) {
			
			messager.printMessage(Diagnostic.Kind.WARNING, 
					"Hello "+annotation.toString());
	
			for (Element annotatedElement : env.getElementsAnnotatedWith(annotation)) {
				
				if(annotatedElement.getKind().isClass()) {
				
					messager.printMessage(Diagnostic.Kind.WARNING, 
							"Hello "+annotation.toString());
					
				}
			}
		}
		return true;
	}
}

Here is the content of javax.annotation.processing.Processor file.

com.zoftino.java.annotations.PayTypeAnnotationProcessor

Then export jar file and add it to class path of the project where annotations needs to be processed. Then you need to enable annotation processing for the project. To enable annotation processing, go to project properties, expand java compiler, click annotation processing, select enable annotation processing checkbox, then click factory path, select enable project specific settings and apply. Then do a clean build which will run annotation processor.

Annotation Processing at Runtime

Example shows how to read annotations at runtime and take action based on the values. To read annotation of a class, you can use one of the annotation method on class, getAnnotatedInterfaces(), getAnnotatedSuperclass(), getAnnotations() or getAnnotationsByType().

public class RuntimeAnnotationProcessing {
	public static void main(String[] args) {
		
		doAnnotationProcessing("com.zoftino.java.annotations.Account");
	}
	
	public static void doAnnotationProcessing(String className) {
		Class<?> targetClass;
		try {
			targetClass = Class.forName(className);
		} catch (ClassNotFoundException e) {
			System.out.println("class not found "+className);
			return;
		}
		
		Annotation[] annotations = targetClass.getAnnotationsByType(PayType.class);
		
		for(Annotation annotation :  annotations) {
			PayType payType = (PayType)annotation;
			String name = payType.name();
			String mode = payType.mode();
			
			if("order".equals(name) && "cash".equals(mode)) {
				System.out.println("module for order payment by cash is executed");
			}else {
				System.out.println("other module will be executed");
			}
		}
	}
}