Menu
Coddy logo textTech

Records (Java 16+)

Part of the Object Oriented Programming section of Coddy's Java journey — lesson 51 of 87.

When creating simple data-carrying classes, you often write repetitive boilerplate code: private fields, a constructor, getters, toString(), equals(), and hashCode(). Java 16 introduced records to eliminate this tedium.

A record is a special class designed to hold immutable data. You declare it with the record keyword followed by the component list:

record Person(String name, int age) { }

// That single line automatically provides:
// - private final fields: name and age
// - a constructor: Person(String name, int age)
// - accessor methods: name() and age()
// - toString(), equals(), and hashCode()

Using a record is straightforward:

Person p = new Person("Alice", 30);
System.out.println(p.name());    // Alice
System.out.println(p.age());     // 30
System.out.println(p);           // Person[name=Alice, age=30]

Notice that accessor methods don't use the get prefix—they're simply named after the fields. Records are implicitly final, so they cannot be extended. All fields are final too, making records inherently immutable.

You can add custom methods and override the auto-generated ones if needed. You can also add validation in a compact constructor:

record Person(String name, int age) {
    public Person {
        if (age < 0) throw new IllegalArgumentException("Age cannot be negative");
    }
}

Records are perfect for DTOs, API responses, or any scenario where you need a simple, immutable data container without writing boilerplate code.

challenge icon

Challenge

Easy

Let's build a movie ticket booking system that showcases the elegance of Java records. You'll see how records eliminate boilerplate code while still allowing you to add custom methods and validation.

You'll organize your code across three files:

  • Movie.java: Create a record representing a movie with three components: title (String), genre (String), and durationMinutes (int). Add a custom method called getFormattedDuration() that returns the duration in a friendly format: [hours]h [minutes]m (for example, 142 minutes becomes 2h 22m). Use integer division and modulo to calculate hours and remaining minutes.
  • Ticket.java: Create a record representing a movie ticket with three components: movie (Movie), seatNumber (String), and price (double). Add a compact constructor that validates the price—if the price is less than or equal to zero, throw an IllegalArgumentException with the message Price must be positive. Also add a custom method called printTicket() that returns a multi-line string:
    ========== TICKET ==========
    Movie: [movie title]
    Genre: [movie genre]
    Duration: [formatted duration from movie]
    Seat: [seatNumber]
    Price: $[price]
    ============================
  • Main.java: Bring your booking system together! You'll receive five inputs: movie title (String), genre (String), duration in minutes (int), seat number (String), and ticket price (double).

    Create a Movie record with the first three inputs, then create a Ticket record with the movie, seat number, and price. Print the result of calling printTicket() on your ticket.

You will receive five inputs in order: movie title, genre, duration (minutes), seat number, and price.

Notice how much cleaner records are compared to traditional classes—no need to write constructors, getters, toString(), equals(), or hashCode(). The compact constructor lets you add validation without repeating field assignments, and you can still add custom methods when needed!

Cheat sheet

Java 16 introduced records to eliminate boilerplate code for simple data-carrying classes. A record is a special class designed to hold immutable data.

Declare a record with the record keyword followed by the component list:

record Person(String name, int age) { }

This automatically provides:

  • Private final fields
  • A constructor
  • Accessor methods (without get prefix)
  • toString(), equals(), and hashCode()

Using a record:

Person p = new Person("Alice", 30);
System.out.println(p.name());    // Alice
System.out.println(p.age());     // 30
System.out.println(p);           // Person[name=Alice, age=30]

Records are implicitly final and cannot be extended. All fields are final, making records inherently immutable.

Add validation using a compact constructor:

record Person(String name, int age) {
    public Person {
        if (age < 0) throw new IllegalArgumentException("Age cannot be negative");
    }
}

You can add custom methods to records:

record Person(String name, int age) {
    public String getInfo() {
        return name + " is " + age + " years old";
    }
}

Try it yourself

import java.util.Scanner;

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        // Read inputs
        String title = scanner.nextLine();
        String genre = scanner.nextLine();
        int durationMinutes = Integer.parseInt(scanner.nextLine());
        String seatNumber = scanner.nextLine();
        double price = Double.parseDouble(scanner.nextLine());
        
        // TODO: Create a Movie record with title, genre, and durationMinutes
        
        // TODO: Create a Ticket record with the movie, seatNumber, and price
        
        // TODO: Print the ticket using printTicket() method
        
    }
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming