Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Java 20 / JDK 20: General Availability (openjdk.org)
311 points by mfiguiere on March 21, 2023 | hide | past | favorite | 339 comments


This is exciting, can't wait for the LTS release next year... that said, I don't much care for the "case SomeType t when ..." pattern matching syntax, I don't see the benefit of introducing a new keyword over using "if"... or even just "&&". To pinch the example used in[1]:

    case Tuner t && guitar.isInTune() -> ...;
and

    case Tuner t if guitar.isInTune() -> ...;
both seem as clear as

    case Tuner t when guitar.isInTune() -> ...;

...am I misunderstanding the reasoning here? Is it being introduced to keep the grammar definition simpler?

1: https://foojay.io/today/its-java-20-release-day-heres-whats-...


I work on Dart which is also adding pattern matching [1]. When we designed the syntax for guards, we also considered exactly these three choices before ultimately landing on "when" too.

Our main reasoning was:

"&&" is intuitive but it means that you can have a pattern that is immediately followed by an infix operator. That can be problematic if you ever want to make "&&" a valid pattern infix operator. And, in our case, we ended up doing exactly that, so "&&" would have been ambiguous. If you were to write:

    case foo && true:
Then it could be parsed as either a pattern that matches when the value is equal to the constant "foo" and is equal to the constant "true". Or it could be parsed as a pattern that matches when the value is equal to the constant "foo" followed by a pointless guard that always succeeds.

"if" is nice because it's already a reserved word and the semantics are pretty obvious. But in Dart, an if statement always has the condition in parentheses. Those are pointless in a pattern since we already have another explicit delimiter separating the guard from the case body:

    case foo if (condition):
    //                     ^
We could say that the if condition in a guard doesn't need parentheses but if conditions elsewhere do. But that's likely just an annoying footgun where users will write them unnecessarily (but harmelessly at least) in guards and forget them in if statements and get compile errors.

If Dart didn't require parentheses around if conditions, we probably would have used "if" for guard clauses to (like Scala and Rust do).

So we tried "when" and most users and team members seem to like it. Syntax design is a human-centered process so often the right answer is just what feels right to the most people.

[1]: https://github.com/dart-lang/language/blob/master/accepted/f...


Gotta say I'd've taken the if with parentheses myself. Feels like it's reusing something familiar even if they are extraneous at times.


That was my initial pitch too. But after months, almost no one seemed to have warmed up to it. When we changed it to "when", just about everyone seemed to like it better.

Syntax design is weird. Sometimes the only way to tell if I did it right is when no one says anything. People complained when I used "if". No one did after I switched it to "when". <shrug>


It’s always bugged me when syntax selection is done by popular vote. The most frequent vocal complaints tend to be from people who just have to adjust to something new. But if you only ever show a user what they’ve already seen, you inhibit the ability to innovate / try new things.

The flip side is that it also bugs me when language / library designers make weird choices that don’t seem to have a compelling reason (or at least the compelling reason for the difference doesn’t outweigh the cost of being different).

No win situation ultimately except to figure out who has good language tastes and weight their feedback more, but that’s subjective and something people try to avoid, ignoring that they’ve already done this by virtue of limiting who the experts are working on it in the first place.


> if you only ever show a user what they’ve already seen

Familiarity and intuitive structure in a low context language is very important. A language syntax is for humans to comprehend. If a new syntax is under discussion, it's probably around new functionality (or sugar). Where is "encourages innovation" in the list of reasons to use any specific syntax? I would say, far down on the list.


Thank you, that's excellent insight into the thought process for the same feature at about the same time.

You mention about the human-centred nature of syntax design... do you have any instinct for why one route vs another felt right to users? Do you feel like you've developed a better instinct for this over time, or is it still hard to predict what will feel natural to users?


> do you have any instinct for why one route vs another felt right to users?

That's a good question. It is something I spend a lot of time thinking about when I see how users react to a design. In this case, I don't think I have a good answer as to why "when" seemed to go down easier than "if".

> Do you feel like you've developed a better instinct for this over time, or is it still hard to predict what will feel natural to users?

I'd like to believe so, but "if" was my first pick, so I guess not. :)

I think what our team does have now that really helps is better processes to evaluate a design, talk about it, get feedback from users, and incorporate that feedback into the design. It's all pretty informal, but I think we're iteratively able to get designs users seem to like.

But it's always really hard. There are so many trade-offs and users have different preferences and expectations, so finding the right balance is always difficult. I think that's why it's so endlessly fascinating to me: you can never fully "solve" syntax design.


As a big Dart fan I would say it’s actually in no small part to how the core language team works. Really good mix of thoughtfulness and pragmatism.


C# ended up with `when` for guards as well, a couple years ago :)


Very excited to hear this, if true, I will pick up Dart & Flutter again after this is released.


It's very true. If you build the bleeding edge Dart SDK yourself, it's already turned on. It will be enabled in the next upcoming beta release.


The “&&” one is bizarre, it makes it look like the whole thing is a Boolean expression, which it absolutely is not. That gets even weirder because the part right of it actually is a Boolean expression, so on top of very confusing reading you now make it look like there are strange interactions with operator precedence. Using && is one of the worst choices.

The “if” one does not suffer from any such problem and reads okay to me. I guess one argument against it could be that since the whole thing is a distinct grammatical construct, why not just introduce a distinct keyword instead of making an existing keyword depending on context? That’s mostly a question of style though (e.g. I don’t know right now if Java already has an habit of reusing keywords like that).


As another comment mention, they did use && in the 2nd preview, but then switched it, so you're right, it must have been confusing.

FWIW, though, I don't find it "bizarre". While it's not a boolean expression per Java, at least in my mind it serves the same purpose: "in the case that thing x is of type SomeType and ...some other thing about ((SomeType)x) is true...".

I think it's a tough choice, I'm sure they were probably not wanting to add a new keyword, but at the end of the day "when" is so familiar to anyone who's ever written SQL (which I'm guessing is most Java devs) so seems like a good choice.


> While it's not a boolean expression per Java, at least in my mind it serves the same purpose

But if some construct looks the same, but is not the same, and worse, only "somewhat" the same, you get confusing behavior. At least for newcomers, or even just software engineers who just don't care about language grammar that deeply (which I imagine are not only a lot, but also a lot of the Java target audience specifically).

Case in point, the author I was replying to wondered in a followup whether && would/should still act like a shortcut-and operator would, or not.

There is definitely some subjectivity to it, but personally I much prefer if different constructs look clearly different. Any ambiguity is squashed immediately, and in the case of "when" it's still fully clear how it works. I'm also okay with the "if" variant, because while it reuses a keyword, it's clearly a different "if".


The "&&" approach is how you express that same logic in an if statement in Java today.

Given this pattern matching syntax change, you'd write:

    switch(obj) {
        case Tuner t when guitar.isInTune() -> ...;
        ...
    }
For a switch, but for an if statement it's written as:

    if (obj instanceof Tuner t && guitar.isInTune()) {
        ...
    }

Edit: I do wonder, actually, if avoiding "&&" is to allow the "when" case execution to be reordered (e.g. to allow JIT to extract common "when" conditions prior to the switch expression), which would be wrong to do given the short-circuiting rule implications of "&&"


I'm not a Java user, but I really don't love the overloading of `if` in Python (statement, ternary, comprehensions), so introducing a new keyword here seems pretty reasonable to me.

And don't even get me started on `static` in C++.


OK, no static then, but what about if constexpr (...) ? :D


Also `if consteval {...}` in C++23. But really "if" still semantically means the same thing in all of these. Unlike "static", which is all over the place.


And then there's "static constexpr" (which is only used in places where non-static constexprs are illegal). An overloaded keyword that's become so overloaded, it's even used in places where it's completely unnecessary! :-P


I think Rust uses "if" for its match guards too and I don't recall it upsetting anyone. I'm all for keeping things consistent between languages if only to prevent too much bike shedding.


Rust's if never needs parentheses, so their absence in a match guard is unremarkable, whereas in Java that's incongruous one way or the other.


I think the only reason is that they're very different, if you define them:

    type when expression -> block

    if expression -> block
For parsing works better, for programmers is better since they don't confuse both concepts, and in general pattern matching isn't the same as a if condition or a switch.

https://stackoverflow.com/questions/199918/explaining-patter...

I think the difference is when you start pattern matching on tuples and other types that you cannot do a simple `if ident.isinstanceof(type) ->`.

For example in Elixir you can do:

    def ident(param = {:key => true}):
This will only match when the param type is not only `map`, but also contains a key named `:key` and its value is `true`.

In the end though, it's syntax sugar (but everything is if you put it that way classes are syntax sugar).


In 2nd preview, they used &&: https://openjdk.org/jeps/420

At 3rd preview, they switched to when: https://openjdk.org/jeps/427

In that JEP they state:

> Based upon experience and feedback we propose instead to allow when clauses in switch blocks to specify guards to pattern labels

So I assume people thought && was confusing.


In the case of &&, the left hand side of the expression (Tuner t) isn't a boolean expression.

In the case of "if", you're right, the grammar doesn't work, both because the syntax of Java already says that the if condition must be enclosed in parentheses and also because an if block is a statement and you need an expression here.

To make either of those work, you'd have to make the rest of the pattern matching syntax much worse.


The “&&” one is super confusing indeed, but the “if” one is just a question of style. It would be possible to reuse the “if” keyword for different grammar. python does it for its trinary operator, where there is an expression instead of a statement after “then” (e.g. “foo = if bar then 1 else 2”), but if Java does not have the same habit of reusing keywords already (not sure), it might not want to start now for consistency.


Java has two gramatically different forms of "try" in a similar fashion (one with parantheses and one without), I don't think this would be any worse than that.


Agreed. I guess "when" just reads a little bit better (english-wise), then.


This year. Six months from today.


(just on the following up on this)

https://en.wikipedia.org/wiki/Java_version_history

The non-LTS releases are every 6 months and have support for one year.

LTS releases are every 4th release which falls on odd numbered years in September.


> The non-LTS releases are every 6 months and have support for one year.

Not exactly, they are supported for 6 months (well, till the next JDK release) at least the builds on jdk.java.net page. Other vendors might provide different support.

Azul for example supports some releases (13 and 15) for 3.5 or 2.5 years (MTS)


