You are here
Home > java > Core Java >

Record In Java With Examples

Record In Java With ExamplesAs Oracle Community is continuously providing the new features in every new release, it becomes essential to stay updated as soon as possible. Record In Java is one of the greatest feature that was first introduced in Java 14 as a preview feature, and finalized in Java 17. It has added a new keyword ‘record’ in the list of Java keywords. Some people in the industry have already started using it proactively to reduce a huge amount of boilerplate code.

If you are using any traditional approach to transfer data from one layer to another without changing its value in your project, it is advisable that you can start using record in order to reduce a big amount of boilerplate code. Additionally, if you use the ‘record’, you don’t have to write a single line of code in order to make a class immutable. In this article, we will explore the concept of record in Java with examples, including their syntax, how to create and use them, and some other features in detail.

When to use Record in Java?

If you want to transfer immutable data between different layers of your application, then using a record in Java can be a good choice. By default, Records are immutable in Java, which means that we can’t change their properties after they are created. It can also be helpful in avoiding bugs and improving code reliability. In simple words, we can auto-generate classes using Record in Java.

Where to use a Record in Java?

Generally, we can use records in any situation where you need to declare simple data containers with immutable properties and auto-generated methods. For example, below are a few use cases where records can be useful:

Data transfer objects (DTOs): We can use Records to declare simple data transfer objects that hold data. This is useful when transferring data between different layers of an application, such as between the service layer and the database layer.

Configuration objects: Records can be used to declare configuration objects that hold a set of configuration properties for an application or a module. These objects typically have immutable properties, making them thread-safe and easy to reason about.

Value objects: Records can be used to declare value objects that hold a set of values that represent a particular concept or domain model.

API Responses: When building REST APIs, it is common to return data in the form of JSON or XML. In such cases, you might want to define a simple data structure that represents the API response. Records are ideal for this because they allow you to define a lightweight and immutable data structure that can be easily serialized to JSON or XML.

Test Data: When writing unit tests, it is often necessary to create test data that represents a specific scenario. In such cases, you might want to define a simple data structure that represents the test data. Records can be perfect for this because they allow us to define a lightweight and an immutable data structure with minimal boilerplate code.

Tuple-like objects: Records can be used to declare tuple-like objects that hold a fixed number of related values. This can be useful when returning multiple values from a method or when working with collections of related values.

What is a Record in Java?

A record in Java is a class that is intended to store data. It is similar to a traditional Java class, but in addition it is more lightweight and has some unique features. Records are immutable by default, which means that their state cannot be changed once they are created. This makes them ideal for storing data that should not be modified (immutable data), such as configuration settings or values returned from a database query.

The syntax for creating a record in Java is as follows:

record Record_Name(Fields….)

Example:

public record Book(String name, double price) { }

This creates a new record called “Book” with two fields: “name” and “price”.

You may also read: Java 14 Features and  Java 17 Features

How to create and use Record in Java With Examples?

Let’s understand how to create and use a record in java programmatically.

Creating a Record

Programmatically, creating a record is not very similar to creating a regular class in Java. We use ‘record’ keyword instead of ‘class’. Furthermore, declare the fields with data types within the bracket of record name. For example, below code demonstrates how to create a record in Java:

public record Book(String name, double price) { }

In this example, we have created a record called “Book” with two fields: “name” and “price”. The “public” keyword indicates that this record can be accessed from outside the package it is defined in.

Using a Record

To use a record in Java, we can create an instance of it using the “new” keyword, just like we do with a regular class. Here is an example:

Book book = new Book("Core Java", 324.25);

This creates a new instance of the “Book” record with the “name” field set to “Core Java” and the “price” field set to 324.25. Once a record has been created, its fields can be accessed using the field name itself. There are no getter & setter methods. Instead, field name becomes the method name.

String name = book.name(); 

double price = book.price();

This retrieves the value of the “name” and “price” fields from the “book” record.

In addition to the fields, records also have some built-in methods that can be used to manipulate them. For example, toString(), equals() and hashCode().

Records in Java are immutable by default, which means that once they have been created, their state cannot be changed.

Let’s take a look at some other example of how to create & use records in Java.

Example:

Let’s take an example of a record for storing student information.

public record Student(String name, int age, String subject) {}

This creates a record called “Student” with three fields: “name”, “age”, and “subject”. We can create an instance of this record as follows:

Student student = new Student("John Smith", 20, "Computer Science");

We can then retrieve the values of the fields using the field name:

String name = student.name();
int age = student.age(); 
String subject= student.subject();

