eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

1. Overview

In this tutorial, we’ll explore the basic functionalities of the JavaPoet library.

JavaPoet is developed by Square, which provides APIs to generate Java source code. It can generate primitive types, reference types and their variants (such as classes, interfaces, enumerated types, anonymous inner classes), fields, methods, parameters, annotations, and Javadocs.

JavaPoet manages the import of the dependent classes automatically. It also uses the Builder pattern to specify the logic to generate Java code.

2. Maven Dependency

In order to use JavaPoet, we can directly download the latest JAR file, or define the following dependency in our pom.xml:

<dependency>
    <groupId>com.squareup</groupId>
    <artifactId>javapoet</artifactId>
    <version>1.10.0</version>
</dependency>

3. Method Specification

First, let’s go through the method specification. To generate a method, we simply call the methodBuilder() method of MethodSpec class. We specify the generated method name as a String argument of the methodBuilder() method.

We can generate any single logical statement ending with the semi-colon using the addStatement() method. Meanwhile, we can define one control flow bounded with curly brackets, such as if-else block, or for loop, in a control flow.

Here’s a quick example – generating the sumOfTen() method which will calculate the sum of numbers from 0 to 10:

MethodSpec sumOfTen = MethodSpec
  .methodBuilder("sumOfTen")
  .addStatement("int sum = 0")
  .beginControlFlow("for (int i = 0; i <= 10; i++)")
  .addStatement("sum += i")
  .endControlFlow()
  .build();

This will produce the following output:

void sumOfTen() {
    int sum = 0;
    for (int i = 0; i <= 10; i++) {
        sum += i;
    }
}

4. Code Block

We can also wrap one or more control flows and logical statements into one code block:

CodeBlock sumOfTenImpl = CodeBlock
  .builder()
  .addStatement("int sum = 0")
  .beginControlFlow("for (int i = 0; i <= 10; i++)")
  .addStatement("sum += i")
  .endControlFlow()
  .build();

Which generates:

int sum = 0;
for (int i = 0; i <= 10; i++) {
    sum += i;
}

We can simplify the earlier logic in the MethodSpec by calling addCode() and providing the sumOfTenImpl object:

MethodSpec sumOfTen = MethodSpec
  .methodBuilder("sumOfTen")
  .addCode(sumOfTenImpl)
  .build();

A code block is also applicable to other specifications, such as types and Javadocs.

5. Field Specification

Next – let’s explore the field specification logic.

In order to generate a field, we use the builder() method of the FieldSpec class:

FieldSpec name = FieldSpec
  .builder(String.class, "name")
  .addModifiers(Modifier.PRIVATE)
  .build();

This will generate the following field:

private String name;

We can also initialize the default value of a field by calling the initializer() method:

FieldSpec defaultName = FieldSpec
  .builder(String.class, "DEFAULT_NAME")
  .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
  .initializer("\"Alice\"")
  .build();

Which generates:

private static final String DEFAULT_NAME = "Alice";

6. Parameter Specification

Let’s now explore the parameter specification logic.

In case we want to add a parameter to the method, we can call the addParameter() within the chain of the function calls in the builder.

In case of more complex parameter types, we can make use of ParameterSpec builder:

ParameterSpec strings = ParameterSpec
  .builder(
    ParameterizedTypeName.get(ClassName.get(List.class), TypeName.get(String.class)), 
    "strings")
  .build();

We can also add the modifier of the method, such as public and/or static:

MethodSpec sumOfTen = MethodSpec
  .methodBuilder("sumOfTen")
  .addParameter(int.class, "number")
  .addParameter(strings)
  .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
  .addCode(sumOfTenImpl)
  .build();

Here’s how the generated Java code looks like:

public static void sumOfTen(int number, List<String> strings) {
    int sum = 0;
    for (int i = 0; i <= 10; i++) {
        sum += i;
    }
}

7. Type Specification

After exploring the ways to generate methods, fields, and parameters, now let’s take a look at the type specifications.