Oh really? Sweet. It’s only relatively recently I’ve been able to use Java 17 everywhere (there were straggler codebases).

And now I’ll get 21 soon? Awesome.


Maybe they got inspired by C#?

var favoriteTask = obj switch

{

  Developer dev when dev.YearOfBirth == 1980 => $"{dev.FirstName} listens to metal",

  Developer dev => $"{dev.FirstName} writes code",

  Manager _ => "Create meetings",

  _ => "Do what objects do",
};


they want it to be as ugly as cpp


Maybe so, but please don't post unsubstantive and/or flamebait comments to Hacker News. We're trying for something different here: https://news.ycombinator.com/newsguidelines.html.


What are some good primers to understand the prominent new Java features? Not just in 20, but since, say, 8. Asking as someone who would like to be cursorily familiar with Java, but does not use it day to day. I'm very familiar with things like auto type inference, pattern matching, multi-line strings and structured concurrency from other languages, so I'm not looking for an introduction to Java. I'm looking for something like "Modern Java for the experienced Rust/Python/C++ programmer". Thanks!


Here are some resources I've found helpful and have read or are on my backlog to catch up with these developments:

- https://github.com/wesleyegberto/java-new-features (terse, includes links to JEPs, good jumping off point)

- https://github.com/winterbe/java8-tutorial (quick tour through features of Java 8)

- https://winterbe.com/posts/2018/09/24/java-11-tutorial/ (same for Java 11)

Books:

- Java 8 in Action / Modern Java in Action (Raoul-Gabriel Urma, Alan Mycroft, Mario Fusco; 2014 and 2018 respectively)

- The Well-Grounded Java Developer (Martijn Verburg, Benjamin Evans, Jason Clark; 2022) - not specifically focused on new features but does cover them in the context of going deeper into Java and the JVM.



Java Almanac can show the API differences between versions of Java.

https://javaalmanac.io/


You might find just browsing the JEPs will get you a long way. https://openjdk.org/jeps/0


I've been trying to get caught up while job searching after being laid off, and have found YouTube to be a pretty good source, the Java language advocates are all very keen to show off all the shiny new stuff. Also definitely recommend the JEPs as one of the other sibling comments did. All of the "Data Oriented Programming in Java" stuff is I think especially relevant, Brian Goetz had a good article on InfoQ about it.




Boring answer... but Chat GPT? I asked it what was new in Java 8 and it enumerated a number of features. I could proceed to ask it about more details for each, and then continue Java version by version and I think it would be quite exhaustive.

The other day I did something similar for cell biology. As a layman I wanted to learn more about DNA, mRNA, tRNA, translation, transcription, mitosis, miosis, etc. I think I learned it much better by querying Chat GPT than say reading Wikipedia. Something about the interaction, to ask for more details on what was not clear that helped me understand it.


When I’ve asked chatgpt to explain something to me that I’m very knowledgeable about, distributed systems, it was flat out wrong about many fundamentals in a way that would have seemed completely plausible to a novice. It was making up algorithms, complete with fake but believable names, to solve problems that are provably unsolvable.

My wife, a physician, reported similar errors when I got her to ask it medical questions.

Using chatgpt to learn about something new is flat out dangerous.


You have to provide more context, so that it has something to bridge in the high dimensional space.

> The Byzantine Generals Problem is a more difficult problem than the Two Generals Problem because it requires a consensus algorithm that can tolerate the presence of faulty actors. There are many solutions to the Byzantine Generals Problem, including Byzantine Fault Tolerance, which is a common technique used in distributed systems to ensure that the system can continue to operate correctly even in the presence of faulty actors.

One can't jump over a chasm, you have to literately bridge from the known to the unknown.

https://privatebin.net/?45ed3f39de71f289#eyiwC81q93MtfpimZBH...


You're supporting their point. You can't go to ChatGPT for knowledge you don't already have because it will confidently spout garbage, which is what was being suggested "whats new in java X". If you can build that bridge yourself then you aren't asking it for knowledge, you're asking it to format things for you.


I disagree, I use it all the time for things I don't know about. I then feed those discoveries into semanticscholar, scholar.google.com, wikipeida, libgen.is, etc.

I then ask it so summarize and ELI15. If ChatGPT is feeding you bogus knowledge then you are already consuming bogus knowledge over your existing channels. If you aren't including feedback into your understanding you are recording, not learning.


RP @johl@mastodon.xyz: I wish more people understood that "I want the computer to generate a natural language text that sounds like a plausible answer to a question about x" and "I want the computer to answer a question about x" are two very different problems.

https://aus.social/@johl@mastodon.xyz/110050185180546324


The problem with ChatGPT is that, when it should say "I don't know", it will instead confidently spew plausible sounding garbage.


So, like most people?


There are bullshitters in the real world, but for the most part they tend to just say "I don't know" in a really convoluted way. They won't, for example, completely fabricate entire concepts and speak confidently and in detail about them.

For example, I asked ChatGPT about the meaning of a non-existing verb ("to spoink"). It originally said the word didn't exist, but when I said "are you sure? we talked about it before, it's something to do with trifle" it invented an entire episode of the IT Crowd (Season 3, Episode 3), talked at length about how that word appeared twice. It claimed that one character - Moss - created it as the sound a metal ball would make if it hit a hard surface, and also that it's what happens when you leave a trifle out uncovered in the open and it goes lumpy. It's creative, but it's nonsense.

Very few humans could pull this off convincingly or would even attempt it. They would say "no we didn't talk about the verb 'to spoink' that sounds like nonsense".


Why not? "Spoink" is a perfectly cromulent word!


Haha :D Honestly though, if creative writing and absurd humour was the goal, they absolutely nailed it. I was particularly impressed with the onomatopoeia of the first “spoink” definition


No. When I ask a workmate how something works, they don't just lie to me. YMMV


I've avoided that because of the tedium of having to fact check everything.


> Boring answer... but Chat GPT?

Trained on data up to September 2021.


> Trained on data up to September 2021.

Wrap the ChatGPT API in a ReAct pattern implementation where one of its available actions is a search against the Java 20 docs?

Probably better for reference than tutorial, though.


Thanks! I keep forgetting to reach for this.


Really excited to see how virtual threads are taken up by developers, and if they affect the larger programming language community. They just really seem like "the best of both worlds" to me: the high scalability/low resource usage of async/await, with the ease-of-use experience of threads (e.g. not having to worry about "function coloring").


The fact that they’re cooperatively scheduled makes them quite problematic.

Basically a leaky abstraction, you have to think whether your code will block or not.

Go went through the same process, before eventually figuring out that they do need to make their lightweight threads (goroutines) preemptive.


Can you expand why its a leaky abstraction? Is it because virtual threads can only yield when its doing blocking operations?


Yes.

When you're in a "tight loop" (e.g. a matrix multiplication, which is basically 3 nested loops that only load data, do math, write data), Java's virtual threads just won't yield. So if you write your app in the "wrong" way, you lose concurrency.

There's a lot of discussion about this from the Go side. The original issue was this one: runtime: tight loops should be preemptible https://github.com/golang/go/issues/10958

> it's possible to write a tight loop (e.g., a numerical kernel or a spin on an atomic) with no calls or allocation that arbitrarily delays preemption. This can result in arbitrarily long pause times as the GC waits for all goroutines to stop.

The proposed (and ultimately accepted, AFAIK) solution is described here: https://github.com/golang/go/issues/24543

> has put significant effort into prototyping cooperative preemption points in loops, which is one way to solve this problem. However, even sophisticated approaches to this led to unacceptable slow-downs in tight loops (where slow-downs are generally least acceptable).

> I propose that the Go implementation switch to non-cooperative preemption using stack and register maps at (essentially) every instruction. This would allow goroutines to be preempted without explicit preemption checks. This approach will solve the problem of delayed preemption with zero run-time overhead and have side benefits for debugger function calls

I 100% expect Java will have to do through the same evolution. But first they'll probably try to deny reality for a few years. Funny enough, same as has happened with Go and generics.


I doubt it, since they already did that journey long ago and are now adding virtual threads next to ordinary threads that replaced the original green threads. If you put your long running work into the virtual kind of threadpool, a timing sentinel can easily warn you and after noticing that you easily use the normal threadpool instead.

VertX framework had such a sentinel, but migrating code from the async futures to a normal threadpool can be a bit tedious if your design is poor.


I'm personally not so sure. We already had something like that before (M:N threads), and the world moved away from it, towards letting the kernel manage everything (1:1 threads). So I'd expect instead that operating system kernels gain whatever features are missing for scaling to a higher number of threads, and everything once again goes back to each programming language thread corresponding to one kernel thread.


The problem of scaling threads up further is fundamental and not really solvable by more kernel features. JVM virtual threads can be efficient because the runtime has complete knowledge of the executing code and stack layouts, how the heap is laid out, how the GC works and it can control how code is compiled. The kernel can't do any of these things - it has to assume a process is a black box that could do anything with its stacks, could be compiled by anything and so on.

Note that this advantage obviously goes away the moment you call into native code. Then the JVM is in the same position as the kernel. It doesn't control the compiler or the stack any more, and so that's why a virtual thread becomes "pinned" at that point and you lose the efficiency (the JVM needs to acquire more kernel threads). Fortunately though the JVM ecosystem doesn't rely on native code all that much, so it should be rare in practice.

This advantage can be brought to other non-Java languages too via Truffle. Truffle languages are reimplemented on top of Java and when programs call into native code they have the option of calling into JIT compiled LLVM bitcode instead of real native code (or indeed any JVM bytecode library). In that situation the JVM remains in control and so things should in theory still be Loom-able. Not sure if that's currently true in practice, but it could be.


> JVM virtual threads can be efficient because the runtime has complete knowledge of the executing code and stack layouts, how the heap is laid out, how the GC works and it can control how code is compiled.

Forgive me for staying doubtful, but I recall hearing this same "the JVM can be very fast and efficient because its JIT has complete knowledge and control" spiel back in the 90s, and back then, anyone could clearly see that the JVM was not as fast compared to pre-compiled native code as it was being promised.

> The kernel can't do any of these things - it has to assume a process is a black box that could do anything with its stacks, could be compiled by anything and so on.