How does a Record look like after Compilation?

As record is simply a special kind of a class and the compiler also converts it into a normal class, but with some restrictions, which makes it slightly different from the typical classes. When the compiler converts the record (Java file) into the bytecode after compilation process, the produced ‘.class’ file contains some additional declarations of the record class. For example, below is the bytecode produced for the Student record by the java compiler:

Record in Java File 

record Student(String name, int age) {   }

Record in .class file (After Compilation)

We can also find below converted class if we use javap tool and apply below command from command prompt. It is important to note that the Java command-line includes the ‘javap’ tool, which can be used to view details about the fields, constructors, and methods of a class file.

>javap Student

Conclusion: If we check the bytecode closely, we can have some observations:

  • The compiler has replaced the record keyword by class.
  • The compiler has declared class as final. This indicates that this class cannot be extended. It also means that it cannot be inherited, and is immutable in nature.
  • The converted class extends java.lang.Record. It indicates that all records are a subclass of the Record class defined under java.lang package.
  • There is a parameterized constructor added by the compiler.
  • The compiler has auto-generated the toString(), hashCode(), and equals() methods.
  • The compiler has added methods to access the fields. Note that naming convention of the methods. They are exactly matching with the names of the fields, no get or set before the field names.

Fields in Records

Records in Java specify their state via a set of fields, each with its own name and type. The fields of a record are declared in the record’s header. For example:

public record Person(String name, int age) {}

This record has two fields: “name” of type String and “age” of type int. The fields of a record are implicitly final and cannot be reassigned after the record is created.

We can add a new field, but not recommended. A new field added to the record must be static. For example:

public record Person(String name, int age) {

   static String sex;
}

Constructors in Records

Like traditional class constructors, constructors in records are used to create instances of records. There are two concepts of constructors in Records: Canonical Constructor and Compact Constructor. The compact constructor provides a more concise way of initializing state variables in a record, while the canonical constructor provides a more traditional way with greater flexibility.

Canonical Constructor

Java compiler by default provides us an all-arguments constructor (a constructor with all fields) that assigns its arguments to the corresponding fields. It is known as the canonical constructor. We can also add business logic such as conditional statements to validate data. Below is the example:

public record Person(String name, int age) {

       public Person(String name, int age) {
           if (age < 18) {
              throw new IllegalArgumentException("You are not allowed to participate in general elections");
           }
      }
}

Compact Constructor

Compact constructors ignore all arguments, including the parentheses. The assignment of respective fields happens automatically. For example, below code demonstrates the concept of a compact constructor in records:

public record Person(String name, int age) {

      public Person {
          if (age < 18) {
              throw new IllegalArgumentException("You are not allowed to participate in general elections");
          }
     }
}

In addition to the compact constructor, you can define regular constructors in a record just like in a regular class. However, you must ensure that all constructors initialize all fields of the record.

Methods in Records

Records in Java automatically generate a set of methods for each field in the record. These methods are called accessor methods and have the same name as the field they are associated with. For example, if a record has a field called “price”, then it will automatically have a method called “price()” that returns the value of the “price” field.

In addition to the automatically generated accessor methods, we can also define our own methods in a record, just like in a regular class. For example:

public record Person(String name, int age) { 
   public void sayHello() { 
      System.out.println("Hello, my name is " + name); 
   }
}

This record has a method called “sayHello()” that prints out a greeting using the “name” field.

How does Record help in reducing Boilerplate code?

Let’s take a simple example to see how records in Java can help eliminate boilerplate code. Suppose we have a class called Person that represents a person with a name, age, and email address. Here is how we define the class in the traditional way.

Code Without Using Record

public class Person {

    private String name;
    private int age;
    private String email;

    public Person(String name, int age, String email) {
        this.name = name;
        this.age = age;
        this.email = email;
    }

    public String getName() {

       return name;
    }

    public int getAge() {
       return age;
    }
    
    publicString getEmail() {
       return email;
    }

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

    public voidsetAge(int age) {
       this.age = age;
    }

    public void setEmail(String email) {
       this.email = email;
    }

    @Override
    public String toString() {
       return "Person{" +
         "name='" + name + '\'' +
         ", age=" + age +
         ", email='" + email + '\'' +
      '}';
    }
}

As we can see, this class requires a lot of boilerplate code to define the fields, constructor, getters, setters, and toString() method.

Apart from this, suppose we want to make this class immutable. Then we have to do some additional steps such as:

  1. Declare the class as final, so it cannot be extended.
  2. Declare all fields as private and final, so they cannot be modified outside the constructor.
  3. Do not provide any setter methods for the fields.
  4. If any of the fields are mutable, return a copy of them instead of returning the original object.

Code Using Record

Now let’s see how we can define the same class using a Java record:

public record Person(String name, int age, String email) {}

That’s it! With just one line of code, we have defined a class that has the same fields, constructor, getters, setters, and toString() method as the traditional class. The record syntax takes care of all the boilerplate code for us.

From the above example, it is clear that using records in Java can help eliminate boilerplate code by providing a concise syntax for defining classes with a fixed set of fields. Compared to the traditional approach, records require less code to define and use, and they provide direct access to fields with methods for updating fields. This makes the code more reader-friendly, maintainable, and less error-prone.

Additionally, with the record syntax, we don’t have to do anything extra to make the class immutable. By default, all fields in a record are final, and the record class itself is immutable. Hence, creating an immutable class using the record syntax is much simpler and requires less code than the traditional approach. With records, we don’t have to declare the fields as final, provide a constructor that initializes the fields, or provide getters for all the fields.

Generic Record Classes

In Java, it is also possible to define generic record classes. A generic record class is a record class that has one or more type parameters. Here is an example:

public record Pair<T, U>(T first, U second) {}

In this example, Pair is a generic record class that takes two type parameters T and U. We can create an instance of this record as follows:

Pair<String, Integer> pair = new Pair<>("Hello", 123);

Nested Class Inside a Record

It is also possible to define nested classes and interfaces inside a record. This can be useful for grouping related classes and interfaces together, and can help to improve the organization and maintainability of a codebase.

Here is an example of a record that contains a nested class:

public record Person(String name, int age, Contact contact){

    public static class Contact {

       private final String email;
       private final String phone; 
    
       public Contact(String email, String phone){
           this.email = email;
           this.phone = phone;
       }

       public String getEmail(){
          return email;
       }

       public String getPhone(){
          return phone;
       }
    }
}

In this example, Person is a record that contains a nested class Contact. Contact is a static nested class that contains two private final fields email and phone. It also has a constructor that takes in email and phone, and two getter methods getEmail() and getPhone().

We can create an instance of Person as follows:

Person person = new Person("John",30, new Person.Contact("[email protected]", "123-456-7890"));

In this example, we created a new Person object with name “John”, age 30, and a new Contact object with email ‘[email protected]’, and phone ‘123-456-7890’.

Nested Interface Inside a Record

Here is an example of a record that contains a nested interface:

public record Book(String title, String author, Edition edition){
    public interface Edition{
       String getName();
   }
}

In this example, Book is a record that contains a nested interface Edition. Editionis an interface that defines a single method getName().

We can create an instance of Book as follows:

Book book = new Book("The Hitchhiker's Guide to the Galaxy", "Douglas Adams", new Book.Edition() {

   public String getName() {

      return "Science Fiction";
   }
});

In this example, we create a new Book object with title “The Hitchhiker’s Guide to the Galaxy”, author “Douglas Adams”, and a new anonymous implementation of the Edition interface that returns the name “Science Fiction” when the getName() method is called.

What else Records can do?

  • Records can define custom constructors. Records support parameterized constructors, which can invoke the default constructor with the provided parameters within their bodies. Additionally, Records also support compact constructors, which are similar to default constructors, but can include extra functionality such as checks within the constructor body.
  • Just like any other class in Java, Records can define and use instance methods. This means we can create and invoke methods specific to the record class.
  • In Java Records, defining instance variables as class members is not allowed, as they can only be specified as constructor parameters. However, Records do support static fields and static methods, which can be used to store and access data that is common to all instances of the record class.

FAQ

Can a record implement interfaces?

public interface Printable {
   void print();
}
public record Person(String name, int age) implements Printable {
   public void print() {
      System.out.println("Name: " + name + ", Age: " + age);
   }
}

In this example, we have defined an interface Printable that has a single method print(). We have also defined a record Person that implements the Printable interface. The Person record has two fields name and age, and overrides the print method of the Printable interface to print the values of these fields.

We can create an instance of the Person record and call its print method as follows:

Person person = new Person("John", 30); 
person.print();

This will output Name: John, Age: 30 to the console.

As shown in the example, records in Java can implement interfaces just like regular classes. This can be useful for adding behavior to a record or for ensuring that a record conforms to a specific contract defined by an interface.

 

References

https://docs.oracle.com/en/java/javase/14/language/records.html

https://www.oracle.com/java/technologies/javase/17-relnote-issues.html#NewFeature

Leave a Reply

Top