Home » Java 16 Record to Reduce Boilerplate Code of POJO

Java 16 Record to Reduce Boilerplate Code of POJO

by Imran Shaikh
Java 16 Record to Reduce Boilerplate Code of POJO

Hey, Tea Lovers! Java 16 is released a few days back, well, a month to be precise. It introduced 17 new features. I discussed the 4 features that the developer will use a lot in my previous post, Simplified Java 16 Key Features. In this post, we will deep dive into Java 16 Record to see how it helps us to reduce the boilerplate code of POJO classes.

POJO Boliterplate Code

Whenever you need a simple class to hold the data, you write a POJO class, right? But Even though your need is simple, you need to write tonnes of code for just a few things. Such as getters and setters, constructors, toString, and equals and hashcode. No matter how many different POJOs you write, you have to write this 90% similar methods. These things with little to no changes that we have to write again and again for simple purposes are known as Boilerplate code.

public class Student {
   private final String name;
   private final int rollNo;
 // all the below code is boilerplate code
   public Student(String name, int rollNo) {
     this.name = name;
     this.rollNo = rollNo;
 public String getName() {
     return name;
 public int getRollNo() {
     return rollNo;
   public boolean equals(Object o) {
     if (this == o) return true;
     if (o == null || getClass() != o.getClass()) return false;
     Student student = (Student) o;
     return rollNo == student.rollNo && name.equals(student.name);
   public int hashCode() {
     return Objects.hash(name, rollNo);

In the above example code, all the lines after line number 4 are boilerplate code. Whatever class you create, only the fields will change and everything will remain the same. One other example of such a thing is JDBC connection initialization.

One solution to POJO Boiler Plate Code is Lombok

To overcome this POJO boilerplate code, there is a library called Lombok, which helps you to resolve this situation with Annotations. With this, your POJO only contains the fields that you need to hold the data and nothing more except a few Annotations. And I personally use this in every Java project I work on. You can learn more about it in my previous post, Say Good-Bye to Boilerplate Code with Lombok: Part 2-The Code.

class Student {
   private final String name;
   private final int rollNo;
  // no boilrplate code, all those are getting generated  on bytecode
  //  so it doesn't even clutter the code it self

See? How all that boilerplate code got reduced to a simple @Data annotation? Cool right? But this is an external library. You have to add the dependency of Lombok in your project. And your IDE needs the plugin of it to work. (The latest versions of IntelliJ support the Lombok without any plugin now, which is a good thing).

However, starting Java 16, we won’t be needing it anymore. These additional things will be taken care of by the compiler itself with Java 16 Record. Let’s jump to it and see how. But before, prepare your cup of tea to sip and code.

You can follow me on social @coderstea on TwitterLinkedinFacebook, or Instagram. We also share high-quality videos about programming on our Youtube channel. You can also publish your own post on CodersTea.com, just share your thought on Contact Us or let us know in the comments.

The Things Java 16 Record Do for us

Before jumping into how to use Java 16 Record, let us first see what actually we are trying to solve here. As I already mentioned that you need to create a lot of boilerplate code to simply hold the data. With Java 16 Record you get all these things without any extra work. It generates all the boilerplate code we talked about. In addition to that, we don’t even need to declare the access modifier and final keyword. The things Java Record provides are,

  • private and final immutable fields
  • Constructor
  • Getters
  • equals()
  • hashCode()
  • toString()

We will now explore the Java 16 Record and will see how to use it. I discuss the above-mentioned list one by one.

How to Create a Java Record

Let us start with creating one Record. It is simple, you only require the name of the Record and the fields you need in that record. After defining parameters in the record, these fields will be private and immutable, meaning they will be private and final by default. I will take the same Student class of the earlier sample code and convert it to the Record.

public record Student (String name, int rollNo) {}

Yes, that’s it. That simple record keyword took care of all the boilerplate code we have to generate unnecessarily. And now, you can use it how you use the normal POJO class. Why don’t we now jump to the different parts of it, shall we?

The Constructor of Java 16 Record

By default, it gives us the public constructor with the exact parameter we used while declaring the record. This is used exactly the same way we used it in POJO. For example.

record Student (String name, int rollNo) {}
// another record
record Employee (String name, int id, List<String> skills) {}

This will be called like this,

Student student = new Student ("CodersTea", 1);
var anotherStudent = new Student("Dev Tools by CodersTea", 2);
var emp1 = new Employee("Imran", 1, List.of("nothing", "he knows nothing"))

So, whatever fields you put in the creation will be your default public constructor. But what if you want to do something in the constructor? Such as validation? Let us see that in the next block.

Modify Default Constructor Initialization in Record

Let us say, in the Student record, we don’t want negative or zero as a value. And we don’t want the name to be null. And we want all these to be validated in the existing or default constructor. For this, we need to do the following.

record Student (String name, int rollNo) {
  public Student {
    Objects.requireNonNull(name); // non null name
    if ( rollNo < 1 ) { // not 0 or negative
      throw new IllegalArgumentException("Roll No can not be 0 or negative");

As you can see, I declared a constructor-like block without a parameter bracket and wrote the logic there. In this block, you can access the field directly with or without this keyword.

Add Another Constructor in Java Record

Now, that we have seen how we can modify or do some extra login at the time of initialization of the default constructor, let us see how we can create another constructor.

Creating a new Constructor is a similar process as normal java classes. However, you must delegate it to the default constructor. By delegation, I mean you need to call the default constructor anyhow, either directly or via another constructor. Otherwise, it won’t compile.

For example, for the Employee record, If only name and id are important and skills can be empty (like me 😭), then we can create another constructor with only name and id as parameters. Then in this new constructor, we will call the default constructor with these two parameters and pass an empty list as the 3rd param, skills. Confusing? Ok, see the code.

public record Employee(String name, int id, List<String> skills) {
  public Employee(String name, int id){
    // delegating to default constructor
    // with default skills
    this(name, id, Collections.emptyList());

You can then play around and have as many constructors as you want, but make sure they directly or indirectly call the default one. For example, I want to have another constructor as default id then I have two options after creating a new constructor with only String name param. First, delegate to the constructor we created above, which has default skills define. Or I can directly delegate to default one by giving both skills and id defualt value.

public Employee (String name) {
   // delegate to above constructor
   this(name, 0);
   //  following will delegate to default constructor
   // this(name, 0, Collections.emptyList(); 

Getters in Java 16 Record

Getters generated by Record are not similar to our normal POJO getters. It doesn’t use get as a prefix instead the field name with (). So for our Student and Employee, it can be called like this.

// gives us the name fields value
String student1Name = student.name();
// skills field value
 List empSkills = emp1.skills();
// rollNo of student2
 int student2RollNo = anotherStudent.rollNo();

toString Method in Java Record

By default the toString() value of a class, like Abc, is [email protected]. Which doesn’t seem to be the best output to understand the content of it. So we have to override the method and do string concatenation. In Record, it gets generated automatically. For example, printing the Student and Employee object we created earlier will be printed as follows.


Output would be like,

Student[name=CodersTea, rollNo=1]
Employee[name=Imran, id=1, skills=[nothing, he knows nothing]]

Equals and HashCode in Java 16 Record

To check whether or not two objects are the same, we use equals(). And hashCode() is something we need to override in case we create our own equals(). In case equals() is true, then both Objects must have the same hashCode(). The combination of these two methods is used in Set and Map. I have explained them in detail in our How HashMap Works in Java, so please do check it out.

These methods must be overridden carefully as they will hugely impact the output in case we are using them in Map or Set. With Record, we don’t need to worry about that as it takes care of these things automatically.

String name = "CodersTea.com";
int rollNo = 123;

Student student1 = new Student(name, rollNo);
Student student2 = new Student(name, rollNo);

boolean equals = student1.equals(student2); // true
System.out.println("equals is " + equals);

boolean hashCode = student2.hashCode() == student1.hashCode();  // true
System.out.println("hashCode is " + hashCode);

If and only if all the values in both the records are the same then only you get the same hashCode and true in equals by default. In the above example, if I create another Student object and use a different rollNo then the result will be the opposite of the earlier.

Student student3 = new Student(name, 100); // changes rollNo
student3.equals(student2); // false
student3.equals(student1); // false

var hc = student3.hashCode() == student1.hashCode(); // false
var hc1 = student3.hashCode() == student2.hashCode(); // false

Static Members and Class in Java 16 Record

You can define the static variable and classes. Earlier, in preview, the static members inside the inner static class of the Record were not allowed, but you can do so now starting Java 16. You can pretty much do all the things you do in a normal class.

record Employee(String name, int id, List skills) {
    public static final float PI = 3.14f;

    public static class InnerStaticClass{
    public static final int WRONG_PI = 123;

Inheritance in Java 16 Record

You can do inheritance in Java Record by implementing an interface, but can not extend other classes. The reason is that a record internally converts to a class that extends Record.java, and as you know multiple inheritances are not allowed in Java, explained here.

interface DemoInterface {
  void doSomething();

record Student(String name, int rollNo) implements DemoInterface{
  public void doSomething() {
    // do something


That’s it for this post. I have covered the basics of Record of Java 16. It is just like a normal class but with special features. It will greatly improve productivity and reduce the redundant code exponentially. No extra library of Lombok to be set up in the project anymore.

Please do share this post if you think it has helped you understand the Java 16 Record. See you in the next post.


This website uses cookies to improve your experience. We'll assume you're ok with this, but you can opt-out if you wish. Accept Read More

Privacy & Cookies Policy