The kernel has to assume nothing; it can dictate how userspace processes behave. As an example, a process which plays too many games with its stacks, without kernel cooperation, will quickly find out that signals share the same stack unless the kernel is told to use an alternate stack. A process which uses a register declared in the platform ABI as being for kernel use will find out that it can be unpredictably overwritten on a context switch. There are things like shadow stacks and segment register bases which can only be manipulated when the kernel allows it. And so on.

Of course, for compatibility reasons, the current ABI allows userspace processes to do a lot of unpredictable things, but nothing prevents a new "highly scalable threads" process ABI, with stricter rules, from being developed if necessary. Or it could be that only a few cooperative additions to the userspace to kernel ABI are necessary; we already have things like the many options to the clone() system calls, the futex system call, restartable sequences, etc.


> the JVM was not as fast compared to pre-compiled native code as it was being promised

Well head-for-head Java will still lose to C++ in many benchmarks, but that's not really due to compiled code quality, it's more about language semantics. Java is very fast for the sort of language it currently is. The big wins for C++ are that Java doesn't have value types or support for vector operations. Both are under development, actually vector ops is basically done but it's waiting for support for value types (see discussion elsewhere).

Also GCd languages trend towards a functional style without much in-place mutation, whereas C++ trends in the opposite direction, so C++ will sometimes use the CPU cache more effectively just due to prevailing habits amongst programmers.

> The kernel has to assume nothing; it can dictate how userspace processes behave.

Yes in theory you could fuse the language VM with the kernel and research operating systems like MSR Singularity did that. But a normal kernel like NT, Linux or Darwin can't do this and not only for backwards compatibility. The JVM will do things like move a virtual thread stack back and forth from the garbage collected heap and do so on the fly. Unless the kernel contains a JIT compiler, GC and injects lots of runtime code into the app's process it's going to find it tricky to do the same. By the time you've done the same you haven't implemented better kernel threads, you've made the JVM run in the kernel.


It's been a while since I read up on this, but my understanding is that with OS threads, during a context switch it has to pop the entire process stack, which in Java is 1MB by default. This is expensive. Virtual threads "context switches" have much more lightweight stacks because the JVM knows exactly what kind of state needs to be associated with the virtual thread and thats where the difference lies.


The JVM is comparable to pre-compiled native code these days. It has evolved and improved since the 90s.


But the kernel doesn’t know the semantics — can that blocking call be turned into a non-blocking, or is blocking the wanted functionality here?


M:N is not the interesting aspect of virtual threads at all, automagically turning blocking operations into non-blocking is - which has not really been tried before (with erlang and go being the first).


GNU Pth had "automagically turning blocking operations into non-blocking" ages ago, and it wasn't the first.

I think what you probably had in mind is that C libraries of the 90s that did M:N threading didn't turn blocking operations into non-blocking?

Using blocking operations to switch contexts is really nothing new. Heck, the cooperative multi-tasking systems of the 80s (Mac, Amiga) all essentially did that for processes (not threads), and so did Unix in the 70s.


> I think what you probably had in mind is that C libraries of the 90s that did M:N threading didn't turn blocking operations into non-blocking

Yes, mostly, though my history knowledge is definitely lacking so do correct me if I’m wrong.

But you are right, there was nothing fundamentally missing, probably just no good OS support for non-blocking IO calls in the early days? Though probably the IO-CPU ratio was also different, so the benefits were not as big?


There were bad experiences with the M:N threading of the 90s in Solaris' and others' C libraries. Making those libraries make every file descriptor non-blocking behind the programmer's back was a tricky thing. Think about inheritance of file descriptors via fork() and exec() -- you could have one threaded process sharing an FD with a non-threaded process, and now even non-threaded processes' C library would have to poll(), and now add static linking with older C libraries to the mix and it just couldn't be done. So it wasn't done.


Which makes me wonder why this can be done in Java or Erlang, and the answer is that those tend to be walled gardens from which one does not fork/exec.


> automagically turning blocking operations into non-blocking is - which has not really been tried before (with erlang and go being the first).

sorry, but Haskell has had it way before Go, with proper STM too. Neither Erlang nor Go are offering the same level of ergonomics for compile-time checked M:N threading.


> M:N is not the interesting aspect of virtual threads at all, automagically turning blocking operations into non-blocking is

I'm not very into this Loom virtual threads thing, but... what's the difference between this automagically conversion of blocking into non-blocking in a M:N model and a 1:1 one? I mean, couldn't the same be done with normal threads too?


Well, to a degree this is also done by the OS, IO syscalls are frequent locations where the OS scheduler might decide to schedule another thread, but this is a very slow context switch (flushing caches, including TLB, the switch to kernel mode and back, and since heartbleed and alia it is even more expensive).

Loom implements every IO on top of a more modern async OS calls, and these virtual thread context switches are on the order of function calls, so the overhead and number of switches that can happen are much much lower.


Thanks!


> low resource usage of async/await

Will calling a coroutine do zero heap allocations like async in Rust?

> with the ease-of-use experience of threads

That's highly subjective. Threads usually require locking which is often hard to get performant and correct at the same time. Async/await allows to write concurrent code with no synchronization.


> Async/await allows to write concurrent code with no synchronization

Well you still need some sort of synchronization, because an "await" allows arbitrary other actions to occur. If an await is introduced in code you transitively call then you might find that some invariant you were expecting to hold has now changed across a call when it previously didn't. Fundamentally, locks are about making invariants atomic and that's independent of exactly how code is scheduled and when.


This is impossible because of *gasp function coloring. You can't just make a sync function async without dealing with the fallout explicitly in c#.


You can make an async function contain an await at more places than it previously had. When writing code in colored languages your code tends towards lots of stuff being marked async, so more 'await' points being introduced can change behavior.


You're saying something is bad because it could affect a race condition you had? Doesn't everything fall under that issue? That's not changing from invariant to variant.


I'm not saying it's bad, I'm saying that you can still have races and thus still need some form of synchronization even when using async/await. Whilst in simple cases you can preserve invariants just by carefully choosing where an await is done, as things get more complex you constantly run the risk that someone will introduce another await somewhere else (and maybe more async marked functions to enable that), without understanding that the 'await' can now run code that violates some invariants. Locks and other such mechanisms let you mark certain code as executing atomically regardless of scheduling.


> Async/await allows to write concurrent code with no synchronization.

Hmm,I don't see how async/awaits makes a difference. Care to explain?

Like, if you have multiple sources that can add or read from a queue, unless there is a single thread running all your async loops (ala python), you still need some synchronization. At least that's my experience using coroutines heavily in kotlin.


I'm talking from perspective of Rust's async/await implementation, I'm not sure if the same holds for other languages with async like C# or Kotlin. Nevertheless I can do a loop like this:

    let mut buffer = ... // create buffer for holding data
    let mut input: TcpStream = ... // connect to remote endpoint
    let mut output: TcpStream = ... // connect to remote endpoint
    loop {
       select! {
          _ = input.readable() => {
              input.try_read(&mut buffer)?;
          }
          _ = output.writable(), if !buffer.is_empty() {
              output.try_write(&mut buffer)?;
          }
       }
    }
The mutable buffer is shared between the part that reads from input and the part that writes to output, and reads/writes happen concurrently and independently. Yet there is no explicit locking anywhere!

Synchronization is achieved implicitly by the fact that sequential code executes in only one place at once, so when it executes the reading branch, it does not execute the writing branch. You can apply exactly same reasoning as with any single-threaded, sequential code.

You cannot model this easily with threads. If it was a single thread with blocking I/O, then it could block forever in one branch and stop reacting to events on other branches. If it were multiple threads, then they would somehow need to synchronize accesses explicitly to the shared buffer.


The synchronization is handled by the runtime. While you could still have concurrent access issues, async tasks will not deadlock in c#, unlike lock mechanisms. You don't get concurrency for free, but the finer details of lock management are avoided.


Anything is better than asynchronous.


As a Clojure programmer, I don't care about any of the Java language features or improvements, but I'm super happy that I'm getting a state of the art JVM that is continuously developed, maintained, extended and optimized, over a time scale of decades.

This is incredibly useful: having a good VM to run your code in, with good modern garbage collectors, is not an obvious thing (as many other languages have learned).

This is not the LTS release, so I won't be switching to it, but I'm looking forward to the next LTS.


As Clojure programmer, i tell you that you should, because any moment Java introduce new features libraries will start using and most Clojure libraries are Java libraries wrappers.

In addition if you want to use these new Java libraries in case and Clojure does not catch up with new Java features the ergonomics of using Java libraries with clojure decrease.

And still they are trying to figure out how Ifn Clojure interface with Java functional interfaces.

Also when Project Loom lands on JVM it will benefit Clojure too, allowing to remove code for instance of Clojure futures.

If Clojure catch up with Value classes can increase performance of Clojure too.

But this disregard of Java features or improvements is the kind of Clojure developer so content of what he have that forgots that can get better things.


As another Clojure programmer, I say you should care about developments in Java. After all, the Java module system is precisely why classes became minefields with clojure.core/bean -- illegal reflective accesses and what not.

As someone else noted in this comment section, a lot of useful Clojure libraries are wrappers over Java libraries. So improvements to Java used in these libraries are good for you, too.


The Java module system is a problem on Java as well. They defined "module" in the narrowest terms possible (API visibility) without addressing any of the modularization problems Java developers have to deal with every day, so we end up with another layer on top of the layers of third-party dependency management systems, package repositories, and runtime class loaders that we are forced to use to have a semblance of a working build and deployment toolchain. They could have expanded upon this foundation and came up with the equivalent of Cargo or go modules, but instead they created this n+1 standard that 90% of developers either ignore or disable.

I'm sure it works for OpenJDK though.


> They defined "module" in the narrowest terms possible (API visibility) without addressing any of the modularization problems Java developers have to deal with every day

What other way would be possible, how would you solve it?


I second this, using jRuby


Any user of a guest language should care about what the "systems" language of the platform offers, if nothing else, to understand those stack traces, and FFI to platform libraries.


The same applies to Clojure CLR, right?


I haven't looked into CLR in a long time, but it feels like it has a fraction of JVM's adoption and community size. Microsoft also seems to be prioritizing Typescript and Node internally with its recent moves.


> Microsoft also seems to be prioritizing Typescript and Node internally with its recent moves.

