Java Records

In JDK 14 was introduced a new type declaration called record which is a restricted form of class. Ideal for data carrying, containig only constructor and accessors methods, immutable.

Example record representation:

public record Player(
  String firstName,
  String lastName,
  Position position,
  short jersey
) { }

Accessors

Each field of this record is automatically private and final. At the same time each component of the record gets public accessor method with the same name and type.

In this case those are:

Player::firstName();
Player::lastName();
Player::position();
Player::jersey();

Constructors

Record has public constructor derived from it’s components.

var player = new Player("Json", "Hardcoded", Position.QB, (short) 3);

At the same time it’s possible to define additional constructors.

Like this one to ensure that the firstName and lastName are not null :

public record Player(
        String firstName,
        String lastName,
        Position position,
        short jersey) {
    public Player {
        Objects.requireNonNull(firstName);
        Objects.requireNonNull(lastName);
    }
}

Or separate constructor with different arguments:

public record Player(
        String firstName,
        String lastName,
        Position position,
        short jersey) {
    public Player(String firstName, String lastName) {
        this(firstName, lastName, null, Short.MIN_VALUE);
    }
}

Records constructors follow the same rules and requirements as regular Java classes.

Interfaces

Record can’t extend class but can implement interfaces.

public record Player(
        String firstName,
        String lastName,
        Position position,
        short jersey) implements Whatever, Nice {

    public static String FOO_BAR = "FooBar";
    public static Player quarterback(String sirstName, String lastName) {
        return new Player(sirstName, lastName, Position.QB, (short) 6);
    }
}

Equals & Hash Code

Additionally record get equals and hashCode generated.

Implementation of equals return true only when supplied object has the same type and all it’s fields match corresponding ones.

  @Test
  public void shouldBeEqual() {
    // given
    var player1 = new Player("Json", "Hardcoded", Position.QB, (short) 6);
    var player2 = new Player("Json", "Hardcoded", Position.QB, (short) 6);
    // when
    var actual = player1.equals(player2);
    // then
    assertTrue("Players must be equal", actual);
  }

Implementation of hashCode returns the same value for two records if all their fields match.

  @Test
  public void shouldHaveSameHashCode() {
    // given
    var player1 = new Player("Json", "Hardcoded", Position.QB, (short) 6);
    var player2 = new Player("Json", "Hardcoded", Position.QB, (short) 6);
    // when
    var player1Hash = player1.hashCode();
    var player2Hash = player2.hashCode();
    // then
    assertEquals("Players must have same hash code", player1Hash, player2Hash);
  }

To String

Another nice to have feature is pretty human readable toString implementation containig the field name and value pairs.

Player[firstName=Json, lastName=Hardcoded, position=QB, jersey=6]

Static

Static variables are declared in the same way as it’s done in class.

public record Player(
        String firstName,
        String lastName,
        Position position,
        short jersey) {
    public static String FOO_BAR = "FooBar";
}

Static methods can be declared in a similar way.

public record Player(
        String firstName,
        String lastName,
        Position position,
        short jersey) {
    public static Player quarterback(String sirstName, String lastName) {
        return new Player(sirstName, lastName, Position.QB, (short) 6);
    }
}

Those can then be used as following

var fooBar = Player.FOO_BAR;
var quarterbackPlayer = Player.quarterback("Json", "Hardcoded");

Restrictions

Conclusion

Another awesome addition to Java language tool belt, one more step towards reduction of boilerplate code, great alternative to old POJO used as DTOs.