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

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

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

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

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

reply
This is great advice and it applies to a lot more than just language features. Different architecture, deployment setups, QA approaches are all like this. It's always "approach A is no good", "but company X uses approach A and they're doing very well", "yeah but look at all of these problems they have". Maybe a fair argument but the approach B people also have their fair share of problems...
reply
What's funny is that the only languages in the same popularity league as Java are Python and JS/TS (and possibly C and C++ if you want to extend things to more domains) yet I rarely ever hear people saying Java should be more like these languages. It's because many of the people who dislike Java also don't like any of the most popular languages (even those who like, say, Python better don't think Java should be more like Python).

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

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

reply
GUI frameworks have similar arguments around their design choices.
reply
> All of the language differences between .NET and Java fall in this "non-consensus" zone

Curious if you think fibers vs async/await is still in this zone (amongst experts). It seems fibers are objectively better. But I'm no expert*

reply
I think user-mode threads are better when the language already has threads. I'm not sure they would have been better for JS. But yeah, this one is probably less controversial than things like properties and extension members, which we also said "yeah, nah" to.
reply
Value types kind of definitively don't have null, right? You can have a zero int but not a null int. So nullability is not entirely orthogonal to value types, its an advantage for value types where they are practical.
reply
I didn't say nullability is orthogonal to value types; I said it was orthogonal to the two-projections world, which is what that text in the article was about rather than nullability.

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

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

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

I'm going to hard disagree here. And the syntax proposed in the Null-Restricted Value Class Types JEP is a major step backwards.

I want to banish nulls from my codebase, completely. I can currently do this with a variety of annotations (at the package-info.java level) and tooling, though it's not integrated well with the language.

Forcing exclamation marks into every variable and parameter is a lot of annoying noise that quite simply nobody will do. The default should be non-nullable, especially for value types.

Declaring whole types as non-nullable is less noisy and errorprone than annotating every variable declaration. If you aren't going to give me "declare the whole codebase as non-nullable" then at least give me something coarse-grained.

reply
> the syntax proposed in the Null-Restricted Value Class Types JEP is a major step backwards. Forcing exclamation marks into every variable and parameter is a lot of annoying noise that quite simply nobody will do. The default should be non-nullable, especially for value types.

Then you didn't read the JEP draft (it's not an accepted JEP) carefully. It says, under "future work":

Providing a mechanism in the language to assert that all types in a certain context are implicitly null-restricted, without requiring the programmer to use explicit ! symbols.

In other words, the draft already incorporates your point, but JEPs (both drafts and actual JEPs) follow the pattern we've found to work so well, that features are best delivered piecemeal rather than in a big bang.

Having said that, I don't know the current plans for this matter, as that document is only in Draft status, so saying it's good or bad is pointless, as it's not even a proposal yet, just something being explored.

reply
It's a proposal for a proposal, fine. Consider this my proposed feedback.

I don't think this syntax is desirable as currently proposed, and that one line under "future work" is doing far too much lifting. My sincere hope is that there are people closer to the process that also feel this way, they will provide similar feedback, and the next draft will be something completely different.

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

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

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

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

reply
I think I mostly got this, but just to test it, it would be like in Typescript where I might say:

    type Foo = { x: number; }
    type Bar = { x: number; y: number }
    type FooBar = Foo | Bar;
    function baz(x: FooBar) {
      if ('y' in x) {
        // compiler now knows x is a Bar
      }
    }
In this case, the variable `x` has a property that is determined by the compiler based on control flow. i.e. it isn't explicitly carried by the type of `x`.
reply
In case you want to edit it back in: in the 3rd paragraph second sentence, the star in your int* got gobble up by formatting to italics.
reply
You can have a null int, it's called Integer.

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

reply
This won't be true in Java, though - in Java, you will have null Integers at least. It seems that int will remain a different thing entirely from Integer, and will remain a JVM-only concept.
reply
But with null-restricted types, Integer! and int has no difference semantically and representation. They plan to introduce null-restricted types in future.
reply
What's wrong with what .NET did with threads? Having async tasks sharing the GUI thread seems like a nice feature. Will we be able to use virtual threads and structured concurrency with Swing, e.g. to wait for a background task in an event listener?
reply
Regarding "What's wrong with what .NET did with threads?", see https://cr.openjdk.org/~rpressler/loom/Loom-Proposal.html (relevant part below):

  An alternative solution to that of fibers to concurrency's simplicity vs. performance issue is known as async/await, and has been adopted by C# and Node.js, and will likely be adopted by standard JavaScript. Continuations and fibers dominate async/await in the sense that async/await is easily implemented with continuations (in fact, it can be implemented with a weak form of delimited continuations known as stackless continuations, that don't capture an entire call-stack but only the local context of a single subroutine), but not vice-versa.

  While implementing async/await is easier than full-blown continuations and fibers, that solution falls far too short of addressing the problem. While async/await makes code simpler and gives it the appearance of normal, sequential code, like asynchronous code it still requires significant changes to existing code, explicit support in libraries, and does not interoperate well with synchronous code. In other words, it does not solve what's known as the "colored function" problem.
