DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Migrating From Lombok to Records in Java
  • Simplifying Data Entities in Spring Data With Java Records
  • Java 21 Record and Pattern Matching: Master Data-Oriented Programming[Video]
  • Introduction to Apache Kafka With Spring

Trending

  • Unlocking AI Coding Assistants Part 1: Real-World Use Cases
  • Internal Developer Portals: Modern DevOps's Missing Piece
  • Develop a Reverse Proxy With Caching in Go
  • It’s Not About Control — It’s About Collaboration Between Architecture and Security
  1. DZone
  2. Coding
  3. Java
  4. A First Look at Records in Java 14

A First Look at Records in Java 14

Here's a first look at records in Java 14.

By 
Mahmoud Anouti user avatar
Mahmoud Anouti
·
Jan. 07, 20 · News
Likes (19)
Comment
Save
Tweet
Share
59.7K Views

Join the DZone community and get the full member experience.

Join For Free

Here's a first look at records in Java 14!


The upcoming release of Java will be version 14 scheduled to be in general availability in March 2020. Similar to the already released versions under the new 6-month release cycle, JDK 14 is expected to have several new features at both the language and JVM levels.

If we look at the feature list, however, we notice quite a few language features that are highly anticipated by developers: records, switch expressions (which exist in JDK 13 but in preview mode), and pattern matching. Let’s have a look at records that seems to be an interesting addition to the language.


You may also like: Introducing Java Record

Prerequisites

All that we’re going to need is the JDK 14 Early-Access binary from the OpenJDK website: https://jdk.java.net/14/.

What Is a Record?

A record is basically a “data class,” a special kind of class that is intended to hold pure data in it. The semantics of records already exist in similar constructs in other languages such as data classes in Kotlin. By declaring a type as a record, the developer is clearly expressing their intention that the type represents only data. The syntax for declaring a record is much simpler and concise, compared to using a normal class where you typically need to implement core Object methods like equals() and hashCode() (often referred to as “boilerplate” code). Records seem to be an interesting choice when modeling things like domain model classes (potentially to be persisted via ORM), or data transfer objects (DTOs).

A good way to think of how records are implemented in the language is to remember enums. An enum is also a class that has special semantics with a nicer syntax. Since both are still classes, many of the features available in classes are preserved, so there is a balance between simplicity and flexibility in their design.

Records are a preview language feature, which means that, although it is fully implemented, it is not yet standardized in the JDK and can only be used by activating a flag. Preview language features can be updated or even removed in future versions. Similar to switch expressions, it may become final and permanent in a future version.

A Record Example

Here’s an example of how a basic record looks like:

Java
 




x


 
1
package examples;
2
 
3
record Person (String firstName, String lastName) {}



We have a Person record defined in a package with two components: firstName and lastName, and an empty body.

Let’s try to compile it — notice the --enable-preview option:

Shell
 




xxxxxxxxxx
1


 
1
> javac --enable-preview --release 14 Person.java
2
Note: Person.java uses preview language features.
3
Note: Recompile with -Xlint:preview for details.



How Does it Look Under the Hood?

As mentioned previously, a record is just a class with the purpose of holding and exposing data. Let’s have a look at the generated bytecode with the javap tool:

Shell
 




xxxxxxxxxx
1


 
1
>javap -v -p Person.class


Plain Text
 




xxxxxxxxxx
1
172


 
1
Classfile examples/Person.class
2
  Last modified Dec 22, 2019; size 1273 bytes
3
  SHA-256 checksum 6f1b325121ca32a0b6127180eff29dcac4834f9c138c9613c526a4202fef972f
4
  Compiled from "Person.java"
5
final class examples.Person extends java.lang.Record
6
  minor version: 65535
7
  major version: 58
8
  flags: (0x0030) ACC_FINAL, ACC_SUPER
9
  this_class: #8                          // examples/Person
10
  super_class: #2                         // java/lang/Record
11
  interfaces: 0, fields: 2, methods: 6, attributes: 4
