Java Annotations – Converting Java Objects to Excel Data

Java Annotations, a concept introduced in Java 1.5 is now one of the most used construct in java programming. Almost all the popular frameworks today, support Annotations to a great extent. It makes the usage of a framework or a library much simpler.

Annotations combined with the Java Reflection API, helps design much more powerful APIs enabling the developer to write clean and re-usable code.

To understand Java Annotations, its usage syntax and how it helps simplify the code, let us look at an example that demonstrates converting java objects into rows in an excel file with a very easy implementation. I have chosen this example because I have seen this to be a very common use case in most of the modern web applications where we export a grid or a spreadsheet like data locked in java objects to excel file.

Let us see how Java Annotations combined with Reflection helps us to achieve this in a much easier way.

Ultimately the code to export a list of beans to excel spreadsheet will look something like this.

//Fetching the list of beans. This could be either from a database or an internal data structure.
List<Student> students = getTestData();

ExcelWriter excelWriter = new ExcelWriter("path to your new xls file");

//Export all the bean data to excel. The method takes data (students) and the type of data as arguments.
excelWriter.writeSheet(students, Student.class);

Isn’t that much simpler. This utility works for any type of bean as long as the bean other than Student that contains annotations related to our utility.

Let us see the Student Bean now

package edu.learn;
import edu.learn.annotations.ExcelCell;

public class Student {

	@ExcelCell(headerName = "Roll Number", columnOrder = 1)
	private int rollNumber;
	@ExcelCell(headerName = "First Name", columnOrder = 2)
	private String firstName;
	@ExcelCell(headerName = "Last Name", columnOrder = 3)
	private String lastName;
	@ExcelCell(headerName = "City", columnOrder = 4)
	private String city;

	@ExcelCell(headerName = "Address", columnOrder = 5)
	private Address address;

	// Just to demonstrate that this class can also contain columns that we do
	// not want to be a part of excel
	private String extraData;

	public Student(int rollNumber, String firstName, String lastName, String city, Address address) {
		this.rollNumber = rollNumber;
		this.firstName = firstName;
		this.lastName = lastName;
		this.city = city;
		this.extraData = "Any other data";
		this.address = address;
	}

	// Getters and Setters for all members
}

If you notice, I have highlighted two lines in the code.

  • Line 2 – Import the annotation
  • Line 6 – Use the annotation on a member field. (And other fields too). We specify a column name that we want to use for that property and also provide column order in which it appears in the output excel. You can eliminate that part of the logic from code if you wish to.

This is all we need to make this object convertible to an excel row.

Lets now see how to actually create an Annotation. See the definition of @ExcelCell Annotation below.


package edu.learn.annotations;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(value = RetentionPolicy.RUNTIME)
public @interface ExcelCell {

	public int columnOrder() default 0;
	public String headerName();

}

See each part of the definition now.

  • Lines 3,4 – Importing the Retention Annotation from java.lang.annotation
  • Line 6 – @Retention Annotation specifies the compiler about the scope of the annotation. RetentionPolicy.RUNTIME specifies that this annotation will be used in runtime by the program so the annotation should not be dropped while generating the bytecode.
    • Other Rentention policies are
      • RetentionPolicy.SOURCE, which means that these can be discarded at Compile time. BestExample is @SuppressWarnings annotation which is an instruction to the compiler not to output warnings during compilation. Once the source is compiled, this annotation makes no sense. So the compiler discards it while generating bytecode.
      • RetentionPolicy.CLASS, which is partially like RetentionPolicy.RUNTIME because they are both included in the byte code but  in RetentionPolicy.CLASS, the class loader will discard the annotations once the classes loaded. Meaning, if you specify an annotation with RententionPolicy.CLASS, then Java class loader will be able to read the annotation, but the it will not be available during runtime for other parts of your program to dynamically read the properties set for this annotation on a particular class.
  • Line 7 – We are declaring the actual Annotation. It is similar to creating a Java Interface, except we are appending an extra ‘@’ . This tells Java that, ExcelWriter is an annotation.

public int columnOrder() default 0;

  • Line 9 – Declaring the properties of the annotation. It does look like a method definition, but that is the syntax specified by java. By default if you are declaring a property in the annotation definition then it is mandatory that you provide a value for it when you use the annotation. You can use a “default” keyword to override this behavior. Using a default keyword makes the property optional since it already has a value to be used at runtime if the user doesn’t specify the value.

Now is the interesting and most important part of the overall code. ExcelWriter.java definition. I have used Apache POI library here to create an excel file. You can modify it to suit whichever library is appropriate to you.

I have attached the complete source code to this post. Below I am just pointing out the significant piece (writeSheet() method) of the ExcelWriter.java class


	public void writeSheet(List<? extends Object> data, Class t) {
		if (data == null || data.isEmpty()) {
			System.out.println("Empty Data");
			return;
		}

		// I defined another annotation in the project that can be applied to
		// Student class to specify the name of the sheet represented by the
		// Student class.
		ExcelSheet sheetAnnotation = (ExcelSheet) t.getAnnotation(ExcelSheet.class);
		Sheet sheet;
		if (sheetAnnotation == null)
			sheet = workbook.createSheet();
		else
			sheet = workbook.createSheet(sheetAnnotation.value());

		// Retreive all the properties of class which are annotated with
		// ExcelCell. Except the property 'extraData' all other properties of
		// the class will be collected here
		List<Field> excelColumns = getAnnonatedColumns(t);
		int rowCount = 0;
		int colCount = 0;

		// Write Headers
		Row headerRow = sheet.createRow(rowCount++);
		colCount = writeHeaders(colCount, excelColumns, "", headerRow);

		// Write Data
		for (Object dataObject : data) {
			Row dataRow = sheet.createRow(rowCount++);
			colCount = 0;
			colCount = writeRowData(colCount, dataRow, excelColumns, dataObject);
		}

		// Autosizing columns to cleanup the appearance of the spreadsheet.
		for (int i = 0; i < colCount; i++) {
			sheet.autoSizeColumn(i);
		}

		FileOutputStream fileOutputStream = null;

		try {
			fileOutputStream = new FileOutputStream(file);
			workbook.write(fileOutputStream);
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (fileOutputStream != null) {
				try {
					fileOutputStream.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

This method does a lot of things. I would like to make a special mention about the signature of the method.

I have used Generics a bit, to make it look somewhat professional. If you have no idea of Generics or if it doesn’t seem good to you, go ahead and replace the signature with the below code. It perfectly works, but there could be couple of places where you need to replace ‘T’ with ‘Object’.


public void writeSheet(List<? extends Object> data, Class t) {

Finally, the main class to use this  utility to generate an excel file.


package edu.learn;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class Main {

	public static void main(String[] args) {
		try {
			System.out.println("Executing...");
			List<Student> students = getTestData();
			ExcelWriter excelWriter = new ExcelWriter("ExcelFile.xlsx");
			excelWriter.writeSheet(students, Student.class);
			System.out.println("Success");
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	static List<Student> getTestData() {
		List<Student> students = new ArrayList<Student>();
		students.add(new Student(1, "Barry", "Allen", "Central City", new Address("A2", 2)));
		students.add(new Student(2, "Oliver", "Queen", "Starling City", new Address("A3", 3)));
		students.add(new Student(3, "Clark", "Kent", "Metropolis", new Address("A1", 1)));
		students.add(new Student(4, "Peter", "Parker", "New York City", new Address("A4", 4)));
		return students;
	}
}

See how simple the main method for the program is? We are just passing a list of beans to the excel writer to convert it to an excel file.

Output looks like below

 

This is it. We have successfully implemented annotations to create a re usable excel writer that you can use with any bean in your project (Provided you use the annotations that we created).

I attached link to complete source code for this example for your Reference.

But there are a couple of things that I would like to mention before we end.

  1. I have not used the “columnOrder” property of the annotation to sort the fields in the order specified. You can use a simple comparator in the writeSheet Class to sort the excelColumns Array. It will do the trick.
  2. This can be used to generate .xls files only. If you want to generate .xslx files using this example, then simply replace new HSSFWorkbook() with new XSSFWorkbook() in the ExcelWriter Constructor and it should perfectly work fine

 

Please try the attached complete source code and let me know if you face any difficulty executing or understanding any part of the code.

Note : Please change the path of output excel in the Main.java before executing the code.

Get the Source Code here

See the final output excel here.