[{"data":1,"prerenderedAt":125},["ShallowReactive",2],{"qa-\u002Fjava\u002Fmodern-java\u002Frecords":3},{"page":4,"siblings":93,"blog":122},{"id":5,"title":6,"body":7,"description":11,"difficulty":14,"extension":15,"framework":16,"frameworkSlug":17,"meta":18,"navigation":19,"order":20,"path":21,"questions":22,"questionsCount":84,"related":85,"seo":86,"seoDescription":87,"stem":88,"subtopic":6,"topic":89,"topicSlug":90,"updated":91,"__hash__":92},"qa\u002Fjava\u002Fmodern-java\u002Frecords.md","Records",{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",2,[],"medium","md","Java","java",{},true,1,"\u002Fjava\u002Fmodern-java\u002Frecords",[23,28,32,36,40,44,48,52,56,60,64,68,72,76,80],{"id":24,"difficulty":25,"q":26,"a":27},"what-is-a-record","easy","What is a record in Java and what problem does it solve?","A **record** (Java 16, JEP 395) is a special class declaration for\n**immutable data carriers**. It eliminates the boilerplate of writing\nconstructors, `equals()`, `hashCode()`, `toString()`, and accessor methods\nfor classes whose sole purpose is to hold data.\n\n```java\n\u002F\u002F Before records — ~30 lines of boilerplate:\npublic final class Point {\n    private final int x;\n    private final int y;\n    public Point(int x, int y) { this.x = x; this.y = y; }\n    public int x() { return x; }\n    public int y() { return y; }\n    @Override public boolean equals(Object o) { ... }\n    @Override public int hashCode() { ... }\n    @Override public String toString() { ... }\n}\n\n\u002F\u002F With a record — 1 line:\nrecord Point(int x, int y) {}\n```\n\n**Rule of thumb:** use a record whenever a class exists purely to group\nrelated data; keep domain logic in regular classes.\n",{"id":29,"difficulty":25,"q":30,"a":31},"record-components","What are record components and what does the compiler generate from them?","The identifiers in the record header are called **components**. For each\ncomponent the compiler automatically generates:\n\n- A `private final` field with the same name and type.\n- A **public accessor method** with the same name as the component (not\n  `getX()` — just `x()`).\n- A **canonical constructor** that accepts all components and assigns them.\n- `equals()`, `hashCode()`, and `toString()` based on all components.\n\n```java\nrecord Person(String name, int age) {}\n\nPerson p = new Person(\"Alice\", 30);\nSystem.out.println(p.name());     \u002F\u002F \"Alice\"  — accessor\nSystem.out.println(p.age());      \u002F\u002F 30\nSystem.out.println(p);            \u002F\u002F Person[name=Alice, age=30]\nPerson p2 = new Person(\"Alice\", 30);\nSystem.out.println(p.equals(p2)); \u002F\u002F true — all components equal\n```\n\n**Rule of thumb:** record accessors use the component name directly —\n`point.x()` not `point.getX()`.\n",{"id":33,"difficulty":25,"q":34,"a":35},"record-immutability","Are records immutable? What are the caveats?","Record fields are **`private final`**, so the reference cannot be\nreassigned after construction. However, if a component type is mutable\n(e.g., `List`, array), the object it refers to can still be mutated.\n\n```java\nrecord Scores(List\u003CInteger> values) {}\n\nScores s = new Scores(new ArrayList\u003C>(List.of(1, 2, 3)));\ns.values().add(99); \u002F\u002F legal — mutates the list inside the record\nSystem.out.println(s.values()); \u002F\u002F [1, 2, 3, 99]\n```\n\nFor true deep immutability, wrap mutable components with\n`Collections.unmodifiableList()` or copy them defensively in the\ncanonical constructor.\n\n**Rule of thumb:** records give shallow immutability; make components\ndeeply immutable yourself if needed.\n",{"id":37,"difficulty":14,"q":38,"a":39},"canonical-constructor","What is a canonical constructor and how can you customise it?","The **canonical constructor** has the same parameter list as the record\nheader. The compiler generates one automatically, but you can override it\nto add validation or normalisation:\n\n```java\nrecord Range(int min, int max) {\n    Range {                          \u002F\u002F compact canonical constructor\n        if (min > max)\n            throw new IllegalArgumentException(\"min > max\");\n        \u002F\u002F no need to write: this.min = min; this.max = max;\n        \u002F\u002F the compiler appends the assignments automatically\n    }\n}\n\nnew Range(1, 10); \u002F\u002F ok\nnew Range(10, 1); \u002F\u002F IllegalArgumentException\n```\n\nThe **compact constructor** (no parameter list, no `this.x = x` assignments)\nis the preferred style — the compiler appends the field assignments after\nyour body.\n\n**Rule of thumb:** use the compact canonical constructor for validation and\nnormalisation; let the compiler handle the field assignments.\n",{"id":41,"difficulty":14,"q":42,"a":43},"custom-constructor-in-record","Can a record have additional constructors beyond the canonical one?","Yes. A record can define **alternative constructors** that must delegate to\nthe canonical constructor as their first statement (like `this(...)`).\n\n```java\nrecord Point(double x, double y) {\n    Point() { this(0.0, 0.0); }              \u002F\u002F defaults to origin\n    Point(double x) { this(x, 0.0); }        \u002F\u002F y defaults to 0\n}\n\nPoint origin = new Point();   \u002F\u002F (0.0, 0.0)\nPoint onAxis = new Point(3.0); \u002F\u002F (3.0, 0.0)\n```\n\n**Rule of thumb:** alternative constructors must chain to the canonical\nconstructor — the canonical constructor is always the \"source of truth\" for\nfield assignment.\n",{"id":45,"difficulty":14,"q":46,"a":47},"record-vs-class","What are the restrictions that make records different from regular classes?","Records have several compiler-enforced restrictions:\n\n- **Implicitly `final`** — cannot be subclassed.\n- **Cannot extend** any class (implicitly extends `java.lang.Record`).\n- **Cannot declare instance fields** beyond the components (static fields are allowed).\n- Accessor, `equals`, `hashCode`, and `toString` can be overridden but must\n  stay consistent with component semantics.\n- **Cannot be abstract**.\n\n```java\nrecord Pair\u003CA, B>(A first, B second) {}  \u002F\u002F generic records: fine\n\u002F\u002F record MyRecord(...) extends SomeClass {} \u002F\u002F compile error\n\u002F\u002F class Child extends Pair {}              \u002F\u002F compile error — records are final\n```\n\n**Rule of thumb:** if you need inheritance or mutable state, use a regular\nclass; use a record only when the class is a pure data carrier.\n",{"id":49,"difficulty":14,"q":50,"a":51},"record-implements-interface","Can a record implement an interface?","Yes. Records can implement any number of interfaces, including providing\ncustom implementations of the interface's methods.\n\n```java\ninterface Printable { void print(); }\n\nrecord Invoice(String id, double amount) implements Printable {\n    @Override\n    public void print() {\n        System.out.printf(\"Invoice %s: $%.2f%n\", id, amount);\n    }\n}\n\nnew Invoice(\"INV-001\", 199.99).print(); \u002F\u002F Invoice INV-001: $199.99\n```\n\nRecords are commonly used with `Comparable`, `Serializable`, or custom\ndomain interfaces.\n\n**Rule of thumb:** records implement interfaces freely; they just can't\nextend a class.\n",{"id":53,"difficulty":14,"q":54,"a":55},"record-vs-lombok","How do records compare to Lombok's @Data or @Value?","| Aspect | Java Record | Lombok `@Value` \u002F `@Data` |\n|---|---|---|\n| Accessor style | `x()` | `getX()` (JavaBean convention) |\n| Immutability | Enforced by compiler | `@Value` enforces, `@Data` does not |\n| Inheritance | Cannot extend or be extended | Normal class inheritance |\n| Build tool dependency | None — JDK feature | Requires Lombok on classpath and annotation processor |\n| Customisation | Compact constructor | `@Builder`, `@NonNull`, etc. |\n\n```java\n\u002F\u002F Lombok @Value:\n@Value public class Point { int x; int y; }\npoint.getX(); \u002F\u002F JavaBean getter\n\n\u002F\u002F Java record:\nrecord Point(int x, int y) {}\npoint.x();    \u002F\u002F component accessor\n```\n\nRecords are the language-native solution; Lombok is more flexible for\nlegacy codebases that need `getX()` style or mutable builders.\n\n**Rule of thumb:** prefer records for new code on Java 16+; use Lombok when\nyou need `getX()` compatibility with frameworks like Jackson (before proper\nrecord support), or when you need mutable builders.\n",{"id":57,"difficulty":14,"q":58,"a":59},"records-with-jackson","How do you use records with Jackson for JSON serialisation?","Jackson supports records natively since **Jackson 2.12**. It serialises\nusing the component accessors and deserialises using the canonical\nconstructor. No extra annotations are needed for simple cases:\n\n```java\nrecord User(String name, int age) {}\n\nObjectMapper mapper = new ObjectMapper();\nString json = mapper.writeValueAsString(new User(\"Alice\", 30));\n\u002F\u002F {\"name\":\"Alice\",\"age\":30}\n\nUser u = mapper.readValue(json, User.class);\n\u002F\u002F User[name=Alice, age=30]\n```\n\nFor constructor parameter mapping on older Jackson versions or when\ncomponent names don't match JSON keys, add\n`@JsonProperty` on the compact constructor parameters.\n\n**Rule of thumb:** Jackson 2.12+ handles records out of the box; for\nolder versions add `jackson-module-parameter-names` and\n`-parameters` compiler flag.\n",{"id":61,"difficulty":14,"q":62,"a":63},"records-in-switch","How do records interact with pattern matching in switch expressions?","Records are first-class citizens of Java's **deconstruction patterns**\n(Java 21). You can destructure a record's components directly in a\n`switch` or `instanceof` pattern:\n\n```java\nsealed interface Shape permits Circle, Rectangle {}\nrecord Circle(double radius) implements Shape {}\nrecord Rectangle(double w, double h) implements Shape {}\n\ndouble area(Shape s) {\n    return switch (s) {\n        case Circle(double r)         -> Math.PI * r * r;\n        case Rectangle(double w, double h) -> w * h;\n    };\n}\n```\n\nThe components are bound as local variables in the case body without an\nexplicit cast or accessor call.\n\n**Rule of thumb:** records + sealed interfaces + switch pattern matching\nwork together as a modern alternative to the visitor pattern.\n",{"id":65,"difficulty":14,"q":66,"a":67},"record-serialisation","How do records behave with Java serialisation?","Records support Java serialisation natively when they implement\n`Serializable`. Serialisation is based on the **record components** rather\nthan the normal field-based mechanism, which provides better safety\nguarantees:\n\n- No `serialVersionUID` is required (though you can add one).\n- Deserialization always goes through the **canonical constructor**, so\n  validation in the compact constructor is enforced even during\n  deserialisation — unlike regular classes where deserialization bypasses\n  constructors.\n\n```java\nrecord Point(int x, int y) implements Serializable {}\n\n\u002F\u002F Serialise and deserialise:\nbyte[] bytes = serialize(new Point(1, 2));\nPoint p = (Point) deserialize(bytes); \u002F\u002F canonical constructor called\n```\n\n**Rule of thumb:** records are safer to serialise than regular classes\nbecause the canonical constructor (and its validation) is always called on\ndeserialisation.\n",{"id":69,"difficulty":14,"q":70,"a":71},"generic-records","Can records be generic?","Yes. Records support type parameters just like regular classes:\n\n```java\nrecord Pair\u003CA, B>(A first, B second) {}\nrecord Result\u003CT>(T value, String message) {}\n\nPair\u003CString, Integer> p = new Pair\u003C>(\"hello\", 42);\nSystem.out.println(p.first());   \u002F\u002F \"hello\"\nSystem.out.println(p.second());  \u002F\u002F 42\n\nResult\u003CList\u003CString>> r = new Result\u003C>(List.of(\"a\", \"b\"), \"ok\");\n```\n\nType bounds also work: `record Bounded\u003CT extends Comparable\u003CT>>(T value) {}`.\n\n**Rule of thumb:** generic records are a clean replacement for generic\ntuple or wrapper classes — concise and type-safe.\n",{"id":73,"difficulty":25,"q":74,"a":75},"record-static-members","Can records have static fields and methods?","Yes. Records can have **static fields**, **static methods**, and\n**instance methods** (beyond the generated accessors). They cannot have\nadditional non-static (instance) fields.\n\n```java\nrecord Temperature(double celsius) {\n    static final double ABSOLUTE_ZERO = -273.15;\n\n    static Temperature ofFahrenheit(double f) {\n        return new Temperature((f - 32) * 5.0 \u002F 9.0);\n    }\n\n    double toFahrenheit() {\n        return celsius * 9.0 \u002F 5.0 + 32;\n    }\n}\n\nTemperature t = Temperature.ofFahrenheit(98.6);\nSystem.out.println(t.celsius());      \u002F\u002F 37.0\nSystem.out.println(t.toFahrenheit()); \u002F\u002F 98.6\n```\n\n**Rule of thumb:** instance methods are fine in records as long as they\ndon't require mutable state — if you need mutable state, use a class.\n",{"id":77,"difficulty":25,"q":78,"a":79},"records-as-map-keys","Why are records good candidates for Map keys or Set elements?","The compiler-generated `equals()` and `hashCode()` are based on **all\ncomponents**, making records correct `Map` keys and `Set` elements\nimmediately without any manual implementation.\n\n```java\nrecord Point(int x, int y) {}\n\nMap\u003CPoint, String> labels = new HashMap\u003C>();\nlabels.put(new Point(0, 0), \"origin\");\nlabels.put(new Point(1, 0), \"unit-x\");\n\nSystem.out.println(labels.get(new Point(0, 0))); \u002F\u002F \"origin\"\n\u002F\u002F new Point(0,0).equals(new Point(0,0)) is true — hashCode matches\n```\n\nCompare with a regular class where you must manually implement\n`equals()`\u002F`hashCode()` or face silent bugs where equal-looking objects\nhash differently.\n\n**Rule of thumb:** records are safe `HashMap`\u002F`HashSet` keys out of the\nbox; regular classes need a careful `equals()`\u002F`hashCode()` override.\n",{"id":81,"difficulty":14,"q":82,"a":83},"record-local","Can records be declared locally inside a method?","Yes. **Local records** (Java 16+) can be declared inside a method, just\nlike local classes. They are useful as lightweight data holders scoped to\na single method.\n\n```java\nList\u003CString> topEmails(List\u003CUser> users) {\n    record Ranked(User user, int score) {}   \u002F\u002F local record\n\n    return users.stream()\n        .map(u -> new Ranked(u, computeScore(u)))\n        .sorted(Comparator.comparingInt(Ranked::score).reversed())\n        .limit(10)\n        .map(r -> r.user().email())\n        .toList();\n}\n```\n\nLocal records are implicitly `static` (they don't capture enclosing\ninstance state).\n\n**Rule of thumb:** local records are ideal for intermediate pipeline\nvalues that need a name but don't deserve a top-level class.\n",15,null,{"description":11},"Java records interview questions — record syntax, canonical constructor, compact constructor, immutability, records vs POJOs, records vs Lombok, generic records, records with interfaces, and limitations of records.","java\u002Fmodern-java\u002Frecords","Modern Java","modern-java","2026-06-20","LId2ywaOLWSB9Hn9ystYr978RLpW6H-v_KRdAGuKzGs",[94,95,98,102,106,110,114,118],{"subtopic":6,"path":21,"order":20},{"subtopic":96,"path":97,"order":12},"Sealed Classes","\u002Fjava\u002Fmodern-java\u002Fsealed-classes",{"subtopic":99,"path":100,"order":101},"Switch Pattern Matching","\u002Fjava\u002Fmodern-java\u002Fswitch-pattern-matching",3,{"subtopic":103,"path":104,"order":105},"Text Blocks","\u002Fjava\u002Fmodern-java\u002Ftext-blocks",4,{"subtopic":107,"path":108,"order":109},"instanceof Pattern Matching","\u002Fjava\u002Fmodern-java\u002Finstanceof-pattern-matching",5,{"subtopic":111,"path":112,"order":113},"Virtual Threads","\u002Fjava\u002Fmodern-java\u002Fvirtual-threads",6,{"subtopic":115,"path":116,"order":117},"Record Patterns","\u002Fjava\u002Fmodern-java\u002Frecord-patterns",7,{"subtopic":119,"path":120,"order":121},"Sequenced Collections","\u002Fjava\u002Fmodern-java\u002Fsequenced-collections",8,{"path":123,"title":124},"\u002Fblog\u002Fjava-records","Java Records Explained — Immutable Data Classes Without the Boilerplate",1782244117524]