When you are a programmer you spend most of your time writing code. But, at some point, you need to write a piece of code which will generate another code. In Java, you can do it with JavaPoet, a library for generating Java source files.



You won’t need to generate Java source files unless you build some kind of library or plugin. In everyday programming, this library can not help you, but there are some use cases where you will find it useful.

What is JavaPoet

JavaPoet is a library developed by Square, and it provides a simple API for generating Java source files. With just a few lines of code, you can generate anything you need. Here is a simple example which generates Java class called “SuperHero”. This class is generated in “src” folder.

//Generated class will be located in "src" folder File sourcePath = new File("src"); //Generated class will be named SuperHero TypeSpec superHero = TypeSpec.classBuilder("SuperHero") .build(); //Generated class will have "xyz.ivankocijan.generated" package name JavaFile javaFile = JavaFile.builder("xyz.ivankocijan.generated", superHero) .build(); try { superHeroFile.writeTo(sourcePath); } catch (IOException e) { e.printStackTrace(); }

When you run this code it will generate a class which looks like this:

package xyz.ivankocijan.generated; class SuperHero { }

It looks easy, and it is because JavaPoet has a simple and powerful API. Let’s see a more complex example where we will generate 3 classes. 1 base class, 1 interface and 1 class which extends generated class and implements the generated interface.

Superheros example

This example will generate 3 java source files:

1. SuperHero – abstract class

2. SuperPower – interface

3. IronMan – Class which will extend SuperHero class and implement SuperPower interface

For each of these classes, we need to write a specification so that we know what we are going to generate.

SuperHero class specification

1. Public and abstract

2. Abstract method getName() with String return type

3. Class field “name” with String class type

Before creating the class, we need to declare it’s methods. JavaPoet has a class called MethodSpec and it’s builder is used to declare all specification for that method.

To add modifiers for this method we will use addModifiers(Modifier… modifiers) method. Make sure you import Modifier enum from javax.lang.model.element package.

Adding return type is simple and .returns(Type returnType) method is used for that.

After declaring the method we are going to add it to TypeSpec object by using its classBuilder and addMethod(MethodSpec spec) method. We’re also going to need one field and to do that we’ll use addField(Type type, String name, Modifier… modifiers) method.

File sourcePath = new File("src"); //Specification for getName() method MethodSpec getNameMethod = MethodSpec.methodBuilder("getName") .returns(String.class) .addModifiers(Modifier.ABSTRACT, Modifier.PROTECTED) //method will be abstract and protected .build(); TypeSpec superHero = TypeSpec.classBuilder("SuperHero") .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) //Class will be abstract and public .addMethod(getNameMethod) //Add getName() method to SuperHero class .addField(String.class, "name", Modifier.PROTECTED) .build(); JavaFile superHeroFile = JavaFile.builder("xyz.ivankocijan.generated", superHero).build(); try { superHeroFile.writeTo(sourcePath); } catch (IOException e) { e.printStackTrace(); }

After running this code, a class named SuperHero is generated in “src” folder inside “xyz.ivankocijan.generate” package.

package xyz.ivankocijan.generated; import java.lang.String; public abstract class SuperHero { protected String name; protected abstract String getName(); }

SuperPower interface specification

1. Public

2. GetSuperPower method with return type String

Creating an interface with methods is almost the same as creating a class but now we are going to use TypeSpecs interfaceBuilder to build specification for this interface.

File sourcePath = new File("src"); MethodSpec getSuperPowerMethod = MethodSpec.methodBuilder("getSuperPower") .returns(String.class) .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) .build(); TypeSpec superPowerInterface = TypeSpec.interfaceBuilder("SuperPower") .addMethod(getSuperPowerMethod) .addModifiers(Modifier.PUBLIC).build(); JavaFile superPowerFile = JavaFile.builder("xyz.ivankocijan.generated", superPowerInterface).build(); try { superPowerFile.writeTo(sourcePath); } catch (IOException e) { e.printStackTrace(); }

Here is how generated interface looks like:

package xyz.ivankocijan.generated; import java.lang.String; public interface SuperPower { String getSuperPower(); }

Now we have a base class and an interface. Let’s create a class which will use them.

IronMan class specification

1. Public

2. Constructor with one parameter called name

3. Extend SuperHero class and override getName() method

4. Implement SuperPower interface and override getSuperPower() method

We are going to build specification for constructor by using TypeSpecs constructorBuilder().

For parameters we will use the addParameter(Type type, String name, Modifier… modifiers) method. We also need one line of code in constructor which will assign our name field from abstarct class to constructor parameter. For that, we will use addStatment method and $N which refers to our name field. We do it like this

MethodSpec constructor = MethodSpec.constructorBuilder() .addParameter(String.class, "name") .addStatement("this.$N = $N", "name", "name") .addModifiers(Modifier.PUBLIC).build();

For getName() and getSuperPower() methods which we override from SuperHero class and SuperPower interface we’ll need to specify these methods again but this time we add Override annotation and a return statement. It would be smarter to make that specification more generic, but for our example it’s not a problem to have it twice.

MethodSpec getSuperPowerMethod = MethodSpec.methodBuilder("getSuperPower") .returns(String.class) .addAnnotation(Override.class) .addStatement("return $S", "hasMoney") .addModifiers(Modifier.PUBLIC) .build();

The tricky part with IronMan class is accessing generated classes to extend and implement them. We’re going to do that with ClassName class.

ClassName superClass = ClassName.get("xyz.ivankocijan.generated", "SuperHero"); ClassName superPowerInterface = ClassName.get("xyz.ivankocijan.generated", "SuperPower");

When declaring IronMan class specification we will add those classes with superClass(TypeName superclass) and addSuperInterface(TypeName superInterface) methods.

TypeSpec ironMan = TypeSpec.classBuilder("IronMan") .superclass(superClass) .addSuperinterface(superPowerInterface) .build();

Everything else is more or less the same what we did for SuperHero and SuperPower classes. Here is the code for generating IronMan class.

//Generated class will be located in "src" folder File sourcePath = new File("src"); //getName() and getSuperPower() methods MethodSpec getNameMethod = MethodSpec.methodBuilder("getName") .returns(String.class) .addAnnotation(Override.class) .addStatement("return this.$N", "name") .addModifiers(Modifier.PROTECTED) .build(); MethodSpec getSuperPowerMethod = MethodSpec.methodBuilder("getSuperPower") .returns(String.class) .addAnnotation(Override.class) .addStatement("return $S", "hasMoney") .addModifiers(Modifier.PUBLIC) .build(); //Constructor for IronManClass MethodSpec constructor = MethodSpec.constructorBuilder() .addParameter(String.class, "name") .addStatement("this.$N = $N", "name", "name") .addModifiers(Modifier.PUBLIC).build(); //Get name for superclass and interface ClassName superClass = ClassName.get("xyz.ivankocijan.generated", "SuperHero"); ClassName superPowerInterface = ClassName.get("xyz.ivankocijan.generated", "SuperPower"); //Building IronMan class TypeSpec ironMan = TypeSpec.classBuilder("IronMan") .addModifiers(Modifier.PUBLIC) .superclass(superClass) .addMethod(constructor) .addMethod(getNameMethod) .addMethod(getSuperPowerMethod) .addSuperinterface(superPowerInterface) .build(); JavaFile ironManFile = JavaFile.builder("xyz.ivankocijan.generated", ironMan).build(); try { ironManFile.writeTo(sourcePath); } catch (IOException e) { e.printStackTrace(); }

Finally, after running the code generated IronMan class looks like this:

package xyz.ivankocijan.generated; import java.lang.Override; import java.lang.String; public class IronMan extends SuperHero implements SuperPower { public IronMan(String name) { this.name = name; } @Override protected String getName() { return this.name; } @Override public String getSuperPower() { return "hasMoney"; } }

When you run all this code you’ll have three generated classes in your “src” folder.

You can download the code used to generate these classes from this gist.

Is this for me?

As mentioned at the beginning of this blog post, you won’t use JavaPoet in your everyday development but when you need to generate Java code it’s a powerful solution.

I have covered only a small part of JavaPoet functionality. You can do a lot more like generating enums and anonymous inner classes. Visit their official GitHub page for more info.

Thanks to oFca for proofreading this blog post.