12
Constant pool:
13
   #1 = Methodref          #2.#3          // java/lang/Record."":()V
14
   #2 = Class              #4             // java/lang/Record
15
   #3 = NameAndType        #5:#6          // "":()V
16
   #4 = Utf8               java/lang/Record
17
   #5 = Utf8               
18
   #6 = Utf8               ()V
19
   #7 = Fieldref           #8.#9          // examples/Person.firstName:Ljava/lang/String;
20
   #8 = Class              #10            // examples/Person
21
   #9 = NameAndType        #11:#12        // firstName:Ljava/lang/String;
22
  #10 = Utf8               examples/Person
23
  #11 = Utf8               firstName
24
  #12 = Utf8               Ljava/lang/String;
25
  #13 = Fieldref           #8.#14         // examples/Person.lastName:Ljava/lang/String;
26
  #14 = NameAndType        #15:#12        // lastName:Ljava/lang/String;
27
  #15 = Utf8               lastName
28
  #16 = Fieldref           #8.#9          // examples/Person.firstName:Ljava/lang/String;
29
  #17 = Fieldref           #8.#14         // examples/Person.lastName:Ljava/lang/String;
30
  #18 = InvokeDynamic      #0:#19         // #0:toString:(Lexamples/Person;)Ljava/lang/String;
31
  #19 = NameAndType        #20:#21        // toString:(Lexamples/Person;)Ljava/lang/String;
32
  #20 = Utf8               toString
33
  #21 = Utf8               (Lexamples/Person;)Ljava/lang/String;
34
  #22 = InvokeDynamic      #0:#23         // #0:hashCode:(Lexamples/Person;)I
35
  #23 = NameAndType        #24:#25        // hashCode:(Lexamples/Person;)I
36
  #24 = Utf8               hashCode
37
  #25 = Utf8               (Lexamples/Person;)I
38
  #26 = InvokeDynamic      #0:#27         // #0:equals:(Lexamples/Person;Ljava/lang/Object;)Z
39
  #27 = NameAndType        #28:#29        // equals:(Lexamples/Person;Ljava/lang/Object;)Z
40
  #28 = Utf8               equals
41
  #29 = Utf8               (Lexamples/Person;Ljava/lang/Object;)Z
42
  #30 = Utf8               (Ljava/lang/String;Ljava/lang/String;)V
43
  #31 = Utf8               Code
44
  #32 = Utf8               LineNumberTable
45
  #33 = Utf8               MethodParameters
46
  #34 = Utf8               ()Ljava/lang/String;
47
  #35 = Utf8               ()I
48
  #36 = Utf8               (Ljava/lang/Object;)Z
49
  #37 = Utf8               SourceFile
50
  #38 = Utf8               Person.java
51
  #39 = Utf8               Record
52
  #40 = Utf8               BootstrapMethods
53
  #41 = MethodHandle       6:#42          // REF_invokeStatic java/lang/runtime/ObjectMethods.bootstrap:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;
54
  #42 = Methodref          #43.#44        // java/lang/runtime/ObjectMethods.bootstrap:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;
55
  #43 = Class              #45            // java/lang/runtime/ObjectMethods
56
  #44 = NameAndType        #46:#47        // bootstrap:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;
57
  #45 = Utf8               java/lang/runtime/ObjectMethods
58
  #46 = Utf8               bootstrap
59
  #47 = Utf8               (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;
60
  #48 = String             #49            // firstName;lastName
61
  #49 = Utf8               firstName;lastName
62
  #50 = MethodHandle       1:#7           // REF_getField examples/Person.firstName:Ljava/lang/String;
63
  #51 = MethodHandle       1:#13          // REF_getField examples/Person.lastName:Ljava/lang/String;
64
  #52 = Utf8               InnerClasses
65
  #53 = Class              #54            // java/lang/invoke/MethodHandles$Lookup
66
  #54 = Utf8               java/lang/invoke/MethodHandles$Lookup
67
  #55 = Class              #56            // java/lang/invoke/MethodHandles