To declare a type, we can use the TypeSpec which can build classes, interfaces, and enumerated types.

7.1. Generating a Class

In order to generate a class, we can use the classBuilder() method of the TypeSpec class.

We can also specify its modifiers, for instance, public and final access modifiers. In addition to class modifiers, we can also specify fields and methods using already mentioned FieldSpec and MethodSpec classes.

Note that addField() and addMethod() methods are also available when generating interfaces or anonymous inner classes.

Let’s take a look at the following class builder example:

TypeSpec person = TypeSpec
  .classBuilder("Person")
  .addModifiers(Modifier.PUBLIC)
  .addField(name)
  .addMethod(MethodSpec
    .methodBuilder("getName")
    .addModifiers(Modifier.PUBLIC)
    .returns(String.class)
    .addStatement("return this.name")
    .build())
  .addMethod(MethodSpec
    .methodBuilder("setName")
    .addParameter(String.class, "name")
    .addModifiers(Modifier.PUBLIC)
    .returns(String.class)
    .addStatement("this.name = name")
    .build())
  .addMethod(sumOfTen)
  .build();

And here’s how the generated code looks like:

public class Person {
    private String name;

    public String getName() {
        return this.name;
    }

    public String setName(String name) {
        this.name = name;
    }

    public static void sumOfTen(int number, List<String> strings) {
        int sum = 0;
        for (int i = 0; i <= 10; i++) {
            sum += i;
        }
    }
}

7.2. Generating an Interface

To generate a Java interface, we use the interfaceBuilder() method of the TypeSpec.

We can also define a default method by specifying DEFAULT modifier value in the addModifiers():

TypeSpec person = TypeSpec
  .interfaceBuilder("Person")
  .addModifiers(Modifier.PUBLIC)
  .addField(defaultName)
  .addMethod(MethodSpec
    .methodBuilder("getName")
    .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
    .build())
  .addMethod(MethodSpec
    .methodBuilder("getDefaultName")
    .addModifiers(Modifier.PUBLIC, Modifier.DEFAULT)
    .addCode(CodeBlock
      .builder()
      .addStatement("return DEFAULT_NAME")
      .build())
    .build())
  .build();

It will generate the following Java code:

public interface Person {
    private static final String DEFAULT_NAME = "Alice";

    void getName();

    default void getDefaultName() {
        return DEFAULT_NAME;
    }
}

7.3. Generating an Enum

To generate an enumerated type, we can use the enumBuilder() method of the TypeSpec. To specify each enumerated value, we can call the addEnumConstant() method:

TypeSpec gender = TypeSpec
  .enumBuilder("Gender")
  .addModifiers(Modifier.PUBLIC)
  .addEnumConstant("MALE")
  .addEnumConstant("FEMALE")
  .addEnumConstant("UNSPECIFIED")
  .build();

The output of the aforementioned enumBuilder() logic is:

public enum Gender {
    MALE,
    FEMALE,
    UNSPECIFIED
}

7.4. Generating an Anonymous Inner Class

To generate an anonymous inner class, we can use the anonymousClassBuilder() method of the TypeSpec class. Note that we must specify the parent class in the addSuperinterface() method. Otherwise, it will use the default parent class, which is Object:

TypeSpec comparator = TypeSpec
  .anonymousClassBuilder("")
  .addSuperinterface(ParameterizedTypeName.get(Comparator.class, String.class))
  .addMethod(MethodSpec
    .methodBuilder("compare")
    .addModifiers(Modifier.PUBLIC)
    .addParameter(String.class, "a")
    .addParameter(String.class, "b")
    .returns(int.class)
    .addStatement("return a.length() - b.length()")
    .build())
  .build();

This will generate the following Java code:

new Comparator<String>() {
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
});

8. Annotation Specification

To add an annotation to generated code, we can call the addAnnotation() method in a MethodSpec or FieldSpec builder class:

MethodSpec sumOfTen = MethodSpec
  .methodBuilder("sumOfTen")
  .addAnnotation(Override.class)
  .addParameter(int.class, "number")
  .addParameter(strings)
  .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
  .addCode(sumOfTenImpl)
  .build();

Which generates:

@Override
public static void sumOfTen(int number, List<String> strings) {
    int sum = 0;
    for (int i = 0; i <= 10; i++) {
        sum += i;
    }
}

In case we need to specify the member value, we can call the addMember() method of the AnnotationSpec class:

AnnotationSpec toString = AnnotationSpec
  .builder(ToString.class)
  .addMember("exclude", "\"name\"")
  .build();

This will generate the following annotation:

@ToString(
    exclude = "name"
)

9. Generating Javadocs

Javadoc can be generated using CodeBlock, or by specifying the value directly:

MethodSpec sumOfTen = MethodSpec
  .methodBuilder("sumOfTen")
  .addJavadoc(CodeBlock
    .builder()
    .add("Sum of all integers from 0 to 10")
    .build())
  .addAnnotation(Override.class)
  .addParameter(int.class, "number")
  .addParameter(strings)
  .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
  .addCode(sumOfTenImpl)
  .build();

This will generate the following Java code:

/**
 * Sum of all integers from 0 to 10
 */
@Override
public static void sumOfTen(int number, List<String> strings) {
    int sum = 0;
    for (int i = 0; i <= 10; i++) {
        sum += i;
    }
}

10. Formatting

Let’s recheck the example of the FieldSpec initializer in Section 5 which contains an escape char used to escape the “Alice” String value:

initializer("\"Alice\"")

There is also a similar example in Section 8 when we define the excluded member of an annotation:

addMember("exclude", "\"name\"")

It becomes unwieldy when our JavaPoet code grows and has a lot of similar String escape or String concatenation statements.

The String formatting feature in JavaPoet makes String formatting in beginControlFlow()addStatement() or initializer() methods easier. The syntax is similar to String.format() functionality in Java. It can help to format literals, strings, types, and names.

10.1. Literal Formatting

JavaPoet replaces $L with a literal value in the output. We can specify any primitive type and String values in the argument(s):

private MethodSpec generateSumMethod(String name, int from, int to, String operator) {
    return MethodSpec
      .methodBuilder(name)
      .returns(int.class)
      .addStatement("int sum = 0")
      .beginControlFlow("for (int i = $L; i <= $L; i++)", from, to)
      .addStatement("sum = sum $L i", operator)
      .endControlFlow()
      .addStatement("return sum")
      .build();
}

In case we call the generateSumMethod() with the following values specified:

generateSumMethod("sumOfOneHundred", 0, 100, "+");

JavaPoet will generate the following output:

int sumOfOneHundred() {
    int sum = 0;
    for (int i = 0; i <= 100; i++) {
        sum = sum + i;
    }
    return sum;
}

10.2. String Formatting

String formatting generates a value with the quotation mark, which refers exclusively to String type in Java. JavaPoet replaces $S with a String value in the output:

private static MethodSpec generateStringSupplier(String methodName, String fieldName) {
    return MethodSpec
      .methodBuilder(methodName)
      .returns(String.class)
      .addStatement("return $S", fieldName)
      .build();
}

In case we call the generateGetter() method and provide these values:

generateStringSupplier("getDefaultName", "Bob");

We will get the following generated Java code:

String getDefaultName() {
    return "Bob";
}

10.3. Type Formatting

JavaPoet replaces $T with a type in the generated Java code. JavaPoet handles the type in the import statement automatically. If we had provided the type as a literal instead, JavaPoet would not handle the import.

MethodSpec getCurrentDateMethod = MethodSpec
  .methodBuilder("getCurrentDate")
  .returns(Date.class)
  .addStatement("return new $T()", Date.class)
  .build();

JavaPoet will generate the following output:

Date getCurrentDate() {
    return new Date();
}

10.4. Name Formatting

In case we need to refer to a name of a variable/parameter, field or method, we can use $N in JavaPoet’s String formatter.

We can add the previous getCurrentDateMethod() to the new referencing method:

MethodSpec dateToString = MethodSpec
  .methodBuilder("getCurrentDateAsString")
  .returns(String.class)
  .addStatement(
    "$T formatter = new $T($S)", 
    DateFormat.class, 
    SimpleDateFormat.class, 
    "MM/dd/yyyy HH:mm:ss")
  .addStatement("return formatter.format($N())", getCurrentDateMethod)
  .build();

Which generates:

String getCurrentDateAsString() {
    DateFormat formatter = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
    return formatter.format(getCurrentDate());
}

11. Generating Lambda Expressions

We can make use of the features that we’ve already explored to generate a Lambda expression. For instance, a code block which prints the name field or a variable multiple times:

CodeBlock printNameMultipleTimes = CodeBlock
  .builder()
  .addStatement("$T<$T> names = new $T<>()", List.class, String.class, ArrayList.class)
  .addStatement("$T.range($L, $L).forEach(i -> names.add(name))", IntStream.class, 0, 10)
  .addStatement("names.forEach(System.out::println)")
  .build();

That logic generates the following output:

List<String> names = new ArrayList<>();
IntStream.range(0, 10).forEach(i -> names.add(name));
names.forEach(System.out::println);

12. Producing the Output Using JavaFile

The JavaFile class helps to configure and produce the output of the generated code. To generate Java code, we simply build the JavaFile, provide the package name and an instance of the TypeSpec object.

12.1. Code Indentation

By default, JavaPoet uses two spaces for indentation. To keep the consistency, all examples in this tutorial were presented with 4 spaces indentation, which is configured via indent() method:

JavaFile javaFile = JavaFile
  .builder("com.baeldung.javapoet.person", person)
  .indent("    ")
  .build();

12.2. Static Imports

In case we need to add a static import, we can define the type and specific method name in the JavaFile by calling the addStaticImport() method:

JavaFile javaFile = JavaFile
  .builder("com.baeldung.javapoet.person", person)
  .indent("    ")
  .addStaticImport(Date.class, "UTC")
  .addStaticImport(ClassName.get("java.time", "ZonedDateTime"), "*")
  .build();

Which generates the following static import statements:

import static java.util.Date.UTC;
import static java.time.ZonedDateTime.*;

12.3. Output

The writeTo() method provides functionality to write the code into multiple targets, such as standard output stream (System.out) and File.

To write Java code to a standard output stream, we simply call the writeTo() method, and provide the System.out as the argument:

JavaFile javaFile = JavaFile
  .builder("com.baeldung.javapoet.person", person)
  .indent("    ")
  .addStaticImport(Date.class, "UTC")
  .addStaticImport(ClassName.get("java.time", "ZonedDateTime"), "*")
  .build();

javaFile.writeTo(System.out);

The writeTo() method also accepts java.nio.file.Path and java.io.File. We can provide the corresponding Path or File object in order to generate the Java source code file into the destination folder/path:

Path path = Paths.get(destinationPath);
javaFile.writeTo(path);

For more detailed information regarding JavaFile, please refer to the Javadoc.

13. Conclusion

This article has been an introduction to JavaPoet functionalities, like generating methods, fields, parameters, types, annotations, and Javadocs.

JavaPoet is designed for code generation only. In case we would like to do metaprogramming with Java, JavaPoet as of version 1.10.0 doesn’t support code compilation and running.

The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.
Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

Course – Summer Sale 2026 – NPI EA (cat= Baeldung)
announcement - icon

Yes, we're now running our only Summer Sale. All Courses are 30% off until 20th July, 2026:

>> EXPLORE ACCESS NOW

Course – Summer Sale 2026 – NPI (All)
announcement - icon

Yes, we're now running our only Summer Sale. All Courses are 30% off until 20th July, 2026:

>> EXPLORE ACCESS NOW

eBook Jackson – NPI EA – 3 (cat = Jackson)