For the most of us developers, generating Java source files is an occasionally happening task and we’re dealing with it e.g. when writing annotation processors, writing tools or interacting with meta-data files.
JavaPoet is a nice library to simplify such tasks, offering an intuitive fluent-builder API to generate source files in no time.
In the following tutorial I’d like to share a few examples by writing code generators with the help of this library.
Dependencies
Using Gradle here, we need to add just one dependency to our build.gradle
dependencies {
compile 'com.squareup:javapoet:1.0.0'
}
Example 1: Creating Classes and Methods
In our first example, we’re going to write a generator for the following simple Java class with one public method:
package com.hascode.tutorial;
import java.lang.String;
public class CustomerService {
public String greetCustomer(String name) {
return "Welcome, "+name;
}
}
And this is our generator – as we can see, JavaPoet makes it easy to create methods and classes, we simply need to use the corresponding build .. e.g. methodBuilder or typeBuilder.
Taking a look at the method’s body there is one special feature we should mention: addStatement works similar to String.Format but allows some interesting, special placeholders:
-
$S is for strings so we don’t need to add quotes,
-
$N references by a name. So in our example, the method takes a parameter named “name” and we’re referencing this parameter with $N and using the parameters name.
package com.hascode.tutorial;
import java.io.IOException;
import javax.lang.model.element.Modifier;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
public class ClassAndMethodExample {
public static void main(final String[] args) throws IOException {
MethodSpec greetCustomer = MethodSpec.methodBuilder("greetCustomer").addModifiers(Modifier.PUBLIC).returns(String.class).addParameter(String.class, "name")
.addStatement("return $S+$N", "Welcome, ", "name").build();
TypeSpec customerService = TypeSpec.classBuilder("CustomerService").addModifiers(Modifier.PUBLIC).addMethod(greetCustomer).build();
JavaFile javaFile = JavaFile.builder("com.hascode.tutorial", customerService).build();
javaFile.writeTo(System.out);
}
}
Example 2: Creating Control Flow Structures
In the second example, we’re going to implement a simple control flow using an old, boring for-loop.
This is the class we’d like to generate.
package com.hascode.tutorial;
class Counter {
public void count() {
for (int i = 0; i < 10; i++) {
total += i;
}
}
}
This is our generator – beginControlFlow, addStatement and endControlFlow do the work for writing our loop.
package com.hascode.tutorial;
import java.io.IOException;
import javax.lang.model.element.Modifier;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
public class ControlFlowExample {
public static void main(final String[] args) throws IOException {
MethodSpec count = MethodSpec.methodBuilder("count").addModifiers(Modifier.PUBLIC).beginControlFlow("for (int i = 0; i < 10; i++)").addStatement("total += i").endControlFlow().build();
TypeSpec counter = TypeSpec.classBuilder("Counter").addMethod(count).build();
JavaFile javaFile = JavaFile.builder("com.hascode.tutorial", counter).build();
javaFile.writeTo(System.out);
}
}
Example 3: Using References
In the third example, we’re going to write a class with references between its two methods – this will be our generated class:
package com.hascode.tutorial;
import java.lang.System;
class NumberUtil {
public long doubleNumber(long number) {
return number*2;
}
public void printDoubleNumber(long number) {
System.out.println("Your number doubled is: "+doubleNumber(number));
}
}
And this is our generator – in the second method spec we’re referencing the first method-spec using the literal placeholder: $L
package com.hascode.tutorial;
import java.io.IOException;
import javax.lang.model.element.Modifier;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
public class SelfReferencesExample {
public static void main(final String[] args) throws IOException {
MethodSpec doubleNumber = MethodSpec.methodBuilder("doubleNumber").addModifiers(Modifier.PUBLIC).addParameter(long.class, "number").returns(long.class).addStatement("return $L*2", "number")
.build();
MethodSpec printDoubleNumber = MethodSpec.methodBuilder("printDoubleNumber").addModifiers(Modifier.PUBLIC).addParameter(long.class, "number")
.addStatement("$T.out.println(\"Your number doubled is: \"+$N($L))", System.class, doubleNumber, "number").build();
TypeSpec numberUtil = TypeSpec.classBuilder("NumberUtil").addMethod(doubleNumber).addMethod(printDoubleNumber).build();
JavaFile javaFile = JavaFile.builder("com.hascode.tutorial", numberUtil).build();
javaFile.writeTo(System.out);
}
public static int fib(final int number) {
if (number == 1 || number == 2) {
return 1;
}
return fib(number - 1) + fib(number - 2);
}
}
Example 4: Creating a JAX-RS Service with Annotations and Type References
Now to a more real-life example: We’re going to generate the sources for a RESTful webservice following the JAX-RS standard.
To import the JAX-RS API we simply need to add the following dependency to our build.gradle: compile ‘javax.ws.rs:jsr311-api:1.1.1′
This is source for the service that we’d like to generate:
package com.hascode.tutorial;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Response;
@Path("book")
class BookService {
@Path("/{id}")
@GET
Response findById(@PathParam("id") final long id) {
return Response.ok().build();
}
}
And again this is our generator. When we need to add more complex parameters or annotations, the AnnotationSpec and its builder and the ParameterSpec and its builder help us to write them up in no time:
package com.hascode.tutorial;
import java.io.IOException;
import javax.lang.model.element.Modifier;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Response;
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.TypeSpec;
public class JAXRSAnnotationExample {
public static void main(final String[] args) throws IOException {
AnnotationSpec path = AnnotationSpec.builder(Path.class).addMember("value", "$S", "/{id}").build();
AnnotationSpec classParam = AnnotationSpec.builder(Path.class).addMember("value", "$S", "book").build();
AnnotationSpec pathParam = AnnotationSpec.builder(PathParam.class).addMember("value", "$S", "id").build();
ParameterSpec id = ParameterSpec.builder(long.class, "id", Modifier.FINAL).addAnnotation(pathParam).build();
MethodSpec findById = MethodSpec.methodBuilder("findById").addAnnotation(path).addAnnotation(GET.class).returns(Response.class).addParameter(id)
.addStatement("return $T.ok().build()", Response.class).build();
TypeSpec bookService = TypeSpec.classBuilder("BookService").addMethod(findById).addAnnotation(classParam).build();
JavaFile javaFile = JavaFile.builder("com.hascode.tutorial", bookService).build();
javaFile.writeTo(System.out);
}
}
Example 5: Creating Constructors and Anonymous Inner Classes
In our last example, we’re going to write up a generator to create an outer class with an inner class with a method that’s making use of an anonymous class.
This is the class-to-be-generated:
package com.hascode.tutorial;
import java.lang.Override;
import java.lang.Runnable;
import java.lang.String;
import java.lang.System;
import java.lang.Thread;
class Outer {
public Outer(final String str) {
System.out.println("outer created with "+str);
}
class Inner {
public void runInner() {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("inner runs in a thread");
}
});
}
}
}
And this is our generator – for anonymous classes, the TypeSpec offers a specialized builder for anonymous classes as for constructors, the MethodSpec offers a specialized constructor builder.
package com.hascode.tutorial;
import java.io.IOException;
import javax.lang.model.element.Modifier;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
public class ConstructorsAndAnonymousInnerClassesExample {
public static void main(final String[] args) throws IOException {
MethodSpec run = MethodSpec.methodBuilder("run").addModifiers(Modifier.PUBLIC).returns(void.class).addStatement("$T.out.println($S)", System.class, "inner runs in a thread")
.addAnnotation(Override.class).build();
TypeSpec anonRunnable = TypeSpec.anonymousClassBuilder("").addSuperinterface(Runnable.class).addMethod(run).build();
MethodSpec runInner = MethodSpec.methodBuilder("runInner").addModifiers(Modifier.PUBLIC).addStatement("new $T($L)", Thread.class, anonRunnable).build();
TypeSpec inner = TypeSpec.classBuilder("Inner").addMethod(runInner).build();
MethodSpec outerConstructor = MethodSpec.constructorBuilder().addParameter(String.class, "str", Modifier.FINAL)
.addStatement("$T.out.println($S+$N)", System.class, "outer created with ", "str").addModifiers(Modifier.PUBLIC).build();
TypeSpec outer = TypeSpec.classBuilder("Outer").addMethod(outerConstructor).addType(inner).build();
JavaFile javaFile = JavaFile.builder("com.hascode.tutorial", outer).build();
javaFile.writeTo(System.out);
}
}
class Outer {
public Outer(final String str) {
System.out.println("Outer created with " + str);
}
class Inner {
public void runInner() {
new Thread(() -> System.out.println("inner runs in a thread")).start();
}
}
}
Tutorial Sources
Please feel free to download the tutorial sources from my GitHub repository, fork it there or clone it using Git:
git clone https://github.com/hascode/javapoet-tutorial.git