68
  #56 = Utf8               java/lang/invoke/MethodHandles
69
  #57 = Utf8               Lookup
70
{
71
  private final java.lang.String firstName;
72
    descriptor: Ljava/lang/String;
73
    flags: (0x0012) ACC_PRIVATE, ACC_FINAL
74
 
75
  private final java.lang.String lastName;
76
    descriptor: Ljava/lang/String;
77
    flags: (0x0012) ACC_PRIVATE, ACC_FINAL
78
 
79
  public examples.Person(java.lang.String, java.lang.String);
80
    descriptor: (Ljava/lang/String;Ljava/lang/String;)V
81
    flags: (0x0001) ACC_PUBLIC
82
    Code:
83
      stack=2, locals=3, args_size=3
84
         0: aload_0
85
         1: invokespecial #1                  // Method java/lang/Record."":()V
86
         4: aload_0
87
         5: aload_1
88
         6: putfield      #7                  // Field firstName:Ljava/lang/String;
89
         9: aload_0
90
        10: aload_2
91
        11: putfield      #13                 // Field lastName:Ljava/lang/String;
92
        14: return
93
      LineNumberTable:
94
        line 3: 0
95
    MethodParameters:
96
      Name                           Flags
97
      firstName
98
      lastName
99
 
100
  public java.lang.String toString();
101
    descriptor: ()Ljava/lang/String;
102
    flags: (0x0001) ACC_PUBLIC
103
    Code:
104
      stack=1, locals=1, args_size=1
105
         0: aload_0
106
         1: invokedynamic #18,  0             // InvokeDynamic #0:toString:(Lexamples/Person;)Ljava/lang/String;
107
         6: areturn
108
      LineNumberTable:
109
        line 3: 0
110
 
111
  public final int hashCode();
112
    descriptor: ()I
113
    flags: (0x0011) ACC_PUBLIC, ACC_FINAL
114
    Code:
115
      stack=1, locals=1, args_size=1
116
         0: aload_0
117
         1: invokedynamic #22,  0             // InvokeDynamic #0:hashCode:(Lexamples/Person;)I
118
         6: ireturn
119
      LineNumberTable:
120
        line 3: 0
121
 
122
  public final boolean equals(java.lang.Object);
123
    descriptor: (Ljava/lang/Object;)Z
124
    flags: (0x0011) ACC_PUBLIC, ACC_FINAL
125
    Code:
126
      stack=2, locals=2, args_size=2
127
         0: aload_0
128
         1: aload_1
129
         2: invokedynamic #26,  0             // InvokeDynamic #0:equals:(Lexamples/Person;Ljava/lang/Object;)Z
130
         7: ireturn
131
      LineNumberTable:
132
        line 3: 0
133
 
134
  public java.lang.String firstName();
135
    descriptor: ()Ljava/lang/String;
136
    flags: (0x0001) ACC_PUBLIC
137
    Code:
138
      stack=1, locals=1, args_size=1
139
         0: aload_0
140
         1: getfield      #16                 // Field firstName:Ljava/lang/String;
141
         4: areturn
142
      LineNumberTable:
143
        line 3: 0
144
 
145
  public java.lang.String lastName();
146
    descriptor: ()Ljava/lang/String;
147
    flags: (0x0001) ACC_PUBLIC
148
    Code:
149
      stack=1, locals=1, args_size=1
150
         0: aload_0
151
         1: getfield      #17                 // Field lastName:Ljava/lang/String;
152
         4: areturn
153
      LineNumberTable:
154
        line 3: 0
155
}
156
SourceFile: "Person.java"
157
Record:
158
  java.lang.String firstName;
159
    descriptor: Ljava/lang/String;
160
 
161
  java.lang.String lastName;
162
    descriptor: Ljava/lang/String;
163
 
164
BootstrapMethods:
165
  0: #41 REF_invokeStatic java/lang/runtime/ObjectMethods.bootstrap:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;
166
    Method arguments:
167
      #8 examples/Person
168
      #48 firstName;lastName
