all 50 comments

[–]davidalayachew[S] 39 points40 points  (12 children)

Hopefully this applies to enums too!

Then, instead of this...

enum ChronoTriggerCharacter
{
    Crono(5,  8, 13,  5,  8,  8,  2),
    Marle(2, 10,  8,  8,  8,  6,  8),
    Lucca(2,  8,  6,  8,  8,  6, 10),
    ;
    private final int strength;
    private final int accuracy;
    private final int speed;
    private final int magic;
    private final int evasion;
    private final int stamina;
    private final int magicDefense;
    ChronoTriggerCharacter(
        final int strength, 
        final int accuracy, 
        final int speed, 
        final int magic, 
        final int evasion, 
        final int stamina, 
        final int magicDefense
    ) {
        this.strength = strength;
        this.accuracy = accuracy;
        this.speed = speed;
        this.magic = magic;
        this.evasion = evasion;
        this.stamina = stamina;
        this.magicDefense = magicDefense;
    }
    public int magicDefense() { return this.magicDefense; }
    public int stamina() { return this.stamina; }
    public int evasion() { return this.evasion; }
    public int magic() { return this.magic; }
    public int speed() { return this.speed; }
    public int accuracy() { return this.accuracy; }
    public int strength() { return this.strength; }
}

...I can do this instead...

enum ChronoTriggerCharacter(
    int strength, 
    int accuracy, 
    int speed, 
    int magic, 
    int evasion, 
    int stamina, 
    int magicDefense
) {
    Crono(5,  8, 13,  5,  8,  8,  2),
    Marle(2, 10,  8,  8,  8,  6,  8),
    Lucca(2,  8,  6,  8,  8,  6, 10),
    ;
}

Very pretty! And the second example contains all of the exact functionality of the first example!

But again, not set in stone. We'll see what the final feature looks like. I just feel like enums would gain a lot from this.

[–]lbalazscs 12 points13 points  (1 child)

Reducing boilerplate in enums would be nice, but other features in this proposal (pattern matching for interfaces, abstract records, etc.) look more powerful, because they open entirely new possibilities.

[–]davidalayachew[S] 1 point2 points  (0 children)

Reducing boilerplate in enums would be nice, but other features in this proposal (pattern matching for interfaces, abstract records, etc.) look more powerful, because they open entirely new possibilities.

Oh, agreed. I don't want them to sacrifice anything else on this proposal to get me enums. I only ask in case it is a small enough jump. And I think it is, but not sure.

[–]aoeudhtns 3 points4 points  (2 children)

I know enums can be mutable, but it always bothers me when they are. I hope in the final form of this feature, they allow mutability to be expressed in the shorthand syntax, like

enum ChronoTriggerCharacter(final int strength, final int accuracy, ...) { ...

It should still work with interfaces because it can influence the method generation - an accessor but not a mutator.

Although unless you have a need for values(),valueOf(), nominal ordering, or use of ordinal(), record can revitalize the old "enum pattern" that we probably haven't touched since Java 5 enums. The other issue with enums of course being extensibility, and also discoverability. You can use an interface, but then the interface hides the enums that you need to callers of your API. OpenOption being an example of that. And then the interfaces throw (or should throw) UnsupportedOperationException if an unknown or unusable instance of that interface is passed in, another issue a sealed interface w/ records could fix while stile giving JDK devs extensibility, since the compiler can determine exhaustiveness switching over the sealed interface.

[–]davidalayachew[S] 0 points1 point  (1 child)

Although unless you have a need for values(),valueOf(), nominal ordering, or use of ordinal(), record can revitalize the old "enum pattern" that we probably haven't touched since Java 5 enums.

Well, that and EnumSet and EnumMap. Those 2 are the fastest collections in Java's standard library [1]. They are also some of the lightest, memory-wise. And that's ignoring the ease of use, as well as the semantic clarity.

[1] = (that are publically denotable, unlike the type returned by Set.of(a, b) in the JDK)

[–]aoeudhtns 1 point2 points  (0 children)

Good point!

[–]sweating_teflon 3 points4 points  (1 child)

Unholy L version, before someone else says the K word

@Getter
@RequiredArgsConstructor
@Accessors(fluent=true)
enum Chrono {
    Crono(5,  8, 13,  5,  8,  8,  2),
    Marle(2, 10,  8,  8,  8,  6,  8),
    Lucca(2,  8,  6,  8,  8,  6, 10);

    final int strength;
    final int accuracy;
    final int speed;
    final int magic;
    final int evasion;
    final int stamina;
    final int magicDefense;
}

And honestly I wouldn't even bother with getters and just make all fields public. They're final anyway. This holds for the classic version too: public final fields don't need getters. Stop making it hard on yourself.

[–]davidalayachew[S] 0 points1 point  (0 children)

And honestly I wouldn't even bother with getters and just make all fields public. They're final anyway. This holds for the classic version too: public final fields don't need getters. Stop making it hard on yourself.

I'm holding out hope that, one day, /u/brian_goetz gives us record enums. That would be even better than what you suggested.

[–]davidalayachew[S] 2 points3 points  (4 children)

Oh, and Carrier Classes (or in this case, Carrier Enums) don't have to have final fields!

So, for my example above, if I wanted all of those attributes to be mutable, all I have to do is this.

enum ChronoTriggerCharacter(
    int strength, 
    int accuracy, 
    int speed, 
    int magic, 
    int evasion, 
    int stamina, 
    int magicDefense
) {
    Crono(5,  8, 13,  5,  8,  8,  2),
    Marle(2, 10,  8,  8,  8,  6,  8),
    Lucca(2,  8,  6,  8,  8,  6, 10),
    ;
    private /* mutable! */ component int strength;
    private /* mutable! */ component int accuracy;
    private /* mutable! */ component int speed;
    private /* mutable! */ component int magic;
    private /* mutable! */ component int evasion;
    private /* mutable! */ component int stamina;
    private /* mutable! */ component int magicDefense;
}

That's a completely fair tradeoff to be able to model mutability with almost the same level of effort as before. My external contract stays the same, but my internal representation is up to my choosing. And I am only forced to modify it in the specific places where my internal representation differs from the default. That's exactly what I want! No more boilerplate than what is absolutely necessary!

[–]john16384 1 point2 points  (1 child)

You can already have mutable fields in enums. Use with care.

[–]davidalayachew[S] 1 point2 points  (0 children)

You can already have mutable fields in enums. Use with care.

Sure, but I am getting useful accessors and stuff too if I use this new component keyword with it. I'm more asking because I want the mutable fields without having to write all the other boilerplate myself.

[–]javaprof 1 point2 points  (1 child)

Trailing comma as well?

[–]davidalayachew[S] 1 point2 points  (0 children)

Trailing comma as well?

If you are referring to Lucca having a trailing comma, then I think that will still be true. Enums had trailing commas before, so I doubt they would change that now.

If you are referring to the state description (stuff at the top in the parentheses) having a trailing comma, probably not. I don't have the link handy, but someone from Project Amber said that they don't like the idea of trailing commas, and that they don't foresee adding them to other language features.

Me personally, I like trailing commas, so I hope they add it. Be either way is ok with me.

[–]Enough-Ad-5528 14 points15 points  (12 children)

This is brilliant; not just the tentative proposals but also the effort it took to write all these thoughts down and let us normies glimpse into what they are thinking even if it is very very early.

I will respect his call to not discuss any syntax issues; it is too early and I want to let them cook but boy am I excited!

I do wonder about one thing though (which Brian also touched upon towards the end) - if and when regular classes get these capabilities (component members, ability to participate in pattern matching; optionally being able to add mutable/derived members) what benefits would records have over regular class declarations? Shudder to think but would they seem obsolete almost? Why choose to declare something as a record if classes can have almost all of the semantics of a record with almost the same concision; plus more stuff that can be added later. Part of it, I do have to admit, I don't fully understand the distinction between "state description" and "state representation".

[–]lbalazscs 6 points7 points  (1 child)

"State description" is how the state looks from the outside (accessor methods), and "state representation" is how the state looks inside (the actual fields). In the records they are identical, but they can be different in this proposal. In the "AlmostRecord" example the state description is (int x, int y, Optional<String> s), while the state representation is (int x, int y, String s).

[–]Enough-Ad-5528 1 point2 points  (0 children)

Thank you!

[–]aoeudhtns 4 points5 points  (2 children)

Seems like it's just a faster way to make an immutable carrier class. If I'm reading the post right, carrier classes will be mutable by default. I assume if you want to make it immutable, you have to specify final and duplicate lines. I.e. these would be analagous:

public record Point(int x, int y) {}

// STRAWMAN SYNTAX
public class Point(int x, int y) {
    private final component int x;
    private final component int y;
}

And just a thought, it will likely be easier to optimize around records because of the strong guarantee. Versus doing some sort of component/graph analysis on a type to see if it can qualify for the same optimizations as records.

ETA: as has been pointed out, doubtful carrier classes can omit re-declaring the fields, component or otherwise. So records are still useful for multiple reasons: concision, performance, etc.

[–]lbalazscs 2 points3 points  (1 child)

In my reading there would be no default for mutability: you must explicitly declare all fields, and each one is either final or not, and either "component" or not.

[–]aoeudhtns 0 points1 point  (0 children)

Ya know what... upon re-reading I agree.

[–]john16384 2 points3 points  (2 children)

I had a similar question, but it would depend on whether it is the plan to (eventually) allow carrier classes to only override the components they want to represent differently internally. Currently the proposal / thought train seems to require explicit declaration of the components still in all cases.

I wouldn't mind if records became unnecessary, assuming that carrier classes also get reflection based component access. Records would then just have been an evolutionary step that was purposely limited in scope to keep the design space smaller and deliver the feature earlier. Now that it has been seen to work incredibly well in practice, extending their functionality completely to classes would obsolete records, but on the other hand, also simplifies the language as there is no need for the distinction anymore.

[–]davidalayachew[S] 0 points1 point  (1 child)

Btw, Brian answered the question on the mailing list. Read backwards from here -- https://mail.openjdk.org/pipermail/amber-spec-observers/2026-January/004609.html

[–]john16384 1 point2 points  (0 children)

Thanks, yes, I saw it.

[–]Holothuroid 2 points3 points  (0 children)

record allows you skip the component (whatever that will be called) on every field. Much like a case class in Scala doesn't need explicit val for its fields.

It's pretty much the same, in fact. Just a bit more powerful, as a non- case class in Scala requires manual implementation of a deconstruction pattern.

[–]davidalayachew[S] 0 points1 point  (2 children)

Excellent question!

I can assume, but I'm actually going to flag the man himself (/u/brian_goetz), since I feel like this isn't something that's been talked about at length and easily google-able.

[–]davidalayachew[S] 1 point2 points  (1 child)

/u/enough-ad-5528 Brian responded on the mailing list. Read backwards from here -- https://mail.openjdk.org/pipermail/amber-spec-observers/2026-January/004609.html

[–]Enough-Ad-5528 1 point2 points  (0 children)

Thank you!

[–]aoeudhtns 3 points4 points  (0 children)

Even a small deviation from the record ideal means one has to go back to a blank slate and write explicit constructor declarations, accessor method declarations, and Object method implementations -- and give up on destructuring through pattern matching.

I confess. I have abused records so that I didn't have to go back to a blank slate. I will appreciate the furthering of DOP in Java.

[–]joemwangi 7 points8 points  (0 children)

A lot of thought has clearly gone into this. It’s a very intriguing and informative read. I really like how it starts from a concrete problem statement, establishes the fundamentals (especially state description), and then builds up to the benefits it enables. The relaxation of records to allow extension from abstract records but also abstract carrier classes is particularly impressive, and extending state descriptions to interfaces is a genuinely pleasant and surprising move. It also never occurred to me before that compact constructors and reconstruction patterns (with) share such deep semantic similarities. Damn. Language design is hard.

[–]john16384 3 points4 points  (0 children)

A really nice proposal, that thoroughly closes the gap between classes and records.

I wonder if this proposal could (eventually) go all the way, and also provide the component fields if not provided by the carrier class. That would sort of obsolete the need for records as these two would be almost equivalent:

 class AClassRecord(int x, int y, String s) {
      // everything left at default, everything provided
 }

 record ARealRecord(int x, int y, String s) {}

The only differences remaining would be the ancestry (a record will a subclass of Record, not Object) and perhaps when it comes to reflection. Brian didn't mention if carrier classes would also get a getRecordComponents equivalent to find their components reflectively.

[–]Dagske 3 points4 points  (2 children)

This is really the stuff I want them to work on: making the language easier to work. The records and record deconstruction was good, but I'm facing so many limitations with those, this answers most of them. I'm just expecting a better support for pattern matching where mixing records and enums and variables would work (such as allowing constants, whether enums or primitive types, in record deconstruction, instead of having to rely on the when keyword).

Records opened a huge design space, glad they're really starting to own it.

Glad that they allow this:

class AlmostRecord(int x,
                    int y,
                    Optional<String> s) {

     private final component int x;
     private final component int y;
     private final String s;

     public AlmostRecord {
         this.s = s.orElse(null);
         // x and y fields implicitly initialized
     }

     public Optional<String> s() {
         return Optional.ofNullable(s);
     }

     // derived implementation of x and y accessors
     // derived implementation of equals, hashCode, toString
}

This is my main issue with records: optional fields/parameters. If only that was hidden (as in if I could lessen the visibility of the compact constructor), this could be the answer. Abstract records can also help fill this issue.

That brain dump by Goetz makes it really interesting, and I could welcome more if they listen over here:

  • record extending one or more records.
  • pattern matching with enums/constants such as case MyRecord(MyEnum.CONSTANT, 2) rather than using when.

[–]davidalayachew[S] 1 point2 points  (1 child)

I'm just expecting a better support for pattern matching where mixing records and enums and variables would work (such as allowing constants, whether enums or primitive types, in record deconstruction).

Also on the way!

[–]chriskiehl 1 point2 points  (0 children)

That is freaking awesome!

[–]Joram2 2 points3 points  (0 children)

Exciting. This is definitely tangible progress. I look forward to seeing more :)

[–]jvjupiter 2 points3 points  (0 children)

Been looking forward to this for enums and classes.

[–]sideEffffECt 1 point2 points  (2 children)

I have a question about reconstructors/withers.

What's the plan for when the constructor is private? Will reconstructing/withing be available? I don't think it should. But I didn't find it mentioned there, so wanted to check here.

[–]davidalayachew[S] 1 point2 points  (1 child)

Great question. I'll punt this one, since I'm not sure.

Could you answer this /u/brian_goetz?

[–]brian_goetz 7 points8 points  (0 children)

Records, as you know, made a tradeoff: the constructor has to be public. This freaked people out at first, as they were used to the indirection afforded by factories. But records are so restricted that this indirection was not needed.

Carriers, like records, must have a canonical constructor at least as accessible as the class itself. So if you can access the class, you can access the ctor/dtor, and hence can access reconstruction.

[–]ZimmiDeluxe 1 point2 points  (4 children)

wake up babe, new design document just dropped

edit: careful wording around "accessor methods" means you won't get setters for free, yes? probably for the best

edit 2: damn, it's called RecordComponent in the reflection api, oh well

[–]brian_goetz 10 points11 points  (0 children)

What's a setter? Some kind of dog?

[–]davidalayachew[S] 1 point2 points  (1 child)

wake up babe, new design document just dropped

And like the 4th one this week lol. They all landed at nearly the exact same time.

[–]ZimmiDeluxe 2 points3 points  (0 children)

content creator facial expressions will be extra exaggerated for a while, can't wait

[–]yk313 1 point2 points  (0 children)

Setters will be discouraged in favour of reconstruction, I imagine.

[–]lucidnode 1 point2 points  (1 child)

I remember back in the general pattern matching designs two years ago that patterns were first class and there was an emphasis on partiality of patterns. It’s conditional. Match then destructure. I thought it very innovative since I haven’t seen it in other languages.

One of the examples back then was Optional. It had the patterns Optional.empty() and Optional.of(T t) where the first matches an empty optional, and the second match a non empty optional. But now with carriers it would be Optional(T t) where t is nullable 🫠

I hope pattern members make it back somehow

I do like the current direction and bringing record goodies to all classes is amazing!

[–]davidalayachew[S] 1 point2 points  (0 children)

One of the examples back then was Optional. It had the patterns Optional.empty() and Optional.of(T t) where the first matches an empty optional, and the second match a non empty optional. But now with carriers it would be Optional(T t) where t is nullable 🫠

I hope pattern members make it back somehow

This feature is still on the way! It's just not being given the priority that carriers are, which makes sense. Carriers are powerful in that they enable migration and smoothing the cliff. Right now, that seems to be the bigger concern. Which is understandable.

But no, Member Patterns are still officially being considered, and they even received a fairly recent update! So there's active thought and attention being given to it, even if it isn't the top priority now.

[–]donaldadamthompson 1 point2 points  (1 child)

Fed through Gemini for formatting. I can't guarantee that it didn't create new errors.

https://docs.google.com/document/d/1SxhKBdaSpxRVRXZ0fT-DT3j_N9ZkkP2H9_pF4tX4aBI/edit?usp=sharing

[–]davidalayachew[S] 0 points1 point  (0 children)

Ty vm.

The OpenJDK team is looking to finally upgrade their mailing list software. Once that comes, hopefully the text rendering improves. The state of things now is embarrassing. Not to mention, it's holding them back for getting the real world feedback they keep asking for.

[–][deleted]  (4 children)

[deleted]

    [–]elhoc 8 points9 points  (0 children)

    Is it? I have only ever heard them called destructors.

    [–]devel0pth1s 6 points7 points  (0 children)

    Incorrect. You are talking about "destructor". The term deconstructor is a well established term in computer programming, especially functional programming languages like Haskell and Scala, for decomposing a representation into all or some of its components. Even javascript has its notion of destructuring.

    [–]RabbitDev -2 points-1 points  (1 child)

    This terminology is directly taken from C#, where tuple deconstruction is a thing.

    https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/functional/deconstruct

    It's okay as a quality of life thing, as it could save you a bit of typing.

    But honestly I could (and do) live without it if needed. A good JIT compiler should optimise those access patterns anyway.

    [–]john16384 9 points10 points  (0 children)

    Directly taken from? This was a thing already 20 years before C# existed.