Is that actually a good thing?
But Oracle started playing a game, for better or worse, where they decided to couple the "language version" with a single specific runtime's release schedule. For example, in "Java 27" there are exactly 0 language changes and 1 minor feature addition to the TLS library.
Everything else is OpenJDK runtime internals which don't impact the language or how you use it. So if you don't use OpenJDK (such as if you use Oracle's other runtime, GraalVM), then Java 27 basically doesn't even exist at all. Skimming the past couple of C# releases, it doesn't look like Microsoft is playing that game, so the release cadence will of course be different.
Admittedly, the terminology here is almost designed to be maximally confusing, and I’ve never read a good post that laid out how everything relates.
I do think it’s plausible this is a smaller release. Not that this was the real point of the discussion, but I think it’s still just a good idea to have more than one release a year. It keeps things moving smoothly, and lowers the cost of missing a release, which has beneficial effects.
The closest C# has is mono.
This sort of thing is bound to happen with that situation. Heck, it happens with C++ whenever a new C++ version comes out. Some C++11 features took years to make their way into all the compilers.
I don’t think there’s a single alternate implementation that doesn’t leverage a good chunk of openjdk somehow
Embedded systems versions tend to have their own ways, which is why despite everything Android using Dex isn't a first in the Java ecosystem.
All these years afterwards it quite clear that there is just similar fragmentation, and implementation differences between all OEMs selling every kind of devices, and as you say the format doesn't really provide that much benefits.
What ART has going for it, are all the improvements they started on Android 7 and later, by having a mix of handwritten interpreter in Assembly, JIT compiler with cache, AOT compilation with the device on idle, and sharing of PGO metadata via the PlayStore across devices.
Ironically Windows Phone did it first, with MDIL on Windows Phone 8 followed by .NET Native on Windows Phone 10, using compilation via the Windows Store, but Microsoft fumbled the delivery.
And of the 4 non-preview JSRs in the Java 27 release, only 1 of them is actually part of the "platform version".
The other 3 are strictly changes to Hotspot internals with no platform involvement at all. They did not change any aspect of any Java platform in any way whatsoever. That is what I'm referring to. I'm not referring to the fact that the core library, language syntax, and runtime specs are all part of the same version. I'm referring to the fact that Hotspot specific behaviors and adjustments are also branded as being part of the platform release.
Like there's no Java 27 platform spec that says that G1 is the default garbage collector. That would of course be an absurd platform spec change. But that is still somehow a "feature" of the Java 27 release according to Oracle.
BTW, Java is developed "code first", which means that we first work on the implementation in OpenJDK, and then extract the relevant spec changes from it.
> But that is still somehow a "feature" of the Java 27 release according to Oracle.
It's a feature of the OpenJDK JDK, which is, indeed, the Java implementation done by Oracle (with contributions from others). The language is very careful, as you can see in the announcement: "JDK 27, the reference implementation of Java 27". The Java SE 27 spec is here: https://www.jcp.org/en/jsr/detail?id=402
I'd read it.
So now they have a precise release cadence that features can fall into. If it is a large feature, it better get worked in incrementally (via feature previews) because it is unlikely to be able to land completely within the release window.
One could pessimistically say the faster release cadence partially serves to provide more opportunities for extended support revenue, though.
Expression<Integer> add(Expression<Integer> a, Expression<Integer> b);
Expression<Double> add(Expression<Double> a, Expression<Double> b);
because in the end they both look like Expression add(Expression a, Expression b)
we have ways to cope, like unerasing the types by rewriting the names... And now you've got a reason to do balls to the walls metaprogramming! Similarly if I do a lot of C# or Scala or something I will get into the habit of doing things I can't do in Java..NET on the other hand did not keep backwards compatibility, so a List is not a List<X> so .NET had a schism where some API functions use generic collections and others use non-generics which was annoying in its own way.
Something like that is how all methods in Java are virtual whereas methods in C# may or not be virtual. All-virtual is probably not the best for performance, but it is simple for understanding. You never have to think "do I make this virtual or not?" or think "is that method virtual or not and does that have consequences for how I use it?"
int is not an Object, so just erasing is no longer a valid approach, you need to specialize the class/method itself to use int-specific byte code.
A big difference between both ecosystems is that the Java world is like C and C++, even though Java isn't defined by ISO or ECMA, since Sun days the main implementation is only a reference, there are official documents for everything, and there is a plethora of implementations, with various kinds of JIT, GC and AOT approaches.
You can pick the real time versions for embedded from PTC and Aicas, the cloud first from IBM and Azul with finance markets in mind, the Android cousin, the various implementations for M2M gateways, copiers and phone dashboards (Ricoh, Xerox, Cisco), IoT with microEJ, and many more.
Whereas Microsoft hardly cares about ECMA nowadays, most of Mono/Xamarin is gone replaced by Core CLR and modern .NET, .NET Compact is gone, community maintained and so on.
That alone, regardless of the languages on top of JVM, or CLR, makes a big difference on the audiences when one silos themselves to a single ecosystem.
They made a conscious decision to switch to a regular six-month cadence and it's been all the better for it. The preview-mechanism has been terrific there too, allowing half-baked features to be aired without absolutely committing to something that turns out to be flawed.
Edit: Ninja-ed by Romario77's sibling comment :)
This is a good thing. In Java everything has multiple community offerings, so before doing anything you have to evaluate the community offerings and decide which one to go with. If you go with the wrong one you may end up having to switch at some point, and that can be painful. This happens so often that most of the time spent when using Java is doing these evaluations and comparisons. With C# you just use the one built into .NET platform. Saves a ton of time.
All of my projects are based on Spring and I don't really have to look outside of that ecosystem. It almost acts as an aggregator of different open source solutions and often works by abstracting the functionality so that differences are not that big. I recently switched messaging providers and didn't have to change much of my code.
That's a pretty low bar to beat.
I had a sheaf of notes about the problem and figured out the math to build a proper dependency resolver for Python and tested out a lot of ideas such as being able to use http range requests to get the metadata out of wheels on PyPi without having to download the whole wheel.
The problem I had no solution for though was "how to stop developers from trashing the environment that the dependency manager runs in." The data scientists I worked with had an astonishing target for wrecking anything at all. Myself I would have my poetry's environment got bad for reasons I didn't understand every few months ago.
I also found the Python community just didn't care that pip didn't really work right. The most seductive form of blub is "I can accept using things that fail intermittently." I got a job coding Java and Javascript and never built the package manager.
Then uv came along and managed to sell itself as "crazy fast" which did connect with people more than "correct". Written in rust, uv would have beaten my system in the fast department, and since it is a binary, there is no way anyone can screw up a Python it depends on -- as I see it, both technical and marketing genius!
I used conda back in that period, it had a correct solver, and it was easy to manage my own packages, but it was slow in the technical sense of "it takes forever to build an environment" and slow in the business sense in that you got something curated which was not always the greatest or the latest but would, back in the day, "just work." Actually you could vendorize any software you need and have your own conda wheels, like I made wheels with different versions of CUDA drivers which are just DLLs so you could be running models with two versions of tensorflow that required two different versions of CUDA and never have to touch the NVIDIA installer.
But today it is a more "just works" experience to use PyPi instead of conda so I don't use conda.
Poetry was a big improvement over pip but I don't believe the resolver was 100% correct (like from looking at the source code) and performance was not that good, not so much because it was written in Python but because it did not have a proper cache, did not exploit concurrency. The Python way would be to use a world class SMT solver for the CPU intensive bit but when the bits hit the bus Rust is better at exploiting concurrency.
So I am happy to have uv.
The Core (smaller, but also properly crossplatform) project begun in 2014, fairly major rewrites with breaking changes in the new releases up until 2019 (.NET Core 3.1) and 2020 (the 5.0 release that became the official major "unification" with most parts of Framework having newer alternatives and being "complete" even if 6.0 and 7.0 patched holes).
Projects started with core 3.0/3.1 in 2019 have a pretty easy and clear upgrade path without major breaking changes up until today.
It's not JS/Node volatility, and the cleanups in the language/runtime were well worth it in hindsight (still maintaining old 4.8 applications running under IIS), also 4.8 is still nominally supported so there's no immediate stress in upgrading (There are better semantics today, but with huge projects those semantic differences, mainly no lazy-loading by default are a risk).
There is this division of labor between systems programmers and application programmers and often we think systems programmers are better because they know more about algorithms and data structures and compilers and assembly language and such. On the other hand, application developers understand how to reconcile the mental model of managers and employees and customers with computers, reality and common sense and, once they get experienced, see the commonalities between all the run-of-the-mill bizapps that we are coding all the time.
Application programmers do a lot better at applications framework than systems programmers and make things like Ruby on Rails and Spring. Systems programmers make terrible things like ASP.NET MVC (I worked out a way to do MVC with ordinary ASP.NET, why couldn't they, with access to the platform internals?)
https://en.wikipedia.org/wiki/Log4Shell
https://learn.microsoft.com/en-us/dotnet/core/install/window...
I think this is because the JRE/JDK upstream releases are a bit like Linux kernel releases: all the major first-party feature development goes on in subprojects that maintain their own "living forks" during feature development, with the teams on these features doing PRs against the fork's own "main"; that "fork's main" having its own subproject maintainers who ensure a mess isn't made of it; and then those maintainers eventually polishing up that fork-main into a single big one-shot PR to upstream once the feature-as-a-whole is ready.
(Compare/contrast: the Linux kernel's mm, rt, and kvm feature development efforts.)
Because of this, the top-level "project maintainers" (i.e. the people who decide what gets merged into upstream main) aren't really the same people as these subproject people who care deeply about these new features. They want to ship stuff people want, but they personally mostly deal all day with requests to merge 1. small bugfixes, and 2. features so small that no JEP is needed.
But then, every once in a while, they have to deal with a request to merge one of these huge subproject upstreaming PRs. And sure, it's already heavily reviewed by the subproject's maintainers, who they trust. But they do still have to audit it and learn it and create a stabilized release path for it. "Handover" stuff. And that's tiring!
So, given that the toplevel project maintainers write the release notes, I'm not surprised they come off as weary about releases.
(That being said, for purely PR reasons, the toplevel maintainers could ask the subproject staff to contribute their perspective to the release notes of a release that merges their work? But this could also just-as-well be a separate blog post—which would probably be better for sharing. I don't think I've ever seen a centralized Java blog [is there one?] but I think the subproject teams do tend to have them.)
Java tried to do fairly large updates and sometimes the release cycle would be very unpredictable as things would slip and take much longer than anticipated.
So to make it more predictable and to keep updates coming they switched to 6 months cadence with long term support (LTS) every two years.
This I think is a pretty good way of doing things, makes people who plan things figure out how to split feature development into these 6 months cycles, it made JEPs more granular and I think it made project Valhalla possible, if they tried doing it the old way it would never happen.
Splitting things in small chunks clarified what needs to be done and the path forward. It still takes very long time, but doesn't cause big incompatible changes and I think overall Java has good progress without being stalled.
Second was about licensing and Apache Harmony.
So, eventually they dropped most of the big things that were planned - Project Lambda with closures, Project Jigsaw with modularisation, Collection Literals. They eventually came back, but took a while to implement, so it was a prudent decision to make.
It may be that we are shielded from edge cases because we are based on Spring, which is probably the most tested piece of software before new versions of Java are released. But it's my impression that the risk of upgrading to a new version of Java is not the same today as it was in the past. The only advantage of an LTS is that it is supported longer, so that you can postpone the upgrade if you really want. It's not as if the intermediate releases are inferior or less safe.
You can almost think of the LTS releases as a major release and the non-LTS as a minor release, so really this could be 25.2. The current Java release schedule is to maintain a consistent and predictable release cadence instead of pushing big new features every 6 months.
While still being behind on most features?
> Let me get this straight: we're behind the [other languages] and we're going to catch up to them by going slower than they are?
Gotta go faster if you ever wanna catch up. However, Java is also purposefully slow. Everything is extremely considered. And while it means it takes a while before you get a feature it tends to be pretty good.
Used to work at a company which had services both in Java and C#, so some of Java's decisions or indecisions felt like pain points when switching between the two:
- Proper IEnumerable with proper iterators that in turn enables Linq (but in general permeates everything and is insanely easy to use and build upon). E.g. building an async service that behaves like an IEnumerable? Implement two methods.
Collection is halfway there, but I honestly cannot remember what was irking me about it in comparison to C#.
- Properties. Yeah, yeah, sealed classes, records and all that. Often you still need plain old classes.
- object initialisers. Which makes constructing anything a breeze. And on top of that you don't need manual .of methods for anything Colleciton-like if it'sa an IEnumerable.
- extension methods.
- named and optional arguments in functions
- null coalescing operator
- generics over primitive types (unless it was already implemented, I remember seeing a JEP about it)
- async/await. Yes, I know: different approaches to concurrency and all that. A lot of unnecessary verbiage could still probably be hidden behind a friendlier syntax.
- (sadly impossible in JVM to type erasure, only including this because I remember needing it many moons ago) generics metadata in runtime
- .... definitely a bunch more I don't remember at this point ...
Are you referring to generators?
> Properties
As far as I'm aware, it is a deliberate choice not to implement them, and I can see their point of view.
> object initialisers
I believe the same justification applies here, it's mainly syntactic sugar, and can result in certain undesired behavior by bypassing constructors where validation can happen.
> extension methods
Typeclasses are currently being explored, which are a superior approach.
> named and optional arguments in functions
Those would be nice (at least named arguments). I can see how optional arguments could complicate things.
> generics over primitive types
As you mentioned, it's in the works
> async/await... verbiage could still probably be hidden behind a friendlier syntax
The approach they took does not need any extra syntax.
Both I guess.
Main thing is https://learn.microsoft.com/en-us/dotnet/api/system.collecti... which seems to be everywhere in the language and the library.
> I believe the same justification applies here, it's mainly syntactic sugar, and can result in certain undesired behavior by bypassing constructors where validation can happen.
This is mostly due language design. Java heavily relies on properties and provides no facilities for them. Hence the builder pattern instead of object initializers.
In C# object initializers synergize with properties: https://learn.microsoft.com/en-us/dotnet/csharp/programming-...
> The approach they took does not need any extra syntax.
You mean it needs 15 lines whete C# needs one? ;)
Can you elaborate? The async/await approach is much more than 1 line when you need to switch a call between them. Whereas the green thread approach does not need anything.
x = await someFunction()
await waitForSomeOtherFunction()
(both are async)var x = someFunction() waitForSomeOtherFunction()
That's semantically equivalent in the two languages.
That's absolutely a valid language design and many people prefer that, but I personally prefer a bit smaller language with a bit more IDE auto complete, but where you never have to think about what exactly does a line do.
(And then there is also Go that falls off the other edge of the cliff with useless if err checks spamming the code making actually functioning error handling hard)
If you need IDE to autocomplete, then you definitely spend more time to understand what a line does ;)
One reads much faster than they write, I don't really see a problem with easily guessable auto complete.
I think that java found a decent spot where both factors are low. It's not a particularly verbose language, especially in modern times (records, type inference, I even mention lambdas because some people live in caves), like if you do POJOs and stuff with getter setters than you get a no information increasing extra 3+3 lines of code per field and that's most of it.
Method bodies are not more verbose than other typical languages.
Probably not a good idea since they break encapsulation by exposing internals of the class. There is work on withers, which should make defining builders far simpler.
> - extension methods.
They make code harder to understand. If they ever come they would have to be declared at the top of each source file.
> - null coalescing operator
Maybe we'll get it, maybe not, but they want to first introduce proper nullable types, lest there is a risk of painting themselves into a corner.
> - async/await
There is a fork in the road, and Java has gone into the direction that leads to virtual threads and Structured Concurrency, for the simple reason that there is no simpler syntax than plain old synchronous code.
> - ... generics metadata in runtime
There are plans to add a kind of reified generics, so maybe we'll get it.
And thousands of manual get/set functions don't?
Thousands of lines of builders don't?
Object initializers are that plus much better handling of fields/properties that doesn't require hundreds of lines of tedious manual code: https://learn.microsoft.com/en-us/dotnet/csharp/programming-...
> for the simple reason that there is no simpler syntax than plain old synchronous code.
But it's not synchronous code, is it? It's easily dozens of lines wrangling Futures, and Thread initialisers, and Executors, and...
Java always opts out for "let the developer handle all the complexity all the time even for the simplest most used parts of the code".
By definition, they don't.
They are verbose and hard to maintain, but if ever in the future you would want to keep the same API surface but change the internal implementation detail, they let you.
> But it's not synchronous code, is it? It's easily dozens of lines wrangling Futures, and Thread initialisers, and Executors, and...
No, it's done under the hood by the JVM. You only ever see a blocking call on a new "thread", via debugger via everything. Best of both worlds
So do properties in C# which object initialization relies on. With significantly less manual code, or the need for tedious builder chains and withers.
`{ prop = x }` is no more encapsulation breaking than ` .setProp(x) `, but actually makes developer experience better.
> No, it's done under the hood by the JVM. You only ever see a blocking call on a new "thread", via debugger via everything. Best of both worlds
What's Java's equivalent of
x = await someFunction()
await waitForSomeOtherFunction() var x = someFunction()
someOtherFunction()
If you would have written var xTask = SomeFunctionAsync();
await WaitForSomeOtherFunctionAsync();
string x = await xTask;
then it would be: try (var scope = StructuredTaskScope.open()) { // JDK 24+ preview feature
var x = scope.fork(() -> someFunction());
scope.fork(() -> waitForSomeOtherFunction());
scope.join();
String result = x.get(); // already completed
}Looks like Java's "there is no simpler syntax than plain old synchronous code" is just a lot of extra manual wrangling of stuff
They won't block the thread, and you can have millions of them.
Like it's no accident that c# with their goldfish attention span wanted to ship virtual threads as well next to all their millions of features.
I hope you know and understand the difference. Then strangely enough all my examples will make sense.
My statement doesn't apply to mere data carrier classes. Anyway, getters and setters are an antipattern as well since one can just as well make all the fields public.
> Thousands of lines of builders don't?
With withers most of these will go away. And a class will be able to choose which things can be set, which is not the case for initializers.
> But it's not synchronous code, is it? It's easily dozens of lines wrangling Futures, and Thread initialisers, and Executors, and..
That code won't look that much different with async/await.
I Java? Yes. Because of the language design, and not because of some inherent "encapsulation" or something.
Somehow `x with { a = b }` doesn't break encapsulation and offers nice DX. But object initializers? Lol
> And a class will be able to choose which things can be set, which is not the case for initializers.
The class in C# can easily chose what can and cannot be set. Because unlike Java it actually cares about things like this.
Quick example
class Test {
public int x { get; set; }
public int y { get; }
}
var t = new Test{ x = 1, y = 2 };
I can give you a hint: this will not compile.Another example:
public class Matrix
{
private double[,] storage = new double[3, 3];
public double this[int row, int column]
{
// The embedded array will throw out of range exceptions as appropriate.
get { return storage[row, column]; }
set { storage[row, column] = value; }
}
}
var identity = new Matrix
{
[0, 0] = 1.0,
[0, 1] = 0.0,
[0, 2] = 0.0,
[1, 0] = 0.0,
[1, 1] = 1.0,
[1, 2] = 0.0,
[2, 0] = 0.0,
[2, 1] = 0.0,
[2, 2] = 1.0,
};
Incomprehensible abilities for Java-land> That code won't look that much different with async/await.
- Value types is a huge one to add if we're looking at what is actually in the wild.
- Scopeless `using` declarations are nice for RAII like behavior.
- IMO C# builds are actually way way nicer than Java. Sln and .csproj files and nuget are actually a lot easier to deal with than javac/ant/mvn/Gradle. Maybe that's more a .NET thing than a C# feature.
Granted, the maintainers are more inclined to deprecate and remove parts of the API than has historically been the case but it is mostly obsolete things like applets. And you may need to keep a close eye on runtime flags and their effects.
Their process is still very deliberate, they go to some lengths to avoid getting it wrong when they add new features to the standard. New features have to get through their preview phase successfully before becoming final. [0]
They're also pretty committed to not breaking existing source code or bytecode.
[0] https://openjdk.org/jeps/12 JEP 12: Preview Features
But Java 8 was stable (as in APIs, not judging its quality here) and since then it's gotten good again.
A fixed release schedule makes development more relaxed so it can be properly done, no need to rush for some release date.
If it's not yet ready, there is 6 more months to get it merged.
> Isn't the fact that it's just sugar a huge benefit?
My main gripe is that I cannot remember what is allowed and not allowed between multiple versions of the same language. On a daily basis I hop between apps versioned in .NET Framework 4.8 all the way to .NET 10. I have to constant remember, are nullable types allowed here? What about 'new(); vs. new Object();', new collection syntax, new switch syntax, new extensions syntax, etc..
Plus, I just find it obnoxious that the same thing can be written so many different ways. I can think of 7 ways to assign a new empty List<T>.
List<T> foo = new List<T>();
var foo = new List<T>();
List<T> foo = new();
List<T> foo = new List<T> { };
var foo = new List<T> { };
List<T> foo = [];
var foo = (List<T>)[];
There are probably more that I am forgetting. What irks me most is Java is older than C#, and from what I can remember, it is not nearly this ridiculous in terms of syntactical sugar. So, what is the true benefit behind all this sugar? It hardly saves any keystrokes in the age of autocomplete in IDEs.
I am inclined to believe most of the sugar is an attempt to make the language appeal to a newer generations of programmers. But I would argue features are more attractive than syntactical sugar. I believe Rust is truly impressive language. In my opinion, its syntax is uglier than sin, but that does not seem to deter many from using Rust.
That's why you get `new List<T> { };` Because it could be `new ComplexObject { <fileds and properties> }`.
Same for `new`.
Some come from type inference which Java also has.
That's why you can have `List<T> foo = new List<T>();` and `var foo = new List<T>();`
It's not really "7 ways to assign a new empty List<T>". It's "7 ways to create an object", and Java several of them, too.
They really aren't, and they are IMHO an antipattern since they break encapsulation. One might argue that encapsulation doesn't matter with mere data classes, but Java will cater to that use case by introducing withers.
They don't. Java had to come up with the extremely verbose builder pattern for the exact same thing. And withers are basically the same tedious manual builder pattern, just with a different name.
For withers C# just has the with keyword: https://learn.microsoft.com/en-us/dotnet/csharp/language-ref...
Not 100% on board with the collection expression changes (I found fluent Linq chains usually more readable), but they're improving painpoints so I think it'll work out in the end hopefully.
I mean it's short and concise and there are additional resources that provide more detail. IMHO it's not a bad thing.
The vibe selects the audience perhaps.
People who are tired and just want to finish their work like the first style. People who want to do more cool work more quickly, maybe without finishing, like the second. Depends on the work, I suppose.
Anyway, not every release can be filled to the brim with new features, and people were also kinda busy whipping Project Valhalla into shape. INHO it's still preferable to stick to a predictable schedule instead of creating uncertainty in the community.
Seems about right
Open Source? No way. Git? No, they relied as die hard MS believers on the MS software called Team Foundation or something like that, that was integrated into Visual Studio Pro - sorry, I forgot about it, I considered it kind of bloat and outdated. Also I couldn't stand the nomenclature. A project was called "Solution" - I died inside, because this sounded like utter nonsense to me, because how do they know it would be one in the end?
While JetBrains as well as Linux quickly iterated through everything and got traction as well as a cadence that overall kind of was paced around sprint cycles that lasted two or four weeks, the company finally started to break up with project management and implemented Scrum.
As the JavaScript guy, the only one, because a customer wanted a SaaS "solution" but with static web content this wasn't really dynamic. I knew one of the founders who was a managing partner and he asked me to join as Web Developer.
Overall, all were very skeptical towards me because how could someone bet on JavaScript at the time? Well I turned the argument around and said the same about C# with its closed source walled garden approach to everything relying on MS to solve their problems with no way of giving feedback while there was no real release cycle and roadmap available - hopium and copium.
Statically typed languages for the win they said, blabla. I wasn't against static types, but did pure magic in JS, that they saw me as magician and I got some fans and I found one team mate who wanted to be coached by me on JS, Ajax and stuff.
So, there you have it. History.
I think there are pros and cons to any approach as always. Both language suffer from feature creep.
C# is still tightly knit into some products from MS and there are some backwards compatibility issues to take care of that limit certain progress and need substantial change.
Java isn't that way and was near dead and went OS. That's why they moved to the current model. Former versions were also hardly changed, have a look at everything before Java Version 12 or so.
Java wasn't community driven all the time.
So, C# has its merits, TypeScript for the win, so MS won over JavaScript ironically but only on the outside.
I shocked my MS fanboy colleagues when I really gave them a shock therapy regarding security when they mocked me with the examples given by MS why JavaScript was so bad and C# would beat it. We all know the infamous type coercion examples with mixed types, arrays etc.
So I shocked them with eval function of course but then gave them nightmares and mental overload with Function.prototype.toString and new Function() trickery.
It blew their mind, there was nothing remotely available in their world. Not introspection, nothing.
JavaScript was kind of assembler like I said. Highly flexible, you need to use modules, like jQuery did but have to build your own.
So TypeScript used exactly this flexibility: compiling to JavaScript. A metalanguage.
For the true insider, JavaScript won.
This has nothing to do with Oracle. All good that you hear from Java in the last few years, is the great community and good old people from Sun working at Oracle.
They had to commit to half a year release cycles and LTRs every 2 years. Since then the releases became a lot more predictable. Whatever is not ready is not released (or is there as a preview feature).
This more agile approach is a lot better in my experience and we see that the changes made are more relevant and what people actually want.