169
      #50 REF_getField examples/Person.firstName:Ljava/lang/String;
170
      #51 REF_getField examples/Person.lastName:Ljava/lang/String;
171
InnerClasses:
172
  public static final #57= #53 of #55;    // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles



Interesting… Several things we can notice:

  1. The class is marked final, which means we cannot create a subclass of it.
  2. The class extends java.lang.Record, which is the base class for all records, much like java.lang.Enum is the base class for all enums.
  3. There are two private final fields named after the two components of the record: firstName and lastName.
  4. There is a public constructor that is generated for us: public examples.Person(java.lang.String, java.lang.String). By looking at its body, it’s easy to see that it just assigns the two arguments to the two fields. The constructor is equivalent to:
  5. Java
     




    xxxxxxxxxx
    1


     
    1
    public Person(String firstName, String lastName) {
    2
        this.firstName = firstName;
    3
        this.lastName = lastName;
    4
    }


  6. There are two getter methods named firstName() and lastName().
  7. Three other methods are generated: toString(), hashCode() and equals(). They all rely on invokedynamic to dynamically invoke the appropriate method containing the implicit implementation. There is a bootstrap method ObjectMethods.bootstrap that takes the component names of the record and its getter methods, and generates the methods. Their behaviors is consistent with what we would expect to have:
  8. Java
     




    xxxxxxxxxx
    1
    13


     
    1
    Person john = new Person("John", "Doe");
    2
    System.out.println(john.firstName());         // John
    3
    System.out.println(john.lastName());          // Doe
    4
    System.out.println(john);                     // Person[firstName=John, lastName=Doe]
    5
     
    6
    Person jane = new Person("Jane", "Dae");
    7
    Person johnCopy = new Person("John", "Doe");
    8
     
    9
    System.out.println(john.hashCode());          // 71819599
    10
    System.out.println(jane.hashCode());          // 71407578
    11
    System.out.println(johnCopy.hashCode());      // 71819599
    12
    System.out.println(john.equals(jane));        // false
    13
    System.out.println(john.equals(johnCopy));    // true



Adding Member Declarations in Records

We cannot add instance fields to records, which is expected, given that such state should be part of the components. We can, however, add static fields:

Java
 




xxxxxxxxxx
1


 
1
record Person (String firstName, String lastName) {
2
    static int x;
3
}



We can define static methods and instance methods that can operate on the state of the object:

Java
 




xxxxxxxxxx
1
11


 
1
record Person (String firstName, String lastName) {
2
    static int x;
3
 
4
    public static void doX() {
5
        x++;
6
    }
7
 
8
    public String getFullName() {
9
        return firstName + " " + lastName;
10
    }
11
}



We can also add constructors, and modify the canonical constructor (the one that takes the two String parameters). If we want to override the canonical constructor, we can omit the parameters and the assignments to the fields:

Java
 




xxxxxxxxxx
1
12


 
1
record Person (String firstName, String lastName) {
2
    public Person {
3
        if(firstName == null || lastName == null) {
4
            throw new IllegalArgumentException("firstName and lastName must not be null");
5
        // We can also omit assigning fields, the compiler will auto-add them
6
        }
7
    }
8
 
9
    public Person(String fullName) {
10
        this(fullName.split(" ")[0], fullName.split(" ")[1]);
11
    }
12
}



Conclusion

Records introduce the capability of properly implementing data classes, without the need to write verbose code. Plain data classes are reduced from several lines of code to a one-liner. There are other language features in progress that work well with records, such as pattern matching. For a much deeper dive into records and background information, see Brian Goetz’s exploratory document on OpenJDK.

Further Reading

Introducing Java Record

Record (computer science) Java (programming language)

Published at DZone with permission of Mahmoud Anouti, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Migrating From Lombok to Records in Java
  • Simplifying Data Entities in Spring Data With Java Records
  • Java 21 Record and Pattern Matching: Master Data-Oriented Programming[Video]
  • Introduction to Apache Kafka With Spring

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!