You may want to look at Blazor Webassembly (https://dotnet.microsoft.com/en-us/apps/aspnet/web-apps/blaz...) and Blazor United (https://devblogs.microsoft.com/dotnet/asp-net-core-updates-i...)

They scrapped the old CLR and started over with a new "CLR Core".

Then they ported the new CLR Core along with the framework BCL to webassembly and has the CLR running in the browser, upon which they built Blazor Webassembly.

So you can now target the CLR and have your code run in the browser.

And now they are making progress on Blazor Unified where components can start of as server-side rendered exclusively and transparently and automatically move to webassembly rendering within the same application or page. It really is crazy stuff.

Typescript is not the only thing going on in MS Engineering.


To be exact, they have three active CLRs: CoreCLR, (Framework) CLR and MonoVM. WebAssembly, Android and iOS apps use the MonoVM because it is optimized on AOT.


At the risk of starting a flame war, the CLI and the coreCLR are fare superior VM and platform from a technical stand point. A lot of the features schedule for jave 21 / project Valhalla are just basically catching up to modern VM design. Of course there is more to the choice of a platform than just the technical differences.


There would be a world for this argument if the CLR had anything even remotely resembeling hotspot runtime optimization.

What you describe is the result of different philosophies/priorities. CLR focuses on static compile-time optimization, while the JVM is a highly dynamic construct with unmatched runtime analysis. In the 90s, there was a hope that with sufficient escape analysis, the need for user-defined primitives would vanish, which is why value types have not been done prior.

By themselves, accessing values via stack and not by reference is technically trivial. The problem lies in backporting that kind of stuff.


> What you describe is the result of different philosophies/priorities

Maybe, it might also be the results of bad design decisions.

> highly dynamic construct with unmatched runtime analysis

As someone who spend quite a bit of time working on custom optimization around hotspot, i failed to see how anyone can describe the current state of the JVM ( J9 is a bit better) as unmatched. V8 and some some extend Julialang have much strong dynamic analysis.

> CLR focuses on static compile-time optimization

Sources ?


> Maybe, it might also be the results of bad design decisions.

Most assuredly not. Back in the 90s, the cost of loading memory and performing a CPU instruction was essentially equal. Today, fetching data from RAM takes 100x longer than a CPU instruction. This makes locality of data absolutely crucial and is a consequence of computing throughput increasing, but latency remaining stagnant (think of it like a database transaction).

With focus on garbage collection, it made sense to throw everything into the heap and use runtime analysis to inline as much as possible. Nobody has forseen how such hardware fundamentals would change over the next decades.

As far as I'm concerned, the proposed JVM spec for value types is the most promising model I have seen anywhere. Instead of a binary choice between entities in the heap and values on the stack, you have a more granular control with incremental benefits and constraints.

> As someone who spend quite a bit of time working on custom optimization around hotspot, i failed to see how anyone can describe the current state of the JVM ( J9 is a bit better) as unmatched. V8 and some some extend Julialang have much strong dynamic analysis.

Didn't know that, probably worth looking into. Though, I wonder how much you can compare V8 and the JVM, given the fundamental difference between a static and dynamic language.

> CLR focuses on static compile-time optimization

I thought that was common knowledge. I mean, does the latest CLR perform any significant amount of runtime optimizations? From what I've read the CLR makes sue of cpu-specific instructions such as SIMD, but no cache/layout optimizations or any inlining during runtime.

https://learn.microsoft.com/en-us/archive/blogs/davidnotario...


The Java JVM was originally designed with a very dynamic language in mind, e.g. Java's support for dynamic loading, dynamic binding, reflection etc. The influences at the time were Smalltalk and ObjectiveC. As Java has evolved to be a much more statically typed language (especially Java 5), the JVM has somewhat struggled to exploit this while maintaining backwards compatibility. It's nowhere near as bad as the Python situation though. I believe the CLR was built with things like parametric specialisation in mind from the beginning.


I agree with all of this. I am not sure if you meant this as a opposition to my comment.


Not opposition, I was just trying to adding some historical context to your comment. I agree the JVM is poorly suited for modern Java and Oracle know this too, hence they are developing GraalVM.


Im what aspect is the VM “far superior”? Where are CLR’s state-of-the-art GC or low-level GC? Or does it have observability tools like JFR?


I do not use C# (in fact, I do my best to avoid Microsoft products). But I can tell you that there are obvious workarounds C# employs to work around its lackluster GC. Stack allocation and spans immediately come to mind.

As far as I know, Java offers no way to mark objects as stack-allocated, but C# does. Spans in C# allow programmers to produce subarrays without copying. Enums in C# are stack-allocated, unlike Java. So on, so forth. None of this is a huge deal for Java since some of the best GCs in the world are implemented atop the JVM. But I do think C# offers its workarounds when GC performance gets in the way.


But these are all tredeoffs, that make C# almost as complex as C++. Sure, there are cases where these low-level optimizations allow for better performance, but don’t forget that the more things we specify, the less freedom does the runtime have. SQL is a good example here, it specifies the what and not the how, and this makes a good db very hard to beat on complex queries.

The way I have seen it described somewhere: C# has a slightly higher performance ceiling, but a naive application may very well run faster in Java.


In Java, primitives go on the stack. Escape analysis helps in other ways.



Note all the new cool stuff is incubator and preview status and I'm a little worried that this will still be the case for the JDK 21 stable release.


I have hopes to see Virtual Threads in Java 21, also being the next LTS.


I think this is supposed to be the last preview version of FFI and the other JEPs from Project Panama, so hopefully we will have them at least but I definitely share a bit of that concern.


Probably not this go-around: https://openjdk.org/jeps/442

Better to be right then rushed though.


IIRC they are going to release pattern matching for switch and record deconstruction as stable in JDK 21.


I really hope to see the SIMD vector related improvements come through, I know there are some independent JDK code bases that have more evolved SIMD support, but for a lot of big data processing tools, switching away from jdk based languages appears to be the way SIMD related performance improvements have been realized because of this missing crucial feature set. JDK 11 was when this stuff was first in development, and it's truthfully insane that it hasn't fully been released yet.


Most of these low-level APIs simply just wait for value types - they don’t want to hardcode something into the language, that might not be a perfect fit for those. Which makes sense, frankly.


Within limits. Valhalla seems to be still far from launching, and allowing features to back up behind it might just recreate the situation Java has previously found itself in during the 7/8 era where slippage in massive projects caused everything else to slip too. The new release cycle was meant to fix that, but if half the new stuff ends up waiting for value types then the problem has just reappeared in a new form.

The odd thing is that part of Valhalla is about how to migrate existing types to being value types, and they already have code that can simulate the same restrictions. So it's not really clear why these features have to wait. Migration is a part of the Valhalla plan anyway.


its amazing how much the world hasn’t been able to keep up with java. github PRs still have no syntax highlighting support for record and var


Github uses Linguist for syntax highlighting and has a section in their CONTRIBUTING document for fixing bad syntax highlighting [0].

Admittedly I couldn't figure out exactly what repo is being used for Java's syntax highlighting since vendor/README.md says Java is using tree-sitter/tree-sitter-java and grammar.js in that repo appeears to already include references to record.

languages.yml [1] shows ace_mode java, and ace editor's java_highlight_rules.js doesn't mention record anywhere. I'm not clear if github is using ace editor or code mirror, since both are mentioned there.

Hopefully this helps get started on improving the poor syntax highlighting experience.

[0] https://github.com/github/linguist/blob/master/CONTRIBUTING....

[1] https://github.com/github/linguist/blob/c34f887f48d81e2ed42b...

[2] https://github.com/ajaxorg/ace/blob/a2e89b94b4dcdff28bf3c8f4...


'The world' in this case also includes a large portion of java programmers; it takes years for Java programmers to adopt new language features, if they do so at all. They're almost as bad as C++ devs.

And if you consider the history of Java as a language since the 1990's, it has historically had very slow feature releases. If you review the release history of Java, almost half of major relases and changes to the language came after 2014+. var in Java came out in 2018, record was introduced out in 2020 - feaures that were copied from other languages that already had them for years. Java as a language is still is playing catchup here.


>>Java as a language is still is playing catchup here.

It's a feature, not a bug.


With Java it's always win/win. If external software works with it, "Java has great tooling", if it doesn't, the rest of the world is slow.


Why do you think that is? I thought things like the language server protocol made this a self service kind of problem. Who needs to be catching up?


Because syntax highlighting in JavaScript is (presumingly) not covered by LSP but by a generic syntax parser. And these updates take endless through the supply chain pipeline.

And as a side note: LSPs are not exactly free these days anymore. Java is not considering it core project (I guess outsourced to RedHat and Eclipse) and Microsoft has a very erratic behavior towards what is in and out the LSP or DAP.


It's highly amusing to watch java folks frame this as the rest of the world not "keeping up" when it's pretty obvious it's the rest of the world not caring enough to do anything about it.

I wonder how long it will be before they realize they are in the slow, sad, decline regardless of how many features get added.


I think this is true for young companies - for example every crypto startup I’ve interacted with makes their backend in either Node.js, Golang, or Rust.

There is quite a lot of legacy software that uses Java though.


Sure, no doubt there. Where i work has hundreds of millions of lines of java code, and i was the director responsible for the java team for years ;)

But:

1. The enterprise users aren't spending their time chasing java versions, and probably already gave up and have a support contract with Azul or someone, or just stick with an unsupported versions.

This is why all the cloud vendors, for example, either have such deals, or provide their own guarantee of support.

So they almost certainly do not care about Java 20 anytime soon unless they are in the weird category of "serious java shop" (rather than legacy java shop), which is quickly shrinking. That is not a big enough class that people will "try to keep up" to frame it in the java-centric version used by GP.

2. The non-enterprise users are also a shrinking base.


Now even Java has pattern matching, what's Javascript's excuse?

Literally the only feature preventing Typescript from being my perfect language.


https://github.com/tc39/proposal-pattern-matching

I'm not sure if it's stalled or not. But it's being discussed.


We're still developing this proposal. be patient LOL


Wow that's a proposal! I really like the idea of pattern matching, and I'm glad you're steering clear of modifying switch, but I find the sketches in the proposal pretty hard to read, especially with the combinator examples. I hope you guys take your time and stay more proud of what you leave out than what you put in.


The combinator example is the JS equivalent of what was discussed in another comment on this article, https://news.ycombinator.com/item?id=35247615. I like having the clarity of using different "and" and "or" keywords instead of overloading && and ||.

