Java has a substantially better ecosystem. The tooling is just miles ahead of what exists in the DotNet world. Until very recently you developed in Visual Studio and you ran on Windows in production and that was that. They're working on Linux support and a broader ecosystem but they are probably a decade behind what is available on Java. Even stuff like package management is crude, NuGet is a joke compared to Gradle.
Tooling might be slightly behind Java's, but honestly, there's not much in it. Dotnet Core might only have been available on Linux for a few years, but Mono was around for many before that.
The dotnet CLI also recently got the concept of 'global tools', which are akin to NPMs.
A lot of work is also underway on better cross-platform performance analysis, crash dump handling and the like for runtime in production[0]
Is there anything in particular you miss?
[0] https://devblogs.microsoft.com/dotnet/introducing-diagnostic...
If I'm a Java developer who has been using Maven for such a long time. I don't see the allure switching to .NET just because NuGet exist or whatever Packet stands for.
The big data ecosystem is all JVM/Java.
Android started off as Java. Sure they have Kotlin now, but I don't know how many developers switching to Kotlin in drives.
Basically, the ecosystem of Java is already _there_ when MSFT is trying to catch it up so experienced Devs (a.k.a people who are already comfortable with the tooling) do not see the reason to switch.
As for me myself, I used to intern at MSFT back in the mid 2000 drinking the .NET kool-aid. One day I woke up and realize that part of the hi-tech world that interest me, relies a lot on OSS and the large portion of OSS was Java. Fast forward to today, FAANG is mostly Java shop. Java seems like a safer ground for me to have a longer career.
That's just me and my 2c. I'm too lazy to switch to .NET since there's no added value at the moment unless if I want to do back-office in-house web-app.
Yep, as a seasoned Java developer I agree. There are times when I miss a part of the Java syntax but they are few and far between.
> Java has a substantially better ecosystem. The tooling is just miles ahead of what exists in the DotNet world.
Also true, although it is getting closer fast.
Possibly more important: they are both in another league compared most other languages. (Of the other languages/stacks I have some production experience with Angular/TypeScript is the only that I feel has anything close to the tooling support that C# and especially Java has.)
Absolutely true, but so does C#. In the end it turned out that .NET's reified generics were a mistake (which makes language interop on .NET painful), and more recently async/await.
Do you have any resources that explain this point further?
Reified generics have often been praised as the thing that CLR got right (and JVM got wrong). I never understood fully why that is, especially since other languages with generics (e.g. Haskell and OCaml) don't have anything reified (although in all honesty also don't have RTTI so it's really not necessary).
I know some people think that say they like reified generics in C#, but those are mostly people who aren't aware of what cost to the entire ecosystem they're paying in exchange to what is a minor convenience in C#.
BTW, reification of reference-type generics is not to be confused with specializing collections for value types ("arrays-of-struct") an extremely important feature that CLR indeed has, and Java is now working on getting.
Part of it is that a lot of that stuff is gamedev-related, for me. It's not the biggest thing in the world, but for a lot of the stuff I find myself writing in C#, I find myself wishing for type erasure to make throwing data around a little easier. On the other hand, though, when writing web stuff on the JVM--I absolutely will not waste my time doing this in .NET, ASP.NET Core is not very good and EF Core is awful--I often wish for type reification, so it's just a horses-for-courses thing.
They really don't. They have posed severe restrictions on features in languages like Scala and F#, and take a look at Clojure, Python and JS interop on the JDK as opposed to .NET.
> and async/await had become a starting point for similar designs in many other languages.
And it's a mistake in most of them. Java has copied some of C's mistake, C# has copied some of Java's, and others may copy some of C#. Every language/runtime both repeats others' mistakes and adds a good helping of its own.
> I don't think you'll find many people who actually write code in that ecosystem agreeing with either of those claims.
I can only express my own opinions (and I think reified generics, as implemented in the CLR, is a far bigger mistake than async/await, which only affects the C# language), but I am far from being alone in having them. Also, I have no doubt that async/await is an improvement on the previous situation, which is why people like it, but nevertheless I think it's a mistake as there are alternatives that are more convenient, more general, and have less of an adverse impact on the language (e.g. Go's goroutines).
I saw the link to Clojure page you posted in another comment, but I don't see any fundamental problem with generics there. Yes, if you're invoking a statically typed language from a dynamically typed one, you have to occasionally jump through hoops when dealing with things like method overloads that require types to distinguish. The same goes for Python. I have actually used Python embedded in C# more than once, with interop both ways, and in practice it "just works" most of the time.
Conversely, I don't see why statically typed languages should surrender valuable type information (and associated perf and expressivity gains) for the sake of convenience of dynamically typed ones, especially on the platform where static typing is the norm, and libraries are expected to be designed around it.
As far as async/await vs Go's goroutines - since we're talking about language interop, how many languages can Go coroutines interop with? Async/await easily flows across language boundaries, as you can see in WinRT - any language that has the notion of first-class function values can handle that pattern. Goroutines are essentially a proprietary ABI. And the worst part is that once the language has them, all FFI has to pay the tax to bridge to the outside world, regardless of how much you actually use them. It may be an argument for standardizing some form of green threads on platform ABI level, so that all code on that platform is aware of their existence and capable of handling them. Win32 tried with fibers, without much success. Perhaps it was too early and the design was too flawed, but it's not encouraging.
It does make it harder to add features to the language that do not map to the current reified "generics" spec, for example higher-kinded polymorphic types.
Of course, the JVM has plenty of issues supporting alternative languages too, for example lack of tail-call optimisations, or switch-on-type, for functional languages.
That's true (and will be addressed), but that's a problem that can be fixed by adding a feature, not removing a central one, which is why I said that both Java and .NET have made mistakes, and reified generics was one of .NET's.
> or switch-on-type, for functional languages.
There is no difficulty supporting that. In fact, the Java language is about to get that without JVM changes. Perhaps you mean switching on A<Foo> and A<Bar>, where Foo and Bar are reference types (and possible with a subtype relationship between them), well even Haskell can't do that, and if there was a language that thought this is a good idea, it would be able to do it quite easily.
Glad to hear it! It's probably the biggest issue trying to do functional programming on the JVM.
> There is no difficulty supporting that. In fact, the Java language is about to get that without JVM changes.
The technique to implement ADTs in functional languages on the JVM has often been to add an integer tag to every subtype and switch on that, but it's ugly enough for interop that IIRC Scala doesn't do it (and is thus less efficient).
It wouldn't interop with C# generics (although I don't see why it couldn't interop with C# by other, less convenient means). But if it can't be properly mapped to them when they're reified, why is there an expectation that it should? It seems to me like the gist of the argument here is that we can conflate two features into one, if only we remove all the conflicting bits of one of the features - which also happens to be the one much more broadly used at the moment. It's a strange trade-off.