Regarding Swing, virtual threads are "just" threads so no reason they (and structured concurrency) can't be used.
reply
So does Java provide an API for continuations or just the virtual threads hidden behind the threading API? You can't use threads to wait for something on the UI thread (which is why e.g. SwingWorker exists), but you can with await.
reply
The colorless functions approach has well-known disadvantages though, including providing less control and making interop a pain. It isn't like one approach is the correct one and another is a mistake.
reply
.NET made different decisions.

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

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

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

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

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

reply
Java stagnated for quite a while. Seemed like everyone was stuck on Java 6 for about a decade. But JDK8 was a huge step forward. Lambdas, streams, and a date/time API that is the best I've seen.
reply
I think DotNet had a bit of benefit, in that the language was still new enough to do the hard breakage. It was only about 3.5 years between NET1.0 and 2.0 (Where generics were added.)
reply
> The stewardship of Java seems rather lacking

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

reply
> They have added proper algebraic data types to the language

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

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

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

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

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

reply
Or just do as Kotlin and embrace null, but in a type aafe way.
reply
"Funnily", having nullable types be practically `T | Null` gives you union types, not sum types (the latter is, importantly are a disjunct union!)

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

reply
Your point being?
reply
Except this is completely wrong.

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

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

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

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

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

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

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

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

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

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

reply
optional is not how algebraic data types are implemented in Java. Basically it's the combination of sealed types and records.
reply
deleted
reply
Given the mess of some .NET frameworks currently, and how bad it has taken for non nullable references to be widely adopted, I don't see those correct decisions on the last releases.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

reply
I'm honestly happy with java lang's stewardship over the past decade, this particular JEP notwithstanding (it's fine, but the good parts come later.) They're conservative in adopting new features whereas I see every other language bolting on everything under the sun with reckless abandon. I prefer the "let's see what shakes out" and adopt "the good parts" which seems to be Java's approach. Sugar like "var" from kotlin, project loom event loop like nodes, etc.
reply
Type inference was on DLang far before that Kotlin even existed. The only difference it's that reuses "auto" keyword.
reply
I mean, type inference goes decades before Dlang.
reply
How .net got so many things right where java did not is a mystery to me, but appreciated (it has its own flaws, of course). Java, in my understanding, is still of core relevance to Oracle, and tied into a lot of contracts that require very little effort from them to maintain. But you are correct in observing that they want to be a datacentre/compute business more and more these days; they may have in fact overcomitted to this due to the AI craze, since shareholders are already complaining.
reply
> How .net got so many things right where java did not is a mystery to me

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

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

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

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

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

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

This cracked me up

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

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

reply
The problem is how to do it without breaking ABI, 30 years of Maven Central is very relevant, Java isn't doing a Python over value types.
reply
But if you define a new type, how is that breaking backward compatibility?
reply
Because that is missing the point.

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

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

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

reply
Dlang this this before. You have classes and struts, with different semantics.
reply
Ironically, they still do need Java for Azure, https://devblogs.microsoft.com/java
reply
reply
Which recently decided that Go was a better option than C# for the Typescript rewrite, exactly because not all decisions were done correctly to make C# a better fit for the problem.
reply
Go was chosen mainly because it aligned more with how the existing compiler is designed. They did not want to redesign the compiler which eliminated C# as a choice. So Go is apparently just a better fit for quickly porting JavaScript code to.
reply
That was the original motivation yes, although they acknowledged later that the weaker type system from Go required redesigning the data structures anyway.

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

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

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

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

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

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

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

Second mover advantage.

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

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

I use Java and C# and appreciate them both.

reply
C# did not ship with async/await, and Java didn't have virtual threads back then. I am specifically referring to the initial choices made in C#'s foundation.
reply
> giving them a perfect blank slate to fix the architectural flaws they had already encountered

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

reply
As someone who works with Java on a daily basis alongside a dozen other technology stacks, let me go out on a limb and say that I believe Oracle has been a stellar steward of the language. Java has been evolving quite nicely and at a reasonable pace, all without breaking the ecosystem or causing fragmentation. It certainly has its drawbacks, but doesn't everything?
reply
C# often feels like Java with hindsight; Java feels like Java with 30 years of backward compatibility debt.
reply
Hence why so many .NET projects keep being .NET Framework instead having migrated to modern .NET.
reply
> particularly when compared to that of .net, where MS etc. mostly seemed to make the correct decisions from the start.

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

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

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

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

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

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

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

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

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

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

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

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

> You can't wrap streams in .NET

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

reply
Yeah, me too. Java always seemed to consider design a lot more than C# which seems to have taken more of a kitchen sink approach to language design. That stuff piles up over time (see c++)
reply
Agreed. I jumped on the .NET bandwagon in 2000 and was on it for several years but ended up going back to Java by 2005.
reply
What do you like about Java compared to C#?
reply
First, huge open source ecosystem and culture. Mature open source projects, culture of writing blogs and tutorials (that one will die due to changes in search engines, but it was super nice while it lasted).

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

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

reply