But I definitely agree with the sentiment - every new feature is another thing that a developer needs to learn if they encounter that code. There should be a large "barrier to entry" for new features.


This is something that far too few folks put in their progress/change logs.

"We didn't do ..." is so incredibly important.


I've got infinite patience for the brave souls who propose and implement TC-39 specs. =)


Sorry, that was rude of me. I know it's a ton of work, and am endlessly appreciative of the work you (I assume?) and others have been doing to push this forward.


Given the terrible result we came up with in the Python community, I understand the JS wants to take their time. It's a great feature, but it's hard to get right, especially on an existing language.

Not to mention JS is not typed, but pattern matching that can't match typescript types would be terrible.


What's wrong with match? I've used it quite a bit and it is quite powerful.


The syntax is reusing regular python syntax with completely different effects, so you have to learn 2 syntaxes.

Trying to do simple things like matching the content of a variable will fail if you forget you have to use a dotted path if you want to avoid binding.

Trying to do advanced things such as "match a dict with variable key names and values, but you wish to enforce the number and the types and unpack them in variables" is ridiculously twisted to do, when it's even possible.

I use match/case, but it like the early years of type hints or asyncio: terrible ergonomics, and we know there is a ceiling to how much it can improve.


Its incredibly powerful! One downside is variables captured in the match are lifted to the function level. The lack of block level scoping in the match statement leads to a lot of people shooting themselves in the foot by overwriting a variable that they didn't intend to. On a personal note, I find the 2 levels of indentation to reach the case body to be too much as well... but that's just a personal preference.


I don't see a huge benefit for pattern matching in JS outside of it being expression based rather than statement based.

Either way, the benefit has to be enormous. It's a feature that can easily break existing code.


How would it break existing code?


Because it is an expression that introduces a new keyword.

You could say “return match…”

It’s not easy to introduce without breakage. Not saying it cannot be done.

But PHP didn’t manage to for example. It also isn’t real pattern matching but that’s a different issue.


Oh, so you were referring to the specific syntax in the current proposal, rather than pattern matching in general?

Java and C# show how the syntax can be done in a fully backwards-compatible manner. It looks like it's just not the JS way by choice - apparently every new keyword that was added since ES5 is an actual keyword reserved in all contexts, not context-dependent?


That’s good to hear!


In a world where upgrading node versions without language changes breaks existing code I wouldn’t be too worried.


What’ the hype with pattern matching? What does it solve?


If you've ever done any assembly programming or worked with other old or low level languages, you may have encountered an environment where you can write simple operator expressions, but you can't compose them. So this is OK:

    a = b + c
    d = e - f
    g = a * d
But the compiler doesn't allow:

    g = (b + c) * (e - f)
You have expressions that produce, but they don't compose. You can't produce a value from a more complex, nested expression. We, rightly, no longer use languages like that.

Pattern matching parallels that, except for assignment and decomposing values. Many languages today let you write:

    topLeft = rect.topLeft.x;
    left = topLeft.x;
    top = topLeft.y;
    bottomRight = rect.bottomRight;
    right = bottomRight.x;
    bottom = bottomRight.y;
Or even:

    left = rect.topLeft.x;
    top = rect.topLeft.y;
    right = rect.bottomRight.x;
    bottom = rect.bottomRight.y;
(Because at least you can compose expressions on the RHS.) But they don't let you write:

    (topLeft, bottomRight) = rect;
    (left, top) = topLeft;
    (right, bottom) = bottomRight;
Or even:

    ((left, top), (right, bottom)) = rect;
Pattern matching gives you that. It is freely composable destructuring.

Also, the "matching" part means that in many languages you can also ask questions about values as you destructure them, which enables a particularly nice style of programming.


All of your examples basically map to Javascript destructuring, which is already fully supported. Comment you are replying to is asking about pattern matching for flow control or conditional assignment, which JS doesn't currently support.


> the "matching" part means that in many languages you can also ask questions about values as you destructure them,

I started implementing Lox with Java's sealed classes + pattern matching on switch. The exhaustiveness has been really nice to ensure I cover each new token/expression as I add them.


Is Dart's pattern matching influencing the JS proposal as well? Or are both sort of separate without much influence from either side?


I have read the JS proposal many times, so it's an influence on Dart. I suspect Dart hasn't had much influence on JS because our proposal is newer.


Hype with pattern matching? That sounds quite funny to me considering certain languages (Haskell, Erlang, OCaml...) have had that for decades.

What convinced me of the power of pattern matching was seeing a red-black binary tree being implemented effortlessly in Ocaml (I think), while in C++ and Java it was a really difficult algorithm to implement.

When you have provably exhaustive pattern matching (i.e. the compiler forces you to handle every possible case), certain things that are very difficult to write otherwise become very easy.


It's really nice syntactic sugar. Complicated conditional logic cluttered with redundant types turns into a series of simple patterns. "Just" makes it easier to not make stupid bugs (I'm all for it).


Agreed. Example from Scala

  // Given an Option
  val maybeThing: Option[String] = getThing()  

  // Classic
  if (maybeThing.isDefined) {
    useThing(maybeThing.get)
  } else {
    NotFoundResponseEtc()
  }  

  // Pattern matching (not too IDE auto generated exhaustive match cases!)
  maybeThing match {
    case Some(thing) => useThing(thing)
    case None        => NotFoundResponseEtc()
  }

This scales well when matching a higher cardinality of things like a variety of Exceptions or enums or other tuple responses like Either etc.


It makes it very easy to deal with nested data structures. Imagine creating a nested map/struct literal, and then extract the values out using a very similar syntax on a single line.

It's one of those things that when you get used to, you wonder why other languages don't implement it.


I think this answers the question very well, thank you. I found this [1] documentation from Ruby explain what it solves pretty good aswell.

[1] https://docs.ruby-lang.org/en/3.0/syntax/pattern_matching_rd...



A good coverage of all the JEPs included in JDK 20: https://foojay.io/today/its-java-20-release-day-heres-whats-...


Damn, time flies.

I remember the excitement from the different alpha/beta versions that were coming out in 1994/1995.


Time only started flying with the release of Java 9. Prior to that versions stuck around for years.


I still remember the Java launch party that was done by Sun Microsystems (regional office).


Coming from the telegram bot of hacker news, where this post has a lot of downvotes (21 down to 6 up, which is quite a lot for how many reactions usually are there), could someone (perhaps even from people who downvoted) explain why this news is met with such negativity?

I kind of assume it's due to the cliché Java-oriented hatred, but curious to hear opinions...



Minor typo in the linked site:

"JDK 20 reached General Availability on 21 March 2022"

Should be 21 March 2023

Not sure who can edit that page but putting here.


Just adding too, I think some of the switch statement example code is wrong:

    Object obj = 123L;
    String formatted = switch (obj) {
        case Integer i -> String.format("int %d", i);
        case Long l    -> String.format("long %d", l);
        case Double d  -> String.format("double %f", d);
        case String s  -> String.format("String %s", s);
        default        -> o.toString();
    };
Unless I'm not understanding something, the default statement should be `obj.toString()` as `o` isn't defined.


JFC, I was going to say they should have used `String.valueOf(obj)` since I expected `Object obj = null; switch (obj) { default ->` to do something sane but no, the stupid PoS NPEs because `switch` itself evidently calls `Objects.requireNonNull`

The Billion Dollar Mistake meets the Power of Compounding Interest to produce an inflation adjusted number that just keeps on giving


You can add a 'case null' explicitly and merge it with default using 'case null, default -> ...'.

See [1] for more.

[1] https://openjdk.org/jeps/433


This was fixed by Mark Reinhold


I'm curious what is meant by a "preview feature, or second preview feature, or..." in a released product?? I don't suppose they are going to remove it later. So in what way is it a preview? Are they signalling that it might have breaking changes in the future?


"A preview feature is a new feature whose design, specification, and implementation are complete, but which is not permanent, which means that the feature may exist in a different form or not at all in future JDK releases."

https://docs.oracle.com/en/java/javase/19/language/preview-l...


Yes, they reserve the possibilty to completely redo or even remove these preview features. They are not enabled by default, you have to opt in with a compiler flag.


ok, that makes some sense.


You can read all about it here: https://openjdk.org/jeps/12

with a slight addendum here: https://openjdk.org/jeps/8300604

and podcast that discusses both: https://inside.java/2023/03/21/podcast-030/


Here's me wondering how many more years will pass before the Wayland support is going GA.


If applets and web start are dead, and Wayland doesn’t render remotely, how would this work? Would a Java app server have to ask the user to run something on the desktop?


There are java desktop applications, e.g. IntelliJ, SQLDeveloper, etc.


IntelliJ is one of the few GUI apps on my work laptop, but my company is encouraging everyone to use JetBrains Gateway with a remote server farm instead (which sort of seems like a browser DOM only for their IDEs).

When a backend needs an internal UI here, it's always a Javascript app in a browser.


Desktop apps are a niche, but a well-needed niche. Plus it’s not like there isn’t already a huge amount of Java desktop apps, and those are all running in X instead of Wayland.


Could the pundits of HN please compare this release with Kotlin?


Picking the release notes apart:

- ScopedValue (Incubator). Seems like a replacement for ThreadLocal that is intended to be a bit less dangerous (it is notorious for leaking memory and file handles). The Kotlin equivalent would be CoroutineScope. I'd say the latter is the cleaner solution. And probably ScopedValues came into existence for the same reason (co-routines & structured concurrency kind of breaking ThreadLocal a bit).

- Record Patterns (Second Preview). This looks a bit like Kotlin's smart casts. Useful. I think the Kotlin implementation with contracts is a bit more powerful and broadly applicable in more use cases. But nice of course.

- Pattern Matching for switch (Fourth Preview). That looks to me like an attempt to beef up the Java switch, which is a welcome change. The Kotlin equivalent would be when. Not a whole lot of difference at first glance. But combined with smart casts, Kotlin is quite nice already IMHO. Scala developers might disagree about a thing or two here..

- Foreign Function & Memory API (Second Preview). Looks like a nice JVM level feature for integrating native code that is potentially also of use to the Kotlin language developers.

- Virtual Threads (Second Preview). The second iteration of Loom. The JVM level implementation for this is going to make Kotlin's co-routines potentially even nicer. I don't think it should be that disruptive for Kotlin developers already using co-routines but performance increases are nice.

