Project Valhalla, Explained: How a Decade of Work Arrives in JDK 28

(jvm-weekly.com)

455 points | by philonoist 12 hours ago ago

263 comments

  • mattstir 5 hours ago ago

    > But the difference in memory is fundamental. The JVM can now store the values themselves in the array, laid out densely one after another: 8 bytes per point (plus a possible null flag), in a contiguous block. No headers per element. No pointers. No jumping around the heap.

    How much was this article proof-read? Didn't they just get finished talking about how heap flattening won't work for objects with > 64-bit representations? Their `Point` is at least 65 bits (two 32-bit ints plus the null flag). The "plus a possible null flag" and oddly short following statements seem to suggest this was some AI that got sidetracked by trying to make emphatic statements... oh and also the "[IMAGE: the same Point[] array in two variants..." block halfway down the page is unfortunate.

    • ericol 4 hours ago ago

      > No headers per element. No pointers. No jumping around the heap.

      that smells of AI [1], and thus lazy writing. I'm all in for using AI to help you write, but if you don't put your voice to it then there's no reason to read it.

      [1] https://en.wikipedia.org/wiki/Wikipedia:Signs_of_AI_writing#...

      • rayiner 37 minutes ago ago

        You see this in all the AI generated Tik Toks. What causes AI to use such a weird construction.

        • wongarsu 8 minutes ago ago

          I learned that exact style of writing in a marketing workshop, pre-AI. It's effective, satisfying, and a random third thing I can't be bothered to come up with right now.

          As a proportion of all easily crawled text on the internet, a lot of it will be random marketing copy. That influenced the writing style of early AIs, and since then everyone has trained on transcripts from every other AI chatbot

          • rayiner 7 minutes ago ago

            Oh my god did we inadvertently train AIs on idiotspeak.

        • ericol 25 minutes ago ago

          Wait until in 5 year's time all kids speak in rule of 3

      • jeberle 3 hours ago ago

        Also, the images were ruinously far off from what they intended to convey. Dude, just draw a picture by hand & take a picture of it.

      • einpoklum an hour ago ago

        > I'm all in for using AI to help you write

        Don't be all-in. It's important for humans to be able to write for themselves, and also to stand by what's been written in their name, which is much less likely if someone/something else has done the writing.

        (proofreading is another matter though.)

      • ameliaquining 3 hours ago ago

        That particular section is indeed AI, though other parts of the article aren't: https://www.pangram.com/history/89b28cd1-58e6-4065-9f1d-111a...

      • zeristor an hour ago ago

        I had that until I used Opus 4.8.

        It stopped being infuriatingly sloppy and took time to ensure the article had integrity.

        It did having said that I did burn through a lot of tokens trying to do a deep analysis cross data pipeline debug.

      • sgt an hour ago ago

        Exactly! But AI is here to stay. Sooner or later, even comments on HN will be 100% AI generated. And we won't be here to read them - our AI agents will. /s

    • smokel 3 hours ago ago

      18446744073709551616 possible values and you can't spare 1 for null? :)

      TIL that Rust has NonZeroU64 which you can combine with Optional to get the required behaviour with only 64 bits per entry. [1]

      [1] https://doc.rust-lang.org/std/num/type.NonZeroU64.html

      • Dylan16807 35 minutes ago ago

        Especially because signed integers aren't symmetrical. Reserve INT_MIN and you get 8 billion NaN values and multiplying by -1 always gives you a valid location.

    • dlopes7 4 hours ago ago

      The obviously used too much AI, I stopped after 2 paragraphs

      • tzs 3 hours ago ago

        The first two paragraphs are

        > On June 15, Oracle engineer Lois Foltan confirmed what a good chunk of the industry had stopped believing: JEP 401: Value Classes and Objects will be integrated into the main OpenJDK repository and is targeting JDK 28.

        > The change is so large that the remaining committers were asked to hold off on bigger commits during the integration. The pull request alone adds over 197 thousand lines of code across 1,816 files.

        What in those paragraphs is obviously AI?

        • yubblegum 2 hours ago ago

          These days I'm having second thoughts about pictures that I am pretty certain can't be AI but just have that look. It's strange but I've noticed it happening more and more.

        • Raphael_Amiard 28 minutes ago ago

          I mean I’m only answering that because you’re asking, nothing set me off personally there, but now that you ask:

          « The pull request alone adds over 197 thousand lines of code across 1,816 files. »

          I noticed that both Claude and GPT are fond of those kind of stupid accounting statements that don’t mean a lot in and of themselves, but look impressive in a « wow numbers » way. Which is kind of ironic since counting remains one of their weak points

      • 725686 2 hours ago ago

        I'm tired of people jumping in to point out "AI, AI". The article is very good and informative. That is the only important thing. Geez.

        • robotresearcher an hour ago ago

          No, it’s not the only important thing. The complaints about it should tell you that.

          People care about provenance a lot.

          Whether it’s a drawing my daughter did of her mother, a Picasso napkin sketch, a worn 1960s Stratocaster, or an blog essay, the provenance is value on top of the correctness of the item.

          • xp84 36 minutes ago ago

            Strange comparison, the Strat has sentimental and historical value. I don’t collect and cherish articles about Java.

            • robotresearcher 9 minutes ago ago

              Yep, I used an example where the provenance value was obviously large to make it very clear.

    • cogman10 4 hours ago ago

      > This is exactly the moment where non-nullability stops being cosmetics and becomes a lever for performance.

      Looks like they just missed the `!`. It should be `Point![]`.

    • Groxx 3 hours ago ago

      I'm confused about the 2008 Bloomberg article image in the first slot... right after implying the effort started in 2014. With nothing mentioning anything in there.

      Is there a way we can request a "flag as AI garbage" downvote for articles? Or should we just flag them?

  • rf15 11 hours ago ago

    I appreciate the hard work that went into the things that did make it into Valhalla eventually, but:

    > The model was powerful, but also mentally heavy

    No it isn't! it is this interpretation that kills off the null-safety debate entirely. Saying you have a variable that cannot be null is not a mentally taxing distinction, especially since everything is labelled thoroughly.

    > The team, faithful to the lesson “simplify the model for the user, even at the cost of the performance ceiling,” ultimately dismantled this dualism.

    but it would have simplified it for the user.

    The whole attitude and process around this and the other topics gives me very little faith that Java can be steered in a sensible direction here. The type system of a programming language is supposed to give convenient guarantees to the developer on a CPU that can only do numbers. There is no reason to reduce the optional(!) safety guarantees you can offer with the excuse of "too mentally taxing".

    Hell, they even get there half way by recognising:

    > the language model and the JVM model don’t have to overlap one hundred percent

    • andyjohnson0 10 hours ago ago

      > The whole attitude and process around this and the other topics gives me very little faith that Java can be steered in a sensible direction here.

      I agree. The stewardship of Java seems rather lacking - particularly when compared to that of .net, where MS etc. mostly seemed to make the correct decisions from the start.

      Does Java even have any value or mindshare at Oracle nowadays? The company seems to be a datacentre/compute business at this point, with appendiges for its legacy activities and a vast overhang of debt.

      I sometimes wonder if the only parts of Oracle that are still profitable are the Legal and Lawnmower divisions.

      • pron 8 hours ago ago

        First, your parent comment misunderstood what the section they were critiquing is referring to. It's not about nullability (which is orthogonal) but about reference/value projections.

        Now, as a member of the Java team (although I'm not directly involved in Valhalla), I'm obviously biased so let me just say that both designers and fans of programming language features would do well to remember two things:

        1. Opinions about features are almost never universal, even among experts, and almost each of them is about a tradeoff where different people prefer different sides. It is rare that some scientific study settles the issue.

        2. These preferences are often not evenly split. Even when both sides are equally confident that their preference is the right one, sometimes 80% or 90% of programmers share a preference. The people with the strongest opinions are more often than not in the minority, because most programmers don't think so much about the programming language (nor, I would say, should they).

        All of the language differences between .NET and Java fall in this "non-consensus" zone, and at least in one area I was deeply involved with, virtual thread, I can say that we thought that whatever we do we mustn't do what .NET did and that what they chose didn't work out well for them at all.

        • resonious 6 hours ago ago

          This is great advice and it applies to a lot more than just language features. Different architecture, deployment setups, QA approaches are all like this. It's always "approach A is no good", "but company X uses approach A and they're doing very well", "yeah but look at all of these problems they have". Maybe a fair argument but the approach B people also have their fair share of problems...

          • pron 14 minutes ago ago

            What's funny is that the only languages in the same popularity league as Java are Python and JS/TS (and possibly C and C++ if you want to extend things to more domains) yet I rarely ever hear people saying Java should be more like these languages. It's because many of the people who dislike Java also don't like any of the most popular languages (even those who like, say, Python better don't think Java should be more like Python).

            These people may point out that languages become more or less successful not because of the things these people care about but because of other factors. And they're right, but then the question is, shouldn't a smart product team focus more on the things that actually matter more to more people?

            Programming languages are tools, and so their value is not intrinsic, but comes from the value of the software they're used to create. Now, some people claim that Java's success is largely the result of it being one of the most hyped languages of the late 90s and early 00s, alongside VB, Delphi, FoxPro, and C#. But this claim doesn't stand up to even the slightest scrutiny.

          • throwaway2037 2 hours ago ago

            GUI frameworks have similar arguments around their design choices.

        • nixon_why69 6 hours ago ago

          Value types kind of definitively don't have null, right? You can have a zero int but not a null int. So nullability is not entirely orthogonal to value types, its an advantage for value types where they are practical.

          • pron 5 hours ago ago

            I didn't say nullability is orthogonal to value types; I said it was orthogonal to the two-projections world, which is what that text in the article was about rather than nullability.

            As to value types and null, I'm not sure about the current picture, but the general idea is that you declare what semantic properties you want - identity or not, nullable or not, tearable or not - and then the compiler picks the best technical in-memory representation for each use. For example, the compiler could choose not to flatten variables that could be null in the heap but to flatten them in the stack. That's the general idea, but I'm not sure about the details, some of which may yet change.

            More generally than just Java, nullability is often a property not of a type but of a variable. For example, in C, an int may not be null, but a pointer to an int may be. Now, in C, `int` and `int*` are two different types, but that's exactly a distinction that the original projection-spit design made and we wanted to avoid. But you could still end up with a variable that could hold either an integer or a null and another that may hold an integer but not a null, only this is separate from the reference/value projection, which combines both identity and nullability (in C, `int*` is not only nullable, but also has identity).

            • zoogeny an hour ago ago

              > More generally than just Java, nullability is often a property not of a type but of a variable.

              This is a tangent, but I'm not sure I follow this. Can you give an example to make this clear?

              • pron 3 minutes ago ago

                Yes, but it comes from Java having both runtime and compile-time types; it's harder to make the distinction in languages that don't have runtime types.

                In Java, you can ask, `x instanceof T` (and this is a runtime test), which means, is x one of the values in the set of values allowed by T. `null instanceof Integer` is false, even though a variable of type `Integer` can be assigned a null. So you can think of `Integer x` as being `Integer|null x`, i.e. x can hold a null, even though `null instancof Integer` is false.

            • ScoobleDoodle 4 hours ago ago

              In case you want to edit it back in: in the 3rd paragraph second sentence, the star in your int* got gobble up by formatting to italics.

          • gf000 6 hours ago ago

            You can have a null int, it's called Integer.

            What was taken away is the other, identity-having functionality of Integer and similar (e.g. no synchronization).

          • tsimionescu 6 hours ago ago

            This won't be true in Java, though - in Java, you will have null Integers at least. It seems that int will remain a different thing entirely from Integer, and will remain a JVM-only concept.

            • joe_mwangi 2 hours ago ago

              But with null-restricted types, Integer! and int has no difference semantically and representation. They plan to introduce null-restricted types in future.

        • ptx 3 hours ago ago

          What's wrong with what .NET did with threads? Having async tasks sharing the GUI thread seems like a nice feature. Will we be able to use virtual threads and structured concurrency with Swing, e.g. to wait for a background task in an event listener?

          • revetkn 3 hours ago ago

            Regarding "What's wrong with what .NET did with threads?", see https://cr.openjdk.org/~rpressler/loom/Loom-Proposal.html (relevant part below):

              An alternative solution to that of fibers to concurrency's simplicity vs. performance issue is known as async/await, and has been adopted by C# and Node.js, and will likely be adopted by standard JavaScript. Continuations and fibers dominate async/await in the sense that async/await is easily implemented with continuations (in fact, it can be implemented with a weak form of delimited continuations known as stackless continuations, that don't capture an entire call-stack but only the local context of a single subroutine), but not vice-versa.
            
              While implementing async/await is easier than full-blown continuations and fibers, that solution falls far too short of addressing the problem. While async/await makes code simpler and gives it the appearance of normal, sequential code, like asynchronous code it still requires significant changes to existing code, explicit support in libraries, and does not interoperate well with synchronous code. In other words, it does not solve what's known as the "colored function" problem.
            
            Regarding Swing, virtual threads are "just" threads so no reason they (and structured concurrency) can't be used.
      • PaulHoule 6 hours ago ago

        .NET made different decisions.

        I was at a conference on scientific programming in Java very early on that Geoff Fox put on up at Syracuse and we had a list of requests from Sun that they didn't give us but Microsoft gave many of them right away.

        On the other hand I really like Java's all-virtual approach to inheritance because the .NET model gives programmers more ways to screw up and get confused.

        Both languages slipped in generics after 1.0. Java used type erasure in a way that made it so a List<String> is really a List so generics could be retrofitted easily to existing code. .NET's implementation of generics let you do more but caused a rift in the ecosystem between generic and non-generic collections.

        I'd say long term Oracle's stewardship of Java has been very good. JDK 8 puts lambdas on your fingertips with a very fluent syntax that belies the idea that Java is terribly verbose. Since then Java has gotten steadily better release after release while maintaining great compatibility.

        I work with people who are conservative about updates because they are worried about breaking things but for the last few LTS releases I've said "it ought to be really easy, let's give it a try" and it is really easy and we get performance improvements we can feel.

        • rjrjrjrj 3 hours ago ago

          Java stagnated for quite a while. Seemed like everyone was stuck on Java 6 for about a decade. But JDK8 was a huge step forward. Lambdas, streams, and a date/time API that is the best I've seen.

        • to11mtm an hour ago ago

          I think DotNet had a bit of benefit, in that the language was still new enough to do the hard breakage. It was only about 3.5 years between NET1.0 and 2.0 (Where generics were added.)

      • gf000 10 hours ago ago

        > The stewardship of Java seems rather lacking

        In what way? If anything Java's main developers (employed by Oracle for the most part, working on the completely open source and free OpenJDK) are extremely knowledgeable and are responsible a big jump in how fast the platform evolves. They have added proper algebraic data types to the language, delivered virtual threads and garbage collectors that decouple pause times from heap size. Like if anything, Java is at the best place it has ever been.

        • lmm 10 hours ago ago

          > They have added proper algebraic data types to the language

          No they haven't. E.g. they added a class that superficially looks like Option but subtly breaks the rules that Option is meant to follow, ensuring that no-one can ever manage to migrate existing codebases away from using `null`.

          • gf000 10 hours ago ago

            Sealed classes/interfaces and records are proper sum and product types.

            The stdlib's Option type predates this language update by a long shot, so it doesn't use sealed classes, but it is now possible to have the usual FP "Maybe" type in Java:

            ``` sealed class Maybe<T> permits Some, None { record Some<T>(T obj) {} record None() {} } ```

            (You will probably have to write Maybe.Some and I might have messed up the generic syntax as I wrote it on my phone, but that's mostly how it looks)

            • ahoka 8 hours ago ago

              Or just do as Kotlin and embrace null, but in a type aafe way.

              • gf000 8 hours ago ago

                "Funnily", having nullable types be practically `T | Null` gives you union types, not sum types (the latter is, importantly are a disjunct union!)

                The main difference is that (T | Null) | Null = T | Null, while Maybe<Maybe<T>> is different from Maybe<T>

                • ahoka 5 hours ago ago

                  Your point being?

            • tsimionescu 5 hours ago ago

              Except this is completely wrong.

              First, a record can't extend anything, it's not even valid syntax, so a sealed class can't permit record subclasses. So no, it's not possible to create a Maybe<T> class in Java that can only represent a Some<T> or a None<T> record. You could do it with regular classes, or if it's ok for Maybe<T> to be an interface.

              Secondly, regardless of the sealing, nothing in any current or near future of Java prevents you from assigning `null` to any class of any kind you might create. So you can always have `Maybe<T> x = null`, or even `Some<T> x = null`.

              None of this will change with the adoption of value classes either. So no, there is absolutely no way in Java to create a real Optional/Maybe type that would guarantee that a variable is either an object of a given type or None. There is probably some way to do it for your specific project using annotation processors, of course, but that is very different from having built-in support.

              • munksbeer 3 hours ago ago

                It's probably a typo. He meant sealed interface. I think that should be quite obvious.

              • gf000 4 hours ago ago

                Mistyped, it's sealed interface.

                > So you can always have `Maybe<T> x = null`, or even `Some<T> x = null`.

                Yeah and? Practically every type system have escape hatches, like Haskell can also do side effects without the IO monad, does it make the latter useless?

                • tsimionescu 4 hours ago ago

                  The whole point of using Optional/Maybe is to prevent the possibility of accidemtally creating nulls. If you don't make mistakes, then nullability is not a problem. If you do make mistakes, then a class that only helps when you don't make mistakes is basically useless.

                  This also has significant impact for serialization/de serialization - a classic place where you get unexpected nulls, that Java Optional/Maybe don't help with at all.

                  • joe_mwangi 2 hours ago ago

                    Since they plan to have null-restricted types, then I don't see any issue.

                    • Dylan16807 27 minutes ago ago

                      > plan to

                      Well wasn't that the argument above, that the stuff they added so far isn't proper at least in part because they didn't fix that problem yet?

          • _kidlike 10 hours ago ago

            optional is not how algebraic data types are implemented in Java. Basically it's the combination of sealed types and records.

      • pjmlp 6 hours ago ago

        Given the mess of some .NET frameworks currently, and how bad it has taken for non nullable references to be widely adopted, I don't see those correct decisions on the last releases.

        It is all about having AI on the framework, Aspire, multiple Web and Desktop frameworks all over the landscape.

        Those interceptors and inline arrays via attributes instead of proper language grammar aren't that great either.

        • drdexebtjl 2 hours ago ago

          >Those interceptors and inline arrays via attributes instead of proper language grammar aren't that great either.

          Yeah. Even when they add new grammar nowadays, it's always just something that trivially sugars away into previous grammar (see: records, `with` clones, extension properties, required, etc).

          The moment they need something that it's slightly more complex... Out of scope. Even when it's completely necessary for the thing to be useful in practice.

          For example, they added `required`, `record`s and property initializers, giving us good reasons to write `new Foo { A = a, B = b }` instead of `new Foo(a, b)`. A and B must be positive, so you'd write:

            public required int A { get; init => field = value > 0 ? value : throw ... ; }
            public required int B { get; init => field = value > 0 ? value : throw ... ; }
          
          This is pretty standard C# code that you might see in an example for records.

          But then the requirements change: A and B must be positive, or they must both be zero at the same time.

          This cannot be expressed at all with initializers. You simply cannot add code that runs after all initializers are called. You're stuck chasing every single initialization of Foo and using a constructor or factory method instead. Shipped it as a public API? Too bad. Should have seen it coming!

          The new features are filled with this sort of thing. As if Microsoft never used them beyond the most basic examples. Or maybe they did, and explicitly chose not to fix it and solve later.

          • pjmlp an hour ago ago

            I have this idea, that since they went open source, but failed to gain the adoption on UNIX shops they were expecting, there are tons of features to try to make it cool again somehow.

            The reality, and I can see this on my bubble, is that the .NET shops are mostly former Microsoft shops now saving Windows licenses by deploying on Linux.

            Stuff like MAUI remains pretty much constrained to former Xamarin customers.

            Thus minimal APIs, aspire, Blazor, and whatever comes up to support those use cases first.

            There are some podcast interviews from David Fowler and Maddy Montaquila where they touch the adoption issue among newer generations.

      • rf15 10 hours ago ago

        How .net got so many things right where java did not is a mystery to me, but appreciated (it has its own flaws, of course). Java, in my understanding, is still of core relevance to Oracle, and tied into a lot of contracts that require very little effort from them to maintain. But you are correct in observing that they want to be a datacentre/compute business more and more these days; they may have in fact overcomitted to this due to the AI craze, since shareholders are already complaining.

        • Someone 8 hours ago ago

          > How .net got so many things right where java did not is a mystery to me

          Part of the reason for that is that Java is older. https://en.wikipedia.org/wiki/C_Sharp_(programming_language)...:

          “In interviews and technical papers, he has stated that flaws in most major programming languages (e.g. C++, Java, Delphi, and Smalltalk) drove the fundamentals of the Common Language Runtime (CLR), which, in turn, drove the design of the C# language.”

          Also, some of Java’s design warts may be there because Java was initially envisioned for much smaller devices.

          • toyg 7 hours ago ago

            This. C# was basically always meant to be "Java but done right". It came several years later, after Microsoft was legally barred from "EEE"-ing Java and required a direct competitor.

            • newsoftheday 4 hours ago ago

              > It came several years later, after Microsoft was legally barred

              That is an eloquent way of re-writing the history of Microsoft stealing Java and not being allowed to get away with it.

              • toyg 3 hours ago ago

                They didn't "steal" anything, iirc; they started as a legitimate licensee and then tried their usual embrace/extend/extinguish as "J++" (the EEE I mentioned). Sun sued for breach of license and won, barring Microsoft from extending Java outside of the (Sun-controlled) process. So they dropped it and built their own version, with blackjack and hookers.

                • dingi 2 hours ago ago

                  > So they dropped it and built their own version, with blackjack and hookers.

                  This cracked me up

            • cm2187 7 hours ago ago

              But what I don’t get reading the original article is that they present how to insert struct in an object oriented language as an intractable problem, whereas a good implementation with .net (as far as I can tell) has been out there for nearly 30 years. And C# was shameless about stealing from other languages.

              • Someone 6 hours ago ago

                > how to insert struct in an object oriented language as an intractable problem, whereas a good implementation with .net (as far as I can tell) has been out there for nearly 30 years. And C# was shameless about stealing from other languages.

                I think (but may be wrong) their concerns are about the insert part. C# always had structs, Java wants to add them in a backward-compatible way. They want, for example, existing generic container classes pulled in from a .jar (i.e. already compiled) to support Java value types.

              • pjmlp 6 hours ago ago

                The problem is how to do it without breaking ABI, 30 years of Maven Central is very relevant, Java isn't doing a Python over value types.

                • cm2187 6 hours ago ago

                  But if you define a new type, how is that breaking backward compatibility?

                  • pjmlp 5 hours ago ago

                    Because that is missing the point.

                    All the types that are value types in semantics, e.g. Optional, should be proper value types on Valhalla.

                    Additionally, they should be compatible with existing code that expects them as parameters, fields,.... without being recompiled from source.

                    If it is a complete new type without backwards compatibility, no one is going to adopt it, other than a few niche cases.

              • Zardoz84 5 hours ago ago

                Dlang this this before. You have classes and struts, with different semantics.

            • pjmlp 6 hours ago ago

              Ironically, they still do need Java for Azure, https://devblogs.microsoft.com/java

        • jbellis 9 hours ago ago
          • pjmlp 6 hours ago ago

            Which recently decided that Go was a better option than C# for the Typescript rewrite, exactly because not all decisions were done correctly to make C# a better fit for the problem.

            • Rohansi 4 hours ago ago

              Go was chosen mainly because it aligned more with how the existing compiler is designed. They did not want to redesign the compiler which eliminated C# as a choice. So Go is apparently just a better fit for quickly porting JavaScript code to.

              • pjmlp 3 hours ago ago

                That was the original motivation yes, although they acknowledged later that the weaker type system from Go required redesigning the data structures anyway.

                And as proven in the recent announcement, they had to rewrite parcel from C++ into Go, as they didn't found a comparable library in Go ecosystem.

                There is also another interview, where again they mention having used AI as tool for code rewriting as well.

                Also to note that it was pointed out that Native AOT wasn't up to the job, again something that both Java and C# failed not having done it properly from day one.

                • Rohansi 40 minutes ago ago

                  They said the prototyped in a few languages before settling on Go. Based on what you said it sounds like they didn't do a great job at that and stuck with their decision anyway.

                  > Also to note that it was pointed out that Native AOT wasn't up to the job, again something that both Java and C# failed not having done it properly from day one.

                  It's been working fine for a few years now. The only problem I know is there is little to no reflection allowed (by design) so a lot of code out there is not compatible with it yet. Not sure if that's what turned the TypeScript team away from it.

        • amitport 9 hours ago ago

          The mystery of why .NET got so many things right is simply that C# was built several years later by the exact same Microsoft engineers who had previously worked on extending Java, giving them a perfect blank slate to fix the architectural flaws they had already encountered

          Second mover advantage.

          • ah1508 4 hours ago ago

            virtual thread instead of async/await is a counter example.

            Java is more used than C#, they can wait before delivering a new feature (given their leader position) but cannot deliver a flawed implementation that would stay in the language forever. Glad to have virtual threads and the backward compatibility that comes with it instead a Async version of sync methods + async and await keywords all over the code and Task as a return type in my interfaces methods to allow implementations to do non blocking I/O calls if they need.

            I use Java and C# and appreciate them both.

            • amitport 3 hours ago ago

              C# did not ship with async/await, and Java didn't have virtual threads back then. I am specifically referring to the initial choices made in C#'s foundation.

          • lyu07282 2 hours ago ago

            > giving them a perfect blank slate to fix the architectural flaws they had already encountered

            and then they make everything nullable by default in c#...

      • drzaiusx11 5 hours ago ago

        I'm honestly happy with java lang's stewardship over the past decade, this particular JEP notwithstanding (it's fine, but the good parts come later.) They're conservative in adopting new features whereas I see every other language bolting on everything under the sun with reckless abandon. I prefer the "let's see what shakes out" and adopt "the good parts" which seems to be Java's approach. Sugar like "var" from kotlin, project loom event loop like nodes, etc.

        • Zardoz84 5 hours ago ago

          Type inference was on DLang far before that Kotlin even existed. The only difference it's that reuses "auto" keyword.

          • gf000 4 hours ago ago

            I mean, type inference goes decades before Dlang.

      • dingi 3 hours ago ago

        As someone who works with Java on a daily basis alongside a dozen other technology stacks, let me go out on a limb and say that I believe Oracle has been a stellar steward of the language. Java has been evolving quite nicely and at a reasonable pace, all without breaking the ecosystem or causing fragmentation. It certainly has its drawbacks, but doesn't everything?

      • pfannl 6 hours ago ago

        C# often feels like Java with hindsight; Java feels like Java with 30 years of backward compatibility debt.

        • pjmlp 6 hours ago ago

          Hence why so many .NET projects keep being .NET Framework instead having migrated to modern .NET.

      • watwut 10 hours ago ago

        > particularly when compared to that of .net, where MS etc. mostly seemed to make the correct decisions from the start.

        Wut? I did worked on .net projects and all it achieved was making me like java a lot more then previously.

        • andai 8 hours ago ago

          I had the opposite experience, spent a year with each language, first Java then C#, and to me C# felt like "Java done right". (Which appeared to be the original design goal behind the language!) So I'm curious about your experience.

          To me it felt a bit less like a religion and more like a language. It didn't force me to do things a particular way, quite as much. (Still more than I would have liked, though! After all, it's called that[0] for a reason :)

          [0] https://www.reddit.com/r/ProgrammerHumor/comments/ddc4b0/mic...

        • ivolimmen 6 hours ago ago

          Same for me. I have worked with Java since 1.2.2 and used .NET for something like 10 years (don't remember the versions). Most important differences are:

            -Java always has an API, .NET is about extending an existing application (Servlet API vs IIS)
            -Java has a nicer IO as .NET has bidirectional streams (You can't wrap streams in .NET).
            -Linq is nice but has a huge caveat: if a Linq provider does not implement it fully to falls back to the .NET collections. So trying to 'Skip' and 'Take' on a ActiveDirectory will fall back to collections in memory and cause a crash on a huge AD in production (Yes had the pleasure).
            -Java's Eco-system is way bigger.
          • jaen 5 hours ago ago

            > -Linq is nice but has a huge caveat: if a Linq provider does not implement it fully to falls back to the .NET collections. So trying to 'Skip' and 'Take' on a ActiveDirectory will fall back to collections in memory and cause a crash on a huge AD in production (Yes had the pleasure).

            How do you expect this to work then? If the provider is bad, blaming LINQ for it makes no sense...

            You either have a high level of abstraction and possible performance pitfalls - or a low level of abstraction, and also performance pitfalls since the code is less modular, more coupled and harder to read.

            LINQ can in many cases improve performance significantly in large applications when used properly, since it avoids N+1 query problems due to implementation hiding/modularity, and allows composing parts of queries across different vertical subsystems of the application (vs. each subsystem doing its own query and then joining them with more boilerplate).

            Nothing in Java compares to this. jOOQ and Hibernate (and the rest in the ORM ecosystem) are pale shadows, exactly due to lacking language features (such as reified expression trees), and even then, they only work with databases.

          • Rohansi 4 hours ago ago

            > .NET is about extending an existing application (Servlet API vs IIS)

            I don't think this is true anymore since ASP.NET Core. While you can still run under IIS but it's a more typical reverse proxy setup instead of running inside IIS.

            > You can't wrap streams in .NET

            You've always been able to wrap streams in .NET so I'm not sure what you mean by this

        • peterashford 5 hours ago ago

          Yeah, me too. Java always seemed to consider design a lot more than C# which seems to have taken more of a kitchen sink approach to language design. That stuff piles up over time (see c++)

        • newsoftheday 4 hours ago ago

          Agreed. I jumped on the .NET bandwagon in 2000 and was on it for several years but ended up going back to Java by 2005.

        • bazoom42 7 hours ago ago

          What do you like about Java compared to C#?

          • watwut 7 hours ago ago

            First, huge open source ecosystem and culture. Mature open source projects, culture of writing blogs and tutorials (that one will die due to changes in search engines, but it was super nice while it lasted).

            Second, working in C# felt clunky, as if every other thing was done to check the checkbox "done" and the author called it the day once it sorta kinda worked. There was some additional syntactic sugar in that language that was nice, but it did not made that much difference in practice and I don't miss it after coming back to java.

            Third, I found the obsession with bashing java by people who have no idea how java projects look like and which problems they have annoying.

    • _kidlike 10 hours ago ago

      so your complaint is about the blogger, not the Java language?

      also, null markers are coming too: https://openjdk.org/jeps/8303099

      Its just that they have to deliver things incrementally. This PR that introduces value classes/objects is already 200k lines long.

      • rf15 5 hours ago ago

        I agree, but I have seen the previous proposals/jeps and the discourse around them is rather discouraging. I hope this one can find it's way out of Draft, but I'll only believe it when I see it.

    • drzaiusx11 6 hours ago ago

      They just decided to tackle non-nullable value types in a follow-on JEP. I don't think they're saying it's untenable. You don't eat the elephant in one bite and all that.

      That said, we've been gnawing on this limb for a while...

      • arein3 5 hours ago ago

        Ill be old when null safety will finally arrive

        This takes longer than game of thrones books

    • 21asdffdsa12 9 hours ago ago

      Nullable is just a different loadout state in Railway Orientated Programming. So, no reason to put different flavours of state into the language directly, when its a solved thing since (checks slides) 2012. There is just rails - going to A or going to B, depending on the trains loadout.

      If you have language-wars about a concept going in and out of existence, that is a hint that there is demand and the language does not properly handle the demand or when it handles it, it creates mental overload.

      > Value

      > Errorstates

        > Null
      
        > IoExceptions
      
        > WeirdOsStatesNeededToHandleUpstairs
      
      https://fsharpforfunandprofit.com/rop/

      As the pythons said: Get on with it!

    • pron 8 hours ago ago

      > it is this interpretation that kills off the null-safety debate entirely. Saying you have a variable that cannot be null is not a mentally taxing distinction, especially since everything is labelled thoroughly.

      I think you've missed what this is referring to. It isn't about null safety (which is orthogonal) but about having reference/value projections analogous to Integer/int.

      What the Valhalla team ended up doing is, instead of having two projections for each type, one with identity and one without, value types never have identity and so Integer and int are synonymous, and the memory layout is determined automatically based on context and optimisation decisions. This is why the semantics of == for the primitive wrappers (like Integer) were changed, as they now don't depend on whether the "reference projection" or the "value projection" is used.

      > There is no reason to reduce the optional(!) safety guarantees you can offer with the excuse of "too mentally taxing".

      This is not what happened here.

      • tsimionescu 5 hours ago ago

        > and so Integer and int are synonymous

        Except they're not, as I can do Integer x = null, but not int x = null. So an Integer is forced to occupy more memory, for very very unclear reasons. And this is also deeply weird - there is no other (mainstream?) language that allows null value types.

        • pron 5 hours ago ago

          That's not quite how it works in Valhalla. Because Integer and int already exists, your declarations above will be interpreted with those meanings, but (assuming some TBD nullability annotation), they will be equivalent to `int? x` and `Integer! x` respectively. In other words, the nullability of a variable is a separate concern from the data type, and other than the different defaults on variable declarations (as these types already exist), Integer and int become the same type.

          • tsimionescu 4 hours ago ago

            This may be true, hopefully, in a future version of Java, if the article isn't wrong. In JDK 28 with the Preview feature enabled, int is not nullable and Integer is nullable, and they are thus different types under the hood. Which also means that on most CPU architectures, Long[] will be just as inefficient compared to long[] as it was in any previous version of Java.

            • pron 3 hours ago ago

              > int is not nullable and Integer is nullable, and they are thus different types under the hood

              Yes and no, because in Java we have runtime types and compile-time types. The frontend compiler will treat these types as having different defaults on nullability, but they'll compile down to the same representation (when appropriate). I.e. if the compiler sees that some Integer variable is never null, it will compile down to the same thing it would if it were declared an int.

              You're right, however, that on the heap, until the language adds nullability information, the compiler cannot generally know that an Integer will never be null (unless it's a final field), so it's likely that, unlike on the stack, you'll get a different representation.

        • mike_hearn 5 hours ago ago

          It's not that weird. The goal is to enable existing types to be turned into value types without porting the users, so stdlibs and other libraries can mark types as value types without an API break.

          That goal is an ideal and can't be reached perfectly. Converting a type to a value type will break clients that synchronize on them, or rely on identity for some reason. But such cases are rare, and can be weighed up on an individual basis when making the decision about whether to do it. Storing things in a nullable variable on the other hand is very common and changing the rules to prevent it would make every such change a source incompatible breaking change.

    • moomin an hour ago ago

      I mean, I appreciate the huge engineering and design challenges here, but C# had non-null value types in 2002. It had generic value types in 2005 and it gained not-null “types with identity” in 2019 and no-one has batted an eyelid. (Indeed the type system support for value types still includes stuff Java isn’t even considering yet.)

      Saying the mental model is too hard is basically saying your userbase is stupid. This stuff is not tricky.

    • rzmmm 10 hours ago ago

      This is mainly for performance and memory layouts, it would not have improved safety guarantees of java.

      • rf15 10 hours ago ago

        It would have implicitly brought some null-safety to java with primitive-like classes that can not be null.

    • theptip 3 hours ago ago

      > a single type would have two projections: a value variant (flat, never null, behaving like a primitive) and a reference variant (a box that allows null). Across various iterations this was written as Point.val/Point.ref, and later they experimented with the Point! and Point? syntax.

      This seems heavier? Having two representations and manually having to refer to .val or .ref?

      You can argue that the extra flexibility lets you write safer (non-nullable) code but naively it seems more complex at the language level.

    • groundzeros2015 5 hours ago ago

      > very little faith that Java can be steered in a sensible direction here.

      What? It’s been getting better with each release. Valhalla brings features that address key problems, and they didn’t rush to it either.

    • jmyeet 6 hours ago ago

      Java made several mistakes. It also made some questionable (yet often defensible) decisions. It's understandable. Type erasure was one I believe was a mistake. It's talked about in the article. Yes, you kept binary compatibility but you that created so many other problems such as not being able to use value types in generics. Notably, C# looked at that and said "nope". Type erasure is also hurting Valhalla here and the issue of value classes in generics is the second phase so is being pushed far into the future.

      But a huge mistake (IMHO) was not having nullability part of the type system. You can still do this with type erasure.

      Anyway, I read your comment as "nullability isn't complex" (paraphrased) but that's not the author's point. What's complex is having a value class and a regular class of every class and you don't necessary know which one you're dealing with at the language level.

      C++ is a great example of this. You can create an object ont he stack or the heap and that's really what we're talking about with that proposal. And that's a nightmare. Combined with pointers it meant you never knew if you could free something or not and that ownership had to be passed around with vague comments like "// retains ownership".

      Anyway, the whole article is a great tale of how difficult it is to retrofit things later and how difficult it can be to fix mistakes later (eg java.util.Date).

      • inigyou 6 hours ago ago

        How would a non-nullable class field work in Java when it can be initiqlized by arbitrary imperative code that can read it while it's being initialized?

        • tsimionescu 5 hours ago ago

          Look at how Go did it. Any value type has a defined 0 value, and any variable of field of that type is initialized to 0 by default. So any un-initialized non-null able value type field could have the corresponding 0 value.

          • inigyou 4 hours ago ago

            That's worse than null. Now every object has an invalid state. It works for Go because Go is trying to keep language complexity low. It has no good design principles behind it other than that. Also the zero value of a reference is null so you still haven't answered how a non-nullable reference field would work.

            • tsimionescu 4 hours ago ago

              Sorry, I misinterpreted your question to be about non-nullable value classes, not non-nullable classes more generally. For reference variables, it seems that the a priori best approach would have been to generate a compiler error if the field was read before it was written; since this ship has long since sailed with final fields, it seems that the same approach would have to be taken - the "not null able" guarantee only applies after the class is fully initialized, and you can observe it as null during initialization. This is probably not a huge problem, given that initialization code always has to deal with a class breaking its invariants, since they are only established at the end of initialization.

              Regarding the 0 value choice in Go, I don't agree that this is worse than null. It simply applies a design constraint that is not usually very hard to satisfy - that the 0 value of your type must have well defined semantics.

        • wiseowise 5 hours ago ago

          How can Kotlin do it?

          • inigyou 4 hours ago ago

            Probably with hacks. Did you know a final field in Java can change its value? And I'm not talking about his reflection to make it non-final. With ordinary code only, you can read a final field before it's been initialized, so it still holds its default zero value. For example "final int x = calcX();" and have calcX print the value of x, it will be zero.

            There's a whole bunch of specification language describing how constants aren't actually constant in specific situations.

            I don't know Kotlin but I assume it does the same thing: until the non-nullable field gets initialized, it holds null and violates the type system.

            • wiseowise 3 hours ago ago

              Nothing stops me from sticking my dick into a blender either, yet it works fine for most of the things (the blender). Both Java’s notion of final and Kotlin’s nullability work great for 99.999 use cases and you really need to go out of your way to break them.

          • mike_hearn 5 hours ago ago

            A lot of the language rules are required to make its approach to nullability work. Hence odd keywords like "lateinit var".

        • jmyeet 6 hours ago ago

          The type erasure version of this would look a lot like Hack [1]. So generic arguments would simply have a ? if they allow nulls eg List<?Point>. The list itself couldn't be null unless it was ?List<?Point>.

          Now, one can argue that this is just smoke and mirrors with type erasure and it is but you can already put a Date into a List<Point> if you're so inclined because the JVM doesn't know the difference, hence type erasure. So this is no different.

          I'm no JVM expert but from reading the article it seems like the chosen solution for value classes is to treat them all as a single L-type in the JVM where each primitive type is its own L-type. If I read the correctly, it means that if you have a Point value class then on the JVM level you'll be able to stuff any value class into there if you're so incline, just like with List<Point>.

          Obviously we need to be concerned with fuzzing (moreso in C++) but here really we're just trying to have sensible defaults that aren't guaranteed because we can't design the language how we want from the ground up without making a new language.

          Oh and there is a prosopal for this [2]. Personally, I prefer the Hack version.

          [1]: https://docs.hhvm.com/hack/types/nullable-types/

          [2]: https://openjdk.org/jeps/8303099

          • inigyou 5 hours ago ago

            you seem to have ignored the question and answered a completely different one

    • imtringued 7 hours ago ago

      Yes, in this respect Java is 100% doomed. They've made a terrible decision and they're sticking with it for the sake of "consistency".

      • groundzeros2015 5 hours ago ago

        I think “Doomed” is slightly dramatic for preserving some decades old null ability conventions?

    • lowbloodsugar 3 hours ago ago

      Like yeah, every field is final but omg non-null is a bridge to far! Wtf?

  • w10-1 15 minutes ago ago

    A bit fuzzy and dramatic; luckily the original documents are quite readable:

    top-level page: https://openjdk.org/projects/jdk/28/spec/

    JEP status: https://bugs.openjdk.org/secure/Dashboard.jspa?selectPageId=...

    I'd really like to see someone trace related developments in C#, Swift, Java, and Rust, since they all have been racing to catch up to hardware, and I believe they are cross-pollinating.

    (My concern is how all this will affect the FFI memory shares.)

  • tomaytotomato 10 hours ago ago

    A lot of the comments on here are a bit unfair on what is great work being done and even more awesome work (JEPs) in the pipeline for the future.

    If Java was a child, imagine it being brought up by loving parents for the first few years (Sun) then it was thrown in a garage with some other children and neglected by its evil guardian (Oracle)

    Neglected and unloved till JDK 8, its basically been playing catch up.

    So when people say "oh so its now got structs or value types of X", yes it has but that's because it has been stunted in its development due to big bureaucratic and hostile corporate processes, but its free now and is getting love through the OpenJDK family.

    I will continue to enjoy writing once and deploying anywhere!

    • gf000 9 hours ago ago

      > If Java was a child, imagine it being brought up by loving parents for the first few years (Sun) then it was thrown in a garage with some other children and neglected by its evil guardian (Oracle)

      Whether you like oracle or not, this is simply not a correct description of Java's history. It was brought up by loving parents, who due to financial problems had to put Java into a foster home where she was neglected.

      But later it was adopted by new, loving parents (Oracle) and she bloomed and become a healthy and stable adult.

      Like, it was Oracle that completed the open-sourcing of the platform, making OpenJDK the reference implementation. They also open-sourced the previously proprietary jfr, mission control etc tools.

      They also managed to keep many of the original members of the language team, which is quite rare during these acquisitions, and Java has seen a huge improvement both on the language and runtime front.

      • cogman10 6 hours ago ago

        Yup. I was around for and skeptical of the Oracle purchase of Sun. I was worried about what it would mean for Java.

        The Java team has been delivering nice language and environment improvements regularly since Java 10.

    • homebrewer 9 hours ago ago

      It was neglected during its last few years at Sun. Oracle started moving it forward at never before seen pace, while mostly maintaining backward compatibility (unlike .NET that "did things right from the start", which is what .NET Framework/.NET Core/.NET split/rewrite is according to some in this very discussion. And .NET had Java to copy and learn from, but still fucked up.)

      Same with MySQL, btw. "Dead" according to this site, risen from the dead under Oracle for those who actually know it.

    • pron 7 hours ago ago

      > If Java was a child, imagine it being brought up by loving parents for the first few years (Sun) then it was thrown in a garage with some other children and neglected by its evil guardian (Oracle).

      > Neglected and unloved till JDK 8, its basically been playing catch up.

      These two statements are contradictory. The last Java version under Sun was in 2006. Oracle bought Sun in 2010. JDK 7 came out in 2011 and JDK 8 in 2014.

      The team largely remained the same, and the main difference was that Oracle ended the neglect and funded us more, which is why Java picked up the pace after the acquisition.

      > its basically been playing catch up.

      Catch up with who or what? There are only two languages in the world as popular as Java or more: JS/TS, and Python. People who are saying Java is "playing catch up" usually compare it to languages that are doing far, far worse than Java. It's just that people who like certain features think that the language that has them is doing poorly despite them and not because of them. Many times I see people insist that other languages are "doing it right" (or better than Java) even though it is clear that the people who say this are in the minority when it comes to preferred features.

      > So when people say "oh so its now got structs or value types of X", yes it has but that's because it has been stunted in its development due to big bureaucratic and hostile corporate processes, but its free now and is getting love through the OpenJDK family.

      If anything, the opposite is the case. Managers love to see things ship quickly. It is our technical leadership - all people who were there in the Sun days - who insist we have to move deliberately and carefully and get things right. You can agree or disagree with the decisions, but comparing Java unfavourably to languages that are doing far worse is unconvincing.

      Rather, what I think the vibe is because Java is not as popular as it was in, say, 2003. And it certainly isn't. But guess what? No other language is, either, because that time was anomalous not only for Java, but for the entire software ecosystem, which had never been as consolidated and unfragmented before or since.

    • wffurr 3 hours ago ago

      >> I will continue to enjoy writing once and deploying anywhere!

      Except to the browser, iOS, embedded systems...

      WebAssembly is the real write once deploy anywhere tech now. JVM had its turn and lost.

      • cenamus 3 hours ago ago

        Embedded systems? Like sim cards? There's even real time Java, I think they used it for some missile guidance stuff aswell at some point

        • tennfown 3 hours ago ago

          > Embedded systems? Like sim cards?

          Serious question: I remember the old installer, six billion devices or whatever. I’ve heard about Java ME, old set-top boxes and DVD players, etc.

          But how much of that is active today. I can’t say I’ve ever seen a job listing for an embedded Java developer or even Java ME in my entire career. Are people actually still using it?

          • cenamus 31 minutes ago ago

            Yeah I didn't mean it as praise for Java (SE, ME, or whatever), but I don't think you're better off finding embedded WebAssembly jobs

    • jmyeet 6 hours ago ago

      To take your analogy further, not only was it thrown in the garage, but it was used to sue for billions of dollars in child support (Google) so really it had just become a cash grab.

      Anyway, I wouldn't even call Java "stunted". It made choices, some reasonable, some not, and those are incredibly hard to fix later. Heck, just look at C++. Semi-compatibility with C is (IMHO) an unfixable 150 foot albatross around its neck and so many versions from C++11 onwards have simply been about making that 150 foot albatross more bearable.

      I personally think treating all value classes as a single L-type in the JVM (like primitive types, basically) is a fairly neat solution to a difficult problem. But all this comes down to the original Java 2 decision to implement generics as type erasure to maintain backwards-compatibility, something that C3 NOPEd out of as a result.

  • juancn an hour ago ago

    It's still too unpredictable trying to be transparent IMHO.

    Scalarization can fail in surprising ways just due to what a maximal atomic write can be on the target platform, and then it fall back to heap allocated objects.

    Even if there's type erasure.

    I much rather have the compiler balk at me than let me write something that may or may not work as expected.

  • DarkNova6 11 hours ago ago

    You could probably a whole tech thriller on the evolution on Value Types in Java.

    I’ve been reading the mailing lists and watched all videos on the topic and it is truly inspiring how much they managed to consolidate the design to something that always looked like java.

    But while also going far deeper in granularity and understanding what it even means to be a value type and what optimizations can be done where

    • joe_mwangi 2 hours ago ago

      And the only syntax change is adding 'value'.

  • mwkaufma an hour ago ago

    Footnote 6 "How is this different from struct in C#" is inaccurate. Since the article is littered with AI-generated images, I assume the writing, or at least the research, is littered with hallucinations?

  • layer8 10 hours ago ago

    > But careful: == looks at internal state, which isn’t always what the object represents, so for “is this the same data” comparisons keep using equals.

    So == for value classes will basically be like memcmp(). That is a bit unfortunate, as it breaks encapsulation, exposing implementation details. Client code can use this to do case distinctions based on how a given value is internally represented. In a way, it’s worse than identity comparison, because identity comparison at least doesn’t expose internal state.

    • usrusr 10 hours ago ago

      Value types are a concept very far away from the "magic black box organism" school of OOP thinking. It's not a novel way of doing classic OOP (does anyone still do that?), it's a way for a language born in OOP ideology get one step further into the post-OOP world.

      • layer8 10 hours ago ago

        That’s just not true, you can have a completely value-based language without OOP that still doesn’t leak implementation details of the values, while also supporting UDTs.

        • jstimpfle 9 hours ago ago

          OOP isn't just about values vs objects. Yes, the idea that everything needs identity is a big part of the problem. But another big problem is the idea that the implementation and representation of types should be hidden by default. The mindset that there isn't a known and useful data representation for a given type. That everything is done by methods parameterized by a type. It's a misguided idea. There is a place for objects and implementation hiding. But the idea that this should be done on a type granularity is a complete and utter failure.

          To see why, consider that to do any useful work, data from different objects (also from different types) has to be combined. To be able to do that in the OOP framework, the encapsulation has to be unwrapped. That's why Java code is littered with getters and setters that don't do any useful work at all, they just make it too painful to get any real work done.

          Again, there is a place for objects and implementation hiding, but it's at the highest levels of an architecture where different components get integrated.

          • tsimionescu 5 hours ago ago

            All of this would be valid, except that value classes still pretend that their fields can be private.

            This also has huge implications in a language that emphasises dynamic loading like Java. And it also flies in the face of all of the pretenses that ABI compatibility is sacrosanct and no feature that breaka it can be considered, that the design team often touts.

            • usrusr 4 hours ago ago

              Why pretend? "private" on value types just means nothing to see here except when you happen to be one of the functions conveniently namespaced with the struct.

              But I'd say that GP's complaint about inequality leaking makes no sense anyways, because what could be more unequal than different implementation, or different internal state implying different behavior down the line? The public subset isn't some arbitrary interface that could have different implementations. And even then, "equals under interface I1" would have to be considered a very special type of "equality", not the general case.

          • rowls66 5 hours ago ago

            There is no requirement in the Java language to use getters and setters.

            • jstimpfle 5 hours ago ago

              But why are there so many of them?

      • DarkNova6 8 hours ago ago

        Not if you do DDD where a calue type has exactly those semantics and for record types this is actually a free lunch.

    • ahartmetz 10 hours ago ago

      If your bags of data have internal state, there's something wrong with your bags of data. I assume that the Java guys thought far enough to either exclude padding from comparisons or force padding bytes to be zero.

      It should work even for strings: They will surely continue to be heap-allocated, and memcmp-ing pointers (inside the new "structs") is exactly an identity comparison.

      • layer8 10 hours ago ago

        There’s nothing wrong with having non-normalized representations, that’s why there is equals().

        For example, you might have a value class for representing (limited-precision) fractions using two longs internally, for the numerator and denominator. For efficiency trade-off reasons, you don’t want to always shorten the fraction. But now client code can distinguish 2/3 from 4/6 using ==.

        Scenarios of that sort are conceivable where this actually leaks sensitive information. In any case, it creates dependencies on implementation details where you don’t want to have them.

        When designing a value class, you are now in the dilemma of either always having to normalize the representation, costing performance, or having your class be a funnel for leaking implementation details.

        • ahartmetz 10 hours ago ago

          Well. I'd be upset if custom operator==() for plain-old-data structs was removed from C++, but Java never had it to begin with, so for Java, it just means that you have to fall back to using traditional classes (or compare using something other than ==) if you need such "fancy" features.

        • inigyou 8 hours ago ago

          Java can also distinguish a 2/3 object from a 4/6 object using == when they are not value types. It can even distinguish a 2/3 object from a different 2/3 object.

        • jstimpfle 9 hours ago ago

          > There’s nothing wrong with having non-normalized representations

          There is a lot wrong with that: complexity, bloat, and slowness.

          > But now client code can distinguish 2/3 from 4/6 using ==

          That's a great way to obfuscate code. Not a good idea. The right way to do the comparison is, just make a function called CompareRational().

    • bishabosha 8 hours ago ago

      the whole point of value class is that they should not encapsulate state, i.e. its a totally transparent data holder

    • jmyeet 6 hours ago ago

      I wanted to comment on this as well. The article mentions it but if you've never used Java in anger (is there any other way?) then readers may not understand the true implications of this because it's a breaking change, something Java rarely does. I'll explain for the non-Java people.

      Java separates checking identity and equality for objects. == basically checks if two pointers are the same. Equality is a subjective concept based on an interface (ie equals/hashCode). So this means:

          new Integer(1000) == new Integer(1000) // true, used to be false
          new Integer(1000).equals(new Integer(1000)) // true
          new Integer(10) == new Long(10) // compiler error, used to false
          new Integer(10) == new Integer(10) // true
      
      There's a lot going on here. The complication is that in previous versions of Java (and I'm not sure when this changed), integers below a certain value would be replaced with canonical types below a certain value. I think it was 128 but its's been awhile. This led to the difference between 10 and 1000. That's now changed, I suspect because the above comparisons are being implicitly unboxed. That didn't used to happen either. I saw this because the Integer/Long comparison used to return false and it's now a compiler error so there must be unboxing going on.

      You may still be able to get the old behavior through variables too.

      Anyway, if value classes lose identity then == changes from pointer equality to bitwise equality. That will hopefully resolve a bunch of corner cases like this but it is a breaking change, technically.

      • papercrane 5 hours ago ago

            new Integer(10) == new Integer(10) // true
        
        Before value classes this would always be false. The only time comparing Integer objects with == could be true is if Integer object was create by going through Integer.valueOf (or obviously if they were the same object reference.) By default the cached values where -127 to 127, but that is tuneable at runtime.

        https://github.com/openjdk/jdk/blob/jdk-27%2B27/src/java.bas...

        • tsimionescu 5 hours ago ago

          It could also be true if the instances were created through auto-boxing (e.g. arrayList.add(10); arrayList.add(10); arrayList.get(0) == array List.get(1) //would return true, but false if you used 1000 instead of 10).

          • papercrane 4 hours ago ago

            Yes, because auto-boxing is just compiling to Integer.getValue under the hood, the bytecode for Integer.getValue(1) and ((Integer) 1) is the same. They'll both compile to something like:

               iconst_1
               invokestatic java/lang/Integer.valueOf:(I)Ljava/lang/Integer
            • tsimionescu 4 hours ago ago

              Sure, I was just talking about Java syntax, not the bytecode internals.

        • jmyeet 4 hours ago ago

          So you've made my point in showing how complex this is because you're incorrect [1][2]:

          > By default, Java maintains a cache of Integer objects for values between -128 and +127.

          [1]: https://stackoverflow.com/questions/3130311/weird-integer-bo...

          [2]: https://dev.to/marzuk16/understanding-integer-caching-in-jav...

          • xxs 3 hours ago ago

            it's easier to remember that it originated from the Byte range, where all bytes could be kept in. Character didn't have negative values so it did [0-128) instead. Long and Short are the same as Byte.

            Years before the autoboxing/Integer.valueOf() caching stuff (and before generics), (I) used to have IntegerProvider that did similar stuff to higher ranges. Personally, I have considered autoboxing on integers net-negative for Java

  • torginus 11 hours ago ago

    I know its a faux pas in the Java world to acknowledge the existence of .NET, but how does this differ from .NET structs?

    Value types, generic specialization, boxing - a quick skim makes it looks like they picked the same choices.

    • DarkNova6 7 hours ago ago

      C# actually has a fair amount of gotchas and Java aims to make these explicit. So where C# mostly copied C from a low level perspeCtive, the Java guys approached this high level and analyzed in detail which constraints give you what kind of benefit.

      So where in other languages, the struct/class taxonomy is binary, Java allows more granular control, reflection the semantics of the underlying domain. Snd as it turns out, structs have a wide range of footguns, especially in a parallel context.

      • jaen 5 hours ago ago

        Could you actually explain/exemplify any of the gotchas and what's been made better (or is this just handwaving)?

        • cogman10 3 hours ago ago

          Part of the reason Java hasn't reified generics is because C# did and it was a real big headache that also limited non-C# languages on the C# runtime (CLI?). Everything had to be recompiled to work with newer C# runtimes. While it's pretty easy to run a bunch of language on the JVM (Javascript, python, ruby, clojure) doing the same for C# is somewhat a nightmare, particularly for non-type aware languages.

          For example, Imagine you have an api like `void do(List<Foo> foos)`. In the erasure environment of the JVM that looks like `void do(List foos)`. From python it's pretty easy to call with a `foos = [Foo()]`. But not so much if your python implementation needs to figure out how and if it can coarse it's `List` type into a `List<Foo>` type.

          • kuhsaft 3 hours ago ago

            I don’t think that’s the case. You can absolutely implement a type-erased language on top of the CLR. Your language will just have the same constraints of a type-erased language like Java.

            Having reified generics in the CLR just lets you store more type information. There isn’t much of a trade off for CLR end-users.

            Compare this to the constraints and workarounds that Kotlin and Scala have due to type-erasure on the JVM.

            • pregnenolone 2 hours ago ago

              > Compare this to the constraints and workarounds that Kotlin and Scala have due to type-erasure on the JVM.

              The creator of Scala disagrees: https://youtu.be/Xn_YpUtXWT4?t=850

            • cogman10 3 hours ago ago

              You CAN do it, but it's much more difficult.

              And as far as I'm aware, both kotlin and Scala don't really suffer due to type erasure.

              • kuhsaft 3 hours ago ago

                I mean, the language is what it is. But, it definitely constrains the language developers. Especially when considering interop with other JVM languages.

                That being said, it is easier to write a language on top of the JVM with good interop, since there are less ways to implement features. Essentially, your language has to interop with Java.

                And it is harder to have good interop between CLR languages because there are more ways to implement features. Essentially, your language has to interop with C#.

        • kuhsaft 4 hours ago ago

          The gotcha is the potential boxing of structs onto the heap, but that can be avoided using `ref struct`s.

          https://news.ycombinator.com/item?id=48599273

        • bbg2401 5 hours ago ago

          Why would you presume the parent is "just handwaving"? It's odd how people in the .NET community struggle to earnestly engage in conversation with Java folk. The reverse isn't true.

    • _old_dude_ 10 hours ago ago

      The article has a section about that.

      For me, a struct in C/C# can be modified and is passed by copy while a value class can not be modified and is passed by value.

      I do not think you can do stack allocation in Java.

      • kuhsaft 4 hours ago ago

        Like @layer8 said, pass by copy and pass by value are the same.

        C# copies C++ behavior where you can pass a struct by value or reference, and you can mark the parameter as readonly. C# also has in/out parameters. Essentially, you can program in C# exactly like you would in C++.

        https://learn.microsoft.com/en-us/dotnet/csharp/language-ref...

        The footgun with C# structs are that you can accidentally box them onto the heap. To avoid that you can define `ref struct`s that cannot be boxed. `ref struct`s follow the C# disposable pattern.

        https://learn.microsoft.com/en-us/dotnet/csharp/programming-...

        https://learn.microsoft.com/en-us/dotnet/csharp/language-ref...

      • layer8 10 hours ago ago

        I don’t see a difference between pass by copy and pass by value.

        The mutability difference is that part of a struct can be modified in place, which value classes can’t: the value of a complete value-class variable (or array slot) can only be modified (reassigned) as a whole. This is presumably because object references to value-class objects can be created, and those objects should be immutable so their identity doesn’t matter.

        • _old_dude_ 10 hours ago ago

          I think pass by copy is a consequence of being modifiable.

          The other solution is to stack allocate and pass a pointer but as i said, unlike in C#, i do not think it's possible to do that in Java.

          In Go, you can stack allocate but when you send a pointer (that escapes), the compiler will heap allocate the object.

          • layer8 10 hours ago ago

            My point is that pass by copy and pass by value do the same thing, they copy the value representation. In other words, pass by copy means exactly pass by value.

            • gf000 9 hours ago ago

              Actually, Java only has pass-by-value, even for reference types. (The same way as C does).

              People really misuse/misunderstand this term: Java objects are passed by their pointers ("references") being copied.

              The alternative is pass by reference, which is done by e.g. c++, rust, who actually have references (Java doesn't). A good litmus test is whether you can write a swap method that actually changes your local variables.

              • steveklabnik 4 hours ago ago

                Rust is also “pass reference by value,” not pass by reference.

            • _old_dude_ 9 hours ago ago

              For me, the difference is that if methods are inlined, the compiler is still required to do a copy for structs but not for value classes.

              I do not know how this is called.

        • gf000 10 hours ago ago

          I think that's mostly a semantic difference - Java avoided the problem of strange lifetimes, captures, tearing by fixing the semantics as immutable value objects, while C# has to deal with these issues.

          But under the hood it can (and will) do a modification in place.

    • rf15 11 hours ago ago

      Functionally they don't - java is just catching up with (by now) ancient practice.

      The false dichotomy of

      > A struct in C# has identity and mutation, so the semantics of copying on assignment or passing have to be precisely defined, which gives a heavier model for the programmer and less freedom for the runtime.

      Doesn't really match with what they're describing. While yes, it will not have identity in a java class ref sense, it of course will still have identity in being a unique structure in memory at a certain address. This is just splitting hairs about Java nomenclature.

      • oddx 10 hours ago ago

        > it of course will still have identity in being a unique structure in memory

        No, it will not. The design allows multiple objects to share one structure in memory across multiple records, or not have such a structure at all (see Scalarization in the article).

        • rf15 9 hours ago ago

          > The design allows multiple objects to share one structure in memory across multiple records, or not have such a structure at all

          Yes. I fear you are missing the point.

      • torginus 10 hours ago ago

        I don't wanna badmouth Java people, but how they push the idea that this thing is some sort of genuine breakthrough that took multiple PhDs years of cutting-edge research to implement, when in fact they basically copied what .NET did from basically year 1, is not a good look.

        Again, not trying to turn this into a .NET vs Java thing, I'd have been much happier if they reached some new and interesting conclusions.

        • gf000 10 hours ago ago

          > genuine breakthrough

          Well, it is - because they had to make it with almost perfect backwards compatibility for one of the most popular languages with trillions of lines of code produced over decades.

          Sure, adding it to a new language is not hard. Adding it to Java which has primitives, generics and boxing, finding ways that seamlessly cover the differences between objects and primitives, while trying to plan for the future is hard.

          As a general note, if you come to the conclusion that one of the best designer teams on Earth "basically copied what .NET did from year 1 is not a good look", then maybe your mental model needs adjusting on how these stuff works? Java has a public mailing list, you can browse through the related discussions. Implementation is the least of these things. But I can assure you they most definitely know what they are doing.

          • Rohansi 2 hours ago ago

            > because they had to make it with almost perfect backwards compatibility for one of the most popular languages with trillions of lines of code produced over decades.

            At what cost? A key benefit of value types is improved performance but AFAIK Valhalla doesn't even let you pass them by reference. Efficiently passing them through registers is great but won't help you out with larger value types.

          • sysguest 10 hours ago ago

            idk maybe java should adopt something similar to rust's "edition"?

            • gf000 10 hours ago ago

              Correct me if I'm wrong, but Rust editions are a source code-level feature. So given you have the source code of newer and older rust code, you can compile them together.

              That's materially distinct from Java's model of basically dynamic loading already compiled class files. Though class files do have "editions", and there are extra code to deal with different versions. But still, it should be possible to e.g. send a new value class to an old library's class that has never heard of them, and that should just work.

              • simonask 7 hours ago ago

                The important thing is that Rust editions affect semantics and name resolution. In such an analogy, JVM bytecode is the equivalent of Rust code - various semantics are baked in, but stuff like name resolution isn't (at least not completely).

          • tsimionescu 5 hours ago ago

            Except of course they are breaking backwards compatibility, in relatively subtle ways, for anything that uses the standard library wrapper types. So every use of Integer, Boolean , etc is an opportunity for either a compilation error or a runtime bug.

        • coldtea 9 hours ago ago

          >I don't wanna badmouth Java people, but how they push the idea that this thing is some sort of genuine breakthrough that took multiple PhDs years of cutting-edge research to implement, when in fact they basically copied what .NET did from basically year 1, is not a good look.

          Oversimplifying a big semantic and backend change to a huge codebase on which some of the most crucial customer and government and business systems depend on, and which has to be made as seamless, correct, and performant as possible, to "they just copied .NET", just because .NET has the same functionality, is an even worse look.

          It's a "HN "Dropbox is just rsync + some scripts"-style bad look.

        • misja111 10 hours ago ago

          This is exactly what made it so difficult. It is much easier to have a feature like this from year 1 than to add it to a language that has grown and evolved for 18 years already.

          • rf15 10 hours ago ago

            I agree with this sentiment. The work they put in deserves a lot of respect, and took a lot of effort, no doubt. It's just the framing they push to the public that could use some work.

        • DarkNova6 7 hours ago ago

          The article is shit, the actual concept and roadmap goes well beyond the capabilities of C# or the CLR.

  • smallnix 7 hours ago ago

    > [IMAGE: the same Point[] array in two variants: “before” (an array of arrows → scattered boxes with headers) and “after” (a uniform strip of number pairs)]

    The `Point[]` in the image tag of your LLM output crashed your image generation post processing.

  • orthoxerox 10 hours ago ago

    > Will I get a fast, flat `ArrayList<Point>`? Not yet.

    Sad. Hope they can do this by the next LTS JDK.

    • tsimionescu 4 hours ago ago

      As I understand it, this is anyway an extremely limited perf enhancement - for any class whose data size isn't guaranteed to be atomically writable on your CPU, after including the nullability overhead, it doesn't do anything, basically. On a CPU where 64 bits is the max guaranteed atomic read/write, even Point[] will not get optimized, since you need at least 65 bits of memory for a point value (since it has two 32 bit int fields, and it needs an extra bit to specify if it's null or not - so in practice it will take up 72 bits at the very least, possibly more with alignment requirements). But even after fixing this, if you have 3D points or if you need 64bit coordinates, your value type 3DPoints will still be individually heap allocated and your 3DPoint[] will store pointers to them, just like today, on most processors.

      Given that the JVM could already do escape analysis and allocate regular classes on the stack in certain scenarios, it's very unclear what benefit, if any, this will bring for normal processors for anything except the base wrapper types - even after implementing generic support and nullability for value types in a future JVM.

      • lowbloodsugar 3 hours ago ago

        Which is why this is the wrong approach. Again. This is a major misstep.

    • piokoch 9 hours ago ago

      Yup. That's a big disappointment they could not cram universal generics faster. But I get the problem - they have to preserve backwards compatibility. I can take 30 y.o. Java 1.0 JAR and run it on Java 27 and it will work.

  • cogman10 4 hours ago ago

    > There’s a catch worth knowing about here, though: flattened data has to be readable and writable atomically (otherwise it risks “tearing” under concurrent access).

    I really hope they give an escape hatch for this. It will make it really hard to extract a lot of the benefit of valhala if you can't make a thread unsafe value class. It's also one of those problems that will be quite hard to run into. You basically need something like this

        class Bar {
          static Foo value[] = new Foo[10];
          static void setFooFromManyThreads(Foo foo) {
            value[0] = foo;
          }
          
          value record Foo(int x, int y, int z) {};
        }
    
    Not something you typically run into and generally already a thread safety problem.

    The solution is also simple, a `synchronized{}` block will fix it if you need to have a tearable class that's written from multiple threads.

    But the other thing is that for SIMD operations, you really need flattening, and that really does typically mean having something like `Foo(double x, double y, double z)` in play. It'd be a shame if the way we have to do this is a struct of arrays.

  • leiroigh 8 hours ago ago

    I'll be interested in seeing the fallout of the (unavoidable) compat issue:

    If I have a function that has a value `x` that erases to `java.lang.Object` (e.g. a parametric function with no lower bound); then it used to be safe to check for nullity and then synchronize on the object.

    This is no longer safe: This can now throw `IdentityException` into your face. (it was _never_ a good idea)

    In other words, a lot of old code must be reviewed.

    I suspect that `-XX:DiagnoseSyncOnValueBasedClasses=2` will need to stay (with the semantics: if user tries to synchronize on identity-less object, then log a JFR event and make it a NOP, don't throw an exception)!

    The current JEP text is a little too ambiguous to figure out whether that is the plan, anyways.

    • watt 6 hours ago ago

      You lost me at "I have a function that has a value `x`". How does function "have" a value?

      • porridgeraisin 5 hours ago ago

        I think they meant function that takes a value x as an argument.

  • spbaar 7 hours ago ago

    I have such an urge to comment "lgtm" on the 197k line change PR

  • vlovich123 5 hours ago ago

    > Before we pop the champagne, though: this is preview, disabled by default, and, as Brian Goetz was quick to cool everyone down, “only the first part of Valhalla.” Goetz added a great observation that the “they’ll never ship it” crowd will now smoothly switch over to “but they didn’t ship the most important part” (and a joke has been going around the community for years that we’ll sooner end up in Valhalla ourselves, the Norse-afterlife one, than the project ships).

    I don’t know if this is fair way to try to disarm your critics. The only thing that’s remained after this decade is the slogan so it’s a real ship of Theseus question if Valhalla has shipped since what’s delivered doesn’t achieve it. Congrats on the accomplishment, but from looking at what ended up, I’m not sure it’s a huge improvement.

    > The trouble is that this optimization is unpredictable and fragile.

    Is this describing escape analysis or value classes? Because the list of exclusions where this does anything is so large and the conversion to a heap type under the hood is so transparent and opaque, I think it can describe this technique as well.

    Also, the whole “works like an int” motto is violated - int is never null, int-> integer boxing is explicit and well understood.

    > In the new model, the wrapper classes themselves become value classes (when preview is on, Integer, Long, Double, and company lose their identity

    Oh neat, they sidestep that by changing the definition of an int. I’m sure it’ll be trivial to turn this on in the wild on code that may be relying on identity for boxed numerics. I think this alone shows this project can’t ever be turned on by default and now we’ll have a decade of two Java languages (one with value types and one without) as they try to convince everyone to migrate and then just turn it on (ie python3).

    So much opportunity squandered and dismissing critics as always having something to complain about is a neat way to sidestep legitimate criticism that this approach is not going to work out for Java.

  • Alexander-Barth 9 hours ago ago

    I think this is quite similar to julia's handling of a struct. An array of mutable structs is just an array of pointers, where every pointer directs to the underlying structure. However with an array of structs (immutable is the default), there is no such indirection. The value of all fields are stored as array element (unless you have an array of heterogeneous elements).

    If you want to change an element of such an array you need to create a new immutable struct which in practice it is quite fast, but a bit verbose to write.

  • narag 4 hours ago ago

    I found a solution for what seems to be the same problem, in a different language: a particular type of lists, where the class metadata is stored once and the data for each instance is contiguously stored in a flat array.

    Not sure if it covers exactly the same terrain, but perusing the article, it seems to be the case, with a single instance being the degenerate case.

    • cogman10 3 hours ago ago

      Yup, it's the same terrain.

      I've made something like this in the past. And I did it exactly because `List<Foo>` was too expensive and slow.

          class FooSOA extends Collection<Foo> {
            double x[];
            double y[];
            double z[];
            
            Foo get(int index) { return new Foo(index); }
            
            record Foo(int index) {
              double x() { return FooSOA.this.x[i]; }
              double y() { return FooSOA.this.y[i]; }
              double z() { return FooSOA.this.z[i]; }
            }
          }
  • minitech 5 hours ago ago

    > How is this different from struct in C#? A struct in C# has identity

    Since when? I’m pretty sure structs didn’t have identity last time I used C#, and that would be a very surprising thing to add.

  • exabrial 4 hours ago ago

    Tons of armchair critics but dang this is freaking cool!!! Thanks everyone for working on this an THANK YOU for moving slow and getting the design right!

  • newsoftheday 4 hours ago ago

    I just got my projects up to JDK 21 a few months ago. Working on trying to get one upgraded to JDK 25 now and now they're talking about delivering JDK 28 in less than a year from now. How are you supposed to keep up with these rapid updates?

    • jeroenhd 2 hours ago ago

      JDK 21 is a few years old now, so you were a few years behind. JDK 25 was released last year, which makes for the next stable LTS for a while. JDK 28 is expected in 2028, and these features are not going to be enabled by default for years after that.

      Java is generally backwards compatible, so unless you're using fat frameworks that use shady internals or known-deprecated APIs, you should generally be fine immediately upgrading to the latest LTS, possibly even non-LTS versions if you have confidence in your stack.

    • cogman10 4 hours ago ago

      What did you go from to get to 21?

      Mostly just hit the LTSes is what we've been doing and since about 17 it's been a pretty easy process in general.

      Protip: If you ditch lombok everything gets a lot easier.

    • bpodgursky 3 hours ago ago

      How much stuff actually broke? I feel like you can treat Java upgrades as minor upgrades most of the time, maybe you have to rebuild a few binaries but I'd be shocked if it required significant code changes.

  • maelito 6 hours ago ago

    What I have in mind when I read Valhalla : https://valhalla.openstreetmap.de/

  • ahartmetz 10 hours ago ago

    From the article:

    > In 1995, a memory access cost roughly the same as a CPU operation

    Uhm... no?!

    Here's a CS paper from 1993(!) about prefetching from cache(!!) because the cache was slower than the ALU. https://www.eecs.umich.edu/techreports/cse/93/CSE-TR-152-93....

    It would perhaps make Java look a little bad to say that, in 1995, the prevailing attitude in certain circles was "If it's too slow, just wait for faster hardware - Moore's Law forever baby!" (Of course, Sun was selling, at the time, relatively fast hardware - the slower the software, the faster the required hardware)

    • rob74 6 hours ago ago

      Yeah, when I read that, I thought "this guy was either born wayyy after 1995, or he doesn't know the first thing about computer hardware history, or both". 1995 was the year the Pentium Pro was launched, which was (one of?) the first CPU(s) to integrate the L2 (!) cache into the same package as the CPU - they were still separate chips, but the interconnection could be made faster by putting them into the same package.

      • tialaramex 5 hours ago ago

        Yeah, 1995 is much too late. I'd say somewhere about 1990. Maybe you can go as early as the i386 and 68030 in the late 1980s but I'd be hesitant to include them.

    • Athas 10 hours ago ago

      Yes, this also stood out to me. I usually think of CPUs and memory having parity in the early 80s, but I never bothered to check for sure. I do remember some early computer architects writing about memory being faster than the CPU!

      • ahartmetz 10 hours ago ago

        Early 80s is also what I remember, mainly from articles about old CPUs on HN - like the zero page on the 6502 that served as a sort of L2 register file.

      • dboreham 4 hours ago ago

        Well, yes but no:

        The Z80 took 3 cycles to load from memory. A register to register transfer took 4 cycles (including fetching the instruction). Only one of those cycles was instruction execution.

        I think the only reasonably mainstream scenario where the CPU would be significantly slower than memory would be the serial CPU designs such as the PDP-8/s.

        That said, at the time people were doing cool stuff with 8-bit CPUs, they weren't running software remotely like what we're discussing here. That would have been done on a VAX, which had instruction and data caches.

        What really happened, that the article is alluding to is that memory didn't get much faster in absolute terms since the 1980s. CPUs on the other hand did.

        E.g. in the 1980s we had 60ns DRAM. Today DDR5 I believe allows about 10ns random access reads best case (6X). Over the same period CPU clock speeds have increased from about 8MHz to 5GHz (600X).

  • drzaiusx11 6 hours ago ago

    Am I understanding this correctly: a value type really only works when it fits on a 64 bit "cache line", and when larger, it falls back to normal heap allocated objects as before? Seems extremely limiting, no? Great for a boxing optimization, but not much else unless you're deal with very small data types regularly...

    • exidex an hour ago ago

      Larger types are supported, there is A notion of tearing. According to JVM spec even long and double could tear, not sure about practical implications though

    • mattstir 5 hours ago ago

      That's true for arrays of these value classes. Scalarization would help for larger local values though, since those would avoid pointer indirection for purely local values.

    • logicchains an hour ago ago

      Surely it can't be that, it destroys basically the entire value proposition of value types, unless you use a preprocessor to write everything as SOA.

    • lowbloodsugar 3 hours ago ago

      And one bit for null! So “smaller data types” means 32bits! Yay! It’s 1995 again!

  • nasso_dev 8 hours ago ago

    stopped reading when i saw the AI illustration. wholly unnecessary, and it feels insulting to be fed slop like this...

    if you really want a fun drawing get a human artist to do it. it doesn't need to be complicated, for example https://www.code-cartoons.com/ is mostly just stick figures and does an excellent job

    but you don't even need any of that, a mermaid diagram would have worked perfectly fine too. instead you chose to use a technology that is known to be harmful

    • jeroenhd 2 hours ago ago

      I simply do not trust articles that use slop imagery like this. I assume the text of this article (and realistically, most articles posted here) is also slop, but it's often difficult to tell.

      If you don't have the time or put in the effort to make your article, I'm not going to spend time and effort reading it. You really don't need some generic cartoon guy hovering over your graphs, draw them in MS paint or something.

    • piiritaja 5 hours ago ago

      Thank you, no idea how this stuff gets upvoted here. The whole article reads like something Claude came up with.

  • nu11ptr 4 hours ago ago

    I'm a little unclear as to when and under what conditions this results in non-heap objects, now (<= 64-bits?) and in the future (???). I thought that was the _ENTIRE_ point of this project, so I was surprised to see they can be null (did that change from before?). If it is always and forever limited to 64-bits, I fail to see the point of this entire project, as it would have been far simpler to add syntactic sugar (simply pass primitives underneath the covers) as Scala did to create value types vs. JVM changes.

  • Hendrikto 7 hours ago ago

    > The pull request alone adds over 197 thousand lines of code across 1,816 files.

    And that across 2819 commits.

    Wow, that’s insane.

  • DarkmSparks 4 hours ago ago

    Why remove identity from Double and Integer? This is going to break so much stuff for no reason when double and int were already a thing.

  • dllrr 4 hours ago ago

    And here I thought engineers were mostly logical and objective. This thread is very entertaining.

  • aykutseker 9 hours ago ago

    I think a lot of people will file this under Java got structs.

    That seems off. They're still objects, the new thing is that they can give up identity.

  • jessinra98 6 hours ago ago

    The article has a section about that. For me, a struct in C/C# can be modified and is passed by copy while a value class can not be modified and is passed by value.

    I do not think you can do stack allocation in Java.

  • pregnenolone 8 hours ago ago

    Looking into the negative comments is quite amusing. Not only do most of them contain technical inaccuracies, but of course, they also need to mention how great .NET supposedly has been from the beginning and how Java supposedly copied everything.

    Let's take a stroll down memory lane. First of all, .NET literally started as a Java copy. On top of it, a non-cross-platform one for almost two decades! After having shamed Linux for so long Microsoft finally started porting .NET to other platforms in a non-backward compatible way. A lot of .NET proponents will tell you porting from legacy .NET to .NET Core (which was renamed once again to .NET) would be a quick fix, but it isn't. For example, the shop I used to work in had some important cryptographic libraries which were very painful to port. And then, there's .NET's simplistic garbage collector, which can be quite annoying because it tries to be a one-fit-all solution that basically cannot be tweaked at all, often resulting in unresolvable latency problems. There’s a lot of other stuff, like its ghetto-like ecosystem and the insane fragmentation of GUI libraries.

    I also don't get the C# praise. Over the years, it has become quite the bloated language. It feels like Microsoft tries to implement every feature possible without realizing that an enterprise language is supposed to be streamlined. Async/await? Very ugly, very annoying. Java has solved this a lot better with virtual threads and structured concurrency.

    I could go on, but these "language wars" are silly and pointless. Both platforms have their pros and cons. Besides, I have a lot of bad things to say about the JVM as well, but it's nice to see Valhalla finally beocming reality. Too late for me personally though.

    • ozim 8 hours ago ago

      I like how you point out inaccuracies and next paragraph you deliver couple more, finishing with “language wars are silly and pointless”.

      • pregnenolone 8 hours ago ago

        > inaccuracies

        Like what?

        • ozim 2 hours ago ago

            legacy .NET to .NET Core (which was renamed once again to .NET)  
          
          It was always .NET, only that new one had 1 till 4 had additional "Core" to clarify any confusion that could come from having same numbers as old.

            here's .NET's simplistic garbage collector ... it tries to be a one-fit-all solution that basically cannot be tweaked at all  
          
          Definitely tweaking GC is not a thing in .NET land but it is far from "cannot be tweaked at all".
  • rom1v 9 hours ago ago

    > The difference in the code is exactly one word: value.

    What is unclear to me is why the decision to use a Point instance as a value or as a reference is made in the class definition rather than by the caller.

    > Point[] point = new Point[10];

    For the same class, I might need an array of values in one place and an array of references elsewhere within the same codebase.

    • tikotus 9 hours ago ago

      What about the case of just needing one, not a collection? And when a function receives a Point, how does it know if it's a value or a reference?

  • LelouBil 6 hours ago ago

    I'm wondering what this means for Kotlin now

  • GYLQ 4 hours ago ago

    The gap between demo and production is always bigger than it looks. Things that work great on the examples in the README tend to fall apart on edge cases that aren't covered. Worth running it against your actual data before committing to it.

  • jeandrek 7 hours ago ago

    Anyone know why the article's 4th picture is about the Jobs obituary gaffe? (It's not just for me, right?)

    • Wilduck 7 hours ago ago

      It's because the author is comparing that incident to how they were prepared with the bulk of their analysis in advance. Ready to publish as soon as it happened.

  • greekrich92 5 hours ago ago

    Great write-up. Java is getting so good. The improvements over the last decade have been unbelievable. The negativity here is bizarre. Just a reflex I suppose.

    • joe_mwangi 2 hours ago ago

      And I notice, people aren't aware more things are being planned for. For example, the carrier classes being proposed, they will separate state description with state representation and this is where value classes will shine syntactically.

  • geokon 10 hours ago ago

    a few questions for the pros

    > "The defining trait: no identity"

    I get that this makes objects behave like primitive types. Maybe thats reason enough. But is it necessary for the performance boost and de-fluffing the objects? Seems like an orthogonal objective

    > There’s a catch worth knowing about here, though: flattened data has to be readable and writable atomically (otherwise it risks “tearing” under concurrent access).

    Isn't this a race condition and "undefined bahvior"..? Having to limit yourself to atomic sizes seems like a huge limitation, to accomodate what is most likely buggy code. Is all the effort only gunna help lil toy ColorRGB examples?

    > The points array is a million pointers. Each pointer leads to a separate Point object lying somewhere on the heap.

    Does this happen in actuality? One would assume the allocator tries to put stuff sequentially on the heap? Its not a guarantee as with these Value Types, but I'd think you could get similar-ish perf with prefetching in cache. I dunno whats happening under the hood.. But when writing Clojure apps the JVM always reserves absurd amounts of heapspace on my machine (to my annoyance). Id assume it can find some place to do contiguous allocations..

    Which i guess gets me to my last question... where are the benchmarks broski? It all sounds great, but does it actually yield the insane speedups promised?

    Great article, well written. But a benchmark would have been a nice "punchline"

    • lmm 10 hours ago ago

      > is it necessary for the performance boost and de-fluffing the objects?

      Yes. The one part of the JVM GC that can't run concurrently is heap compaction; objects that can be moved by copying and then deleting would be a huge help for that. And it would be awkward to say the object has an identity but can't be wait/notify'd, at which point you need somewhere for the monitor to go.

      > Does this happen in actuality? One would assume the allocator tries to put stuff sequentially on the heap?

      Yes. Of course it tries, but semantically the pointers are just pointers and the prefetcher can guess but the system still has to chase them.

    • rf15 10 hours ago ago

      > I get that this makes objects behave like primitive types. Maybe thats reason enough. But is it necessary for the performance boost and de-fluffing the objects? Seems like an orthogonal objective

      It feels like an orthogonal objective and honestly arbitrary distinction, yes.

      > Isn't this a race condition and "undefined bahvior"..? Having to limit yourself to atomic sizes seems like a huge limitation, to accomodate what is most likely buggy code.

      I think they meant it like the appearance of atomic behavior from a java multithreading view.

      > Does this happen in actuality?

      Yes, it does happen. Having guarantees on this front leads to better performance.

      > But when writing Clojure apps the JVM always reserves absurd amounts of heapspace on my machine (to my annoyance)

      Might be a configuration problem?

    • gf000 10 hours ago ago

      > Is all the effort only gunna help lil toy ColorRGB examples

      Arguably flattening mostly makes sense for these only.

      And yeah, you are right that allocations happen on something called a thread local allocation buffer, which is basically just a pointer bump in cost and objects allocated one after the other should be physically close in memory for the most part (though an object's creation may require a bunch of other object's creation that would sit in-between). But these have headers, so not as dense as they could be (though due to GCs being generational, they may end up actually closer in the next gen? The in-between temporary objects wouldn't survive for the most part)

      • pwagland 8 hours ago ago

        There are plenty of cases where flattening an object that takes 64bits would make sense.

        The current code will help with `Integer[]`, `Char []`, etc, as well as combinations of `byte`, `char`, and `int`. Past that it doesn't really help much.

        It would be fantastic if we could also flatten something like `Pair` or `Tuple`. However, even with compressed pointers, that is 64 bits, so that, plus the `null` bit, means it can't be flattened, which is a real shame. For various reasons, I have `List<Long>` in numerous places in my code, It would be great if that could also be flattened. However, since a Long is 64 bits, it _also_ can't be flattened. https://openjdk.org/jeps/8316779 would go a long way to to helping here, since then at least the null bit could be thrown away, which would allow more things to be flattened.

        And then, if you want to go Wishlist land, something that would allow SSO (Small String Optimisation) would also be awesome, but that would require something akin to unions in Java, which we can _kind_ of do with sealed classes, but, since String is a final class, can't be retrofitted back into the language.

        Does anyone know if Valhalla will flatten "simple" sealed classes, where every sealed class is small enough to be flattened? Since that would also be a powerful example to share.

        • geokon 7 hours ago ago

          Is there some reason there isn't simply a write-lock/semaphore on Value Types that are over 64bits? The overhead should beat pointer-chasing. I mean maybe someone wants to concurrently write to values from different threads with no coordination, but that's not super common. As you illustrate, having "fat" Value Types would open up a lot of potential.

          In the current setup will a Pair Value Type be a compiler error, or will it silently just have bad perf?

  • theanonymousone 11 hours ago ago
    • orphea 3 hours ago ago

        1 comment
      
      Probably not.
  • gib444 2 hours ago ago

    It's slop o'clock

  • lowbloodsugar 3 hours ago ago

    “plus a null flag” is going to be the GIL of the JVM.

  • FrustratedMonky 5 hours ago ago

    So, is this going to allow an F# like language to run on the JVM?

  • fsuts 6 hours ago ago

    Java = Oracle = Ellisons way of doing business

    Unless your company forces you to use Java for new projects, consider a change

    • recursive-call 6 hours ago ago

      name another statically typed, compiled, mature language with a bunch of packages for everything and maybe I will /srs

      • fsuts 5 hours ago ago

        Rust