Close

Java 16 - Records Features, Quick Walk-through

[Last Updated: May 19, 2021]

The following are quick features of Java Records:

Records cannot extend other classes

class Human {
}

public record Person(String name, String gender, int age) extends Human {
}
$ javac Person.java
Person.java:4: error: '{' expected
public record Person(String name, String gender, int age) extends Human {
^
1 error

Note that a record class already extends java.lang.Record (implicitly).

Records are final

Records are implicitly final and cannot be extended.

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

class Employee extends Person {
  public Employee(String name, String gender, int age) {
      super(name, gender, age);
  }
}
$ javac Person.java
Person.java:4: error: cannot inherit from final Person
class Employee extends Person {
^
1 error

Records can implement interfaces

import java.io.Serializable;
import java.util.Arrays;

public record Person(String name, String gender, int age) implements Serializable {

  public static void main(String[] args) {
      System.out.println("Interfaces implemented by Person");
      Arrays.stream(Person.class.getInterfaces()).forEach(System.out::println);
  }
}
$ javac Person.java

$ java Person
Interfaces implemented by Person
interface java.io.Serializable

Records cannot create extra instance fields

public record Person(String name, String gender, int age) {
  private int height;
}
$ javac Person.java
Person.java:3: error: field declaration must be static
private int height;
^
(consider replacing field with record component)
1 error

The components of a record are implicitly final

public record Person(String name) {

  public void changeName(String newName){
      this.name = newName;
  }
}
$ javac Person.java
Person.java:5: error: cannot assign a value to final variable name
this.name = newName;
^
1 error

Records can have static fields

public record Person(String name, String gender, int age) {
  private static int count = 10;

  public static void main(String[] args) {
      Person person = new Person("John", "Male", 34);
      System.out.println(person);
      System.out.println(Person.count);
  }
}
$ javac Person.java

$ java Person
Person[name=John, gender=Male, age=34]
10

The static methods, static fields, static initializers are allowed

public record Person() {
  static int count;
  static{
      count = 20;
  }

  public static void showCount(){
      System.out.println(count);
  }

  public static void main(String[] args) {
      Person.showCount();
  }
}
$ javac Person.java

$ java Person
20

We can declare constructors

If we want our record's constructor to do more than initializing its private fields, we can define a custom constructor for the record. However, unlike a class constructor, a record constructor doesn't have a formal parameter list; this is called a compact constructor.

The primary reasons to provide an explicit declaration of the constructor methods are to validate constructor arguments,

import java.util.Objects;

public record Person(String name, String gender, int age) {
  public Person {
      Objects.requireNonNull(name);
      Objects.requireNonNull(gender);
      if (age <= 0) {
          throw new IllegalArgumentException("Age must be greater than 0");
      }
  }

  public static void main(String[] args) {
      new Person("Tina", "female", 0);
  }
}
$ javac Person.java

$ java Person
Exception in thread "main" java.lang.IllegalArgumentException: Age must be greater than 0
at Person.<init>(Person.java:8)
at Person.main(Person.java:13)

We can add explicit accessors

We can also declare a getter which will replace the implicit getter:

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

  public int age() {
      return age < 0 ? 0 : age;
  }

  public static void main(String[] args) {
      Person person = new Person("Joe", "Male", -10);
      System.out.println(person.age());
  }
}
$ javac Person.java

$ java Person
0

Extra instance methods are allowed

public record Person(String name) {
  public void printName(){
      System.out.println(name);
  }

  public static void main(String[] args) {
      Person person = new Person("Joe");
      person.printName();
  }
}
$ javac Person.java

$ java Person
Joe

Nested records are allowed

You can declare records inside a class. The nested records are implicitly static.

import java.lang.reflect.Modifier;

public class MyClass {

    public record Person(String name) {
    }

    public static void main(String[] args) {
        Person person = new Person("Joe");
        System.out.println(person);
        Class<?> enclosingClass = Person.class.getEnclosingClass();
        System.out.println("Enclosing class: " + enclosingClass.getName());

        String modifier = Modifier.toString(Person.class.getModifiers());
        System.out.println("Person modifiers: " + modifier);
    }
}
$ javac MyClass.java

$ java MyClass
Person[name=Joe]
Enclosing class: MyClass
Person modifiers: public static final

Generic records are allowed

public record Line<N extends Number>(N length) {
    public static void main(String[] args) {
       Line<Double> line = new Line<>(5.3);
        System.out.println(line);
    }
}
$ javac Line.java

$ java Line
Line[length=5.3]

Records can be annotated

We can annotate a record. We can also annotate record's individual components .

import java.beans.JavaBean;
import java.lang.reflect.Field;
import java.util.Arrays;

@JavaBean
public record Person(@Deprecated String name) {

    public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException {

        System.out.println("-- Class annotations --");
        Arrays.stream(Person.class.getDeclaredAnnotations())
              .forEach(System.out::println);

        System.out.println("-- Fields annotations --");
        Arrays.stream(Person.class.getDeclaredFields())
              .peek(x -> System.out.println("Field: " + x))
              .map(Field::getDeclaredAnnotations)
              .forEach(annotations -> System.out.println("Annotations: " + Arrays.toString(annotations)));

        System.out.println("-- constructor annotation --");
        Arrays.stream(Person.class.getDeclaredConstructor(String.class).getParameterAnnotations())
              .forEach(annotations -> System.out.println(Arrays.toString(annotations)));
    }
}
$ javac Person.java

$ java Person
-- Class annotations --
@java.beans.JavaBean(defaultProperty="", description="", defaultEventSet="")
-- Fields annotations --
Field: private final java.lang.String Person.name
Annotations: [@java.lang.Deprecated(forRemoval=false, since="")]
-- constructor annotation --
[@java.lang.Deprecated(forRemoval=false, since="")]

Records cannot be abstract

public abstract record PersonPerson(String name) {
}
$ javac Person.java
Person.java:2: error: modifier abstract not allowed here
public abstract record Person(String name) {
^
1 error

See Also