How to use Records in Java

Java 14 came up with a new feature called Records to reduce boiler plate code.

One criticism against Java is that it takes too much code to achieve something compared to other languages like Python.

The recent features in Java have been primarily targeted to reduce its verbosity.

Records are one such feature.

Usually when developers create POJO classes just to act as data carriers , they need to manually create getters and setters for every field , and override equals(), hashCode() and toString() methods. Of course IDE’s can auto generate them and tools like lombok can generate them for you with the help of annotations.

Still Java itself didn’t provide a way to auto generate them.

Starting Java 14 , Records can take care of them.

Let’s say you want to create a Java domain object which represents a Shopping Order.

This is the traditional way to do it:

public class ShoppingOrder {

	private String item;

	private int quantity;

	private float price;

	public String getItem() {
		return item;
	}

	public void setItem(String item) {
		this.item = item;
	}

	public int getQuantity() {
		return quantity;
	}

	public void setQuantity(int quantity) {
		this.quantity = quantity;
	}

	public float getPrice() {
		return price;
	}

	public void setPrice(float price) {
		this.price = price;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((item == null) ? 0 : item.hashCode());
		result = prime * result + Float.floatToIntBits(price);
		result = prime * result + quantity;
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		ShoppingOrder other = (ShoppingOrder) obj;
		if (item == null) {
			if (other.item != null)
				return false;
		} else if (!item.equals(other.item))
			return false;
		if (Float.floatToIntBits(price) != Float.floatToIntBits(other.price))
			return false;
		if (quantity != other.quantity)
			return false;
		return true;
	}

	@Override
	public String toString() {
		return "ShoppingOrder [item=" + item + ", quantity=" + quantity + ", price=" + price + "]";
	}

}

Lots of code!

And to create a shopping order you can do :

	       ShoppingOrder order = new ShoppingOrder();
		
		order.setItem("Item A");
		order.setPrice(100.50f);
		order.setQuantity(100);
		

To create it in a single line you can define a constructor inside the class (additional code) :

public ShoppingOrder(String item,int quantity,float price) {
		
		this.item = item;
		
		this.quantity = quantity;
		
		this.price = price;
	}

And then create a new shopping order like this :

ShoppingOrder order = new ShoppingOrder("Item A",100,100.50f);

Using Records , we can reduce a lot of boiler code and represent a Shopping Order like this:

public record ShoppingOrderRecord(String name,int quantity,float price) {

}

And then create one the same way we did it earlier:

	ShoppingOrderRecord order = new ShoppingOrderRecord("Item A", 100, 100.50f);

That saves a lot of code.

Also records are immutable , you cannot change a record’s value once it is created . You can use it just as a data carrier.

Further , records can do additional functionalities like:

Compact Constructor

Do you want to validate a record’s values while initializing?

You can do it using compact constructor. A compact constructor is a constructor which takes no arguments and can be used to do validation on the initial values of its fields.

For example to make sure the quantity of a order is not zero or negative , we can add a compact constructor inside the record definition:

public record ShoppingOrderRecord(String name, int quantity, float price) {

	public ShoppingOrderRecord {

		if (quantity < 1) {

			throw new IllegalArgumentException("Quantity cannot be zero or negative");
		}
	}
}

Notice that unlike a regular constructor , the above constructor does not have a parenthesis () following its declaration and the quantity field is not referred to using ‘this’ keyword.

Now when you try to create a record with a negative or zero value it throws an exception:

public class Client {

	public static void main(String a[]) {
		
		//ShoppingOrder order = new ShoppingOrder("Item A",100,100.50f);
		
		ShoppingOrderRecord order = new ShoppingOrderRecord("Item A", -1, 100.50f);
		
		System.out.println(order);
	}
}
Exception in thread "main" java.lang.IllegalArgumentException: Quantity cannot be zero or negative
	at ShoppingOrderRecord.<init>(ShoppingOrderRecord.java:8)
	at Client.main(Client.java:8)

Providing default values:

If you want to provide default values to the fields in a record , you can do that using a static factory method inside the record :

Let’s add a default price value of 100 to any shopping record using a static factory method:

public static ShoppingOrderRecord of(String name,int quantity) {
		
		return new ShoppingOrderRecord(name, quantity, 100f);
	}

You can then create a new record like this:

ShoppingOrderRecord order = ShoppingOrderRecord.of("Item A", 100);

it takes a default price value of 100.

The same can be achieved by using an alternative constructor:

public ShoppingOrderRecord(String name, int quantity) {

		this(name, quantity, 100f);
	}

notice that the super constructor is invoked using this keyword and a default value of 100 is passed as a parameter to price.

A new Shopping Order can now be created as:

ShoppingOrderRecord order = new ShoppingOrderRecord("Item A", 100);

Here is the entire record code:

public record ShoppingOrderRecord(String name, int quantity, float price) {

	public ShoppingOrderRecord {

		if (quantity < 1) {

			throw new IllegalArgumentException("Quantity cannot be zero or negative");
		}
	}

	public static ShoppingOrderRecord of(String name, int quantity) {

		return new ShoppingOrderRecord(name, quantity, 100f);
	}

	public ShoppingOrderRecord(String name, int quantity) {

		this(name, quantity, 100f);
	}
}

Records are still a preview feature in Java 14 and to use them you need to enable preview feature.

Edit: Java 16 has been released and Records have now become a permanent feature of Java. You don’t need to follow the below steps if you are using Java 16

In eclipse IDE you can enable it checking an option in Java Compiler properties:

That’s it.


Posted

in

by

Comments

2 responses to “How to use Records in Java”

  1. […] Also Records can be used with Sealed Interfaces. ( Records are a new feature in Java 14 and you can know about it here) […]

Leave a Reply

Discover more from The Full Stack Developer

Subscribe now to keep reading and get access to the full archive.

Continue reading