- Structured Concurrency. Also Loom related. From what I've seen of Loom so far, it should just work with your existing code without too much changes. And co-routines are definitely a nice API to do structured concurrency so not particularly relevant for Kotlin users. But it's going to be a big enabler for Java developers that have lacked this.

- Vector API (Fifth Incubator). Another JVM level optimization that the Kotlin developers should be able to make use of. I imagine projects like the kotlindl framework (a deep learning framework) can make use of this. A bit niche but nice if you use things like that.

So, nice incremental change and a few nice things that Kotlin will benefit from probably. Whether you use Java or Kotlin, it's going to be nicer for everyone once this ships in an LTS jdk.


- ScopedValue doesn't have a direct equivalent in Kotlin, but CoroutineContext.Element and ThreadContextElement inside of CoroutineContext serve the same purpose. I really think Kotlin overcomplicated this idea.

- Record Patterns are stronger than Kotlins smart cast because of nested patterns

- Kotlin Coroutines works exclusively with Structured Concurrency already. The JEP just adds a way to use Structured Concurrency with threads (it can also be used in Kotlin if you use pure Threads(virtual or not), but I don't see any reason not to use Coroutines


Most Kotlin users don't get anything from JVM, as the majority target Android, and Google doesn't care that much about keeping Java up to date, even the latest LTS updates, are partial support.


This seems like an appropriate place to post the official Java rap: https://youtu.be/b-Cr0EWwaTk


I prefer this one: Java Forever And Ever Movie (Java vs Windows .Net)

https://www.youtube.com/watch?v=RnqAXuLZlaE


All the JavaZone videos are amazing. Like this one: https://www.youtube.com/watch?v=1JZnj4eNHXE


Awesome but today this would be a different story line ... maybe: two old conservatives (Java and C#) re-inventing themselves to fight against the hipsters (Go, Swift, JS, ...) ;)


JS is older than C#.


While true, the hipster languages are the reason why minimal APIs came to be.

.NET is loosing people to the "cool languages" in regards to younger generations.


What's the relationship between the Java releases and GraalVM releases?


They're being synchronized at the moment.


As an engineer that last used Java when it was Java 11, I cringe at the thought of diving back in. A trade-off of increased release velocity for any language I suppose.


It's really not as bad as it used to be. I recently picked it up again after playing with Spring boot. I got to say Spring boot seems awesome, and java as a whole it much more ergonomic. Worth another look.


I use Clojure, which benefits from these consistent JVM improvements; things like pattern matching have been accessible to me for years already, but I like that Java slowly trails along as it helps keep me vested in the Java ecosystem.


By contrast I despair of Clojure ever keeping pace with Java updates. Clojure seems to be a functional language which isn't even implemented using the host's functional features which were introduced 9 years ago.


I guess that only matters if we're missing out on performance improvements by not using native language features; be cool to see if anyone has done any profiling/investigation on that.

Clojure the language isn't missing anything I need.


Why? Only the encapsulation difference was a slightly bigger breaking change since 8, after you are done with that it really is smooth sailing.

Or if you mean addition of new features, then Java is very conservative on that front and it is still a very small language compared to most other mainstream ones.


You don’t need to learn or use any of the new features. Most devs seem to still pretend they are writing for Java 8 anyway.


Nearly all of the language changes except for the thread changes are minor quality of life things.


I still don't understand what's the correct way to replace thread locals holding initialization heavy & non thread-safe instances besides using a pool which produces a lot of overhead when using loom/virtual threads. In the past, with ThreadLocal, you'd have one instance per thread, making sure that those instances could only be used once and would only be instantiated for all threads of your (acceptor) thread pool

Does anyone know the solution?


Virtual threads are designed for light, IO-bound, tasks, if your task is heavy/CPU-bound tasks then you won't necessarily want to switch to virtual threads with ScopedValue.


Structured concurrency looks a lot like C#'s Task library without the async/await sugar but with the exact same footguns.

Joining into a synchronous function is an anti-pattern in C# because you consume threads. This seems to be the same, except with the assumption that virtual threads are fine.... But that's not an assumption that can be made by the method writer, right?


Tasks aren't threads in TPL.


What are you trying to say? They still run on threads and can consume them.


They are no different from how Go routines or Java's virtual threads multiplex OS threads per execution path.

In fact, if you feel like doing some advanced stuff, you can write your own schedule algorithm.


Why is it difficult to determine what those features are 429, 432, 433, etc. How difficult would it have been to put links there so one can click on the link to see what it is. Have these people hard of hyper-text markup?


https://openjdk.org/projects/jdk/20/

It's linked in the email.


Does anybody know if there are significant improvement fir java on arm in this release?


Java on my ARM MBP is _very_ fast, by a large margin; faster than my x86 cloud servers by 1.5x-2.5x


Nice, I’ll put your arm laptop in production then


> structured concurrency

That's another huge effort which delivered us an imperfect abstraction.

I would prefer to have HKTs and typeclasses, so I may implement my own IO monad.


> I would prefer to have HKTs and typeclasses, so I may implement my own IO monad.

I'm not a Java developer, and I haven't even read about higher kinded types or typeclasses (the theory side of programming is my weakness); but you picked my curiosity with this.

Can you elaborate how concurrency done this way would look in Java?



Some discussion on zio in loom-dev mailing list recently: https://mail.openjdk.org/pipermail/loom-dev/2023-March/00535...


Loom is useful as an underlying mechanism for an IO monad. What I mean is that Java as language still lacks important features so they have to deliver half-baked things like that "structured concurrency". From my point of view these new abstractions are evil - the better ones were out there for a while.


Other than STM, what is special? Almost everything there has an equivalent in traditional imperative programming.


More contracts enforced by the typer.

> Almost everything there

Everything. But the mere presence of "equivalents" doesn't make pure functional programming less valuable.


I have used cats-effect but not ZIO.

My view has been that reinventing imperative programming (IO monad) on math (pure FP) on imperative programming (the machine) brings me no benefit.

What does a coarse-grained effect type get you? The hard part of concurrent programming is concurrency, rather than knowing what code is effectful.


> What does a coarse-grained effect type get you?

For monofunctors: reliable error handling, an ability to re-interpret the same IO structure multiple times, better reasoning during refactorings due to referential transparency.

For bifunctors: the above plus explicit domain (expected) error encoding and even more reliable error handling.


virtual threads are still in preview. At which stage/release will they be in regular usage? (not preview) Is anyone planning to use virtual threads, at this stage?

i understand that virtual threads are very similar to goroutines in golang


And here I am still on Java 8 :/


I mean, that's OK though. Not sure why this is considered such a bad thing. You're missing out on some new language features, sure, but Java 8 is still reliable and rock solid. There's lots of companies that have hesitation or even inability to upgrade, hopefully there's at least some initiative and/or direction to do so.

A company that is head-in-sand deliberately not upgrading from Java 8 is one thing. A company that is just being conservative and intentional about the upgrade path, that's another entirely different thing.

I don't mind a company that has been using Java so much that it's just a slow process for them to get on the next thing. Old libraries that need to be recompiled for Java modules, etc. Hopefully it happens for you soon.


There's very little reason not to upgrade from Java 8 at this point. Everything should be a drop-in replacement and there are significant performance benefits (garbage collection is leagues better) to doing so.

The bigger problem is that a lot of places are stuck on Oracle JDK - 8u252 is the last free version so a lot of places just decided they'd never upgrade, nor do they want to look at whether Temurin or Coretto would work for them (the answer is usually yes).


Almost. The Java module system (introduced in Java 9 and enforced in Java 11) still plagues companies and/or slow-to-update libraries. Particularly companies that have written a lot of their own in-house libraries and such.

Custom libraries have, unfortunately over the years, picked up the bad habits (by forking/following public libraries) relying on reflection and packages that shouldn't be directly used (sun.* packages, etc.). So companies are wedged in because they made the poor decision to rely on private packages. And now, Java 11+ enforces this by default.

There are open source Java libraries today that don't run unless you "add-exports" to basically everything. google-java-format comes to mind, because it's using an internal java code parser from the JDK, for example.


In the latest LTS those all can still be enabled, so while it is a serious problem when they are finally removed, for now it's not really a good reason to block an upgrade.


I think that's fair. Yes, there is an escape hatch currently available. But what is hard is knowing if your application is relying on any internal/private behavior that it shouldn't. Since the dependency hierarchy of most Java projects is very deep, it's hard to know if any dependencies of A -> B -> C -> D are going to call into restricted areas. You might not even know you have a problem until runtime, because you've --add-exports everything and now you have a stacktrace to try and deal with in production.

But yes, maybe not a good reason to completely block an upgrade. It's just postponing the pain, though.


> Everything should be a drop-in replacement

This is not true for many applications. Due to the removal of many APIs from the JDK with Java 9, I needed the following dependency artifactIds to be able to move a JEE application with SOAP web services to Java 11: jaxb-api, jaxb-core, jaxb-runtime, istack-commons-runtime, jboss-jaxws-api_2.2_spec, glassfish-corba-omgapi, jboss-annotations-api_1.2_spec, activation, jboss-saaj-api_1.3_spec, saaj-impl, stax-ex, jsr181-api, txw2.

Many of these spec API/implementations are provided by different artifacts that are incompatible with each other. Some I only discovered when something failed at runtime as they perform implementation lookups and you don't get compile errors.

Additionally, many of the Maven plugins we used no longer worked and our application server failed to start.


Are you really actually using CORBA? That would be heartwarming if so. Maybe just RMI over IIOP?


It's far less exciting I'm afraid: we use [0] to generate our DB IDs and it implements org.omg.CORBA.portable.IDLEntity.

We could fork it and remove the interface or switch to ULID[1] instead.

[0] https://github.com/stephenc/eaio-uuid/blob/master/src/main/j...

[1] https://github.com/ulid/spec


Shame :(

Given that interface is trivial, you could also just define it in your codebase. I've done that a few times for shimming small bits of log4j and Spring that some library uses, when i would rather not have those as a dependency.


> Everything should be a drop-in replacement

It isn't at all, in my opinion. Consider the introduction of the module system, for example.

Some libraries moved out of the core language.


Except 9, 11, 14 and 16 where breaking backwards incompatible changes were introduced? And then the talk of changes in libraries, remapping imports from JEE to Jakarta... that's a long, long way in Java.


Isn't 202 the last version?


I think that the main problem upgrading beyond Java 8 is Java 9 and module system and a lot of javax package classes that were removed. It will be very helpful a tool that can detect what modules or classes that are being used by your codebase and add them as maven or gradle dependencies and add them to the classpath.


The main problem with upgrading beyond Java 8 is that it's Android! And we all know how THAT went.


> I mean, that's OK though. Not sure why this is considered such a bad thing

For existing software, sure. But the problem I guess is when there are a lot of newer projects starting and everyone is stuck on the older version


Totally fair. I guess I would hope in that case that the new effort would target a modern Java release, preferably Java 17 or greater. If a company is sticking with Java 8 for new initiatives, yeah, they probably have a culture problem and are not going to be happy with that decision.


Impressive how many companies are stuck on quite old java versions. We are a core API for payments services, and we need to force our customers to upgrade to newer Java versions as most of them don't support newer encriptyion protocols


Thank You for your service.


My codebase at work is written in Java 8, I honestly don't see what other benefits upgrading the language would bring. There's already so much business code written in the old Java 8 style (which works), don't go trying to change how the code is written now. To me Java 8 is simple and boring like Go, with some imperfections like the lack of a native map data type.

I'd still upgrade for the new VM's better performance/security patches or whatever, but I don't need any language changes.


I unfortunately encounter this mindset so much in Java programmers, and the similar "we don't need no feature X" even if the feature has proven themselves for a long time in a large amount of languages. I'm hesitant to bring it up, but I see a lot of Blub Paradox [1] among Java programmers. Heck, a lot of places disallow `var`, while over here in Kotlin-Python-Rust-C#-Typescript-Go-etc-land that's been the default since forever.

Taking this comment in good faith, the following language features from 9+ are incredibly useful for everyday programmers, you should give them a serious try before dismissing them:

- `var` local type inference

- record classes

- text blocks

- switch expressions

- sealed classes and interfaces

[1] http://paulgraham.com/avg.html


Var is nice. But I generally prefer to not require people to jump out of the current code to figure out the type of a variable. And yeah, I've worked in those other languages, and navigating unfamiliar code with var everywhere can be confusing, so I try to avoid that kinda thing.

Records are nearly useless to me. Immutability is great, but I need a way to derive new sets of information based upon an set of information. And the only way to do that is bug prone.

Text blocks are nice for writing SQL queries and other multi-line things. But I'm not sure how often I actually use it.

Switch expressions are nice because it gets me compile time checking for things I would previously use a runtime check for. Other than that, as they currently exist, they're meh.

Sealed classes are something I've not had a use for. Maybe libraries will eventually make good use of them.

So I would say there's some nice QOL things in here. But I think "incredibly useful" is overselling it.

The thing is, lots of these things are built to support a longer term roadmap towards better support for data oriented programming. I think that is a worthwhile goal to drive for - and the sum of the parts (many of which are still in preview) will be less than the whole together - but we don't have the whole, yet. That, to me, would pass the "incredibly useful" bar.


`var` in Java is local for a reason. In most contexts I can think of the variable either was created above or was passed in as a parameter/object field. Unless you set it from a weirdly named creation pattern/function.

Regarding records, you never had someone update a POJO, add a field, and forget to update equals and hashCode?

Sealed classes are great for everything parsing/validation, in data modelling. They're not a solution for behavior polimorphism, but I don't think they were supposed to.


My main dependency only supports jdk 8 soooo...


People think boring is good but it’s not. Simplicity is good. Boring is just what you’re familiar with.

New features done right improve simplicity by abstracting away complexity or need to reinvent the wheel. For example Go devs having wrote their own list .map() functions is absurd.


We migrated our codebase because multi line string literals somewhere in jdk14 made writing large SQL queries in java code so much easier.


Big reason is security patches. But if the services are running in a VPN and don’t interact with outside world, it’s fine


My understanding is that jdk8 has support from Oracle until 2030.


paid support. I’m not convinced that most of those companies that use older versions even know where their JDK comes from.


Perfectly fine for already written codebases or codebases that need to target hardware that's stuck in the past?

Just don't use it for new projects on modern hardware. Be on at least the current LTS version for that and enjoy a much more expressive language on a much better JVM.


It's interesting how the introduction of "modules" has forced so many to stick with C++17 and Java 8.


C++ has barely any feature complete compiler implementations and build systems aren’t ready for all of its additions, either (CMake and modules in particular). libstdc++ and libc++, gcc and clang are all not ready for production use or rather untested and/or buggy.

Unless you’re targeting MSBuild Visual Studio Solutions and Windows only, C++17 is currently the most up to date, stable, and battle-proven version.


It's a shame "C with classes", as C++ originally was, didn't stick around for parallel development.

I work with C daily and I'm well aware of its many shortcomings (e.g. its huge list of undefined behavior and its PDP11-centric view of modern architectures), but with some effort I believe it could function as a semi-portable second-level intermediate representation. Nim uses it as such, IIRC.

Compiling to C first would introduce some of its own issues, for sure, but I imagine doing so would alleviate the pressure the C++ standards committee puts on compiler vendors each time they expand the size of the kitchen sink.


Rust ?


Rust compiles to C and then passes that C to a C compiler to generate machine code?


> gcc and clang are all not ready for production use or rather untested and/or buggy.

Which items from this table are lying about GCC v11 support of C++20? [1] Which of the features are buggy in GCC v11-12?

[1] https://en.cppreference.com/w/cpp/compiler_support#cpp20


Gist: ``` JDK 20 introduces six features: - Scoped values for safe sharing of data across threads - Record patterns for declarative data navigation and processing - Foreign function and memory API for interoperation with code and data outside the Java runtime - Virtual threads for lightweight concurrent programming - Structured concurrency for simplified multithreaded programming - Pattern matching for switch statements and expressions for concise and safe complex data-oriented queries ```


...but also thousands of other stability, performance, and security updates.


Don't worry, someone will still manage put in a bug that results in a zero day in the next 2 years.


While the Java community has had some high profile vulnerabilities, I can't recall a major one that was actually tied to the JVM itself since dropped applets. Instead it's usually a popular library or framework.


Correct, but the reason those happen is because JDK, despite being on version 17 is still missing core features that are necessary for modern day use, like logging, backend web stuff, json parsing, e.t.c. So delegation to that is left to a large number of 3d party developers, and risk of pollution goes up, especially when you consider that there is financial incentive in the case of orgs like Apache to keep developing new features.

There should be no reason why someone deliberately writes code for a logging library to go fetch and execute code over the internet, and make that the default behavior. The fact that someone did that, and it got approved and published to production, should be an indicator for anyone competent to stay away from Java completely. There is plenty of other compiled languages out there for whatever use case you need.


> I can't recall a major one that was actually tied to the JVM itself since dropped applets.

Not exactly tied to the JVM, but IIRC one of the prerequisites for the Spring4Shell vulnerability was the existence of a new method added by Java 9. If you were still on Java 8, you were not affected.


As opposed to what, powering off your servers so they can never get vulnerable?


As opposed to not using an enterprise, outdated language that is only used to essentially create jobs.


[flagged]


That people still write this kind of drivel and actually believe it on this site is frustrating.

Every tool has a purpose and Java is a fantastic tool for a lot of use-cases.


That, and there are other languages (like Clojure) that are implemented on the JVM. I don't write any Java, but Clojure running on the JVM pays my bills.


C++ for when you need to be very close to the metal.

Java (and C#) are still legitimate options for server side code when you want speed and type safety but managed memory.


Not only legitimate. Also better for many scenarios. Not only when you consider your existing workforce but also hire new people. It is so much easier if you onboard someone into something which is procedural at its heart and everyone can translate their core C/C++/JS/PHP/Python/Perl/... procedural programming into Java and C#. The reason async/await is so accepted there is because of exactly that.

Try throwing someone into a Go project with a C background. Until they learn the way of concurrency there, a lot of time is gone. Or even go one step further and throw a generic procedural coder into something like F# or Haskell. Or take the example of reactive UIs: It took the industry a decade to move from MVP based UI frameworks to reactive based UI platforms and we are far from being done with that move. Angular literal reactive-under-the-hood existence is due to that fact that people love to work with their traditional ways and are more productive in them.


Why "bad enough"? Java is getting better and the language feature set isn't really that big, I would rather call it austere. The language slowly adopts Scala features but still lags significantly behind it.


Which new hot language of the day are you chasing this week?


Because they are very popular languages with a large experience base?


How is time relevant for languages? Unless everyone has stopped speaking/using it, it's relevant and is never old and only is improved to add new grammar. Applies to both spoken and programming languages.


You are right; lets start a linux kernel in rust. Send me a PR for that bootloader.


There are bits of the Linux Kernel in Rust.


Like I have tic-tac in my car but its not driving the engine.


Bits =/= let's dump C


What is the "cool" language nowadays?


OCaml is having a bit of a moment


Rust or Zig


HolyC


Java 8 is the best Java.


Java 8 is an antiquated runtime. You're doing a disservice to your customers and developers by continuing to use it.


Java 8 is working runtime. Whats wrong in using runtime that works?


Do you drive an old-timer, with the same justification?


Maybe that's why the two Java based products used by a customer of mine both have been using Java 8 since forever. One eventually switched to Java 11 last year.

Or licensing, or having to rewrite too much of those apps for no perceived value. Who knows.


Why? Java 11 is already leaps and bounds better than Java 8.


I'm in Security and 8 generates a lot of revenue for me :)

I'm not sure that it's inherently insecure, there is just a ton of old stuff in the ecosystem still chugging away.


Java 8 was before Oracle started with evil licensing terms and crazy language changes which allowed all kinds of alternative syntaxes.


Not the parent poster, but I can see why: IMHO, the best new feature for Java was try-with-resources (Java 7), and the second best new feature was all the functional stuff in Java 8. On the other hand, Java 9 started the introduction of several breaking changes (it took years before I stopped seeing updates to Java libraries to fix their compatibility with Java 9 and Java 11), so it can be seen why someone would think of Java 8 as the highest point of that language.

(For Java 9/10/11 in particular, there are several small enhancements, but nothing as earth-shaking as the changes we got with Java 7/8.)


IMO you're neglecting the thousands of improvements in the HotSpot runtime.


You can't use any of these thousands of improvements if your software is broken because it depended on something which was removed, or changed in incompatible ways. It can take a lot of work and time until it all works fine, even more when the breakage happened deep within a third-party library, and as far as I could see, there was a lot of these breakages early on the Java 9/10/11 cycle. IMO, it's worth it to make your software compatible with the latest Java LTS release, but I can understand why some people see it as too much work for too little gain.


There were some subtractions for a bunch of stuff that wasn't getting much love to begin with. They removed their JS interpreter which was based on rhino which continued being a side library the entire time that java added it to the Java runtime. The only personal break that had no remediation was a minor helper class in JMX that affected me. Pretty much anything that was removed has some library addition that can add back what was cut. Basically Java 6-8 added a bunch of library support things that were probably better maintained as a third party library to begin with. The module system usually means adding some security policy exclusions for popular libraries, but in general I get a very well performing JVM that feels like it optimizes the code significantly better than in previous generations.


This is by far the biggest improvement to java in the last few years and plays a big role as to why a lot of different types of backend tools still are written in JVM based languages rather than switching it up. This said, hotspot still has some issues with taking full control of SIMD optimizations.


They aren’t critical to most applications, and are invisible to most developers.


As a Java developer you should really set up at least some basic monitoring and perform load testing from time to time.

And even in the unlikely event you don't see any improvement with recent versions of the runtime, something like Spring Boot 3 requiring Java 17+ isn't exactly "invisible" in the Java world.


> as earth-shaking as the changes we got with Java 7

7 didn't introduce anything earth-shaking (just try-with-resources), lambdas were earth-shaking, just like records and switch expressions are.


In locations which JDK Release Notes reach in eight light-years.


So... exactly zero new features? Well, hopefully JVM got faster and future LTS release will be more interesting.


Why not just use C# instead? It was purposefully designed to address and correct the many mistakes of Java, and much more importantly, it's not owned by Oracle.


All the while, adding its own mistakes to the pile.

As an example, idiomatic Java style is moving away from getters/setters and instead favors builders and immutable types. That whole inheritance vs. composition concept is changing the way Java is being used.

Meanwhile, C# has baked getters/setters so deep into their language (as properties) that there's just no moving off of them. Have you tried making a Builder in C# - ugh. Much harder than it should be.

C# took the best of Java and ran deep into a cave with it. Meanwhile, Joshua Bloch's Effective Java has completely changed the way that "good" Java should be written and likely helped kickstart the whole revolution that is modern Java.

I agree with your sentiment in general. C# / CLR are truly remarkable engineering feats. But Java is starting to break away from its old molds and is seemingly adding agility as it goes.

As far as ownership. Java is not owned by Oracle any more than C# is not owned by Microsoft. They are both open languages with a majority backing supporter that yes, has a lot of influence over the languages. I'd argue Microsoft has more influence over C# / CLR than Oracle does on Java / JVM (but that's too close to call and probably too opinionated to suggest).


Yes. And with "records" (record classes, something with the characteristics of a tuple or struct) you can get further away from getter/setter boilerplate code.


I just wish records had an easy way to facilitate and/or associate a builder with them. Wishful thinking, out of scope for what they are. And of course, it's not hard to write a FooBuilder that's defined to help construct a Foo record.

If Java records could be told to have a private constructor, I'd be completely satisfied with them. I just don't like the ability for callers to be able to directly instantiate a record without having gone through my builder to do so. I want to completely enforce that my record is instantiated with all its invariants dealt with properly. A builder is a very nice way of doing that.


I wanted to experiment with creating a "Rust-like" option and result type for Java and so figured that I would need records and pattern matching for this and I ran into exactly what you are talking about here with records and public constructors.

My solution was to create a sealed interface that permitted the None and Some records as the only classes to implement it. Those records are not available outside the package, while the interface is exposed. Using default methods in the interface I could expose a state "create()" method which would then instantiate the appropriate None or Some record. In this way you control the exposure of the construction of the specific record implementations of your interface.

You can then either interact with the option through the methods on the interface, .isOk(), .unwrap(), etc, etc, or with the upgraded pattern matching in switches with this release you could have something like

  switch(option) {
    case option when option.isNone() -> blah
    case option when option.isSome() -> foo
}

Its not as pleasing as Rust matching directly on Some and None, but it gets you pretty close.


This is not really pattern matching, that’s just a regular old if-else.

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

This is absolutely possible in Java and the only less-than-ideal part is the generic type having to be specified in the None case (but a trivial method fixes that as well)

This can be used just like rust and similar languages:

  switch (option) {
    case Some(var x) -> println(x);
    case None -> // TODO
  }
Hell, you can just further pattern match inside Some, like `Some(Point(var x, var y))`


Java is built upon it's community :), look no further: https://github.com/Randgalt/record-builder, although hiding the constructor behind the builder is not something I am sure supported by that library

I also remember a discussion in the mailing list about withers: https://mail.openjdk.org/pipermail/amber-spec-experts/2020-M...

Not sure where it stands now, you can ask in the mailing list about this


> I want to completely enforce that my record is instantiated with all its invariants dealt with properly

Such validation logic can be enforced in the record's compact constructor.


Yes, agreed. But not in a clean "fluent" style. There's something nice about the fluent style that appeals to me (at least).

And I don't like having to provide one constructor for every optional (default) value, when its omitted. There's just not as nice of a style. The "telescoping" constructor pattern is just really hard to use, read and maintain.


Aren't records and init only setters in C# also a way to favor immutable types and fix setters in properties? (Not trying to criticize, I really want to know).


Kind of. It gets you close.

With the Builder pattern, you can easily define complex relationships for your class inner state. Like maybe if A & B are specified, then C shouldn't be specified. But if C is specified, then D should have a default value. Etc.

These types of initialization requirements are not easily duplicated with C# init-only construction. Instead, in C# you have to rely on the callers to know exactly how to correctly instantiate your class, including all the complex logic like in the example above.

So yes, C# init-only setters are kind of the best option you have. You can still create a C# FooBuilder for a Foo class. But generally C# programmers want to go out of their way to directly instantiate a Foo, and the number of bugs you get from this is crazy. It's a culture thing, honestly, not as much a language problem.

My two cents.


Do you have an example of how builders work in Java?


This is probably a pretty good link to start from.

https://blogs.oracle.com/javamagazine/post/exploring-joshua-...


Designing something with an intent is very different than succeeding in it.

Also, absolutely objectively Microsoft has orders of magnitude more control over C# than Oracle has over Java. Both Java, the language and the JVM has a specification, implemented completely independently by several companies, and the reference implementation is used as a business critical infrastructure of almost all Fortune 500 companies, many of which could single handedly continue to support and develop the platform. Meanwhile C# doesn’t have an open-source debugger..


AFAIK Java was GPL'd by Sun (before the Oracle purchase). This resulted in the OpenJDK, which is the basis for all distributions of Java. Oracle is a big contributor to the development, but so are many others. You can get a pure OpenJDK or any of a number of branded JDKs like those from Eclipse, BellSoft, etc. You can get non-free distributions with support too. A sticking point for a number of years was the TCK (Technology Compatability Kit) used to validate Java, but that too was GPL'd. Java has evolved quite a bit since being open sourced. IMHO C# has some great features, but Java is great for certain things. For instance, making native apps for Mac, Win, and Linux based on modular libraries from the JDK is now fairly easy (and with a modern UI using the OpenJFX framework).


Asking honest question here - did Oracle change the terms back so that one can now develop and distribute applications using their JDK without fees so long as one does not also re-distribute/bundle their jdk with your application(s)? I just read something along those lines having thought that one couldn't use their JDK for anything without fee anymore.

If so, is there a reason to go with OpenJDK over Oracle other than GPL purity?

Again, legit question, no axes to grind.


Why not just use C# instead? It was purposefully designed to address and correct the many mistakes of Java, and much more importantly, it's not owned by Oracle.


C#, owned by the most ethical company in software.

Java is very different and much improved from when c# branched off, but many people haven’t been following along and still think it’s the same as in 2002.


This is 2023 and you are saying that Microsoft is less ethical than Oracle.

A lot of things have happened since the 90s, you know?


Sure things changed. But some things will stay the same 'till end of time. Like Oracle and M$.


Or you forming an opinion 30 years ago and never changing it.

Textbook definition of "closed minded".


Those companies do the exact same thing (or even worse!) as 30 years ago.

Of course nothing changed; besides the concrete wording of the PR and marketing materials.

So there is no reason to change any opinions.

Actually M$ got even more dangerous since than as they stopped to fight OSS and switched to their infamous and in this case even more concerning EEE strategy.

But sure, I know some people get blinded by their marketing efforts and don't look to close what this companies are actually doing.


If you think today's Microsoft is anything like the Microsoft of 20 years ago, you need to start reading up on tech news other than Slashdot.


With all my love for .NET, why not continue Java with its new features when it fits to your developer base, existing products etc.

Give it one or two more years and Java is where C# is ... also aesthetically. As did JavaScript.


The feature that everyone is excited above - switch statement with pattern matching - has been available in C# for 6 years now.


The feature everyone is excited about is Virtual Threads. Something where .NET is still playing catch-up https://twitter.com/davidfowl/status/1532880744732758018.


C# doesn't have exhaustive pattern matching yet.


Isn’t openjdk disconnected from Oracle?


Disconnected? No. Oracle pays for 95+% of all OpenJDK commits. Their OracleJDK is pretty much just an OpenJDK with a few logos added — they have over time made every proprietary feature open-source and available in OpenJDK.

But as I expanded a bit more here (https://news.ycombinator.com/item?id=35249701 ), Java is probably the safest bet from a code longevity perspective.


I didn’t know that, interesting!


Not only that, oracle’s jdk is actually a distribution of openjdk.


Oracle is the main contributor in both lines of code and financial backing to OpenJdk.


Or Kotlin? Or Scala?


This feels a bit like discussing the new fancy garden hoe as a farm hand as they are making the tractor.


Java is a conservative language, which means that it waits and sees which features work well for the tractor, and then retrofits them to the hoe.


except no one's making the tractor


Are these hoes somehow related to lemon stealing hoes?


Only if you build your tractors out of garden hoes ;)




Consider applying for YC's Winter 2026 batch! Applications are open till Nov 10

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: