I just saved that as "hello.java" and ran it (using OpenJDK Runtime Environment Homebrew (build 23.0.1) which I happened to have on my Mac already) like this:
java --enable-preview hello.java
This is SO MUCH less crufty than the old "public static void main(String[] args)" thing. I always felt that was a terrible introduction to programming, when Hello World included a whole chunk of boilerplate and a class that didn't need to exist.
Anecdotal, but I had an intro to programming class in high school and the teacher was a wonderful woman, but not a programmer. She didn't know that this was a function declaration or its meaning, so she would just have us repeat out loud "public static void main string box args". I get a laugh whenever I think of that. I hope she's doing well.
I had an Excel class 20-25 years ago where a teacher explained click and double click every class, and emphasising "DOUBLE CLICK" out loud every time she did the action... There were a few facepalms.
The next class was assembly programming, where the teacher didn't bother to show for 4 months and then resumed the class as if we "self-taught up until this point". We were utterly lost in that one.
I imagine things have changed greatly today but back then it was a complete roller coaster.
In elementary school we had a "class" with the "computer teacher" once a week. He was just one of the only adults on the school district payroll who knew how to turn on and off an Apple //e. Nice guy (he also volunteered at the same community theatre I did), but "computer class" was just letting us loose with copies of Fraction Munchers and helping us if we jammed the disk drive or whatever.
I remember being 10 and going to a computer lab full of power macintoshes and playing sim city 2000, kid pix, and Carmen San Diego. This was in between looking at a cd encyclopedia (encarta?), old computers were awesome ;)
I wasn't good at getting educated, but I ended up at a community college to "get my generals" - my first class of the day was computer something or other, and the class was led by a man in extremely thick glasses who said that our general approach in the course was going to be:
* perform actions with the computer
* take a screenshot
* paste the screenshot into mspaint
* print out the screenshot as proof that we had completed the thing
* put that in a folder
* he would review the folder for completion
(for anyone paying attention, anyone could just print out multiple copies)
While it was a significantly younger and less experienced me who quit this bullshit in an absolute huff, I don't know if I could go through it today.
Fast forward a few years later, and I was training folks on tech support(a random reversal) and I had a lady who should have taken his class... every time I told them to click on something she asked "is that right click or a left click?" and each time I would respond "its a left click unless I tell you otherwise" (she didn't last long.)
A lot of Java professors did that in the past, because the "not hello world" parts of "hello world" require a bit more understanding, and in the spirit of getting people started, the idea is to just say "hey memorize this boilerplate stuff, eventually you'll understand what it means."
I haven't written Java in a million years.. so I'm trying to understand better.
But what does this code even mean..? Isn't Java's whole simplifying model that everything lives in "Objects"? This seems to break that fundamental paradigm and makes the language more complicated. If you needed something like global functions then in vanilla Java as I remember you'd stick it into static final Objects that acts as a namespace of sorts. So you don't need to worry about functions floating around in a global namespace soup.
If you're gunna just have free floating functions in your file.. then why not just cut to the chase and have a
println("Hello world");
floating in your file..? (I mean... you don't have REPL so maybe that wouldn't be too meaningful...)
> people basically judge java based on what you learn in AP CS which is basically (a dumbed down version of) java 6 (released 2006).
I don't think people realize Java8 was until very recently the dominant java version in production software, and currently close to 90% of all java projects still run on java11 and earlier.
Edit: after checking New Relic's 2024 report on the java ecosystem, it sounds like java11 and earlier is now at slightly over 60%, with java17 being used in around 35% of the projects.
For software written in that era currently under maintenance mode, I don't expect people to upgrade their java language version though upgrading their JVM is a good idea.
It’s honestly the only programming language that will get its 1996 version compared to a new 2025 version of another language. And the only language that will be criticized by someone who last used it in 2006.
> And the only language that will be criticized by someone who last used it in 2006.
You don't sound like you work with Java. I know for a fact that even some FANGs, with their army of software engineers, still use Java8 for most of their Java projects.
Historically an upgrade could cause issues if the code or dependencies used undocumented functionality (like Byte code manipulation) or calls to native code, or the transitions to modules in v9. Upgrades after version 11 should be less "risky" and be easier to argue to management.
From what I can tell, there are 4 main reasons why some don't upgrade.
1. They have legacy software and are afraid of breaking it, sometimes with good reasons.
2. The devs don't know or care enough about the new versions to bother with it. Noone is promoted because of an upgrade. Work to live, and don't live to work
3. The ones who don't have buy in from management to spend time fixing something that works
4. They are using version 11+ that should be much safer to upgrade than earlier versions, but they are still in the mindset of 1 or 2 above
There have been huge improvements not only in the language itself, but also performance and memory use. They could save money if they upgrade, but in some cases this is acceptable because they can make more money by spending time on a new feature, than an upgrade.
In my last 3 workplaces, they usually tried to use the latest LTS versions. But to be honest, some of the services were in maintenance mode and we didn't get enough benefits to upgrade, so we were sometimes falling behind. Today we have a mix of v17 and v21. Anyone can upgrade anytime they want but noone cares about a service that you seldom work with. I feel kind of bad for that, but not enough to bother. I focus mainly on the services that I work with daily, and make sure they are using the latest versions
> That's debatable, and even if we blindly assume that then it's debatable whether the upgrade justifies the gains
This is not debatable. It’s a factual truth. Every single actual performance review concludes that modern Java vastly out performs the decade old Java 8 runtime.
For an example here is a quote from Hazelcast when comparing performance of modern Java on data heavy workloads [0]:
> JDK 8 is an antiquated runtime. The default Parallel collector enters huge Full GC pauses and the G1, although having less frequent Full GCs, is stuck in an old version that uses just one thread to perform it, resulting in even longer pauses. Even on a moderate heap of 12 GB, the pauses were exceeding 20 seconds for Parallel and a full minute for G1. The ConcurrentMarkSweep collector is strictly worse than G1 in all scenarios, and its failure mode are multi-minute Full GC pauses.
This is from 2020 and the gap has only gotten wider.
I work in Java every day and have upgraded my entire companies fleet of services to Java 21. FANGs are nothing special. And every actual survey shows that there are as many people on a runtime > 8 as there are people on 8:
> And every actual survey shows that there are as many people on a runtime > 8 as there are people on 8:
I don't think you even bothered to read the sources you're quoting, and you're just trying to make baseless assertions.
The very first question on JetBrain's survey is "Which versions of Java do you regularly use?", and 50% answered Java 8 with around 2% mentioning Java 7 and 1% answering Java 6. On top of that, Java11 alone comprised around 38%.
And you're talking about Java21? It doesn't even register in the survey at all.
I'm not talking about Java 21 when referring to that data, I was refuting your baseless claim that I don't work in Java. I don't think you even bothered to actually read or comprehend my comment.
The data there clearly shows that the ecosystem has as many users running a modern version. Which directly counters your assertion that everyone is just running 8.
geokon's point still stands. This is meaningless syntax sugar that does not change anything fundamental about Java except add one more special rule into the language aka it is one more thing to learn. Rather than being helpful, it just becomes friction when an old developer uses the old method and a new developer uses the new method.
You still need to know what a main class is, so that you can reference it in your build.gradle file.
As others mentioned, there is an implicit class here in play.
Basically, imagine a `class Temporary4738 {` and `}` written above and below the .java file's content, that's roughly how it will behave.
It not having a fixed name is deliberate, so if you decide to build a bigger system you won't use this mechanism in a hard to maintain way, and also it needed a good semantic model for "top-level" variables/fields.
But with this implicit class model, top-level definitions are just fields of the class.
> This seems to break that fundamental paradigm and makes the language more complicated
Welcome to the last 10+ years of Java. They're trying to play catch-up to other languages that made fundamentally different and better choices, making Java more and more complicated in the process. They are so deeply entrenched in the enterprise back end space that programmers put up with it, nay, even feel grateful for it. People who are stuck using Java for the rest of their careers are happy to see it grow to accommodate 21st century programming techniques, even if it means that it becomes so complicated and crufty that no beginner will touch it ever again.
In java's defense -- and I share your criticisms -- it offers some very nice things that no other language has.
eg want to run code for 6 months at a go managing hundreds of gigs of data, tens of thousands of threads, at high load, with sustained gigs a second of garbage generation? the jvm will just silently tick away doing just that while being nearly bulletproof. As much as I love ruby and python, you ain't doing that with them.
With hot code loading, introspection, etc. All the stuff that makes developing super-robust long-lived systems way easier.
And the flip side of the clunky language is your code from 20 years ago still works.
Also, conservative feature adoption works in the long term.
Scala mainstreamed a bunch of concepts that java was slow to adopt, but the end result was java only took the good ideas and didn't take the bad ideas.
It doesn't surprise me; that sounds exactly like how C++ has progressed (sans the enterprise beans, hopefully).
10+ years ago I considered myself proficient at C++. It made sense, I didn't mind the pointers, and while the std library still felt clunky, it was fine. But I tabled these skills, and they indeed rusted a bit, but surely I could bring them out when needed again, right?
Smart pointers with multiple variants, judicious use of macros, rampant use of double colons to ensure proper namespace resolution (which IMHO makes the code an absolute eyesore), to name a few. I won't argue these changes aren't necessary, but it isn't pretty.
> So you don't need to worry about functions floating around in a global namespace soup.
Because having classes floating around in a global namespace soup is fundamentally different and should give no worries to anyone. Yet this was argument made in earnest back when arguments about Java's strengths and weaknesses were much more popular.
With the 1:1 intended correspondence of (public?) class names to source file names I get how that could be considered a feature for ease of use, compared to C++ where any file could define any name.
It took almost 30 years to get here. I never thought I'd see the day.
What really threw me for a loop is the article author mentions: "I knew plenty of professors who were bothered by [the public static void main thing]." I could have sworn that OOP die-hards in academia _wanted_ the formal class boilerplate in the original design. Maybe they aged out? Perhaps, times are changing.
Python seems to have started with 'why do those other languages have all this trash' and then spent the time since then learning why and coming up with worse versions.
I use python a lot these days, and like it, but it's pretty funny seeing stuff like the above and type hints.
I hate not knowing what types a function takes and returns.
> Python seems to have started with 'why do those other languages have all this trash' and then spent the time since then learning why and coming up with worse versions.
This seems true to me too. Examples:
* if __name__ == "__main__": main()
* Private members being marked by convention with leading underscore (e.g. def _foo()) instead of being a language feature
* @staticmethod as a decorator instead of being a language feature
* Duck typing vs. abstract base classes
* Static type hints got retrofitted to the language gradually, one feature at a time
* Reference-counted garbage collection seems to be more deterministic than tracing garbage collection and ensures that unreachable resources have their finalizers run as soon as possible... except it's not true
* Having a GIL instead of being truly multi-threaded from day one
* Various OOP concepts that are much better explained in Java than Python: __eq__(), __hash__(), monitor wait() and notify(), object finalizers, thread safety, weak references
* Distinction between str and bytes. This is the biggest change from Python 2 to 3 and caused a lot of incompatibilities. Java separated String and byte[] from the start (though the use of UTF-16 is unfortunate).
The whole switch/pattern matching thing is a true abomination borne from: Well, things in statement position can't be expressions (or however Pythonistas might phrase it), so clearly switch/pattern matching must be a statement... It's such an own goal and undermines the main point of pattern matching.
(I do realize that there was opposition on this, but that the Clearly Wrong side won out is baffling.)
Yeah, it's a shame for Python. I am extremely impressed that Java 14's switch expression ( https://openjdk.org/jeps/361 ) is a generalization of the switch statement (since Java 1.0); it's like they took a page from Rust's language design. I have found the switch expression to be quite useful in my work.
Your quibbles show either a) a fundamental misunderstanding of Python or b) are just flat out incorrect.
> * * if __name__ == "__main__": main()
So, don't use it. Python is frequently run as a scripting language (something Java is fundamentally bad at) and this stems from that. All it does is box logic off when a file is being run directly vs imported. It's a user convention and not a language one....ignore it if you hate it so.
> * Private members being marked by convention with leading underscore (e.g. def _foo()) instead of being a language feature
This is all very well explained in the PEP. The quick run down, Python doesn't do anything to hide logic from users. Contracts are just conventions and python treats it that way; so the developer can say "you shouldn't use this directly, here's a notation to let you know", but nothing will stop a developer from doing what they want. In Java they'll just write a wrapper class, inherit the base class, and expose that logic. Or worse, fork the library just to edit the class' functionality in some minor way. In Python they'll just call it directly.
> * @staticmethod as a decorator instead of being a language feature
@staticmethod is a built-in, it is a language feature. It just doesn't follow your preferred syntax.
The concepts aren't mutually exclusive or even directly related. Java just happens to be bad at one of them, due to it's weirdly non-effective (for a VM'd language, at least) reflection system; so you think it's bad.
> * Static type hints
You're complaining about reflective programming and then complaining about a feature that essentially exists because you can't reflect. It's circular.
> * Reference-counted garbage collection seems to be more deterministic than tracing garbage collection and ensures that unreachable resources have their finalizers run as soon as possible... except it's not true.
GC arguments have run for decades and everyone has their opinions.
> * Having a GIL instead of being truly multi-threaded from day one
Python was created in 1989. Java was created in 1996. Can you guess what major change in computer history was happening around the latter's time?
Everything was GIANT-locked when Python was designed, unless it was specifically intended for SMP mainframes. The entirety of the FreeBSD operating system had a GIANT lock on it at the time, for instance. Linux didn't even exist. Mac OS and Windows both were fundamentally single threaded and cooperatively multitasked. Commercial Unices that supported SMP were only ~4-5 years old and a very small niche.
You might as well be complaining about "why didn't the x86 architecture just have 64-bit capabilities from the outset?"
> * Various OOP concepts that are much better explained in Java than Python: __eq__(), __hash__(), monitor wait() and notify(), object finalizers, thread safety, weak references.
In other words: "it's not Java, so it's bad".
Java is a pure-OO language; Python is a procedural language with OO as an optional feature. To that end, it exposes OO-features in an optional manner versus forcing you to use them against your will.
So if the basis of all your arguments is "OO is better in Java", well the the response is "yeah I'd hope so, since you have no other choice as it's the fundamental paradigm". Guess what? Haskell is much better at functional programming than Java; that also doesn't make a valid argument about whether either is good or bad.
> * Distinction between str and bytes. This is the biggest change from Python 2 to 3 and caused a lot of incompatibilities. Java separated String and byte[] from the start (though the use of UTF-16 is unfortunate).
Java was developed 7 years later and during a time that Unicode was becoming the standard over ASCII. Python was designed when everything was still ASCII and Unicode a mere glint to the general computer science realm.
As you pointed out, even Java made a bad decision here due to their premature adoption of the standards (as any modern designed language is quick to point out).
And your reply is half right, half wrong. Let me address the wrong parts.
> Duck typing vs. abstract base classes / You can do both in Python
I'm saying that the original intent of Python was duck typing, but then people realized that abstract base classes play an important role - e.g. isinstance() testing, static type annotations. So they still ended up where Java started.
> Static type hints / I don't even know what that means.
I'm saying that Python was designed with no type annotations on variables (though values did have types), and then they realized that people wanted this feature... and ended up where C/C++/Java/C# have been all along. Python became "enterprisey".
And then, Python implemented type hints rather badly from 3.0 through 3.10 and beyond. It doesn't include types in the official documentation, so now you have to guess what open() returns (it's typing.BinaryIO/typing.TextIO). It doesn't have an official type checker, instead relying on third-party tools like mypy. It moved items between typing and collections.abc. It requires `from future import __annotations__` for non-trivial programs. It changed typing.List to just list in 3.9. It introduced the | (union) operator in 3.10. And a bunch more little things I can't recall; it just had a bad out-of-the-box experience and kept tweaking things over the decade. Documenting generics and especially protocols in Python takes effort.
> Python was created in 1989. Java was created in 1996
Extremely wrong. Java has primitive numeric types, which do not have methods or fields, and undergo painful boxing/unboxing conversions to interoperate with the OO world. Whether the performance benefits are worth it or not is debatable, but what's not debatable is that Java is not pure OOP. Some say that Java is a bad copy of Smalltalk, which I heard is fully object-oriented.
> Python is a procedural language with OO as an optional feature
Wrong. Every Python value is an object that can be inspected (dir(), .__dict__, etc.). And in the CPython API, every Python value is a PyObject*. I have ample grounds to believe that Python is more OO than Java.
>Wrong. Java was created in 1991, and the first version released in 1996.
Gosling and others started working on it in 1991, but does it really matter? First public release is when you can learn about it and borrow ideas. It doesn’t make your point less valid, of course - Java made a lot of hype back then.
You're purposefully fudging dates to make the argument more favorable to your point. If you want to argue initial source release, then you can maybe make the point for 0.9:
> In February 1991, Van Rossum published the code (labeled version 0.9.0) to alt.sources
And say that Python had it's initial release just slightly before Java had begun design specs. But Python was in use before that and Rossum had developed the initial version well before that (again, 1989):
> The programming language Python was conceived in the late 1980s,[1] and its implementation was started in December 1989[2] by Guido van Rossum
It's ironic that you're trying to make that same argument for Java and dismissing it for Python, when Python was very much in public use pre-1.0 and Java was not (outside of internal teams).
> Wrong. Every Python value is an object that can be inspected (dir(), .__dict__, etc.). And in the CPython API, every Python value is a PyObject*. I have ample grounds to believe that Python is more OO than Java.
I feel like, just based on this point, you've done nothing more than open the CPython source code and searched for "object". This naming was in place well before Python even had any of the OO-functionality that exists today (new-style classes derived from the Object type). If you're going to argue for "old-style" classes being OO in any way but name, you're probably going to fundamentally disagree with any OO fundamentalists, the designers of Python itself, and the Java community/designers. You might as well argue that structs with function pointers in C make it an OO-language, as that's functionally all they are.
PyObject doesn't correlate to a Python class/Object. It's a container data structure to allow for their duck/dynamic typing. Object is a type of PyObject; but it contains an additional layer of functionality to make it a Python Object (specifically PyMethodObject and PyTypeObject, and their correlative functionality). Again, to allow for the reflection/introspection and GC that you so bemoan; and due to the lack of C generics at the time (or really, even today). Being able to introspect a type has nothing to do with it's "OO-ness", although it can be very useful in such languages (such as C#).
As to your other point, sure...using "pure" was probably going too far. But by that same argument, even Haskell isn't pure-functional (nor do any really exist) due to it's need to interface with IO. But Java is about 90% there and strives for it. Python most definitely isn't nor does it make any intentions to do so.
Again, fudging the history/facts/topics to try and make your point. It's not worth discussing with someone who so fundamentally converses in bad faith. Especially since I'm making no claims to which is better, just outlining the flaws in your complaints. I really don't care about "better" languages.
I never understood why people bother with this when writing their python main file, which is never going to be imported because it doesn't make sense in the first place.
REPL-based development. You might be developing a script, but notice a bug. If you import your script as a module, you can run the function you’re debugging, figure out what’s wrong, edit the file, and then reimport the module (using importlib.reload).
Because I dislike interspersing executable statements at module level with function definitions. I’d rather have all code live inside functions and have one main entry point for the script. It’s just a personal preference, nothing wrong with not doing that though.
Isn't that hugely elaborate compared to languages where top level statements are a function, functions return their last statement, and the return value of the entry function is sent to stdout unless invoked in special ways? That print() around the "hello world!" does not appear any less superfluous than all the public static void you can throw at a javac 1.0
Exactly like that (well, almost exactly: if python includes the quotes then it's not really equivalent to print()).
The point was that by including that print(), people already do say yes to including "like you'd do in a real program" ceremony in their supposedly minimal examples.
You have to brew install kotlin for this to work of course. But it's a great way for using a lot of Java stuff as well. Kotlin's Java interoperability is excellent if you are using Java from Kotlin.
IMHO Kotlin is underused as an alternative to python currently for data science stuff. It's surprisingly capable out of the box even with just the standard library and there are a lot of nice data science libraries that make it more useful. Not for everyone; but fairly easy to get started with.
Kotlin scripting is unfortunately not necessarily very user friendly (e.g. imports can be a bit tedious and IDE support is a bit meh). But it can be a nice way to embed some kotlin stuff in a script. Generally, Jetbrains could give this topic a lot more love and attention and it wouldn't even take that much to level up the experience.
KTS works in jupyter as well (there is a kotlin engine for that). And that of course is nice if you want to use Java libraries in jupyter. And developing kotlin DSLs for stuff and then using them in a script is kind of a power move.
> Kotlin scripting is unfortunately not necessarily very user friendly (e.g. imports can be a bit tedious and IDE support is a bit meh). But it can be a nice way to embed some kotlin stuff in a script. Generally, Jetbrains could give this topic a lot more love and attention and it wouldn't even take that much to level up the experience.
Kotlin has been pretty bad at scripting and REPL, and unfortunately the team decided to drop both:
Based on that page, they're dropping the REPL in favor of notebooks, but their're not dropping scripts. They are dropping some script-related functionality to focus on others.
Yes, they are not dropping scripts completely, at least because of Gradle, but they are removing some parts of scripting support and advise against using Kotlin for scripting. And I would prefer a true command line REPL instead of the notebooks. IIRC they were promising various improvements in both scripting and REPL for several years, and now they just gave up, I guess because of shifting the focus to K2 and KMP. I find this quite disappointing.
Also worth noting that notebooks and kts overlap quite a bit. They use the same mechanism for imports and defining remote repositories, for example. A notebook is effectively a kts script. And of course gradle uses kts as well for its Kotlin dialect. These things actually overlap quite a bit.
void main(String[] args) {
println("Hello world");
}
and the previous one ...
class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
And that is SO MUCH less? Are you being ironic?
Should we allow somebody who cannot handle the second one, go on and programming because of the first one?
Really Java has always been kinda okay - the really horrible part of Java was always the "enterprise OOP" programming patterns that were basically a given in any commercial codebase - little things like pointing out the verbosity of a basic single file project are just shorthand for "look at all this shit people have written".
As the adage goes - Java programmers can write Java in any language.
You mean stuff like in the 'Design Patterns' book by the Gang of Four? That was originally written for C++ programmers and predates Java by a few years.
Seems 'Java programmers' were already writing Java before Java's release ;)
Afaik, at the time issue was a lot of programming in xml - where those frameworks are configured in various xmls and hard to understand and maintain. Reflection based frameworks are later development.
The massive amounts of indirection which the IDE wouldn’t help you understand was the difficult thing. What encoding you use for that is pretty irrelevant. JSON would have been worse.
The indirection in Java itself matter significantly less, because you ctrl+click and it gets you where the things are. Plus auto completion. Both were part if Eclipse years and years ago.
The problem with xml was that IDEs were unable to help you.
I agree that can serve as terrible intro to programming. But, often when we have tools either designed for a purpose in mind or a dominant paridigm or reaction to existing set of tooling, this can result in understandable yet extreme abstractions.
Java is designed with OOP in mind and it kind of makes sense to have the user to think in terms of lego blocks of interfaces. Every method or class needs to have clear understanding of its users.
public - software handle is for all users
protected - software handle for current and extending classes
default - software is exposed to current package
private - software is restricted to be used in current class alone and nowhere else
So, the beginning of java programming starts with interface exposed to the user or other programmers. Is it weird and extreme. Yes. At least, it is consistent.
They've also done quite a bit of work to make things such as hosting a web service much much simpler, through the addition of things like SimpleFileServer, as well as some better convenience methods.
It makes it far far closer to the python experience.
It's a good local improvement, but the other hand that old traditional syntax was a good effective "keep away" sign in Java that saved many CS students from being fed Java as a first language. And it's still a bad first language after this change. I wonder if this small improvement might make things worse in the big picture.
These features are explicitly to make teaching Java as a first language easier. From https://openjdk.org/jeps/463
Goals:
Offer a smooth on-ramp to Java programming so that instructors can introduce concepts in a gradual manner.
Help students to write basic programs in a concise manner and grow their code gracefully as their skills grow.
Reduce the ceremony of writing simple programs such as scripts and command-line utilities.
Do not introduce a separate beginners' dialect of the Java language.
Do not introduce a separate beginners' toolchain; student programs should be compiled and run with the same tools that compile and run any Java program.
I learnt to program using Pascal, then C++ in Highschool.
In collage, CS101 and 102 required me to learn and use JAVA, which I felt was a huge step backwards in usability and a lot of excess typing.
But as I reflect on it now, I think my feeling were because I came from knowing how to do some things and therefore not requiring the framework that JAVA offed to first time programmers.
I think the sins of Java as a first language are twofold:
1. The obvious excess incidental complexity
2. and more important, the enterprisey culture that's much less compatible with the intellectual curiosity, looking under the hood, etc attitude that's needed to become a decent programmer.
I've been using Java 21 for Advent of Code this year, and I've been surprised at how much less clunky it is than I remember. `var` and `record`'s are especially nice for how I like to code. It's still not perfect, but it's not something that I hate programming in now.
These were some neat tricks. I've been using the `java myfile.java` approach for running the AoC problems. I didn't realize the "implicit class" thing where you could have top level statements and methods, though, with the automatic import of java.base. That's pretty slick for this.
This is actually a natural extension of classes being allowed inside, which is not well-known for some reason. Nonetheless, I also really like this feature with records.
Just to be clear, the implicit class thing is still a preview feature as of Java 24, which should come out next March. It might be finalized for Java 25, in the coming September. But no guarantees. Until then you'd need to explicitly enable it with a cli parameter.
Launching without compiling, a main interface... a Mark Reinhold story
During jdk 1.1 development, there was obviously no consensus on test suites (i.e., JUnit), and the JavaSoft JCK tests required a ridiculous amount of html documentation for tracing to specs. Mark Reinhold, the jdk tech lead, refused to have his team write tests in JCK form, so instead he wrote a small harness with a compiling class loader: point the harness at a directory tree of .java files, and it would run them all, compiling as needed. The test interface was just a main function, and if it threw an exception, it failed. Pure and simple.
But probably the best magic trick for Java programmers is debugger hot reload. You write some empty methods, start the debugger, and then code iteratively, reloading as needed. (Set up the data and interfaces right, and change method bodies as you wish.) It's so much easier than recompile-println when you're just getting started, and after you've grown up and out, it's easier than rebuild/redeploy when you're working on a big system.
Hey, that's (close to) the traditional Smalltalk introduction-trick! And this has been available since 1.1? How does one concretely do that in Java and why is it not widely known?
1. There's no way to react to hot reload in the normal Java API, so you can't easily clear state.
2. The JDWP protocol lets debuggers redefine nearly anything, but HotSpot doesn't implement the full protocol. In practice many kinds of reload don't work.
3. To fix those issues app frameworks went with classloaders for partial app reloads instead, or reimplemented hotswap logic themselves (see JRebel).
There's no fundamental reason these can't be fixed, and in fact the Espresso JVM fixes both 1 and 2:
You can write JVM plugins that are invoked after redefinition occurs, allowing you (really your app framework) to do something sensible like partially reload state. And it implements the full protocol, so many more kinds of redefinition work.
Using it is easy. Just install Espresso as your JDK, point your build system at it, then debug your app. Recompiling what attached will cause hot reload.
Interesting, I've never heard of Espresso, I've always just used Jetbrains Runtime[1] instead which is the successor of DCEVM[2] in a way. As for plugins I used HotswapAgent[3] at times however I found it to be rather clunky, does Espresso improve on this aspect?
Well, it doesn't sit that well with mutable global/local state. There is a saying that hot reloading works better if you can replace at a smaller, atomic granularity. In the JVM, this kind of reload is at the method granularity, which may or may not be ideal.
But the JVM still has a few tricks up its sleeve, e.g. the class loader can dynamically re-load a newly compiled version of a class at runtime - jrebel is a proprietary extension that uses both tricks, but this latter can also be used by spring for hot swapping many parts of a running system.
Basically, in the JVM a class is unique within the class loader, so with a new class loader you can load as many class instances of the same, or slightly different class as you wish.
> But probably the best magic trick for Java programmers is debugger hot reload. You write some empty methods, start the debugger, and then code iteratively, reloading as needed. (Set up the data and interfaces right, and change method bodies as you wish.)
Do you have any example you can point to for this ?
It’s not a popular choice for it, but PHP is also fantastic for the sort of things described in this article. A relatively recent version is pre-installed on many *nixes, it has a decent enough (and gradual) type system, and standard library absolutely chock full of functionality (albeit very self-inconsistent in naming and structure) for dealing with files, JSON, making network requests, etc.
And because it’s not compiled, you can just whack a hashbang at the top of the file and chmod it to be executable.
I agree it'll work, but from a broader ecosystem perspective it feels like a mismatch, given PHP's historic focus on webstack stuff, and the projected likelihood that someone maintaining the system will know PHP versus Python etc.
But perhaps we can both agree that it shouldn't be in JS/Node. :p
It's worth observing that languages significantly over time, usually for the better. I remember writing Java when it was slow and had no generics. Now it's one of the fastest languages, and has not just generics but also (simple) type inference, which cuts down significantly on boilerplate. The point is: if you have a foggy memory from ten years ago of hating some language, many of the problems may have now been fixed.
Few languages pull off the balancing act Java does, supporting decades of code while rolling out big new features like project Loom in a backward compatible way. Oracle’s work really feels like a lesson in how to evolve a language.
It's hard to fault Java at this point: arguably the best runtime of them all from a perf perspective, great tooling from JetBrains, large, high-quality ecosystem whose quality far exceeds npm on average, and sealed classes as an acceptable sum type. My only remaining issue is how prevalent nulls are, but there's some work in progress on improving that. Until then, I'll prefer Kotlin slightly.
Definitely one of those things where the comment section is only good at spouting ancient memes with little regard to the truth of them.
> It's hard to fault Java at this point: arguably the best runtime of them all from a perf perspective
The JVM is a massive memory sink compared to the tiny (actual) runtime in Go or the total absence of a VM or runtime in Rust or Zig. I allocate at least 5x more memory for Java tasks, but sometimes its more.
Java is great compared to scripting languages or writing raw C, but not compared to modern compiled languages from a syntax (null, thrown exceptions, inheritance, etc..), library selection, CVE/security, memory usage or raw computer power perspective.
Many times the "massive" memory sink is because people give the VM more memory than it actually needs (or they use the default, which is to take up to 25% of RAM whether or not it's really needed for the required performance). I.e. people will say a process "takes" 1gb, when the same program could run just as well if they tell it to use, say, 300mb. The more memory, the faster it could run. The GCs offered by the JVM are more advanced than Go's by a couple of tech generations (the Go-like GC was removed from the JVM when it was superseded by two generations of newer GCs), and memory consumption is already going down with compact object headers [1] and will go down even further with Valhalla.
All in all, Go may take up a little less memory for similar performance, but the JVM is more flexible (and more observable), offering you better performance if you need it. And the footprint will continue dropping, as it has done for years [2].
C++, Zig, or Rust are not really an apples-to-apples comparison. Sure, they take up significantly less RAM, but their performance is more expensive per unit of effort, and it's not a one-time cost, either. It's a permanent tax on maintenance, not to mention Java's superb observability.
Don't get me wrong -- I'm a fan of Zig, and C++ is the language I still program in most, but you can't really compare them to Java. Java makes you pay for certain things -- especially in RAM -- but you do get your memory's worth in exchange, and in a way that translates to pretty significant time and money. If you can't spare the memory as you're running in a constrained environment that's one thing, but if you can, it's all a matter of what you want to use it for: do you want to use it all just on data, or do you want to spare some to reduce maintenance costs/increase development speed and improve performance?
BTW, I'm not sure what is actually meant by a "lack of runtime" for C++/Rust/Zig. They all have standard libraries, just like Java. Rust even has a (crude) GC in its runtime that most Rust programs use (and, like Java, it compiles down to a VM). I think what people mean is that compilation to native is typically AOT rather than JIT, but that has both pros and cons.
I'm interested by your characterisation of Rust. I assume 'crude GC' is a reference to Rc/Arc, but I would be interested to see some statistics for the claim most programs written in Rust use them extensively. Also, Rc/Arc arent a part of any Rust 'runtime', but rather the standard library, and are not available when they eould not be suitable for the target, e.g. UEFI. Moreover, rustc compiles to LLVM IR, but LLVM is not a JVM/CLR VM, and rustc is not the only Rust compiler (though the others are admittedly not production-ready yet).
> I assume 'crude GC' is a reference to Rc/Arc, but I would be interested to see some statistics for the claim most programs written in Rust use them extensively.
Yes, Rust's GC is used through Rc/Arc, and I never said it is used extensively by most programs, only that most programs do use it. It is because it is not used extensively that it can be crude and designed to minimise footprint rather than offer good performance.
> Also, Rc/Arc arent a part of any Rust 'runtime', but rather the standard library
What's the difference between a standard library and a runtime? In the three decades I've been programming, they've been used interchangeably. A language runtime means some precompiled-code that programs use but is not compiled directly from the program.
> rustc compiles to LLVM IR, but LLVM is not a JVM/CLR VM
I never said that LLVM was a JVM -- these virtual machines have very different instruction sets -- but like a JVM, LLVM is a VM, i.e. an instruction set for an abstract machine.
Now, it is true that Rust is typically (though not always) compiled to native code AOT while Java code is typically (though not always) compiled to native code JIT, but I don't understand why that difference is stated in terms of having a runtime. One could have an AOT-compiled JVM (and, indeed, that exists) as well as a JIT-compiled LLVM (and that exists, too).
It is also true that Rust programs can be compiled without a runtime (or a very minimal one) while Java programs can choose to have more or less in their runtime, but even the most minimal runtime is larger than the most minimal Rust runtime.
> What's the difference between a standard library and a runtime? In the three decades I've been programming, they've been used interchangeably.
First of all, you're right. But despite its definition I think people tend to look at it differently.
A runtime is generally thought of as a platform on top of which your code runs on; it needs to start first, and it manages your code. Or perhaps it runs in a side thread.
A language that has a runtime is hard to embed into something via just the C ABI, because a function call wouldn't use just the standard platform calling convention; it would have to start that runtime, perhaps marshal the parameters into something supported by that runtime, and then finally the runtime runs your function's code.
Take for example cgo, for which you'd need to start the garbage collector first (among other things), hence why the cgo FFI is expensive. Take as another example an async Rust function, which would require e.g. a Tokio runtime to be started first. Another example is Java, for which you'd have to start the whole JVM first.
A language that has no runtime, or a minimal runtime, can be called via the C ABI directly. All the function needs is to follow the calling convention, and then its code starts running immediately.
This is just my opinion of other people's opinions, I may be wrong.
> A runtime is generally thought of as a platform on top of which your code runs on
That's not a well-defined thing.
> A language that has a runtime is hard to embed into something via just the C ABI, because a function call wouldn't use just the standard platform calling convention
But Java can be embedded in native code or embed native code. It has a specified FFI in both directions.
> Take for example cgo, for which you'd need to start the garbage collector first (among other things), hence why the cgo FFI is expensive.
Well, Java doesn't quite work like that, and its (new) FFI is free in most important cases (i.e. same as a non-inlined C-to-C call). Also, "starting the garbage collector" is not well-defined. What "starts" Rust's garbage collector?
I understand what you're trying to get at, but things aren't so simple. There are, indeed, differences especially around JIT vs AOT, but it's not as simple as saying "having a runtime" or not, nor is everything similar in all languages (Rust and C don't work the same vis-a-vis the C ABI, and Java, C#, and Go interop with native code are all quite different from each other).
> A language that has no runtime, or a minimal runtime, can be called via the C ABI directly.
A Java program can easily expose any method via the C ABI to be called directly if the process has been started from Java -- i.e. it's easy for Java code to give native code a function pointer to Java code. Going the other way, i.e. embedding Java in a C program, is somewhat more involved, but even C++'s interop with C, not to mention Rust or Zig, is not always straightforward. Like in Java, certain functions need to be marked as "C-interopable".
> But Java can be embedded in native code or embed native code. It has a specified FFI in both directions.
Most languages have an FFI, but I am talking specifically about the C ABI and the platform calling convention; or more specifically, about starting from scratch, and what is necessary to do from there until your code can finally run.
Anything more complex than the C ABI is what makes people say there is a runtime. It's some layer between your code and the other language's code, inserted there by your language. There's usually no way to remove it, and if there is, it severely limits the language features you can use.
> What "starts" Rust's garbage collector?
Nothing; it doesn't start unless the function itself wants to start one, and the function can choose which one to start, through your code (rather than what the language's required runtime provides).
> A Java program can easily expose any method via the C ABI to be called directly if the process has been started from Java
In that case, the runtime has already been started, and is being reused.
> Going the other way, i.e. embedding Java in a C program, is somewhat more involved
That part is the most important part, and is generally why people say Rust has a minimal runtime; it can be embedded with very little setup. The code you write starts executing almost immediately. Java calling C may add a management layer on Java's side, but C/C++/Rust/Zig/etc need very little (hence, minimal runtime).
> Anything more complex than the C ABI is what makes people say there is a runtime.
But Rust (or Zig, or C++ for that matter) don't use the C ABI, either, except for specifically annotated functions.
> In that case, the runtime has already been started, and is being reused.
True, but I'm trying to say that the notion of "starting" the runtime (or the GC for that matter) is not really well-defined. HotSpot does need to be "started", but, say, Graal Native Image, which is sort of an AOT-compiled, statically linked JVM, isn't really "started".
> Java calling C may add a management layer on Java's side, but C/C++/Rust/Zig/etc need very little (hence, minimal runtime).
In some implementations of Java this may be the case in some situations, yes. I would go further and say that that's the typical case, i.e. if you want to embed the stock HotSpot, you will need to call some initialisation functions.
If that's what's meant by "runtime", then it's mostly correct, but it's more an implementation detail of HotSpot. Even without it there will remain more important differences between C++/Zig/Rust/C and Java, and this matter of "runtime" is not the most interesting aspect. For example, that Java is usually compiled JIT and Rust is usually compiled AOT is a bigger and more interesting difference.
> But Rust (or Zig, or C++ for that matter) don't use the C ABI, either, except for specifically annotated functions.
Not only that, but Rust, C, Zig also require some setup before their `main()` can start as well.
That is why people say they have a "minimal runtime", rather than "no runtime". There is still a bit of setup there, without which the languages cannot function, or can only function in a limited mode.
FWIW Cgo FFI is expensive not because of GC but because Go uses virtual threads (goroutines) and prioritizes the simplicity of runtime implementation. The lower bound of FFI cost in .NET is roughly equivalent to direct not-inlined calls in C, despite the GC support.
> I never said it is used extensively by most programs
True, but to make use of Rc/Arc in Rust comparable to the use of GC in Java, almost evrery value would have to be wrapped in one, something I would think is quite rare.
> only that most programs do use it
I would still be interested in seeing statistics for this though. I have only ever used Arc once, and it was only to share a Mutex containing the program's database across threads.
> What's the difference between a standard library and a runtime?
I would say there are three main differences:
- A runtime is (usually?) not optional - see languages like C#/Java/Python that require some sort of higher 'power' to manage their execution (interpreting/JITing code, GC management, etc), or crt0 - compared to a standard library which is just some extra code - see the C standard library function strlen()
- A standard library can generally be implemented in the language itself. I think this is where the distinction starts to get a little fuzzier, with languages like Roc (its standard library is implemented in Zig), and Haskell (I would assume much of the side-effecty code like the implementation of IO is in C)
- The purpose of a standard library is generally to provide 'helper' code that you wouldn't want to write yourself, e.g. Vec<T>, HashMap<T>, filesystem functions, etc. On the other hand, the purpose of a runtime is, per the first point, to manage the execution of the language, etc.
> I never said that LLVM was a JVM
True again, my point was worded badly. I didn't mean to suggest you thought LLVM was a JVM, rather to draw a distinction between LLVM and a Java/.NET style VM. LLVM used to stand for Low Level Virtual Machine, and the JVM has instructions like instanceof (which, fairly obviously, checks if a reference is an instance of the named class, array, or interface type). They operate at quite different abstraction levels, and the JVM is a lot more similar to the CLR.
> Now, it is true that Rust is typically (though not always) compiled to native code AOT
I would be genuinely interested in finding about a JIT compiler for Rust.
EDIT: it's worth me mentioning none of this is backed by any formal education (I'm 18), so it is very possibly wrong.
I agree with you on the memory sink side. For some kinds of applications the combo of startup time and memory consumption make it unsuitable - think of many small services or short-lived functions. Yes, Graal and such are amazing innovations, but they're very incomplete and poorly supported solutions and not something that's a good plan to bank your future on. This has limited our cloud deployment options to a significant degree, and I know I'm not alone.
Being a memory hog was not such a big deal in the pre-cloud era, but we pay real money for that (far too much!) these days, and leaner approaches are worth a lot in real monetary terms.
I wish push back on some of your other points. Java has evolved quite a lot lately, with improved syntax, much improved runtime (Loom!), better GCs, lots of libraries, etc. The community is a bit stale though, and it's viewed as the Oldsmobile of languages. But that old language still has a skip in it's step!
These other languages do not have the programing affordances of Java (generics, easy memory safety). Tooling is a big factor in favor of Java - IDEs, debuggers, observability etc. Also, the quality and availability of libraries is also a debatable point (although, it depends on the application at hand),
I actually disagree with all of these points and I write Java at work.
Memory safety isn't possible in Java (unlike Rust).
Tooling: they all have debuggers and observability tooling (pprof, pprof-rs, etc..). Rust even has zed now.
Libraries: Rust has really high quality packages. On the flip side, Go has almost everything built into the stdlib. Java requires a ton of third party packages of varying quality and guarantees even for basic things like logging or working with JSON. You have to constantly be on the lookout for CVE's.
In fact, 75% of the cloud-native foundations projects are in Go. K8s and docker are Go. Go is much more web-app or microservice focused than Java is actually. Most Java apps are deployed into the cloud using Go.
Meanwhile, Zig makes using the universe of C/C++ code easy.
I highly recommend you try Zig, Rust, or Go out. They weren't created out of ignorance of Java, but because Java and C++ had areas that could be improved.
Zig or Rust neither attempt to nor have any chance of directly competing with Java or any other high level language, other than on the margins (they have a hard-enough time competing with C++, which is unfortunate, because both are better than C++ -- Rust slightly so and Zig significantly so, IMO). They are for low-level development and target a domain where both the calculus of cost and value is vastly different from the high-level application space.
It is usually just as easy to write a program in a low level language as it is in a high-level one, and this is true not only for Zig and Rust, but also for C++. Even in 1998 it was just as easy to write a program in C++ as it was in Java. But the maintenance later -- when you have a large 10-year-old codebase -- is significantly more costly, and necessarily so. In a low-level language, i.e. one where there's more direct control over memory management, how a subroutine uses memory may affect its callers. Whether the correct use in the caller is enforced by the language, as in Rust, or not always, as in Zig, changes in low-level languages require touching more code than in high-level ones, and can have a bigger impact.
The low-level domain is, and will continue to be, extremely important. But the market share gap between low-level and high-level languages/domains has only grown over the past decades, and there are no signs of the trend reversing.
Now Go is a different beast altogether, and is a high level language. But it has both pros and cons compared to Java. The tools it includes in the SDK are more user-friendly, but they, like the language, are less flexible and more limited than in the Java world. Nevertheless, the out-of-the-box experience is nicer, something we should definitely improve in Java, but you pay for that simplicity later in lost flexibility. Performance also isn't quite as good as Java's, and neither is observability.
Java tends to prefer using available memory, before it has to clean it. This results in memory usage growing more than it actually needs.
Services often run in containers, so this is less than a problem today than it was before, because the allocated memory to the container is fixed anyway.
Try configuring the JVM to only use 75% of the container memory. In many cases, it will trigger an early GC and run better with smaller memory instances
There are of course some overhead for objects compared to primitives, but the Valhalla project is working to reduce the difference significantly on many cases.
You can also reduce memory usage and startup time by compiling to a binary image
I use Java/JavaScript/TypeScript at work but I do most of my scripting with .NET using LINQPad. Being able to use LINQ makes scripting so much easier.
Python? Huge respect, huge ecosystem, however I don't know if it's just me but I find it mighty hard to read. PHP with the "->" and Python with the space-sensitivity... Not sure why but it's so hard for me to overcome.
PHP syntax does add to the line widths, so breaking long lines in multiple lines by extracting variables can help with readability. Using return, continue and break early reduces indentation so that helps too, as well as creating smaller functions.
I use Ruby for scripting because it has a big standard library (unlike javascript) and an expressive syntax. The syntax is also very readable and short. It's also easy to install gems globally and use them. Things only need to be imported once.
I wouldn't say these things are qualities for big projects though. I like Typescript for big projects.
For those interested in the rationale for the simplified main methods (and the rationale for not simplifying further), the relevant JEP is a good read.
The parent article mentions and links JEP 477 -- the 3rd preview of the same feature-set. JEP 495 is the 4th preview and is included in JDK 24 which is in "stabilization" mode for release in March 2025.
I learned to code "single file Java" when I was working through Hacker Rank before they added features to the JDK to make it easier. Certainly if you want to do whiteboard coding sorts of problems it is good to know.
I totally agree. I use java for every possible coding script or task. I have been using it in my company for last 5+ years and have understood the language too well to be able to switch to something else and learn its caveats. In my comfort zone with java and I love that its being actively developed and improved every day.
I can only speak for myself, but I was lucky to be exposed to Spring Boot back in the v1.5 days after spending a few years creating Java programs without any framework. No XML at all, configuration by code, and opinionated default configuration that covers most use cases.
The learning curve was not that hard, and I got comfortable with it.
In contrast, I find React, Next.js and other frameworks more complex, and it takes a lot longer to be comfortable
Woah, reading the article felt so refreshing. Being able to see Java in this state is very exciting, this expands its usage into the scripting environment.
I am currently a heavy user of JRuby for quick and dirty scripts but still want to be on the JVM, but I'll give Java 23 a go and see how far it goes.
Very cool to see all these quality of life improvements. I haven't kept up with Java for several years. Still, the dependencies thing seems like an issue (as it is in Python)
Deno has been my go-to scripting tool lately. Similar benefits to Java as used by the OP, but it also allows you to import dependencies directly by URL, which means you can have your script self-define dependencies without a separate manifest file
For error handling they could add error propagation operator `?` from Rust. In Java it could propagate "throws" from the expression it's applied to the signature of a method that contains it. In Rust it's a bit awkward becuse you need to adjust return type. Since Java tracks exceptions separately it would be completely unobtrusive and ultraconvenient.
Groovy is still far far far better at cli scripting in javaland.
You don't even need main methods and class wrappers, can import libraries with annotations with Grape, etc.
The real failing of cli and scripting in jaba and groovy is running cli commands with access to stdin stdout stderr streams. I have figured out the dark art for groovy but it takes quite a few stack overflows to get there.
Maybe it's sort of not the same because it's compiled with graalvm and so not trivial to add any arbitrary Java dependency, but the tech that scratches that itch for me is babashka — a proper REPL, because it's a lisp, sensible Clojure syntax, and easy IO. And all the core Java libraries, available in a much snappier form than Clojure or other compiled languages
Lightweight Java has always been a thing for those who appreciate Java-the-language but despise Java-the-ecosystem.
You don't need to fool with Gradle or Maven or Ant if you'd like to incorporate third-party or your own custom dependencies in single-file "Java scripts".
Just copy some .jar files into ~/lib, and in your `.bashrc`:
I use Java extensively for my personal work and never use Gradle/Maven/Ant. I run Python scripts that marshal javac and java calls, it works great. I do cute tab completion tricks that examine the class layout and extend based on what it finds. I even have a "SmartCompJava" script that finds .java files that have newer modtimes than their corresponding .class files and just compiles them - a bit dangerous if you're not careful, but it's super-fast!
Ah.. the old days of java programming - set up your CLASSPATH and done.. Nowadays; however, most libraries have tons on other dependencies and doing this manually is tedious and a pain in the neck - download the wrong version of the dependency, and you are in for a rabbit-hole trying to figure out the compilation errors.
Do not get me wrong, Maven is ugly and disgusting(so are the other dependency managers for java), but gone are the days of CLASSPATH.. unless you avoid using libraries bloated 3rd part libraries at any cost.
If you like to have most of the simplicity of just setting classpath, but still have convenient dependency resolution, I highly recommend checkig out coursier:
https://get-coursier.io/
Its a cli based artifact fetching tool for the JVM ecosystem, and you can have it just make up classpath strings for you, i.e., to use some Apache dependencies with the single file simplified java running you could just:
Also, maybe do have a look at scala-cli:
https://scala-cli.virtuslab.org/
It does work just fine with Java files, and allows you to write single-file programs that include dependencies as special comments. The tool has some idiosyncrasies (like a background compiler server), but overall works extremely well if you need some of the power of a build tool, but without having a complex project structure.
>Lightweight Java has always been a thing for those who appreciate Java-the-language but despise Java-the-ecosystem.
I always disliked Java until I was converted by some developers of that group in a past job. I suppose it is fair to judge a language by the overall flavour of its ecosystem, but it is a bit disappointing. I wish more people could see how simple and _good_ it can be when you use the JDK itself, and not go through other overcomplicated systems to use it. For example, Spring Boot is basically a massive pile of hacks (regardless of whether you consider it good or bad), and Java only really serves as an underlying support technology for it.
Java is a fantastic "scripting" language as long as you're fine running it in an IDE. Clean and typed syntax, powerful standard library (especially for data structures), great performance. A better choice in these respects than say Python, Bash, or Go.
I've used it for scrapers, Advent of Code, plain ole data munging and of course programming interviews.
That being said, I would never write a webserver in Java in 2024.
> but the Python API isn't all that wonderful, and dynamic typing means that I spend too much time debugging
I don't know, this just seems more like inertia. "I'd rather stick to what I know best than this popular thing." Which is fine, and I'm glad Java has made improvements making it easier to hit the ground running. But blaming the use of Java on the inadequacies of Python? The python API can do just about anything, it has regex toolings, I've never found myself needing anything else. And the typing complaints? Yeah it can be annoying if you're not good at keeping track of your own typing hints, but modern python supports type annotations and linters like mypy[1] catch everything related to that just fine.
I've always admired many of Java's features, but let's not act like the reason for using Java for scripting is the pitfalls of Python. It's just because of an underlying preference for Java.
> Yeah it can be annoying if you're not good at keeping track of your own typing hints
If you write all the code you deal with, then sure. My experiences on big projects tend to be typing problems introduced by libraries. The kind where documentation and the decorators suggest it'll only ever return some specific value type, but then very occasionally it'll return a tuple of that value type and a message.
Fair, but in the context of scripting, which seems to be the focus of this article, how often are you dealing with complex library code? When I write scripts for file manipulation / simple automation, I'm usually not dealing with complex library objects. Plenty of os method calls, plenty of regex matches, but little else in this context.
Big projects are another thing entirely. There's a plethora of reasons why you may want to use a different language for a certain project type. But it doesn't seem fair to imply that python is uniquely handicapped (or otherwise inferior to Java) for scripting and simple automation use-cases.
Having to compile every time (or at least there not being a convenient wrapper that compiles and runs at once), the boilerplate of having to define a class and main method, exception handling, and most of all having to deal with Maven.
you don't need to deal with Maven if Java standard library(which is huge) is enough for you. Also, not sure why such big resistance against maven.
Other pointers are either opinionated or minor. From another hand, if dev has experience in Java, benefit of not learning some new language/ecosystem is kinda huge.
I don't think Java is fit for "the small". And there is nothing wrong with that. My go-to language for "the small" is Go. Java is good for enterprise heavy-lifting, not for quick and nimble tools.
No collection streams means LOTS of mind-numbing/eye-glazing for loops. Things have improved slightly with the slices and maps package but these are just hacks. Maybe with the new iterator support, Go will slowly and steadily get a bit more expressive.
Go is good for middleware coding thanks to Go-routines and and excellent networking support in the standard library but is cumbersome slow and inexpressive for scripts.
Also go.mod does NOT separate dev/test/prod dependencies - unlike Java Maven or Rust Cargo. You don't want your script dependencies coming into production code.
Can you tell us about how you use Go in the small? I like Go, but it doesn't strike me as particularly nimble for scripty use cases like this - error handling is part of it, but I know that's fixable with the Must() pattern.
I just write a small Go program. I agree that error handling is a weak point, but the tooling is well integrated. The article mentions that Java does not know about Maven. The go tool is much more versatile in that sense.
You can quickly whip up a single-file go program anywhere, without any boilerplate or setup:
package main
import "fmt"
func main () {
fmt.Println("hi")
}
// go run file.go
You can write this in any text editor, but using one with LSP support means that `import` statement is added automatically, and with CoPilot or another assistant, the `main` function writes itself as well as any `if err ...` statements. Go is extremely well suited to code generation due to how predictable it is.
Adding dependencies with `go mod` is uneventful.
It will never be as 'scripty' as JS or Ruby, but the readability, safety and performance are totally worth it.
I see 3 or four lines of boilerplate in your example, depending on if the closing brace is counted or not. Compare with the following equivalent program in ruby or crystal (it is valid in both):
puts "hi"
And the crystal version is just as typesafe and performant as the go version. I also find it more readable, but that is a very individual metric.
I don't think minimal examples are so useful. I reach for Go when bash is insufficient. That means there is a minimal starting size that is large enough that package and imports boilerplate are insignificant, all things considered
I love Crystal and have been a sponsor for a long time, but it's not comparable yet. Compiling is much slower, you have to manually add dependencies to shards.yml, cross-compilation is not as simple. And when it comes to scripts and tools, popularity / stability is a main concern.
The question I responded to was 'how you use Go in the small', not a comparison to the post.
As I mentioned, yes, it's verbose, but simple and worth the effort and peace of mind. My primary language is JS, I've written a fair share of shell and Ruby but would still choose go a lot of the time, just because it doesn't pull in any extra complexity.
For the small I like Groovy. Especially as Grapes is like Maven but in annotations that you include in the script file. Being Java-based means that if it goes from "in the small" into something larger you are already on a stable foundation.
That's if they're on the same architecture and operating system as you. Half the people I know are on mac, half of them are on arm. The other half are on windows and x86.
Actually, you can specify to build portable binaries for all 3 of these platforms. You can even make them totally static, so that deployment is as easy as "copy the executable file with windows/Linux/Mac/etc in the name and run it".
This is part of my standard Go build.sh script. You just never know who might want to run a given thing where.
Now I'm having flashbacks to porting a whole stack of applications from x86 to a customized Linux distribution running on a big-endian PC platform. A mishmash of C/C++, Java, Node-JS, Python...
It was really dumb, but that's what the client needed. So much cross compiling.
I regularly end up using Jshell when I’m analysing things like profiling data. Partly because I have access to libraries that parse those formats, but also because typing and autocomplete make it really effective for this sort of interactive thing.
I've abandoned the jvm, but I found Scala much better designed than Kotlin. Things in Kotlin felt consistently ad hoc and I would frequently run into weird special casing whereas Scala actually had general principles that could be applied to more use cases.
Kotlin on the other hand had better IDE support in intellij for obvious reasons. That was not nearly compelling enough for me.
Also, see my comment about kotlin scripting (kts) elsewhere in this thread.
But, in short, you can write kts shell scripts; import any library you want, use anything in the JVM or the Kotlin or Java library ecosystem that you need, etc.
Works on the command line (you need a jvm and kotlin installed from your favorite package manager). The Kotlin Jupyter kernel also uses kts and this is a nice way to use Java stuff in jupyter.
- Top-level functions/main is supported
- data classes are approximately as good as records
- A scripting mode comes built in, and can use annotations to import and use dependencies within the same file
- There's a repl
- The keyword 'var' exists, and the keyword 'val' is more ergonomic than 'final var'
The only thing I remember missing from the article is the implicit imports which I don't remember Kotlin having. Regardless, I'd reach for Kotlin every time. I think funnily enough Java never fully clicked for me until I started using Kotlin, which in many ways is "Java, except many best practices are on by default".
Babashka is truly wonderful and has taken over almost all my scripting projects. But the author started by saying they didn't want to use a language without types so Clojure is probably out!
It's of course always a good idea to read the article before posting, but doubly so here – I was getting ready to yell about all of the annoyances making this infeasible to anyone with a less-than-extreme pain tolerance, but it turns out Java has changed a lot since I last used it around six years ago :)
I wasn't aware that single-file Java without a top-level static class was possible now, that + JBang seems quite useful for small tasks.
One nit:
> Python programmers often use ad-hoc dictionaries (i.e. maps) to aggregate related information. In Java, we have records:
In modern Python it's much more idiomatic to use a `typing.NamedTuple` subclass or `@dataclasses.dataclass` than a dictionary. The Python equivalent of the Java example:
@dataclasses.dataclass
class Window:
id: int
desktop: int
x: int
y: int
width: int
height: int
title: str
@property
def xmax(self) -> int: return self.x + self.width
@property
def ymax(self) -> int: return self.y + self.height
w = Window(id=1, desktop=1, x=10, y=10, width=100, height=100, title="foo")
This is obviously valid, but it's definitely more common in a language like Python to just dump data inside a dict. In a dynamic language it's a far more flexible structure, it's the equivalent of HashMap<? extends CanBeHashed, LiterallyWhatever>, which is obviously a double edged sword when it comes to consuming the API. Luckily more rigid structures are becoming more popular at the API boundary.
As for my experience, nothing beats nodejs for fast scripting. Its literally just `npm install x y z` and then fire it up `$ node script.js`. But Im giving Java a chance.
> I am pretty sure [working around checked exceptions] will never be a part of the JDK, because it is arguably bad for large and serious programs.
And yet so many programming languages, including JVM languages like Scala or Kotlin, just don’t do checked exceptions, and the world hasn’t caught fire yet (and in fact, I can’t think of another mainstream language that does have them). Java could just drop them altogether and everyone (except maybe the most devout Java fans) would be happier.
> The file gets compiled on the fly, every time that I run the script. And that's just the way I want it during development or later tinkering. And I don't care during regular use because it's not that slow. The Python crowd never loses sleep over that, so why should I?
Java takes significantly longer to compile than Python does. My Python takes ~40 ms at first startup of a hello world script, ~20 in later attempts. `java hello.java` is in the 360-390 ms range.
Checked errors are not unique to Java. Rust, HNs darling baby, is praised on this forum every day for checked errors. Swift, F# and countless other languages have checked errors.
There is nothing wrong with checked exceptions and there is really no difference between a Result and a function with a checked exception.
The issue is not with checked exceptions but with Java’s syntax. They have not given programmers the language syntax to easily deal with checked exceptions. You cannot easily escape them without boilerplate and they don’t work correctly across lambdas. This is why Rust ships with ?; Swift has shipped with try!, try?; and Scala ships with try as an expression and is experimenting with checked exceptions that work across high order functions [0].
Programmers across every language ecosystem are moving towards checked errors and away from unchecked runtime crashes. Even Kotlin, a language that shipped with unchecked errors, is planning on adding a form of checked error handling [1].
After years of Python and TypeScript, I've started using Java as my default for everything. It's just so much more productive. The ancient developer memes that Java is slow and clunky don't apply anymore.
Postgres also had a long-held reputation for being slow and difficult, but it made incremental improvements for decades and now it's the default choice for databases.
I see Java in the exact same position, as the Postgres of languages.
The problem with Java since Java 8 has never been Java. It's been about the appalling ecosystem that infected it with reflection frameworks. It was bonkers that "POJO" was ever a thing that had to be defined.
It feels like these frameworks are now just falling away, which is great. I'm not even hearing about Spring anymore, and if there is any reason to not use it, it would be this cringe "how do you do fellow kids?" blurb I just saw on their front page:
> Level up your Java™ code
> With Spring Boot in your app, just a few lines of code is all you need to start building services like a boss.
I personally would reach for Go by default, but I have no ill-will to Java.
Spring boot is itself also very different than Spring, so depending on what was your last experience with these frameworks, you might be surprised.
Given, they are still quite reflection-heavy and full of POJOs and annotations, it supports compile-time resolution for many things now.
Also, you would be hard-pressed to find a more productive developer than a well-versed Spring boot guru for typical backend jobs. You might dislike the framework, but credit where it's due, it is a workhorse and the amount of time someone makes a POC, you can make it with spring properly, in a way that you can build your prod app on top. Like, it's easily as productive as RoR and similar.
Serious question - what could Spring Boot give me for POC/prototyping that Javalin or Micronaut couldn't? I really struggle to understand why most of Java shops out there have set themselves on the Boot path. Is it technology-based decision or politics?
I’m not familiar with either of those frameworks so can’t comment on them specifically, but 10+ years ago not using spring/boot entailed glueing together a lot of disparate libraries to do things spring boot had built in. Spring boot includes pretty much everything. Plus reliability, battle tested, easy to hire people with experience using it.
Spring Data can be ridiculously productive. It is definitely in the magic category, but you can just have an interface with methods named in a specific way, and then just call that from a controller class, and you have a REST API that calls into your DB for any of the usual CRUD queries immediately. And it's not even just a hack that works on the easy case, you can annotate it with complex hand-written SQL (both db-independent, but feel free to use native queries), and easily extend them with real implementations if you need more control.
I choose Spring Boot anytime I have to create a service, just for that reason.
The eco system is a huge force multiplier, and is constantly evolving. Spring Boot simplifies that a lot by simplifying the setup to the point where you often only have to add a single dependency, and everything is configured with default configurations (which you can change if necessary of course)
One example is the new Spring AI, where they have abstracted a lot of functionality and you can change providers (OpenAI, Mistral, AWS, Azure, Google) easily.
The ecosystem factor is real. I was looking at implementing the SCIM protocol [0] in our user management app, and if we were on Spring I could have just added an Apache boot-starter dependency and been done in a few days. However my company uses the Play framework and it’s going to take me weeks to implement it by hand.
If I ever found a startup I will mandate that we use Spring Boot (java/kotlin) until we grow big enough to afford the extra overhead with other frameworks. Spring Boot is truly a Get Stuff Done framework
It would be hard to convince some people of this, because "everyone knows Spring is enterprise". Unfortunately many only have experience with legacy companies, bad code or through reading old blog articles, and base their opinions on that.
It's actually something you need to have experienced yourself to recognize the possibilities.
It may sound strange, but I enjoy going through the long list of projects in the Spring web site. I almost always find something cool and is surprised at how much they can simplify complex functionality
The learning threshold is also relatively low, because they tend to use common best practices in the framework.
I’ve spoken about this before but choosingg Play and not Spring has been my companies biggest mistake. It has lead us down so many half documented custom solutions that would have just worked out of the box if we had chosen the more popular stack.
I like Kotlin, but the lack of checked exceptions really kills it for me. Exception safety is a bigger thing for me than null safety.
It‘s established patterns. Javelin or Micronaut are probably great, but not a lot of people understands how to build a real project with them. With Spring Boot you don’t even think about it.
I agree with the sentiment, but I'd move up to a version with type inference at least. I have nothing against static types and in fact in a vacuum I prefer them, but the particular brand of OO and "weak" generics that Java and C# have feels like filling forms in triplicate. "var" alleviates that significantly.
Any competent Java IDE automatically generates you a variable declaration from the expression you want to assign to it. It’s actually less keystrokes than having to type “var”. Unless you only use a simple editor that doesn’t perform static analysis, less typing is not a good excuse for using “var”.
Conversely, in Java you often use the diamond operator like in:
List<String> items = new ArrayList<>();
(Half of which, again, is code completion in your IDE.)
That doesn’t work with “var”. You’d have to write:
var items = new ArrayList<String>();
while losing the ability to constrain your variable to a narrower type.
The loss of directly seeing the type information in statements of the form
I've wrought my hands over this and eventually came to the conclusion that developers in every language that started with local type inference have embraced it. So I've mostly ignored my concerns and gone with "var for everything".
If you have trouble with a type then rename the function or variable (AKA your "foo" and "bar" example. The names are bad, fix them. Context matters, we have none here. No one has an editor that displays a single line of code at a time.). But in general we haven't had issues on our projects.
Beyond that, my IDE (Intellij) makes it easy to uncover the inferred type if I really need to be 100.00% sure what it is. In general I don't, because in general I don't care, and even before 'var' I was generally ignoring the type anyways.
I think there is a tasteful way to selectively use var where it makes sense.
Even in your example, narrowing the variable doesn't make much sense - you are in a local scope, you control everything. Narrowing is more meaningful in fields, where var won't work by design.
Locally a well-named variable is often significantly more important than some long, somewhat useless type signature, especially when the right hand side makes it clear.
.NET has true generics with full type information and struct type parameter monomorphization which works the same way generics do in Rust.
Edit: C# does have type inference for generics, just not the full HM one. It is also quite more capable for lambdas which is a bit frustrating because it does not resolve nested generics otherwise. I miss it - F# does it the way it always should have been.
There are many other differences small and big that arise from the way Java does generics and the fact that primitives can't participate - you will never see `IntStream` kind of workarounds in C#. Some libraries abuse and misuse generics for no profit (looking at you Azure SDK), but it's not as widespread. Shallow generic types are always inferred from arguments.
> you will never see `IntStream` kind of workarounds in C#.
You may not see that in Java in the future either. Java will have value objects from the Valhalla project, and part of the plan is to replace Integer with a value object. Then there will be less of a reason to use raw int primitives, because the JVM can treat value objects much more efficiently than normal objects.
The difference is that C# has been designed with proper generics since version 2 (pushed by F# research group with Don Syme) and is now on version 13. At the same time, structs have been present since version 1.
All APIs and collection types build on this foundation, with standard library leveraging generic monomorhpization for zero-cost abstractions. Code that would have needed C++ implementation in the past is naturally expressed in pure C#. Generics are fully integrated into the type system at IL level, avoiding special-cased types or bespoke compiler handling (besides monomorphization).
This enables numerous zero-cost features: tuples (unnamed/named), structs with controlled mutability and record structs, pattern matching, stack buffers that do not rely on escape analysis, structs with byref pointers for slice types (Span<T> and friends) which a good two thirds of the standard library accepts. Numeric and vector primitives are used in a simple way without setup requirements like Panama Vectors.
While project Valhalla will get Java's foot in the door in some domains, it will remain a less optimal choice than C#, C++, Rust, etc. Java's evolution primarily serves its ecosystem's needs, whereas C# benefits from being a newer language that learned from both Java and C++ and got influenced by F# and other research projects inside MS. This makes C# more idiomatic and terse. The historical drawbacks - platform lock-in, being closed-source, and having relatively weak compiler - have been resolved over the past ~9 years.
I find pretty much every ORM in any language is problematic. The best you can hope for is a certain amount of internal design consistency, so that even if you can't do what you want it's at least clear what you are doing.
Since I had the "joy" to use TypeORM (node.js stuff), I really value Hibernate, although there are some little corner cases I'd like to be better. But ORMs solve a really hard problem and I haven't seen anything better than Hibernate so far (and don't come up with JOOQ or MyBatis!).
The way you write this makes be think you rawdog'd Hibernate and Spring Framework. Don't do that... you will hate yourself.
Boot is something entirely different. You write very little code and get a ton done. The trade off is you are firmly now a "Boot" codebase, but once you learn how Boot works it's not a big deal.
I've had to maintain a couple Spring Boot apps and I absolutely cannot stand them: they pull in a whole pile of random dependencies and do weird things to your build with little explanation. Then, all the functionality involves a ton of classpath scanning and annotation-based DI that makes it hard to figure out how all the things fit together.
I mean, have you learnt the framework before attempting to do that?
Frameworks are frameworks, not libraries. You can't just start writing/understanding them - frameworks are different from libraries precisely because they call your code, not the reverse.
That's how all frameworks work though. This is not a criticism of Spring Boot. You are criticizing the use of a framework at all.
Some people hate to write the same basic things over and over. That's where a framework excels. Write only the glue/logic you need to make your app work.
The way you have described your experience with Spring Boot seems to imply you did not take the time to learn it at all, and therefore its' unsurprising to us you had a hard time of it.
Not writing the same thing over and over again is a feature of abstraction. A framework is a user-hostile way to abstract because it makes the source code opaque to developers. There's no reason why a library-based approach has to be more repetitive than frameworks.
Right, so you cobble together 48 different libraries, wrangle all of their configurations and stitch them all together yourself.
You do this 18 times because you have 18 different apps with similar requirements but deal with different data/endpoints/whatever.
On your 19th app you decide to standardize how you cobble together all of these libraries so that you don't have to start at ground zero every single time.
CRUD backends have well-understood requirements, in a way, a significant part of the work is done for you. You are there to customize certain parts only.
How else would you architect with this in mind? Given that literally every other framework is quite similar (RoR, PHP's solutions, etc).
There is another niche, the HTTP server libraries, but they are much more low level.
I was working with legacy code bases where the original Devs loved frameworks. Massive performance problems and nobody could understand how it all fit together at runtime.
Hibernate eventually got entirely nuked, Spring we couldn't entirely unwind easily; it had caused a whole bunch of crappy architectural issues and was too much effort to rewrite from scratch.
Although the code looked simpler using the frameworks and annotations, it was actually a huge rotten mess that didn't work well at all, with workarounds for all kinds of weird things in it.
14 years or less. Any version of Postgres before 9.0 was a nightmare to administer as a real production transactional dbms, at least the off the shelf version without a whole lot of tweaking without a (then very rare) pg expert.
I started with Java 1.0 and thought it was great. I still do, although I haven't used it since 2020.
The only thing I don't like is how there is no built-in JSON package which seems like a necessity these days.
Removing the public static void main(String[] args) business seems like pandering to a non-existent audience, or at least a miniscule and insignificant one. Anyone who is going to use Java for a real project is not going to be worried about that, and anyone who thinks that's too difficult to deal with is never going to be a good programmer anyway.
I think gp is right, and I don't think it's gatekeeping. I thought this about Java, it was easy to criticize its verbosity, but I realized how insignificant this is when actually practicing it. There's probably way more interesting and deeper criticism to make about Java.
About the first steps of a newcomer, there's always going to be some level of "don't worry about this now, we'll see what this means later" for any language. I remember this to be the case for every tutorial I read to learn a language. And it's fine, as long as you can try stuff and it doesn't get in the way.
I'd say it's more important for a language and its vocabulary to be well structured and well documented for a newcomer and Java does quite good on this front.
GP is right. If the words public static keep you from learning how to program you were never going to learn anyway. If I introduce someone to soccer and they quit because they couldn't figure out how to put their shoes on, chances are they werent going to learn how to play no matter what.
I think the converse(?) to this though is that the words public static are inconsequential.
Sure, if you are incapable of learning what a couple adjectives mean you won't go far, but that holds for much more than software.
Rather it's not important that the ball is big and blue so much as that you can kick it across the field - learning what the ball means can come later, but it's just unimportant noise (to start).
Java is pretty bad at this, though, insisting on specifying unimportant details up front rather than allowing for qualification. This is OK for a large monolithic application with complex requirements and a litany of edge cases, but inappropriate for many smaller use cases.
Did you know that the first thing John Wooden did with incoming freshman collegiate basketball players at the most prestigious program in the country was teach them how to properly put on their socks?
I still feel like the author is missing the forest for the trees. Bash is not great to write e.g. a red black tree in or do complex image processing, but you don't have to maintain a Java install, download libraries, or setup an editor with an LSP (and really, calling java script.java 10 asdf? Why do I need to invoke Java at all? At that point, I'm probably going to wrap it in a script anyways...)
Python has its own issues but it's a small, embedded install that you don't have to accept 5 different licenses for and worry about it being able to get the same version...
And bash? That's what pacman -S jq is for - anything I can't easily do in bash or batch I just offload to a small utility written in python or rust.
pacman -S jdk-openjdk, this is the reference java implementation and it has the exact same license as the Linux kernel. And java has never done such a breaking change as python did.
There is also jre-openjdk-headless, for 140 MB. How is that any different than Python?
This "java too heavy" is like 30 years out of date, if it has ever been true.
> And java has never done such a breaking change as python did.
I'm not really sure that's true? Java 8 to afterwards, there are breaking changes that mean a lot of old enterprise stuff can't easily move forward, or worse, bytecode incompatibilities mean source-code-less stuff can't be used anymore...
The whole thing about Graal is mentioned almost as an afterthought, my point is that the language etc. is so poorly designed as to be prohibitive to interface with unless...well you're on Java. Yes there are bridges etc, but a big point of the shell, bash, etc. is easy interoperability between many programs, etc.
Java is still today stuck in a mentality of "it doesn't exist if its not in Java", which is why yes, 30 years later, it is still "too heavy". Assuming you are the effective Operating System is an extremely heavy assumption.
> and it has the exact same license as the Linux kernel
Also, I neglected to touch on this point more, perhaps license is not the right word, as much as distribution - I don't know if you have ever tried building the JDK (not simple), or worked with the official JDK vs the open one (not the same functionality), or tried to access old versions of the SDK on the official websites, or had to deal with their installers, etc.
Giant headache and all around.
Not to mention, even if your pacman command works, this is still simply not comparable, the example I used was for installing a jq binary because JRE simply doesn't include this functionality by default...
And now you need the overweight pom/gradle mess to interface with the Java libraries because <insert technical debt reasons here>
The official JDK is the OpenJDK for several years.
Under Sun there were differences, but Oracle open-sourced every last difference and now there is only some Oracle branding logo as the only difference (and maybe some tiny proprietary codec, but your code will run on both the same way).
> If you want to introduce someone to programming, you probably don't want them to worry about what all those 'magic words' do.
I learned Java when i was 15 or 16, reading some random book first and then I stole 35 euros from my mother's purse and bought a copy of "Java how to program" by deitel and deitel[1]. The recommended version at the time was Java 5, and the SJCP certification was still issued by Sun Microsystems.
I can tell you, "public static void main" is not going to be the problem.
[1]: looking back (i'm in my 30ies now) sometimes I wonder if i would have been better off buying weed or alcohol (or both)
So you believe that your experience is a universal one for all learners? Cognitive load is a real consideration in teaching, and having to ignore and filter text is challenging for some folks.
The boilerplate around a main function is 10 units of load. Everything else you have to know to write a simple program is 500-600 units of load. The boilerplate is a rounding error, and just does not matter.
As mongol said in a top-level comment, Java isn't a great language for programming "in the small". It's not a great language for a casual user - or rather, there are languages that are significantly better. If you want to introduce someone to programming in a semester course, "public static void main" isn't going to be a significant hurdle. If you want to introduce someone to programming as a 14-year-old, maybe don't start with Java.
Why? Java is a small language with not many concepts, that is typed so many of your first attempts will be caught with helpful messages at compile time, and it fails safely at runtime with error messages pointing to an exact line number. Besides, it is a language that is actually used in the industry and has one of the biggest ecosystems. It's also plenty performant, can be low-level enough for learning about all kinds of data structures/algorithms, including concurrent ones and has probably the best tooling (IDE, debugger, etc)
What other language would you start with?
And isn't it easier to introduce concepts one at a time? For that reason implicit classes makes sense, and also for the occasional scripting, as in doing something one-off, but it is not as trivial that I can do it with piping 3 commands together.
True. But you need to know too many of them to get anything to run.
> that is typed so many of your first attempts will be caught with helpful messages at compile time
But it doesn't feel that way. It doesn't feel "helpful", it feels nitpicky. It feels like I have to get everything exactly right before anything will run. For a raw beginner, that's very frustrating. It is (emotionally) better to have something run as far as it can run, and then crash. (I agree that exceptions pointing to the line number are very nice.)
Again, for a semester class, the startup overhead for learning Java is too small to worry about - it's maybe a day or two. But for someone on their own, not in a class, a day or two is a huge investment to put in before they can get something running!
What would I start with? Something with a REPL. (It could be a language that is normally compiled, but there needs to be a REPL. Raw beginners need to be able to get something, anything, to work as quickly as possible.)
Yeah, you want to start with basic imperative programming with as little cargo-cult nonsense in the way as possible.
Your csci 101 kids will not benefit from unpacking what it means to compile an object-oriented language down to bytecode to run on a virtual machine. It's not that it's not valuable knowledge, they just won't have the context to make meaningful heads or tails of it.
related: I still puke a little remembering the requirement that students work on assignments in emacs on terminal machines rather than their text processor of choice(which was fine for me, but why on god's green earth would you put usability warcrimes like 'hjkl' in the way of someone just starting to learn? No wonder nobody in the early naughts wanted to learn to program...).
A lot of people learn to program from declarative languages like spread sheets. We should all be happy we have access to defective versions of assembly at too high a level but be angry that we had to use too low level an editor?
I think this is a really important point, even though I suspect you're joking.
Along with all the BS boilerplate text this specific post talks about eliminating, which is great, we simply forget how much legacy tech BS we just assume.
Beginner programmers should not have to know what a "file" is, or what an "editor" is, or that they need an "editor" to "edit" a "file". This is technical debt: these are implementation details which should be invisible.
This goes double for "compilers" versus "interpreters" and "source code" versus "binary code". FFS the noun _code_ means _a system for rendered text unreadable_.
You have a computer. You are talking to it in words, words which resemble English because that is one of the simplest world languages when you think about scripts -- writing systems -- as well as sounds. Hangeul is easier but only works for Korean which is harder than English. Grammatically Chinese is simpler, but spoken Chinese has tones which are very hard, and written Chinese is insane. Typed Cyrillic is no harder but handwritten gets weird and complicated and Russian is much harder than English. And so on.
English wins and so we talk to computers mostly in English.
So, you have a computer, and you type on it in English. That is all you should need to know: how to enter text, how to correct it when you get it wrong, and that is it.
BASIC has a great virtue which all the Unix and even the Lisp fans forget:
It's designed to work at a command prompt. Type a command, the computer does it. Give it a number, it remembers it for later.
This is a profound and important metaphor. It eliminates all the 1960s/1970s legacy BS about "files" and "folders" and "editors" and "compilers". Beginners don't need that. Let them learn that later if they prove to have real aptitude and want to pursue this.
Type a bare expression, the computer does it. Number it, the computer remembers it for later. That is all you need to get writing software.
And just like Python sorted out the problem of spoiled whiny little baby C programmers whinging about their pathetic obsessions with indentation patterns by making indentation semantic so everyone has to comply, line numbers in BASIC are a vital simplifying unifying mechanism, so lean on them: for beginners, make line numbers syntactic.
Don't force kids to learn pro tools like nomenclature and hierarchies. Give them a toy that they can get started with.
At first, they can structure their programs with line numbers, and they learn about leaving space, about RENUMBER commands, about LIST x TO y ranges, and stuff like that, because we are not using a bloody Commodore 64 any more.
But give them IF...THEN...ELSE and WHILE...WEND and REPEAT...UNTIL and named procedures so they can learn structure and not GOTO.
All the rest is baggage and should be deferred as late as reasonably possible.
The reason we use the words "code" and "coding" how's the history behind it. Originally it referred to translating an algorithm description into machine code. Indeed something less readable. The first compilers were called "automatic coders". I.e. their input wasn't code, their output was. Originally the word "compiling" referred to putting together an image of library functions. Something more similar to linking. The terminology shifted and drifted as time went on.
I'm of two minds about it. On the one hand, the verbosity is arguably noise if you are first learning to program, or just want to write something quick, so I can see why people dislike it.
On the other hand, viability, classes, and "staticness" are all fundamental structural concepts in Java. Hiding them for a special case is sort of like lying, and, in the long term, I can actually see this special case causing more confusion for new learners. It's sometimes better to be upfront and transparent and force your users to work with the paradigm and semantics they chose to adopt, rather than pretend it doesn't exist. If Java had been designed to allow for top-level functions from the start, it'd be a different story. I think special casing is a generally bad way to evolve a programming language.
But there is absolutely no hiding, the design is very smart!
You simply get an unnamed implicit class like `class Tmp367 {` written at the top, and the runtime loader has been modified to be more accepting of main methods. There was basically a tiny language change, and no bytecode change, java semantics are just like they always were.
The Main loader just simply will accept an instance method named 'main' for a class with an empty constructor with no args, instead of psvm.
It's hidden from the newbie who never used java before, that's what we're talking about. As you said, all that stuff is done implicitly. The user never sees any of that unless they go digging.
> Removing the public static void main(String[] args) business seems like pandering to a non-existent audience, or at least a miniscule and insignificant one.
Perhaps the audience doesn't exist because of that business. There are many times when I would have used Java over Python to write simple programs for no other reason than having the ability to create a GUI without resorting to third-party libraries. Yeah, Python has tk but it has never clicked with me in the sense that Swing does. Unfortunately, cramming every last thing into an OOP model means that simplicity is rapidly lost. (The same can be said of Python, except Python has not forced it. Java, historically did.)
I agree that for experienced programmers working on large projects a little bit of verbosity around `main` is insignificant. But first impressions matter, especially when there is pervasive word of mouth about "verbosity".
Pretend you are a college student and you are taking your first programming class (e.g. CS 1) and your friends have told you that Java is "verbose". You start with "hello world" and you have to type `public static void` etc. One of your friends shows you the same code as a Python 1-liner.
Or similarly you're a beginning programmer in the workforce and your employer asks you to solve a problem using Java. You've heard Java is verbose and when you start with "hello world" you find that what you heard was true.
This is not a non-existent/minuscule audience. They should have fixed this decades ago. Better late than never.
I've been impressed with the modernization of Java over the last 10+ years. Simplifying "hello world" is a minor change relative to the others, but still an important one.
> But first impressions matter, especially when there is pervasive word of mouth about "verbosity"
I watched most of my comp sci 101/102/201 classmates fail out because they didn’t want to understand how things worked, they just wanted to make a lot of money.
I did cut my teeth on Java back in middle school-ish. It never bothered me at all, I was too busy having fun learning to program. I agree with GP, the mandatory class is a completely overblown complaint about Java.
The problem is when java is used as a first language, this is needless complexity that gets in the way of the actual fundamentals of programming. Access privileges are irrelevant for a beginner who is hopefully not writing libraries that will be consumed by anyone. The distinction between an instance and static method is also confusing and irrelevant. It's just pointless ritual for the student.
Now, if one is learning java as a second language, that's a different story.
If it's part of J2EE, it's in practice "part of Java" since the JDK comes with the J2EE packages built-in...
That is, it came with the J2EE packages built-in, until Java 11 decided to break everything, and force people to get each piece of J2EE from a separate upstream project, with AFAIK no "all of J2EE" uber-jar you could simply copy into your project to restore the status quo. It's no wonder so many projects are to this day stuck on Java 8.
> since the JDK comes with the J2EE packages built-in...
Are you sure about that? I just downloaded the Java 8 JDK, and javax.json is not there. And the documentation doesn't mention it either. What am I missing?
These platforms are not being considered here nor have much relevance anymore. You cannot seriously claim this unless you don't use the tooling side by side at all.
The difference is so stark it's no longer amusing. Performing setup for complex Java projects has me go through similar steps as if they were written in C++. Performing setup for C# projects of comparable complexity usually requires just cloning and hitting 'dotnet run', much like it usually happens with Rust or Go (some may claim I undeservedly bash it but credit is where credit is due).
I love your identity with .NET ecosystem, to the point of nothing else being in the way.
See latest JetBrains Developer Surrey about platforms.
.NET is a great ecosystem, but lets be real where it stands outside Microsoft shops, across everything that has a CPU on them, and the various ecosystem where sadly it doesn't even get a tier 1 support.
Programming languages are tools, a toolbox has space for plenty of them.
Are you comparing "complex Java projects" against just ordinary "C# projects"? Because ordinary Java projects will also be "./gradlew run" or the Maven equivalent, that's nothing special.
And in many other situations "./gradlew run" just doesn't work. Hell, Gradle does not even let you quickly scaffold it without pre-existing knowledge and community advice! (if you want to avoid pitfalls and have the best "streamlined" experience) Something that is not an issue in Rust or Go. Maven is even worse.
Meanwhile I can 'git clone https://github.com/ryujinx-mirror/ryujinx && cd ryujinx/src/Ryujinx && dotnet run -c Release' and it works on the first attempt (though takes a moment to pull nuget packages, it's a big project).
The Java ecosystem has incredible projects from the technical point of view (GC implementations, OpenJDK's JIT compiler), but the tooling and application packaging and distribution seem like the painful parts.
It usually works in my experience, since the toolchains feature was added, as that takes the Java version mostly out of the equation.
There is "gradle init" to scaffold a project, or of course IDEs offer a GUI over that.
Additionally, your "dotnet run" does require the dotnet tool to be installed and of the right version. The Gradle/Maven equivalents now no longer do, because they bundle scripts into your repository that will download and run the build tool itself of the right version. They just need some moderately modern Java installed. Everything the project needs including possibly a newer Java will then be downloaded.
I'm not sure what the point of naming individual projects is. I can point at dozens of projects off the top of my head where you can just clone and run them without incident.
There are painful parts of both Gradle and Maven. Absolutely. They are very far from perfect build systems. But this is partly because they do a lot more than tools like cargo does.
> "dotnet run" does require the dotnet tool to be installed and of the right version
It only needs an SDK installed on the system. If you have the necessary framework dependency, it will just work. If it's missing - the command output will specify this, which is solved by doing `sudo apt install dotnet-sdk-{version}` or just `dotnet-runtime-{version}` (because newer SDKs can build most older targets). You can also usually roll-forward the applications without retargeting them or installing older runtime (which is trivial still). It's a reliable and streamlined process.
Probably one of the best approaches to managing the SDK and framework dependencies that does not rely on any form of help from external tooling or IDEs.
Gradle and Maven need JDK installed in either case. I had Gradle that shipped with the code crash on me because it was sufficiently old to have issues on newer OpenJDK versions. Solved it by installing properly, but you can see how it can be an error-prone process.
------------------------------------
Ultimately, if you're an expert and it's a long-term project - none of this matters, solving odd breaks and tooling issues is part of the job. It's nice when things work, it's not unexpected when they don't. Some languages have more of this and some less, but at the end of the day due to business constraints and company environment none of this is a showstopper per se - you just deal with it.
Do I think the CLI tooling, dependency management, packaging and distribution is painful in Java or Kotlin? Yes, it's what also precludes either from being productive scripting languages unless you have nailed the setup that works around all of these. Does it matter for writing complex applications? Not really, project and environment setup for such is mostly one-time thing. It's coincidentally where Java ecosystem shows its strength. My rant here is posted because I believe we can discuss pros and cons without stating that specific issues don't exist when they do or vice versa.
Among everything I tried Cargo, .NET CLI and Go had the smoothest experience of things mostly working and when they weren't - not requiring to dig through heaps of documentation, possibly dumped into a language model to catch the exact specific piece that would help to solve the puzzle. I heard good things about Python's uv. If actively maintained, Node.js projects also work reliably, not so much when they aren't though. Some C++ projects are kind enough to offer build scripting that works out of box on Unix systems - props to the maintainers, which is also the case with Java projects. But whenever either of the last two didn't work, it often took me the most effort and swearing to get either working, unlike other languages.
I’d be curious to hear more about how you’re using F#? I’ve previously used Python for scripting, but just started developing for a company pretty deeply entrenched in .NET. Currently they’re migrating a lot from VB to C#, but I’ve missed having a handy scripting language like Python for small tools or test applications. Do you think F# could fill that roll?
Powershell is probably best fit for that role. You have to learn a new scripting language but since it runs on .Net you can actually bring in .Net Classes if you need a little more power.
It should be able to! F# has "gradual typing" and full type inference which means you often do not need to specify the types at all, and it also happens to be whitespace-sensitive language much like Python is. Both of these aspects should make it feel quite familiar while also giving you full advantages of static typing.
One thing to note is I find `dotnet fsi {some script name}.fsx` taking more time to start than ideal - up to 800ms is just too much, normal .NET applications usually start in a fraction of this.
I recently posted a submission here for "FSharpPacker" written by my friend that lets you compile F# scripts to standalone applications (either runtime-dependent, self-contained or fully native binaries, much like Go), it also has some comments on getting the best mileage out of it: https://news.ycombinator.com/item?id=42304835
Probably the best feature that also comes with scripting (both C# and F#) is "inline" nuget references e.g. #r "nuget: FSharp.Control.TaskSeq" which will automatically pull the dependency from nuget without ever dealing with manually installing it or tinkering with build system in any other way.
https://github.com/dotnet-script/dotnet-script (C# is also a quite productive language for scripting and small programs because of top-level statements, record types, pattern matching and many other functional features though perhaps not as strongly represented as in F#, it's just very unfortunately abused in enterprise world, with teams often going out of their way to make the code far more bloated than necessary, against the language design intentions)
F# is a leap if it's your first functional / ML style language (but worthwhile). Modern C# is good for small tools and scripting, there is the dotnet-script tool for running single .csx files
There are reasons to not like Java, but this isn't one of them. Java is a fantastic language for large long lasting projects. I can't think of a more suited language for large long lasting projects in fact.
Agree: why? I don't know a single reason, and I've worked on Java docs in the Java industry. This was literally my day job for a while, and I have no clue what they're trying to hint at here.
I remember reading horstmann's books in college and it doesn't surprise me at all that java is the hammer he reaches for given a particular nail.
I have to say I find it an odd choice for small replacements for bash scripts. I think python or golang are probably better choices for that purpose. Java is one of those 'enterprise' backend languages which lend itself to making a team productive over making the individual productive, and I say this as a java / go dev who's most comfortable with java for most backend work.
Dr. Horstmann was my advisor in college, San Jose State.
I just loved his lectures, very dry sense of humor, and extremely funny.
He was just getting started writing books in the early 90s. He has this awesome way of thinking about programming, that I imparted to my own students when it came my turn to teach programming. I wish there some videos of his classes that I could go back to and share with people.
The picture on the website with him in the row boat has a funny story with it. When asked why he is in a row boat, he would reply, "Students are in the row boat with me, learning to program. At some point I push them out of the boat into the eel infested lake. The ones who are clever enough to make it back to the shore will be good programmers." All of this said with a faint hint of a German accent and a sly smile.
If you happen to read this, Dr. Horstman. I made it to shore. Thanks! It has been an awesome journey!
That's an interesting experience. I also had him as a professor a while back and he was awful. He was always looking at his phone or computer especially during project presentations and completely ignoring what anyone said. He also didn't seem to know much course material in general.
What I remember most is his obsession with Emacs.
There was one time that I was grateful though, I had to buy a few of his books and one of them had a defect from the printer, so he helped me get a new copy from the publisher for free.
Wow:
I just saved that as "hello.java" and ran it (using OpenJDK Runtime Environment Homebrew (build 23.0.1) which I happened to have on my Mac already) like this: This is SO MUCH less crufty than the old "public static void main(String[] args)" thing. I always felt that was a terrible introduction to programming, when Hello World included a whole chunk of boilerplate and a class that didn't need to exist.Anecdotal, but I had an intro to programming class in high school and the teacher was a wonderful woman, but not a programmer. She didn't know that this was a function declaration or its meaning, so she would just have us repeat out loud "public static void main string box args". I get a laugh whenever I think of that. I hope she's doing well.
I had an Excel class 20-25 years ago where a teacher explained click and double click every class, and emphasising "DOUBLE CLICK" out loud every time she did the action... There were a few facepalms.
The next class was assembly programming, where the teacher didn't bother to show for 4 months and then resumed the class as if we "self-taught up until this point". We were utterly lost in that one.
I imagine things have changed greatly today but back then it was a complete roller coaster.
In elementary school we had a "class" with the "computer teacher" once a week. He was just one of the only adults on the school district payroll who knew how to turn on and off an Apple //e. Nice guy (he also volunteered at the same community theatre I did), but "computer class" was just letting us loose with copies of Fraction Munchers and helping us if we jammed the disk drive or whatever.
I remember being 10 and going to a computer lab full of power macintoshes and playing sim city 2000, kid pix, and Carmen San Diego. This was in between looking at a cd encyclopedia (encarta?), old computers were awesome ;)
Wow, you reminded me of two experiences at once.
I wasn't good at getting educated, but I ended up at a community college to "get my generals" - my first class of the day was computer something or other, and the class was led by a man in extremely thick glasses who said that our general approach in the course was going to be:
(for anyone paying attention, anyone could just print out multiple copies)While it was a significantly younger and less experienced me who quit this bullshit in an absolute huff, I don't know if I could go through it today.
Fast forward a few years later, and I was training folks on tech support(a random reversal) and I had a lady who should have taken his class... every time I told them to click on something she asked "is that right click or a left click?" and each time I would respond "its a left click unless I tell you otherwise" (she didn't last long.)
A lot of Java professors did that in the past, because the "not hello world" parts of "hello world" require a bit more understanding, and in the spirit of getting people started, the idea is to just say "hey memorize this boilerplate stuff, eventually you'll understand what it means."
I haven't written Java in a million years.. so I'm trying to understand better.
But what does this code even mean..? Isn't Java's whole simplifying model that everything lives in "Objects"? This seems to break that fundamental paradigm and makes the language more complicated. If you needed something like global functions then in vanilla Java as I remember you'd stick it into static final Objects that acts as a namespace of sorts. So you don't need to worry about functions floating around in a global namespace soup.
If you're gunna just have free floating functions in your file.. then why not just cut to the chase and have a
floating in your file..? (I mean... you don't have REPL so maybe that wouldn't be too meaningful...)only since 2017 :) people basically judge java based on what you learn in AP CS which is basically (a dumbed down version of) java 6 (released 2006).
> people basically judge java based on what you learn in AP CS which is basically (a dumbed down version of) java 6 (released 2006).
I don't think people realize Java8 was until very recently the dominant java version in production software, and currently close to 90% of all java projects still run on java11 and earlier.
Edit: after checking New Relic's 2024 report on the java ecosystem, it sounds like java11 and earlier is now at slightly over 60%, with java17 being used in around 35% of the projects.
For software written in that era currently under maintenance mode, I don't expect people to upgrade their java language version though upgrading their JVM is a good idea.
It’s honestly the only programming language that will get its 1996 version compared to a new 2025 version of another language. And the only language that will be criticized by someone who last used it in 2006.
PHP gets the same treatment all the time.
PHP and Laravel are amazing.
> And the only language that will be criticized by someone who last used it in 2006.
You don't sound like you work with Java. I know for a fact that even some FANGs, with their army of software engineers, still use Java8 for most of their Java projects.
Historically an upgrade could cause issues if the code or dependencies used undocumented functionality (like Byte code manipulation) or calls to native code, or the transitions to modules in v9. Upgrades after version 11 should be less "risky" and be easier to argue to management.
From what I can tell, there are 4 main reasons why some don't upgrade.
1. They have legacy software and are afraid of breaking it, sometimes with good reasons.
2. The devs don't know or care enough about the new versions to bother with it. Noone is promoted because of an upgrade. Work to live, and don't live to work
3. The ones who don't have buy in from management to spend time fixing something that works
4. They are using version 11+ that should be much safer to upgrade than earlier versions, but they are still in the mindset of 1 or 2 above
There have been huge improvements not only in the language itself, but also performance and memory use. They could save money if they upgrade, but in some cases this is acceptable because they can make more money by spending time on a new feature, than an upgrade.
In my last 3 workplaces, they usually tried to use the latest LTS versions. But to be honest, some of the services were in maintenance mode and we didn't get enough benefits to upgrade, so we were sometimes falling behind. Today we have a mix of v17 and v21. Anyone can upgrade anytime they want but noone cares about a service that you seldom work with. I feel kind of bad for that, but not enough to bother. I focus mainly on the services that I work with daily, and make sure they are using the latest versions
> From what I can tell, there are 4 main reasons why some don't upgrade.
You missed the most obvious reason to not upgrade: there is no good reason to do it.
> There have been huge improvements not only in the language itself, but also performance and memory use.
That's debatable, and even if we blindly assume that then it's debatable whether the upgrade justifies the gains.
More often than not, it doesn't.
> That's debatable, and even if we blindly assume that then it's debatable whether the upgrade justifies the gains
This is not debatable. It’s a factual truth. Every single actual performance review concludes that modern Java vastly out performs the decade old Java 8 runtime.
For an example here is a quote from Hazelcast when comparing performance of modern Java on data heavy workloads [0]:
> JDK 8 is an antiquated runtime. The default Parallel collector enters huge Full GC pauses and the G1, although having less frequent Full GCs, is stuck in an old version that uses just one thread to perform it, resulting in even longer pauses. Even on a moderate heap of 12 GB, the pauses were exceeding 20 seconds for Parallel and a full minute for G1. The ConcurrentMarkSweep collector is strictly worse than G1 in all scenarios, and its failure mode are multi-minute Full GC pauses.
This is from 2020 and the gap has only gotten wider.
[0] https://hazelcast.com/blog/performance-of-modern-java-on-dat...
I work in Java every day and have upgraded my entire companies fleet of services to Java 21. FANGs are nothing special. And every actual survey shows that there are as many people on a runtime > 8 as there are people on 8:
https://www.jetbrains.com/lp/devecosystem-2023/java/
> And every actual survey shows that there are as many people on a runtime > 8 as there are people on 8:
I don't think you even bothered to read the sources you're quoting, and you're just trying to make baseless assertions.
The very first question on JetBrain's survey is "Which versions of Java do you regularly use?", and 50% answered Java 8 with around 2% mentioning Java 7 and 1% answering Java 6. On top of that, Java11 alone comprised around 38%.
And you're talking about Java21? It doesn't even register in the survey at all.
I'm not talking about Java 21 when referring to that data, I was refuting your baseless claim that I don't work in Java. I don't think you even bothered to actually read or comprehend my comment.
The data there clearly shows that the ecosystem has as many users running a modern version. Which directly counters your assertion that everyone is just running 8.
I have been developing Java for a long time and my brain tends to stay stuck in Java 8 mode a lot of the time.
Definitely not the only language. Other long-lived languages like C++ get this treatment too.
Try doing a cold load of JShell though. Can easily take 2 seconds on a rather fast machine. People will never use it as a quick REPL for that reason.
I use it as a repl all the time. IntelliJ can load your projects classpath into too. It’s really awesome.
Thanks, I'll give it a shot in IntelliJ.
docs if you're interested: https://www.jetbrains.com/help/idea/jshell-console.html
It's implicitly still a class, you just don't need to write all the scaffolding to indicate that. The functions are still methods of that class etc.
But, yes, with static imports, you can just write "println" like that and have it work.
> It's implicitly still a class, you just don't need to write all the scaffolding to indicate that.
did you know that every function in a python script is actually an attribute of that module (which is basically a class). so my point is: who cares.
geokon's point still stands. This is meaningless syntax sugar that does not change anything fundamental about Java except add one more special rule into the language aka it is one more thing to learn. Rather than being helpful, it just becomes friction when an old developer uses the old method and a new developer uses the new method.
You still need to know what a main class is, so that you can reference it in your build.gradle file.
If you're using a build.gradle file then you probably won't see any benefit to this implicit class, yes.
But it's useful for little one-off scripts, where you just go `$ java foo.java` from the command line.
As others mentioned, there is an implicit class here in play.
Basically, imagine a `class Temporary4738 {` and `}` written above and below the .java file's content, that's roughly how it will behave.
It not having a fixed name is deliberate, so if you decide to build a bigger system you won't use this mechanism in a hard to maintain way, and also it needed a good semantic model for "top-level" variables/fields.
But with this implicit class model, top-level definitions are just fields of the class.
> As others mentioned, there is an implicit class here in play.
The same strategy is also followed by C# with their top-level statements. And yes, it's syntactic sugar.
https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals...
> This seems to break that fundamental paradigm and makes the language more complicated
Welcome to the last 10+ years of Java. They're trying to play catch-up to other languages that made fundamentally different and better choices, making Java more and more complicated in the process. They are so deeply entrenched in the enterprise back end space that programmers put up with it, nay, even feel grateful for it. People who are stuck using Java for the rest of their careers are happy to see it grow to accommodate 21st century programming techniques, even if it means that it becomes so complicated and crufty that no beginner will touch it ever again.
In java's defense -- and I share your criticisms -- it offers some very nice things that no other language has.
eg want to run code for 6 months at a go managing hundreds of gigs of data, tens of thousands of threads, at high load, with sustained gigs a second of garbage generation? the jvm will just silently tick away doing just that while being nearly bulletproof. As much as I love ruby and python, you ain't doing that with them.
With hot code loading, introspection, etc. All the stuff that makes developing super-robust long-lived systems way easier.
And the flip side of the clunky language is your code from 20 years ago still works.
Also, conservative feature adoption works in the long term.
Scala mainstreamed a bunch of concepts that java was slow to adopt, but the end result was java only took the good ideas and didn't take the bad ideas.
Fortunately, you don't have to use Java to use the JVM. There's Kotlin, Scala, Clojure, etc., all of which are simpler languages.
It doesn't surprise me; that sounds exactly like how C++ has progressed (sans the enterprise beans, hopefully).
10+ years ago I considered myself proficient at C++. It made sense, I didn't mind the pointers, and while the std library still felt clunky, it was fine. But I tabled these skills, and they indeed rusted a bit, but surely I could bring them out when needed again, right?
Smart pointers with multiple variants, judicious use of macros, rampant use of double colons to ensure proper namespace resolution (which IMHO makes the code an absolute eyesore), to name a few. I won't argue these changes aren't necessary, but it isn't pretty.
Because this is the entrypoint? Having code exec out of an explicitly defined entry point that's an entire file sucks.
I'm having such a hard time figuring out whether this is satirical.
(My guess: It is.)
I know, right?
> So you don't need to worry about functions floating around in a global namespace soup.
Because having classes floating around in a global namespace soup is fundamentally different and should give no worries to anyone. Yet this was argument made in earnest back when arguments about Java's strengths and weaknesses were much more popular.
There's always going to be a namespace soup but at least in Java there's only one kind of thing in the soup.
The famous "anemic namespace soup" language design pattern.
With the 1:1 intended correspondence of (public?) class names to source file names I get how that could be considered a feature for ease of use, compared to C++ where any file could define any name.
Hey, it could be Perl!
It took almost 30 years to get here. I never thought I'd see the day.
What really threw me for a loop is the article author mentions: "I knew plenty of professors who were bothered by [the public static void main thing]." I could have sworn that OOP die-hards in academia _wanted_ the formal class boilerplate in the original design. Maybe they aged out? Perhaps, times are changing.
Song version: https://www.youtube.com/watch?v=yup8gIXxWDU
Thanks for sharing this. I watched it while both enjoying and cringing at it.
what did i just witness....
One of the best metal band Italy has on offer, that's what you just witnessed.
SEMICOLON!!!
this is actually fucking amazing :D
I disagree. Working through each keyword until you understand the full signature felt great when learning.
It’s the Python if __name__ == __main__ trash that was the worst. You’ll never understand it and it doesn’t fit in the language.
Python seems to have started with 'why do those other languages have all this trash' and then spent the time since then learning why and coming up with worse versions.
I use python a lot these days, and like it, but it's pretty funny seeing stuff like the above and type hints.
I hate not knowing what types a function takes and returns.
> Python seems to have started with 'why do those other languages have all this trash' and then spent the time since then learning why and coming up with worse versions.
This seems true to me too. Examples:
* if __name__ == "__main__": main()
* Private members being marked by convention with leading underscore (e.g. def _foo()) instead of being a language feature
* @staticmethod as a decorator instead of being a language feature
* Duck typing vs. abstract base classes
* Static type hints got retrofitted to the language gradually, one feature at a time
* Reference-counted garbage collection seems to be more deterministic than tracing garbage collection and ensures that unreachable resources have their finalizers run as soon as possible... except it's not true
* Having a GIL instead of being truly multi-threaded from day one
* Various OOP concepts that are much better explained in Java than Python: __eq__(), __hash__(), monitor wait() and notify(), object finalizers, thread safety, weak references
* Distinction between str and bytes. This is the biggest change from Python 2 to 3 and caused a lot of incompatibilities. Java separated String and byte[] from the start (though the use of UTF-16 is unfortunate).
The whole switch/pattern matching thing is a true abomination borne from: Well, things in statement position can't be expressions (or however Pythonistas might phrase it), so clearly switch/pattern matching must be a statement... It's such an own goal and undermines the main point of pattern matching.
(I do realize that there was opposition on this, but that the Clearly Wrong side won out is baffling.)
Same principle behind Python's crippled lambdas.
Yeah, it's a shame for Python. I am extremely impressed that Java 14's switch expression ( https://openjdk.org/jeps/361 ) is a generalization of the switch statement (since Java 1.0); it's like they took a page from Rust's language design. I have found the switch expression to be quite useful in my work.
Probably from ML/Haskell, but yes :)
Your quibbles show either a) a fundamental misunderstanding of Python or b) are just flat out incorrect.
> * * if __name__ == "__main__": main()
So, don't use it. Python is frequently run as a scripting language (something Java is fundamentally bad at) and this stems from that. All it does is box logic off when a file is being run directly vs imported. It's a user convention and not a language one....ignore it if you hate it so.
> * Private members being marked by convention with leading underscore (e.g. def _foo()) instead of being a language feature
This is all very well explained in the PEP. The quick run down, Python doesn't do anything to hide logic from users. Contracts are just conventions and python treats it that way; so the developer can say "you shouldn't use this directly, here's a notation to let you know", but nothing will stop a developer from doing what they want. In Java they'll just write a wrapper class, inherit the base class, and expose that logic. Or worse, fork the library just to edit the class' functionality in some minor way. In Python they'll just call it directly.
> * @staticmethod as a decorator instead of being a language feature
@staticmethod is a built-in, it is a language feature. It just doesn't follow your preferred syntax.
> * Duck typing vs. abstract base classes
You can do both in Python:
https://docs.python.org/3/library/abc.html
The concepts aren't mutually exclusive or even directly related. Java just happens to be bad at one of them, due to it's weirdly non-effective (for a VM'd language, at least) reflection system; so you think it's bad.
> * Static type hints
You're complaining about reflective programming and then complaining about a feature that essentially exists because you can't reflect. It's circular.
> * Reference-counted garbage collection seems to be more deterministic than tracing garbage collection and ensures that unreachable resources have their finalizers run as soon as possible... except it's not true.
GC arguments have run for decades and everyone has their opinions.
> * Having a GIL instead of being truly multi-threaded from day one
Python was created in 1989. Java was created in 1996. Can you guess what major change in computer history was happening around the latter's time?
Everything was GIANT-locked when Python was designed, unless it was specifically intended for SMP mainframes. The entirety of the FreeBSD operating system had a GIANT lock on it at the time, for instance. Linux didn't even exist. Mac OS and Windows both were fundamentally single threaded and cooperatively multitasked. Commercial Unices that supported SMP were only ~4-5 years old and a very small niche.
You might as well be complaining about "why didn't the x86 architecture just have 64-bit capabilities from the outset?"
> * Various OOP concepts that are much better explained in Java than Python: __eq__(), __hash__(), monitor wait() and notify(), object finalizers, thread safety, weak references.
In other words: "it's not Java, so it's bad".
Java is a pure-OO language; Python is a procedural language with OO as an optional feature. To that end, it exposes OO-features in an optional manner versus forcing you to use them against your will.
So if the basis of all your arguments is "OO is better in Java", well the the response is "yeah I'd hope so, since you have no other choice as it's the fundamental paradigm". Guess what? Haskell is much better at functional programming than Java; that also doesn't make a valid argument about whether either is good or bad.
> * Distinction between str and bytes. This is the biggest change from Python 2 to 3 and caused a lot of incompatibilities. Java separated String and byte[] from the start (though the use of UTF-16 is unfortunate).
Java was developed 7 years later and during a time that Unicode was becoming the standard over ASCII. Python was designed when everything was still ASCII and Unicode a mere glint to the general computer science realm.
As you pointed out, even Java made a bad decision here due to their premature adoption of the standards (as any modern designed language is quick to point out).
And your reply is half right, half wrong. Let me address the wrong parts.
> Duck typing vs. abstract base classes / You can do both in Python
I'm saying that the original intent of Python was duck typing, but then people realized that abstract base classes play an important role - e.g. isinstance() testing, static type annotations. So they still ended up where Java started.
> Static type hints / I don't even know what that means.
I'm saying that Python was designed with no type annotations on variables (though values did have types), and then they realized that people wanted this feature... and ended up where C/C++/Java/C# have been all along. Python became "enterprisey".
And then, Python implemented type hints rather badly from 3.0 through 3.10 and beyond. It doesn't include types in the official documentation, so now you have to guess what open() returns (it's typing.BinaryIO/typing.TextIO). It doesn't have an official type checker, instead relying on third-party tools like mypy. It moved items between typing and collections.abc. It requires `from future import __annotations__` for non-trivial programs. It changed typing.List to just list in 3.9. It introduced the | (union) operator in 3.10. And a bunch more little things I can't recall; it just had a bad out-of-the-box experience and kept tweaking things over the decade. Documenting generics and especially protocols in Python takes effort.
> Python was created in 1989. Java was created in 1996
Wrong. Java was created in 1991, and the first version released in 1996. https://en.wikipedia.org/wiki/Java_(programming_language)
Python 1 was released in 1994, and 2 in 2000. They had their chance to make breaking changes. https://en.wikipedia.org/wiki/History_of_Python
While you're right that Python predates Java, it's not by as many years as claimed.
> Various OOP concepts that are much better explained in Java than Python
To exemplify, here is how you figure out how to override `==`: https://docs.python.org/3/library/stdtypes.html#comparisons , https://docs.python.org/3/reference/expressions.html#express... . Notably, there is no documentation for object.__eq__().
Here is the Java version: https://docs.oracle.com/en/java/javase/23/docs/api/java.base...
Now for __hash__() / hashCode(), the Java version is clearer: https://docs.python.org/3/reference/datamodel.html#object.__... , https://docs.oracle.com/en/java/javase/23/docs/api/java.base...
Python __del__ is buried in the "Data model" page: https://docs.python.org/3/reference/datamodel.html#object.__... . Java's finalize() is easily found on Object: https://docs.oracle.com/en/java/javase/23/docs/api/java.base... . Furthermore, Joshua Bloch's book "Effective Java" has a chapter titled "Avoid finalizers and cleaners" that explains in detail why finalizers are problematic.
Python explains a lot less about weakref than Java: https://docs.python.org/3/library/weakref.html , https://docs.oracle.com/javase/8/docs/api/java/lang/ref/pack...
> Java is a pure-OO language
Extremely wrong. Java has primitive numeric types, which do not have methods or fields, and undergo painful boxing/unboxing conversions to interoperate with the OO world. Whether the performance benefits are worth it or not is debatable, but what's not debatable is that Java is not pure OOP. Some say that Java is a bad copy of Smalltalk, which I heard is fully object-oriented.
> Python is a procedural language with OO as an optional feature
Wrong. Every Python value is an object that can be inspected (dir(), .__dict__, etc.). And in the CPython API, every Python value is a PyObject*. I have ample grounds to believe that Python is more OO than Java.
>Wrong. Java was created in 1991, and the first version released in 1996.
Gosling and others started working on it in 1991, but does it really matter? First public release is when you can learn about it and borrow ideas. It doesn’t make your point less valid, of course - Java made a lot of hype back then.
> Python 1 was released in 1994, and 2 in 2000. They had their chance to make breaking changes. https://en.wikipedia.org/wiki/History_of_Python
You're purposefully fudging dates to make the argument more favorable to your point. If you want to argue initial source release, then you can maybe make the point for 0.9:
> In February 1991, Van Rossum published the code (labeled version 0.9.0) to alt.sources
And say that Python had it's initial release just slightly before Java had begun design specs. But Python was in use before that and Rossum had developed the initial version well before that (again, 1989):
> The programming language Python was conceived in the late 1980s,[1] and its implementation was started in December 1989[2] by Guido van Rossum
It's ironic that you're trying to make that same argument for Java and dismissing it for Python, when Python was very much in public use pre-1.0 and Java was not (outside of internal teams).
> Wrong. Every Python value is an object that can be inspected (dir(), .__dict__, etc.). And in the CPython API, every Python value is a PyObject*. I have ample grounds to believe that Python is more OO than Java.
I feel like, just based on this point, you've done nothing more than open the CPython source code and searched for "object". This naming was in place well before Python even had any of the OO-functionality that exists today (new-style classes derived from the Object type). If you're going to argue for "old-style" classes being OO in any way but name, you're probably going to fundamentally disagree with any OO fundamentalists, the designers of Python itself, and the Java community/designers. You might as well argue that structs with function pointers in C make it an OO-language, as that's functionally all they are.
PyObject doesn't correlate to a Python class/Object. It's a container data structure to allow for their duck/dynamic typing. Object is a type of PyObject; but it contains an additional layer of functionality to make it a Python Object (specifically PyMethodObject and PyTypeObject, and their correlative functionality). Again, to allow for the reflection/introspection and GC that you so bemoan; and due to the lack of C generics at the time (or really, even today). Being able to introspect a type has nothing to do with it's "OO-ness", although it can be very useful in such languages (such as C#).
As to your other point, sure...using "pure" was probably going too far. But by that same argument, even Haskell isn't pure-functional (nor do any really exist) due to it's need to interface with IO. But Java is about 90% there and strives for it. Python most definitely isn't nor does it make any intentions to do so.
Again, fudging the history/facts/topics to try and make your point. It's not worth discussing with someone who so fundamentally converses in bad faith. Especially since I'm making no claims to which is better, just outlining the flaws in your complaints. I really don't care about "better" languages.
In my humble and correct (trumping anyone else's) opinion, they are both garbage.
You're not wrong... but for different reasons :)
you don't need it until you get to importing modules.
hello world in python is literally just print("hello world!")
You don't even need it afterward, unless you want a file to be both an importable module and an executable script.
Arguably you could be a happy Python programmer without this capability.
I never understood why people bother with this when writing their python main file, which is never going to be imported because it doesn't make sense in the first place.
REPL-based development. You might be developing a script, but notice a bug. If you import your script as a module, you can run the function you’re debugging, figure out what’s wrong, edit the file, and then reimport the module (using importlib.reload).
Okay, that's quite convincing, thanks!
Because I dislike interspersing executable statements at module level with function definitions. I’d rather have all code live inside functions and have one main entry point for the script. It’s just a personal preference, nothing wrong with not doing that though.
But you could have a single main() call without the if statement, couldn't you?
Isn't that hugely elaborate compared to languages where top level statements are a function, functions return their last statement, and the return value of the entry function is sent to stdout unless invoked in special ways? That print() around the "hello world!" does not appear any less superfluous than all the public static void you can throw at a javac 1.0
You mean a language like... Python?
Exactly like that (well, almost exactly: if python includes the quotes then it's not really equivalent to print()).
The point was that by including that print(), people already do say yes to including "like you'd do in a real program" ceremony in their supposedly minimal examples.
You might also like Kotlin script.
You have to brew install kotlin for this to work of course. But it's a great way for using a lot of Java stuff as well. Kotlin's Java interoperability is excellent if you are using Java from Kotlin.IMHO Kotlin is underused as an alternative to python currently for data science stuff. It's surprisingly capable out of the box even with just the standard library and there are a lot of nice data science libraries that make it more useful. Not for everyone; but fairly easy to get started with.
Kotlin scripting is unfortunately not necessarily very user friendly (e.g. imports can be a bit tedious and IDE support is a bit meh). But it can be a nice way to embed some kotlin stuff in a script. Generally, Jetbrains could give this topic a lot more love and attention and it wouldn't even take that much to level up the experience.
KTS works in jupyter as well (there is a kotlin engine for that). And that of course is nice if you want to use Java libraries in jupyter. And developing kotlin DSLs for stuff and then using them in a script is kind of a power move.
> Kotlin scripting is unfortunately not necessarily very user friendly (e.g. imports can be a bit tedious and IDE support is a bit meh). But it can be a nice way to embed some kotlin stuff in a script. Generally, Jetbrains could give this topic a lot more love and attention and it wouldn't even take that much to level up the experience.
Kotlin has been pretty bad at scripting and REPL, and unfortunately the team decided to drop both:
https://blog.jetbrains.com/kotlin/2024/11/state-of-kotlin-sc...
Based on that page, they're dropping the REPL in favor of notebooks, but their're not dropping scripts. They are dropping some script-related functionality to focus on others.
Yes, they are not dropping scripts completely, at least because of Gradle, but they are removing some parts of scripting support and advise against using Kotlin for scripting. And I would prefer a true command line REPL instead of the notebooks. IIRC they were promising various improvements in both scripting and REPL for several years, and now they just gave up, I guess because of shifting the focus to K2 and KMP. I find this quite disappointing.
Also worth noting that notebooks and kts overlap quite a bit. They use the same mechanism for imports and defining remote repositories, for example. A notebook is effectively a kts script. And of course gradle uses kts as well for its Kotlin dialect. These things actually overlap quite a bit.
One is
And that is SO MUCH less? Are you being ironic?Should we allow somebody who cannot handle the second one, go on and programming because of the first one?
>less crufty than the old "public static void main(String[] args)" thing. I always felt that was a terrible introduction to programming
I somewhat agree, but it was kind of exciting to learn what each of those previously meaningless words meant.
Kind of a sneak preview... Tune in for next week when we learn what static means!!
Really Java has always been kinda okay - the really horrible part of Java was always the "enterprise OOP" programming patterns that were basically a given in any commercial codebase - little things like pointing out the verbosity of a basic single file project are just shorthand for "look at all this shit people have written".
As the adage goes - Java programmers can write Java in any language.
You mean stuff like in the 'Design Patterns' book by the Gang of Four? That was originally written for C++ programmers and predates Java by a few years.
Seems 'Java programmers' were already writing Java before Java's release ;)
Python programmers be like:
That is something yet different.
Is the problem design patterns or opaque frameworks that rely on a ton of reflection?
Afaik, at the time issue was a lot of programming in xml - where those frameworks are configured in various xmls and hard to understand and maintain. Reflection based frameworks are later development.
“In XML” is neither here nor there.
The massive amounts of indirection which the IDE wouldn’t help you understand was the difficult thing. What encoding you use for that is pretty irrelevant. JSON would have been worse.
The indirection in Java itself matter significantly less, because you ctrl+click and it gets you where the things are. Plus auto completion. Both were part if Eclipse years and years ago.
The problem with xml was that IDEs were unable to help you.
... But if they do make it in Java, at least you have the tools to analyze and refactor it.
I agree that can serve as terrible intro to programming. But, often when we have tools either designed for a purpose in mind or a dominant paridigm or reaction to existing set of tooling, this can result in understandable yet extreme abstractions.
Java is designed with OOP in mind and it kind of makes sense to have the user to think in terms of lego blocks of interfaces. Every method or class needs to have clear understanding of its users.
public - software handle is for all users
protected - software handle for current and extending classes
default - software is exposed to current package
private - software is restricted to be used in current class alone and nowhere else
So, the beginning of java programming starts with interface exposed to the user or other programmers. Is it weird and extreme. Yes. At least, it is consistent.
You don't need the args!
should work just as well.They've also done quite a bit of work to make things such as hosting a web service much much simpler, through the addition of things like SimpleFileServer, as well as some better convenience methods.
It makes it far far closer to the python experience.
> than the old "public static void main(String[] args)" thing
you forgot surrounding class definition.
It's a good local improvement, but the other hand that old traditional syntax was a good effective "keep away" sign in Java that saved many CS students from being fed Java as a first language. And it's still a bad first language after this change. I wonder if this small improvement might make things worse in the big picture.
These features are explicitly to make teaching Java as a first language easier. From https://openjdk.org/jeps/463
Goals:
Offer a smooth on-ramp to Java programming so that instructors can introduce concepts in a gradual manner.
Help students to write basic programs in a concise manner and grow their code gracefully as their skills grow.
Reduce the ceremony of writing simple programs such as scripts and command-line utilities.
Do not introduce a separate beginners' dialect of the Java language.
Do not introduce a separate beginners' toolchain; student programs should be compiled and run with the same tools that compile and run any Java program.
I learnt to program using Pascal, then C++ in Highschool.
In collage, CS101 and 102 required me to learn and use JAVA, which I felt was a huge step backwards in usability and a lot of excess typing.
But as I reflect on it now, I think my feeling were because I came from knowing how to do some things and therefore not requiring the framework that JAVA offed to first time programmers.
I think the sins of Java as a first language are twofold:
1. The obvious excess incidental complexity
2. and more important, the enterprisey culture that's much less compatible with the intellectual curiosity, looking under the hood, etc attitude that's needed to become a decent programmer.
And yet it is still the first language taught in many programs.
Although my high school programming class was c++, which is even worse as a first language.
I've been using Java 21 for Advent of Code this year, and I've been surprised at how much less clunky it is than I remember. `var` and `record`'s are especially nice for how I like to code. It's still not perfect, but it's not something that I hate programming in now.
These were some neat tricks. I've been using the `java myfile.java` approach for running the AoC problems. I didn't realize the "implicit class" thing where you could have top level statements and methods, though, with the automatic import of java.base. That's pretty slick for this.
Records being allowed in methods has been great for me when doing things like path finding. Makes it easy to write something like:
This is actually a natural extension of classes being allowed inside, which is not well-known for some reason. Nonetheless, I also really like this feature with records.
> which is not well-known for some reason
Really? A whole generation of Java programmers wrong functor classes in-line.
Those are anonymous classes, if we‘re thinking of the same thing. Java also supports named classes bound to a method‘s scope (local classes).
It's allowed because Record automatically implements methods called cost(), row(), and col().
So it’s almost as good as C#? Which would be my favorite language if not for strong ties to MS ecosystem.
Just to be clear, the implicit class thing is still a preview feature as of Java 24, which should come out next March. It might be finalized for Java 25, in the coming September. But no guarantees. Until then you'd need to explicitly enable it with a cli parameter.
Launching without compiling, a main interface... a Mark Reinhold story
During jdk 1.1 development, there was obviously no consensus on test suites (i.e., JUnit), and the JavaSoft JCK tests required a ridiculous amount of html documentation for tracing to specs. Mark Reinhold, the jdk tech lead, refused to have his team write tests in JCK form, so instead he wrote a small harness with a compiling class loader: point the harness at a directory tree of .java files, and it would run them all, compiling as needed. The test interface was just a main function, and if it threw an exception, it failed. Pure and simple.
But probably the best magic trick for Java programmers is debugger hot reload. You write some empty methods, start the debugger, and then code iteratively, reloading as needed. (Set up the data and interfaces right, and change method bodies as you wish.) It's so much easier than recompile-println when you're just getting started, and after you've grown up and out, it's easier than rebuild/redeploy when you're working on a big system.
> debugger hot reload
Hey, that's (close to) the traditional Smalltalk introduction-trick! And this has been available since 1.1? How does one concretely do that in Java and why is it not widely known?
1. There's no way to react to hot reload in the normal Java API, so you can't easily clear state.
2. The JDWP protocol lets debuggers redefine nearly anything, but HotSpot doesn't implement the full protocol. In practice many kinds of reload don't work.
3. To fix those issues app frameworks went with classloaders for partial app reloads instead, or reimplemented hotswap logic themselves (see JRebel).
There's no fundamental reason these can't be fixed, and in fact the Espresso JVM fixes both 1 and 2:
https://www.graalvm.org/latest/reference-manual/espresso/hot...
You can write JVM plugins that are invoked after redefinition occurs, allowing you (really your app framework) to do something sensible like partially reload state. And it implements the full protocol, so many more kinds of redefinition work.
Using it is easy. Just install Espresso as your JDK, point your build system at it, then debug your app. Recompiling what attached will cause hot reload.
Interesting, I've never heard of Espresso, I've always just used Jetbrains Runtime[1] instead which is the successor of DCEVM[2] in a way. As for plugins I used HotswapAgent[3] at times however I found it to be rather clunky, does Espresso improve on this aspect?
1: https://github.com/JetBrains/JetBrainsRuntime
2: https://dcevm.github.io/
3: https://hotswapagent.org/
Well, it doesn't sit that well with mutable global/local state. There is a saying that hot reloading works better if you can replace at a smaller, atomic granularity. In the JVM, this kind of reload is at the method granularity, which may or may not be ideal.
But the JVM still has a few tricks up its sleeve, e.g. the class loader can dynamically re-load a newly compiled version of a class at runtime - jrebel is a proprietary extension that uses both tricks, but this latter can also be used by spring for hot swapping many parts of a running system.
Basically, in the JVM a class is unique within the class loader, so with a new class loader you can load as many class instances of the same, or slightly different class as you wish.
> But probably the best magic trick for Java programmers is debugger hot reload. You write some empty methods, start the debugger, and then code iteratively, reloading as needed. (Set up the data and interfaces right, and change method bodies as you wish.)
Do you have any example you can point to for this ?
It’s not a popular choice for it, but PHP is also fantastic for the sort of things described in this article. A relatively recent version is pre-installed on many *nixes, it has a decent enough (and gradual) type system, and standard library absolutely chock full of functionality (albeit very self-inconsistent in naming and structure) for dealing with files, JSON, making network requests, etc.
And because it’s not compiled, you can just whack a hashbang at the top of the file and chmod it to be executable.
I agree it'll work, but from a broader ecosystem perspective it feels like a mismatch, given PHP's historic focus on webstack stuff, and the projected likelihood that someone maintaining the system will know PHP versus Python etc.
But perhaps we can both agree that it shouldn't be in JS/Node. :p
That's true for every other mainstream interpreted language. Is PHP better than Ruby or Python for those use-cases?
It's worth observing that languages significantly over time, usually for the better. I remember writing Java when it was slow and had no generics. Now it's one of the fastest languages, and has not just generics but also (simple) type inference, which cuts down significantly on boilerplate. The point is: if you have a foggy memory from ten years ago of hating some language, many of the problems may have now been fixed.
Few languages pull off the balancing act Java does, supporting decades of code while rolling out big new features like project Loom in a backward compatible way. Oracle’s work really feels like a lesson in how to evolve a language.
It's hard to fault Java at this point: arguably the best runtime of them all from a perf perspective, great tooling from JetBrains, large, high-quality ecosystem whose quality far exceeds npm on average, and sealed classes as an acceptable sum type. My only remaining issue is how prevalent nulls are, but there's some work in progress on improving that. Until then, I'll prefer Kotlin slightly.
Definitely one of those things where the comment section is only good at spouting ancient memes with little regard to the truth of them.
> It's hard to fault Java at this point: arguably the best runtime of them all from a perf perspective
The JVM is a massive memory sink compared to the tiny (actual) runtime in Go or the total absence of a VM or runtime in Rust or Zig. I allocate at least 5x more memory for Java tasks, but sometimes its more.
Java is great compared to scripting languages or writing raw C, but not compared to modern compiled languages from a syntax (null, thrown exceptions, inheritance, etc..), library selection, CVE/security, memory usage or raw computer power perspective.
Many times the "massive" memory sink is because people give the VM more memory than it actually needs (or they use the default, which is to take up to 25% of RAM whether or not it's really needed for the required performance). I.e. people will say a process "takes" 1gb, when the same program could run just as well if they tell it to use, say, 300mb. The more memory, the faster it could run. The GCs offered by the JVM are more advanced than Go's by a couple of tech generations (the Go-like GC was removed from the JVM when it was superseded by two generations of newer GCs), and memory consumption is already going down with compact object headers [1] and will go down even further with Valhalla.
All in all, Go may take up a little less memory for similar performance, but the JVM is more flexible (and more observable), offering you better performance if you need it. And the footprint will continue dropping, as it has done for years [2].
C++, Zig, or Rust are not really an apples-to-apples comparison. Sure, they take up significantly less RAM, but their performance is more expensive per unit of effort, and it's not a one-time cost, either. It's a permanent tax on maintenance, not to mention Java's superb observability.
Don't get me wrong -- I'm a fan of Zig, and C++ is the language I still program in most, but you can't really compare them to Java. Java makes you pay for certain things -- especially in RAM -- but you do get your memory's worth in exchange, and in a way that translates to pretty significant time and money. If you can't spare the memory as you're running in a constrained environment that's one thing, but if you can, it's all a matter of what you want to use it for: do you want to use it all just on data, or do you want to spare some to reduce maintenance costs/increase development speed and improve performance?
BTW, I'm not sure what is actually meant by a "lack of runtime" for C++/Rust/Zig. They all have standard libraries, just like Java. Rust even has a (crude) GC in its runtime that most Rust programs use (and, like Java, it compiles down to a VM). I think what people mean is that compilation to native is typically AOT rather than JIT, but that has both pros and cons.
[1]: https://openjdk.org/jeps/450
[2]: E.g. https://openjdk.org/jeps/254
I'm interested by your characterisation of Rust. I assume 'crude GC' is a reference to Rc/Arc, but I would be interested to see some statistics for the claim most programs written in Rust use them extensively. Also, Rc/Arc arent a part of any Rust 'runtime', but rather the standard library, and are not available when they eould not be suitable for the target, e.g. UEFI. Moreover, rustc compiles to LLVM IR, but LLVM is not a JVM/CLR VM, and rustc is not the only Rust compiler (though the others are admittedly not production-ready yet).
> I assume 'crude GC' is a reference to Rc/Arc, but I would be interested to see some statistics for the claim most programs written in Rust use them extensively.
Yes, Rust's GC is used through Rc/Arc, and I never said it is used extensively by most programs, only that most programs do use it. It is because it is not used extensively that it can be crude and designed to minimise footprint rather than offer good performance.
> Also, Rc/Arc arent a part of any Rust 'runtime', but rather the standard library
What's the difference between a standard library and a runtime? In the three decades I've been programming, they've been used interchangeably. A language runtime means some precompiled-code that programs use but is not compiled directly from the program.
> rustc compiles to LLVM IR, but LLVM is not a JVM/CLR VM
I never said that LLVM was a JVM -- these virtual machines have very different instruction sets -- but like a JVM, LLVM is a VM, i.e. an instruction set for an abstract machine.
Now, it is true that Rust is typically (though not always) compiled to native code AOT while Java code is typically (though not always) compiled to native code JIT, but I don't understand why that difference is stated in terms of having a runtime. One could have an AOT-compiled JVM (and, indeed, that exists) as well as a JIT-compiled LLVM (and that exists, too).
It is also true that Rust programs can be compiled without a runtime (or a very minimal one) while Java programs can choose to have more or less in their runtime, but even the most minimal runtime is larger than the most minimal Rust runtime.
> What's the difference between a standard library and a runtime? In the three decades I've been programming, they've been used interchangeably.
First of all, you're right. But despite its definition I think people tend to look at it differently.
A runtime is generally thought of as a platform on top of which your code runs on; it needs to start first, and it manages your code. Or perhaps it runs in a side thread.
A language that has a runtime is hard to embed into something via just the C ABI, because a function call wouldn't use just the standard platform calling convention; it would have to start that runtime, perhaps marshal the parameters into something supported by that runtime, and then finally the runtime runs your function's code.
Take for example cgo, for which you'd need to start the garbage collector first (among other things), hence why the cgo FFI is expensive. Take as another example an async Rust function, which would require e.g. a Tokio runtime to be started first. Another example is Java, for which you'd have to start the whole JVM first.
A language that has no runtime, or a minimal runtime, can be called via the C ABI directly. All the function needs is to follow the calling convention, and then its code starts running immediately.
This is just my opinion of other people's opinions, I may be wrong.
> A runtime is generally thought of as a platform on top of which your code runs on
That's not a well-defined thing.
> A language that has a runtime is hard to embed into something via just the C ABI, because a function call wouldn't use just the standard platform calling convention
But Java can be embedded in native code or embed native code. It has a specified FFI in both directions.
> Take for example cgo, for which you'd need to start the garbage collector first (among other things), hence why the cgo FFI is expensive.
Well, Java doesn't quite work like that, and its (new) FFI is free in most important cases (i.e. same as a non-inlined C-to-C call). Also, "starting the garbage collector" is not well-defined. What "starts" Rust's garbage collector?
I understand what you're trying to get at, but things aren't so simple. There are, indeed, differences especially around JIT vs AOT, but it's not as simple as saying "having a runtime" or not, nor is everything similar in all languages (Rust and C don't work the same vis-a-vis the C ABI, and Java, C#, and Go interop with native code are all quite different from each other).
> A language that has no runtime, or a minimal runtime, can be called via the C ABI directly.
A Java program can easily expose any method via the C ABI to be called directly if the process has been started from Java -- i.e. it's easy for Java code to give native code a function pointer to Java code. Going the other way, i.e. embedding Java in a C program, is somewhat more involved, but even C++'s interop with C, not to mention Rust or Zig, is not always straightforward. Like in Java, certain functions need to be marked as "C-interopable".
> But Java can be embedded in native code or embed native code. It has a specified FFI in both directions.
Most languages have an FFI, but I am talking specifically about the C ABI and the platform calling convention; or more specifically, about starting from scratch, and what is necessary to do from there until your code can finally run.
Anything more complex than the C ABI is what makes people say there is a runtime. It's some layer between your code and the other language's code, inserted there by your language. There's usually no way to remove it, and if there is, it severely limits the language features you can use.
> What "starts" Rust's garbage collector?
Nothing; it doesn't start unless the function itself wants to start one, and the function can choose which one to start, through your code (rather than what the language's required runtime provides).
> A Java program can easily expose any method via the C ABI to be called directly if the process has been started from Java
In that case, the runtime has already been started, and is being reused.
> Going the other way, i.e. embedding Java in a C program, is somewhat more involved
That part is the most important part, and is generally why people say Rust has a minimal runtime; it can be embedded with very little setup. The code you write starts executing almost immediately. Java calling C may add a management layer on Java's side, but C/C++/Rust/Zig/etc need very little (hence, minimal runtime).
> Anything more complex than the C ABI is what makes people say there is a runtime.
But Rust (or Zig, or C++ for that matter) don't use the C ABI, either, except for specifically annotated functions.
> In that case, the runtime has already been started, and is being reused.
True, but I'm trying to say that the notion of "starting" the runtime (or the GC for that matter) is not really well-defined. HotSpot does need to be "started", but, say, Graal Native Image, which is sort of an AOT-compiled, statically linked JVM, isn't really "started".
> Java calling C may add a management layer on Java's side, but C/C++/Rust/Zig/etc need very little (hence, minimal runtime).
In some implementations of Java this may be the case in some situations, yes. I would go further and say that that's the typical case, i.e. if you want to embed the stock HotSpot, you will need to call some initialisation functions.
If that's what's meant by "runtime", then it's mostly correct, but it's more an implementation detail of HotSpot. Even without it there will remain more important differences between C++/Zig/Rust/C and Java, and this matter of "runtime" is not the most interesting aspect. For example, that Java is usually compiled JIT and Rust is usually compiled AOT is a bigger and more interesting difference.
> But Rust (or Zig, or C++ for that matter) don't use the C ABI, either, except for specifically annotated functions.
Not only that, but Rust, C, Zig also require some setup before their `main()` can start as well.
That is why people say they have a "minimal runtime", rather than "no runtime". There is still a bit of setup there, without which the languages cannot function, or can only function in a limited mode.
Otherwise agreed on all your other points.
FWIW Cgo FFI is expensive not because of GC but because Go uses virtual threads (goroutines) and prioritizes the simplicity of runtime implementation. The lower bound of FFI cost in .NET is roughly equivalent to direct not-inlined calls in C, despite the GC support.
> I never said it is used extensively by most programs
True, but to make use of Rc/Arc in Rust comparable to the use of GC in Java, almost evrery value would have to be wrapped in one, something I would think is quite rare.
> only that most programs do use it
I would still be interested in seeing statistics for this though. I have only ever used Arc once, and it was only to share a Mutex containing the program's database across threads.
> What's the difference between a standard library and a runtime?
I would say there are three main differences: - A runtime is (usually?) not optional - see languages like C#/Java/Python that require some sort of higher 'power' to manage their execution (interpreting/JITing code, GC management, etc), or crt0 - compared to a standard library which is just some extra code - see the C standard library function strlen() - A standard library can generally be implemented in the language itself. I think this is where the distinction starts to get a little fuzzier, with languages like Roc (its standard library is implemented in Zig), and Haskell (I would assume much of the side-effecty code like the implementation of IO is in C) - The purpose of a standard library is generally to provide 'helper' code that you wouldn't want to write yourself, e.g. Vec<T>, HashMap<T>, filesystem functions, etc. On the other hand, the purpose of a runtime is, per the first point, to manage the execution of the language, etc.
> I never said that LLVM was a JVM
True again, my point was worded badly. I didn't mean to suggest you thought LLVM was a JVM, rather to draw a distinction between LLVM and a Java/.NET style VM. LLVM used to stand for Low Level Virtual Machine, and the JVM has instructions like instanceof (which, fairly obviously, checks if a reference is an instance of the named class, array, or interface type). They operate at quite different abstraction levels, and the JVM is a lot more similar to the CLR.
> Now, it is true that Rust is typically (though not always) compiled to native code AOT
I would be genuinely interested in finding about a JIT compiler for Rust.
EDIT: it's worth me mentioning none of this is backed by any formal education (I'm 18), so it is very possibly wrong.
I agree with you on the memory sink side. For some kinds of applications the combo of startup time and memory consumption make it unsuitable - think of many small services or short-lived functions. Yes, Graal and such are amazing innovations, but they're very incomplete and poorly supported solutions and not something that's a good plan to bank your future on. This has limited our cloud deployment options to a significant degree, and I know I'm not alone.
Being a memory hog was not such a big deal in the pre-cloud era, but we pay real money for that (far too much!) these days, and leaner approaches are worth a lot in real monetary terms.
I wish push back on some of your other points. Java has evolved quite a lot lately, with improved syntax, much improved runtime (Loom!), better GCs, lots of libraries, etc. The community is a bit stale though, and it's viewed as the Oldsmobile of languages. But that old language still has a skip in it's step!
These other languages do not have the programing affordances of Java (generics, easy memory safety). Tooling is a big factor in favor of Java - IDEs, debuggers, observability etc. Also, the quality and availability of libraries is also a debatable point (although, it depends on the application at hand),
I actually disagree with all of these points and I write Java at work.
Memory safety isn't possible in Java (unlike Rust).
Tooling: they all have debuggers and observability tooling (pprof, pprof-rs, etc..). Rust even has zed now.
Libraries: Rust has really high quality packages. On the flip side, Go has almost everything built into the stdlib. Java requires a ton of third party packages of varying quality and guarantees even for basic things like logging or working with JSON. You have to constantly be on the lookout for CVE's.
In fact, 75% of the cloud-native foundations projects are in Go. K8s and docker are Go. Go is much more web-app or microservice focused than Java is actually. Most Java apps are deployed into the cloud using Go.
Meanwhile, Zig makes using the universe of C/C++ code easy.
I highly recommend you try Zig, Rust, or Go out. They weren't created out of ignorance of Java, but because Java and C++ had areas that could be improved.
> Memory safety isn't possible in Java (unlike Rust).
This is obviously some strange use of the phrase "memory safety" that I wasn't previously aware of.
Probably including races.
Zig or Rust neither attempt to nor have any chance of directly competing with Java or any other high level language, other than on the margins (they have a hard-enough time competing with C++, which is unfortunate, because both are better than C++ -- Rust slightly so and Zig significantly so, IMO). They are for low-level development and target a domain where both the calculus of cost and value is vastly different from the high-level application space.
It is usually just as easy to write a program in a low level language as it is in a high-level one, and this is true not only for Zig and Rust, but also for C++. Even in 1998 it was just as easy to write a program in C++ as it was in Java. But the maintenance later -- when you have a large 10-year-old codebase -- is significantly more costly, and necessarily so. In a low-level language, i.e. one where there's more direct control over memory management, how a subroutine uses memory may affect its callers. Whether the correct use in the caller is enforced by the language, as in Rust, or not always, as in Zig, changes in low-level languages require touching more code than in high-level ones, and can have a bigger impact.
The low-level domain is, and will continue to be, extremely important. But the market share gap between low-level and high-level languages/domains has only grown over the past decades, and there are no signs of the trend reversing.
Now Go is a different beast altogether, and is a high level language. But it has both pros and cons compared to Java. The tools it includes in the SDK are more user-friendly, but they, like the language, are less flexible and more limited than in the Java world. Nevertheless, the out-of-the-box experience is nicer, something we should definitely improve in Java, but you pay for that simplicity later in lost flexibility. Performance also isn't quite as good as Java's, and neither is observability.
> The JVM is a massive memory sink
Java tends to prefer using available memory, before it has to clean it. This results in memory usage growing more than it actually needs.
Services often run in containers, so this is less than a problem today than it was before, because the allocated memory to the container is fixed anyway. Try configuring the JVM to only use 75% of the container memory. In many cases, it will trigger an early GC and run better with smaller memory instances
There are of course some overhead for objects compared to primitives, but the Valhalla project is working to reduce the difference significantly on many cases.
You can also reduce memory usage and startup time by compiling to a binary image
I use Java/JavaScript/TypeScript at work but I do most of my scripting with .NET using LINQPad. Being able to use LINQ makes scripting so much easier.
Python? Huge respect, huge ecosystem, however I don't know if it's just me but I find it mighty hard to read. PHP with the "->" and Python with the space-sensitivity... Not sure why but it's so hard for me to overcome.
PHP syntax does add to the line widths, so breaking long lines in multiple lines by extracting variables can help with readability. Using return, continue and break early reduces indentation so that helps too, as well as creating smaller functions.
I use Ruby for scripting because it has a big standard library (unlike javascript) and an expressive syntax. The syntax is also very readable and short. It's also easy to install gems globally and use them. Things only need to be imported once.
I wouldn't say these things are qualities for big projects though. I like Typescript for big projects.
For those interested in the rationale for the simplified main methods (and the rationale for not simplifying further), the relevant JEP is a good read.
JEP 495: Simple Source Files and Instance Main Methods (Fourth Preview) https://openjdk.org/jeps/495
The parent article mentions and links JEP 477 -- the 3rd preview of the same feature-set. JEP 495 is the 4th preview and is included in JDK 24 which is in "stabilization" mode for release in March 2025.
I learned to code "single file Java" when I was working through Hacker Rank before they added features to the JDK to make it easier. Certainly if you want to do whiteboard coding sorts of problems it is good to know.
> As of Java 23, three methods are automatically imported from java.io.IO: println, print, readln
I thought they had just hallucinated `java.io.IO`, but apparently it's a new type, available in Java 23 with --enable-preview: https://docs.oracle.com/en/java/javase/23/docs/api/java.base...
It looks like they took lots of inspiration from Kotlin with these new implicit imports and their structure.
Groovy and Scala already did that way before Kotlin.
I totally agree. I use java for every possible coding script or task. I have been using it in my company for last 5+ years and have understood the language too well to be able to switch to something else and learn its caveats. In my comfort zone with java and I love that its being actively developed and improved every day.
Are you in the comfort zone with Spring as well?
I can only speak for myself, but I was lucky to be exposed to Spring Boot back in the v1.5 days after spending a few years creating Java programs without any framework. No XML at all, configuration by code, and opinionated default configuration that covers most use cases.
The learning curve was not that hard, and I got comfortable with it.
In contrast, I find React, Next.js and other frameworks more complex, and it takes a lot longer to be comfortable
Woah, reading the article felt so refreshing. Being able to see Java in this state is very exciting, this expands its usage into the scripting environment.
I am currently a heavy user of JRuby for quick and dirty scripts but still want to be on the JVM, but I'll give Java 23 a go and see how far it goes.
You might also want to look at scala and ammonite [1]
[1] https://ammonite.io/#ScalaScripts
Also scala-cli https://scala-cli.virtuslab.org/docs/getting_started
I still like to use Ammonite as a REPL, but scala-cli has replaced it for me in those cases where i get fed up writing bash.
exactly my thought. If you think java makes a good scripting language, you will be really happy with scala
Scala-cli would be even better. It explicitly supports Java too!
Very cool to see all these quality of life improvements. I haven't kept up with Java for several years. Still, the dependencies thing seems like an issue (as it is in Python)
Deno has been my go-to scripting tool lately. Similar benefits to Java as used by the OP, but it also allows you to import dependencies directly by URL, which means you can have your script self-define dependencies without a separate manifest file
Python packaging continues to be a pain, but you can specify dependencies for single-file scripts with inline script metadata, see: https://packaging.python.org/en/latest/specifications/inline...
For error handling they could add error propagation operator `?` from Rust. In Java it could propagate "throws" from the expression it's applied to the signature of a method that contains it. In Rust it's a bit awkward becuse you need to adjust return type. Since Java tracks exceptions separately it would be completely unobtrusive and ultraconvenient.
Groovy is still far far far better at cli scripting in javaland.
You don't even need main methods and class wrappers, can import libraries with annotations with Grape, etc.
The real failing of cli and scripting in jaba and groovy is running cli commands with access to stdin stdout stderr streams. I have figured out the dark art for groovy but it takes quite a few stack overflows to get there.
Maybe it's sort of not the same because it's compiled with graalvm and so not trivial to add any arbitrary Java dependency, but the tech that scratches that itch for me is babashka — a proper REPL, because it's a lisp, sensible Clojure syntax, and easy IO. And all the core Java libraries, available in a much snappier form than Clojure or other compiled languages
The Groovy shell can be really fast if you use GroovyServ: https://kobo.github.io/groovyserv/quickstart.html
It feels instant, no need for GraalVM.
Lightweight Java has always been a thing for those who appreciate Java-the-language but despise Java-the-ecosystem.
You don't need to fool with Gradle or Maven or Ant if you'd like to incorporate third-party or your own custom dependencies in single-file "Java scripts".
Just copy some .jar files into ~/lib, and in your `.bashrc`:
I use Java extensively for my personal work and never use Gradle/Maven/Ant. I run Python scripts that marshal javac and java calls, it works great. I do cute tab completion tricks that examine the class layout and extend based on what it finds. I even have a "SmartCompJava" script that finds .java files that have newer modtimes than their corresponding .class files and just compiles them - a bit dangerous if you're not careful, but it's super-fast!
Ah.. the old days of java programming - set up your CLASSPATH and done.. Nowadays; however, most libraries have tons on other dependencies and doing this manually is tedious and a pain in the neck - download the wrong version of the dependency, and you are in for a rabbit-hole trying to figure out the compilation errors. Do not get me wrong, Maven is ugly and disgusting(so are the other dependency managers for java), but gone are the days of CLASSPATH.. unless you avoid using libraries bloated 3rd part libraries at any cost.
If you like to have most of the simplicity of just setting classpath, but still have convenient dependency resolution, I highly recommend checkig out coursier: https://get-coursier.io/
Its a cli based artifact fetching tool for the JVM ecosystem, and you can have it just make up classpath strings for you, i.e., to use some Apache dependencies with the single file simplified java running you could just:
java --enable-preview --class-path (cs fetch --classpath org.apache.commons:commons-collections4:4.4) program.java
Also, maybe do have a look at scala-cli: https://scala-cli.virtuslab.org/ It does work just fine with Java files, and allows you to write single-file programs that include dependencies as special comments. The tool has some idiosyncrasies (like a background compiler server), but overall works extremely well if you need some of the power of a build tool, but without having a complex project structure.
Also, see this project that is in a similar space as Coursier:
https://github.com/bowbahdoe/jresolve-cli
https://github.com/bowbahdoe/jresolve
>Lightweight Java has always been a thing for those who appreciate Java-the-language but despise Java-the-ecosystem.
I always disliked Java until I was converted by some developers of that group in a past job. I suppose it is fair to judge a language by the overall flavour of its ecosystem, but it is a bit disappointing. I wish more people could see how simple and _good_ it can be when you use the JDK itself, and not go through other overcomplicated systems to use it. For example, Spring Boot is basically a massive pile of hacks (regardless of whether you consider it good or bad), and Java only really serves as an underlying support technology for it.
java -cp .:* OneOffTestCase.class
I just had this thought recently.
Java is a fantastic "scripting" language as long as you're fine running it in an IDE. Clean and typed syntax, powerful standard library (especially for data structures), great performance. A better choice in these respects than say Python, Bash, or Go.
I've used it for scrapers, Advent of Code, plain ole data munging and of course programming interviews.
That being said, I would never write a webserver in Java in 2024.
> but the Python API isn't all that wonderful, and dynamic typing means that I spend too much time debugging
I don't know, this just seems more like inertia. "I'd rather stick to what I know best than this popular thing." Which is fine, and I'm glad Java has made improvements making it easier to hit the ground running. But blaming the use of Java on the inadequacies of Python? The python API can do just about anything, it has regex toolings, I've never found myself needing anything else. And the typing complaints? Yeah it can be annoying if you're not good at keeping track of your own typing hints, but modern python supports type annotations and linters like mypy[1] catch everything related to that just fine. I've always admired many of Java's features, but let's not act like the reason for using Java for scripting is the pitfalls of Python. It's just because of an underlying preference for Java.
1. https://mypy-lang.org/
> Yeah it can be annoying if you're not good at keeping track of your own typing hints
If you write all the code you deal with, then sure. My experiences on big projects tend to be typing problems introduced by libraries. The kind where documentation and the decorators suggest it'll only ever return some specific value type, but then very occasionally it'll return a tuple of that value type and a message.
Fair, but in the context of scripting, which seems to be the focus of this article, how often are you dealing with complex library code? When I write scripts for file manipulation / simple automation, I'm usually not dealing with complex library objects. Plenty of os method calls, plenty of regex matches, but little else in this context. Big projects are another thing entirely. There's a plethora of reasons why you may want to use a different language for a certain project type. But it doesn't seem fair to imply that python is uniquely handicapped (or otherwise inferior to Java) for scripting and simple automation use-cases.
Java is still great, same for PHP and they have only been getting better.
Great is pretty domain-specific!
Java has been great for larger projects for a while, but I think smaller things like one-off scripts have been firmly out of reach so far.
It's good to see that change, as being able to use it in the small has obvious synergies.
For CLI scripts, PicoCLI with Graalvm seems to be a good option now.
> but I think smaller things like one-off scripts have been firmly out of reach so far.
why specifically?
The article lists several good reasons:
Having to compile every time (or at least there not being a convenient wrapper that compiles and runs at once), the boilerplate of having to define a class and main method, exception handling, and most of all having to deal with Maven.
you don't need to deal with Maven if Java standard library(which is huge) is enough for you. Also, not sure why such big resistance against maven.
Other pointers are either opinionated or minor. From another hand, if dev has experience in Java, benefit of not learning some new language/ecosystem is kinda huge.
I don't think Java is fit for "the small". And there is nothing wrong with that. My go-to language for "the small" is Go. Java is good for enterprise heavy-lifting, not for quick and nimble tools.
Every function call is 4 lines in Go. No basic error propagation support means Go code bloats up very fast in line count.
No collection streams means LOTS of mind-numbing/eye-glazing for loops. Things have improved slightly with the slices and maps package but these are just hacks. Maybe with the new iterator support, Go will slowly and steadily get a bit more expressive.Go is good for middleware coding thanks to Go-routines and and excellent networking support in the standard library but is cumbersome slow and inexpressive for scripts.
Also go.mod does NOT separate dev/test/prod dependencies - unlike Java Maven or Rust Cargo. You don't want your script dependencies coming into production code.
Go is severely less expressive than Java, so.. disagree. You will end up with longer files with more boilerplate for questionable benefits.
Also, in many areas Java's standard library is more expansive.
Can you tell us about how you use Go in the small? I like Go, but it doesn't strike me as particularly nimble for scripty use cases like this - error handling is part of it, but I know that's fixable with the Must() pattern.
I just write a small Go program. I agree that error handling is a weak point, but the tooling is well integrated. The article mentions that Java does not know about Maven. The go tool is much more versatile in that sense.
Also, go run is almost like running a script.
You can quickly whip up a single-file go program anywhere, without any boilerplate or setup:
You can write this in any text editor, but using one with LSP support means that `import` statement is added automatically, and with CoPilot or another assistant, the `main` function writes itself as well as any `if err ...` statements. Go is extremely well suited to code generation due to how predictable it is.Adding dependencies with `go mod` is uneventful.
It will never be as 'scripty' as JS or Ruby, but the readability, safety and performance are totally worth it.
I don't really see how this is different from `dotnet run`, `python main.py`, or `lua main.lua`. Like the commenter, I don't find Go very nimble.
Is limited features (Go) gonna be better for AI generation then breadth of examples? (C#). I'm not sure.
This is literally longer than the java version mentioned in the article, and that can be started just as simply with a single command.
I see 3 or four lines of boilerplate in your example, depending on if the closing brace is counted or not. Compare with the following equivalent program in ruby or crystal (it is valid in both):
And the crystal version is just as typesafe and performant as the go version. I also find it more readable, but that is a very individual metric.I don't think minimal examples are so useful. I reach for Go when bash is insufficient. That means there is a minimal starting size that is large enough that package and imports boilerplate are insignificant, all things considered
I love Crystal and have been a sponsor for a long time, but it's not comparable yet. Compiling is much slower, you have to manually add dependencies to shards.yml, cross-compilation is not as simple. And when it comes to scripts and tools, popularity / stability is a main concern.
The question I responded to was 'how you use Go in the small', not a comparison to the post.
As I mentioned, yes, it's verbose, but simple and worth the effort and peace of mind. My primary language is JS, I've written a fair share of shell and Ruby but would still choose go a lot of the time, just because it doesn't pull in any extra complexity.
For the small I like Groovy. Especially as Grapes is like Maven but in annotations that you include in the script file. Being Java-based means that if it goes from "in the small" into something larger you are already on a stable foundation.
Go refuses to compile if you have an unused variable and that is the opposite of quick and nimble.
and you get a nice little portable binary that doesn't force someone to install a compatible version of an interpreter/virtual machine to run.
That's if they're on the same architecture and operating system as you. Half the people I know are on mac, half of them are on arm. The other half are on windows and x86.
Actually, you can specify to build portable binaries for all 3 of these platforms. You can even make them totally static, so that deployment is as easy as "copy the executable file with windows/Linux/Mac/etc in the name and run it".
This is part of my standard Go build.sh script. You just never know who might want to run a given thing where.
Now I'm having flashbacks to porting a whole stack of applications from x86 to a customized Linux distribution running on a big-endian PC platform. A mishmash of C/C++, Java, Node-JS, Python...
It was really dumb, but that's what the client needed. So much cross compiling.
Go has cross compilation.
Who the heck is on ARM (outside data centers)? But yeah, with golang that's just:
GOOS=linux GOARCH=amd64 go build
GOOS=linux GOARCH=arm64 go build
GOOS=darwin GOARCH=amd64 go build
Which again, let's me distribute a binary to my end user as a single file, without directing them to install java or python3 or whatever.
(The ARM question is kinda immaterial, but I'm curious)
Who the heck is on ARM (outside data centers)?
Mac people, for a few years now.
Exactly
On Java, a lot of 1.2/1.3 era games are half-broken and threads fail a lot with interpreter from Java 2SE (>1.6/OpenJDK 6) and beyond.
I regularly end up using Jshell when I’m analysing things like profiling data. Partly because I have access to libraries that parse those formats, but also because typing and autocomplete make it really effective for this sort of interactive thing.
Interesting. Anyone have an opinion on how this compares to using Kotlin for similar tasks?
Seems very similar https://kotlinlang.org/docs/kotlin-tour-hello-world.html although I haven't ever used Kotlin like a "script replacement" (e.g. https://kotlinlang.org/docs/run-code-snippets.html#command-l... ) to compare and contrast
Overall, I think Kotlin is a vastly superior language to Java and spanks Scala in discoverability and legibility (less magick moar bettar, IMHO)
I've abandoned the jvm, but I found Scala much better designed than Kotlin. Things in Kotlin felt consistently ad hoc and I would frequently run into weird special casing whereas Scala actually had general principles that could be applied to more use cases.
Kotlin on the other hand had better IDE support in intellij for obvious reasons. That was not nearly compelling enough for me.
What’d you switch to from JVM?
Also, see my comment about kotlin scripting (kts) elsewhere in this thread.
But, in short, you can write kts shell scripts; import any library you want, use anything in the JVM or the Kotlin or Java library ecosystem that you need, etc.
Works on the command line (you need a jvm and kotlin installed from your favorite package manager). The Kotlin Jupyter kernel also uses kts and this is a nice way to use Java stuff in jupyter.
Kotlin is much better in that regard.
I use Kotlin scripts (.main.kts) as a replacement for Bash scripts for years.
You can inline dependencies without additional tooling, and Kotlin is still much more expressive than Java.
I'd consider it equal or better in all regards.
- Top-level functions/main is supported - data classes are approximately as good as records - A scripting mode comes built in, and can use annotations to import and use dependencies within the same file - There's a repl - The keyword 'var' exists, and the keyword 'val' is more ergonomic than 'final var'
The only thing I remember missing from the article is the implicit imports which I don't remember Kotlin having. Regardless, I'd reach for Kotlin every time. I think funnily enough Java never fully clicked for me until I started using Kotlin, which in many ways is "Java, except many best practices are on by default".
Spent a while trying to get (OpenJDK's) JShell to work, it seems like a buggy mess. I would currently not recommend using it for anything serious.
Take a look at Clojure and also Clojure Babashka.
Babashka is truly wonderful and has taken over almost all my scripting projects. But the author started by saying they didn't want to use a language without types so Clojure is probably out!
They should adopt the tag line for Java: "Not as bad as you remember"
I was also surprised when I looked at it again a year ago.
I would have used it for my latest Web app but django just beat Spring boot.
Have a look at any "mature" Java project on Github and you'll quickly come away with the opposite:
"Wow! It's even worse than I remember!"
"Wow! How many files and directories does this project NEED? It's like someone decided that it's easier to manage files than code..."
...and there's still XML everywhere :(
It's of course always a good idea to read the article before posting, but doubly so here – I was getting ready to yell about all of the annoyances making this infeasible to anyone with a less-than-extreme pain tolerance, but it turns out Java has changed a lot since I last used it around six years ago :)
I wasn't aware that single-file Java without a top-level static class was possible now, that + JBang seems quite useful for small tasks.
One nit:
> Python programmers often use ad-hoc dictionaries (i.e. maps) to aggregate related information. In Java, we have records:
In modern Python it's much more idiomatic to use a `typing.NamedTuple` subclass or `@dataclasses.dataclass` than a dictionary. The Python equivalent of the Java example:
This is obviously valid, but it's definitely more common in a language like Python to just dump data inside a dict. In a dynamic language it's a far more flexible structure, it's the equivalent of HashMap<? extends CanBeHashed, LiterallyWhatever>, which is obviously a double edged sword when it comes to consuming the API. Luckily more rigid structures are becoming more popular at the API boundary.
That's deranged, just use a namedtuple and some functions. Even decorators for something this simple are a code smell.
What do you do when another module needs ymin, inheritance?
OO is dead, leave it buried umourned.
As for my experience, nothing beats nodejs for fast scripting. Its literally just `npm install x y z` and then fire it up `$ node script.js`. But Im giving Java a chance.
> I am pretty sure [working around checked exceptions] will never be a part of the JDK, because it is arguably bad for large and serious programs.
And yet so many programming languages, including JVM languages like Scala or Kotlin, just don’t do checked exceptions, and the world hasn’t caught fire yet (and in fact, I can’t think of another mainstream language that does have them). Java could just drop them altogether and everyone (except maybe the most devout Java fans) would be happier.
> The file gets compiled on the fly, every time that I run the script. And that's just the way I want it during development or later tinkering. And I don't care during regular use because it's not that slow. The Python crowd never loses sleep over that, so why should I?
Java takes significantly longer to compile than Python does. My Python takes ~40 ms at first startup of a hello world script, ~20 in later attempts. `java hello.java` is in the 360-390 ms range.
Checked errors are not unique to Java. Rust, HNs darling baby, is praised on this forum every day for checked errors. Swift, F# and countless other languages have checked errors.
There is nothing wrong with checked exceptions and there is really no difference between a Result and a function with a checked exception.
The issue is not with checked exceptions but with Java’s syntax. They have not given programmers the language syntax to easily deal with checked exceptions. You cannot easily escape them without boilerplate and they don’t work correctly across lambdas. This is why Rust ships with ?; Swift has shipped with try!, try?; and Scala ships with try as an expression and is experimenting with checked exceptions that work across high order functions [0].Programmers across every language ecosystem are moving towards checked errors and away from unchecked runtime crashes. Even Kotlin, a language that shipped with unchecked errors, is planning on adding a form of checked error handling [1].
[0] https://docs.scala-lang.org/scala3/reference/experimental/ca... [1] https://youtrack.jetbrains.com/issue/KT-68296
> `java hello.java` is in the 360-390 ms range.
That's still pretty negligible. I don't think he's making a strict comparison, just saying that in both cases it's barely noticeable.
> JTaccuino
Shame the link is broken, looked fascinating!
https://github.com/jtaccuino/jtaccuino
After years of Python and TypeScript, I've started using Java as my default for everything. It's just so much more productive. The ancient developer memes that Java is slow and clunky don't apply anymore.
Postgres also had a long-held reputation for being slow and difficult, but it made incremental improvements for decades and now it's the default choice for databases.
I see Java in the exact same position, as the Postgres of languages.
The problem with Java since Java 8 has never been Java. It's been about the appalling ecosystem that infected it with reflection frameworks. It was bonkers that "POJO" was ever a thing that had to be defined.
It feels like these frameworks are now just falling away, which is great. I'm not even hearing about Spring anymore, and if there is any reason to not use it, it would be this cringe "how do you do fellow kids?" blurb I just saw on their front page:
> Level up your Java™ code
> With Spring Boot in your app, just a few lines of code is all you need to start building services like a boss.
I personally would reach for Go by default, but I have no ill-will to Java.
Spring boot is itself also very different than Spring, so depending on what was your last experience with these frameworks, you might be surprised.
Given, they are still quite reflection-heavy and full of POJOs and annotations, it supports compile-time resolution for many things now.
Also, you would be hard-pressed to find a more productive developer than a well-versed Spring boot guru for typical backend jobs. You might dislike the framework, but credit where it's due, it is a workhorse and the amount of time someone makes a POC, you can make it with spring properly, in a way that you can build your prod app on top. Like, it's easily as productive as RoR and similar.
Serious question - what could Spring Boot give me for POC/prototyping that Javalin or Micronaut couldn't? I really struggle to understand why most of Java shops out there have set themselves on the Boot path. Is it technology-based decision or politics?
Boot has an "app" (err, lib) for everything. It's fully featured, and highly opinionated.
Pretty much any modern computing problem you have, Boot has you covered[1].
So while you may not have ever used a Streaming library before, if you know Boot, then the Spring Boot Streaming library will already be familiar.
[1] https://spring.io/projects/spring-boot
I’m not familiar with either of those frameworks so can’t comment on them specifically, but 10+ years ago not using spring/boot entailed glueing together a lot of disparate libraries to do things spring boot had built in. Spring boot includes pretty much everything. Plus reliability, battle tested, easy to hire people with experience using it.
Spring Data can be ridiculously productive. It is definitely in the magic category, but you can just have an interface with methods named in a specific way, and then just call that from a controller class, and you have a REST API that calls into your DB for any of the usual CRUD queries immediately. And it's not even just a hack that works on the easy case, you can annotate it with complex hand-written SQL (both db-independent, but feel free to use native queries), and easily extend them with real implementations if you need more control.
I choose Spring Boot anytime I have to create a service, just for that reason.
The eco system is a huge force multiplier, and is constantly evolving. Spring Boot simplifies that a lot by simplifying the setup to the point where you often only have to add a single dependency, and everything is configured with default configurations (which you can change if necessary of course)
Just look at all the different projects under the Spring banner: https://spring.io/projects
One example is the new Spring AI, where they have abstracted a lot of functionality and you can change providers (OpenAI, Mistral, AWS, Azure, Google) easily.
The ecosystem factor is real. I was looking at implementing the SCIM protocol [0] in our user management app, and if we were on Spring I could have just added an Apache boot-starter dependency and been done in a few days. However my company uses the Play framework and it’s going to take me weeks to implement it by hand.
[0] https://scim.cloud
If I ever found a startup I will mandate that we use Spring Boot (java/kotlin) until we grow big enough to afford the extra overhead with other frameworks. Spring Boot is truly a Get Stuff Done framework
It would be hard to convince some people of this, because "everyone knows Spring is enterprise". Unfortunately many only have experience with legacy companies, bad code or through reading old blog articles, and base their opinions on that.
It's actually something you need to have experienced yourself to recognize the possibilities.
It may sound strange, but I enjoy going through the long list of projects in the Spring web site. I almost always find something cool and is surprised at how much they can simplify complex functionality
The learning threshold is also relatively low, because they tend to use common best practices in the framework.
I’ve spoken about this before but choosingg Play and not Spring has been my companies biggest mistake. It has lead us down so many half documented custom solutions that would have just worked out of the box if we had chosen the more popular stack.
I like Kotlin, but the lack of checked exceptions really kills it for me. Exception safety is a bigger thing for me than null safety.
It‘s established patterns. Javelin or Micronaut are probably great, but not a lot of people understands how to build a real project with them. With Spring Boot you don’t even think about it.
> The problem with Java since Java 8
I agree with the sentiment, but I'd move up to a version with type inference at least. I have nothing against static types and in fact in a vacuum I prefer them, but the particular brand of OO and "weak" generics that Java and C# have feels like filling forms in triplicate. "var" alleviates that significantly.
Any competent Java IDE automatically generates you a variable declaration from the expression you want to assign to it. It’s actually less keystrokes than having to type “var”. Unless you only use a simple editor that doesn’t perform static analysis, less typing is not a good excuse for using “var”.
Conversely, in Java you often use the diamond operator like in:
(Half of which, again, is code completion in your IDE.)That doesn’t work with “var”. You’d have to write:
while losing the ability to constrain your variable to a narrower type.The loss of directly seeing the type information in statements of the form
should meet a very high bar, IMO.I've found var preferable when reading code.
I've wrought my hands over this and eventually came to the conclusion that developers in every language that started with local type inference have embraced it. So I've mostly ignored my concerns and gone with "var for everything".
If you have trouble with a type then rename the function or variable (AKA your "foo" and "bar" example. The names are bad, fix them. Context matters, we have none here. No one has an editor that displays a single line of code at a time.). But in general we haven't had issues on our projects.
Beyond that, my IDE (Intellij) makes it easy to uncover the inferred type if I really need to be 100.00% sure what it is. In general I don't, because in general I don't care, and even before 'var' I was generally ignoring the type anyways.
I think there is a tasteful way to selectively use var where it makes sense.
Even in your example, narrowing the variable doesn't make much sense - you are in a local scope, you control everything. Narrowing is more meaningful in fields, where var won't work by design.
Locally a well-named variable is often significantly more important than some long, somewhat useless type signature, especially when the right hand side makes it clear.
Generics in Java and C# are _vastly_ different.
.NET has true generics with full type information and struct type parameter monomorphization which works the same way generics do in Rust.
Edit: C# does have type inference for generics, just not the full HM one. It is also quite more capable for lambdas which is a bit frustrating because it does not resolve nested generics otherwise. I miss it - F# does it the way it always should have been.
There are many other differences small and big that arise from the way Java does generics and the fact that primitives can't participate - you will never see `IntStream` kind of workarounds in C#. Some libraries abuse and misuse generics for no profit (looking at you Azure SDK), but it's not as widespread. Shallow generic types are always inferred from arguments.
> you will never see `IntStream` kind of workarounds in C#.
You may not see that in Java in the future either. Java will have value objects from the Valhalla project, and part of the plan is to replace Integer with a value object. Then there will be less of a reason to use raw int primitives, because the JVM can treat value objects much more efficiently than normal objects.
The difference is that C# has been designed with proper generics since version 2 (pushed by F# research group with Don Syme) and is now on version 13. At the same time, structs have been present since version 1.
All APIs and collection types build on this foundation, with standard library leveraging generic monomorhpization for zero-cost abstractions. Code that would have needed C++ implementation in the past is naturally expressed in pure C#. Generics are fully integrated into the type system at IL level, avoiding special-cased types or bespoke compiler handling (besides monomorphization).
This enables numerous zero-cost features: tuples (unnamed/named), structs with controlled mutability and record structs, pattern matching, stack buffers that do not rely on escape analysis, structs with byref pointers for slice types (Span<T> and friends) which a good two thirds of the standard library accepts. Numeric and vector primitives are used in a simple way without setup requirements like Panama Vectors.
While project Valhalla will get Java's foot in the door in some domains, it will remain a less optimal choice than C#, C++, Rust, etc. Java's evolution primarily serves its ecosystem's needs, whereas C# benefits from being a newer language that learned from both Java and C++ and got influenced by F# and other research projects inside MS. This makes C# more idiomatic and terse. The historical drawbacks - platform lock-in, being closed-source, and having relatively weak compiler - have been resolved over the past ~9 years.
yes, this is true. I was talking more about the UX of how to use them, which in both cases is quite painful without type inference.
Ahhhhh, yes. Java itself isn't bad and has been getting better. The frameworks made me want to scream.
which ones specifically? I like Spring Boot tbh
Hibernate mostly. Spring too, but it has been a while. Mostly I just find the abstraction isn't worth it .
I find pretty much every ORM in any language is problematic. The best you can hope for is a certain amount of internal design consistency, so that even if you can't do what you want it's at least clear what you are doing.
Since I had the "joy" to use TypeORM (node.js stuff), I really value Hibernate, although there are some little corner cases I'd like to be better. But ORMs solve a really hard problem and I haven't seen anything better than Hibernate so far (and don't come up with JOOQ or MyBatis!).
The way you write this makes be think you rawdog'd Hibernate and Spring Framework. Don't do that... you will hate yourself.
Boot is something entirely different. You write very little code and get a ton done. The trade off is you are firmly now a "Boot" codebase, but once you learn how Boot works it's not a big deal.
I've had to maintain a couple Spring Boot apps and I absolutely cannot stand them: they pull in a whole pile of random dependencies and do weird things to your build with little explanation. Then, all the functionality involves a ton of classpath scanning and annotation-based DI that makes it hard to figure out how all the things fit together.
I mean, have you learnt the framework before attempting to do that?
Frameworks are frameworks, not libraries. You can't just start writing/understanding them - frameworks are different from libraries precisely because they call your code, not the reverse.
Yes, the problem is exactly that the framework calls your code instead of being called by you code.
That's how all frameworks work though. This is not a criticism of Spring Boot. You are criticizing the use of a framework at all.
Some people hate to write the same basic things over and over. That's where a framework excels. Write only the glue/logic you need to make your app work.
The way you have described your experience with Spring Boot seems to imply you did not take the time to learn it at all, and therefore its' unsurprising to us you had a hard time of it.
Not writing the same thing over and over again is a feature of abstraction. A framework is a user-hostile way to abstract because it makes the source code opaque to developers. There's no reason why a library-based approach has to be more repetitive than frameworks.
Right, so you cobble together 48 different libraries, wrangle all of their configurations and stitch them all together yourself.
You do this 18 times because you have 18 different apps with similar requirements but deal with different data/endpoints/whatever.
On your 19th app you decide to standardize how you cobble together all of these libraries so that you don't have to start at ground zero every single time.
Now you've invented a framework.
CRUD backends have well-understood requirements, in a way, a significant part of the work is done for you. You are there to customize certain parts only.
How else would you architect with this in mind? Given that literally every other framework is quite similar (RoR, PHP's solutions, etc).
There is another niche, the HTTP server libraries, but they are much more low level.
That's a great explanation. Thanks for that.
I was working with legacy code bases where the original Devs loved frameworks. Massive performance problems and nobody could understand how it all fit together at runtime.
Hibernate eventually got entirely nuked, Spring we couldn't entirely unwind easily; it had caused a whole bunch of crappy architectural issues and was too much effort to rewrite from scratch.
Although the code looked simpler using the frameworks and annotations, it was actually a huge rotten mess that didn't work well at all, with workarounds for all kinds of weird things in it.
First time I hear those claims about Postgres. Was that the sentiment 30 years ago?
If say it was only about 15 years ago that the Postgres is slow stuff started dying off. Right around 2010.
It was definitely initially seen as big and cumbersome compared to MySQL, but sentiment shifted.
MySQL was kind of a fast toy database in the early days, but it was good enough to power simple PHP based websites.
14 years or less. Any version of Postgres before 9.0 was a nightmare to administer as a real production transactional dbms, at least the off the shelf version without a whole lot of tweaking without a (then very rare) pg expert.
I don't remember that. But I remember having basic DB features like transactions while MySQL didn't.
what parts do you enjoy now ?
I do agree that new ecosystems (js for instance) makes you miss some of the old big languages development feel. less churn
I started with Java 1.0 and thought it was great. I still do, although I haven't used it since 2020.
The only thing I don't like is how there is no built-in JSON package which seems like a necessity these days.
Removing the public static void main(String[] args) business seems like pandering to a non-existent audience, or at least a miniscule and insignificant one. Anyone who is going to use Java for a real project is not going to be worried about that, and anyone who thinks that's too difficult to deal with is never going to be a good programmer anyway.
The last part seems like a very 'gate keepy' point of view.
If you want to introduce someone to programming, you probably don't want them to worry about what all those 'magic words' do.
At least for their first steps, they won't need to know what a class is or what `public` and `static` mean.
I think gp is right, and I don't think it's gatekeeping. I thought this about Java, it was easy to criticize its verbosity, but I realized how insignificant this is when actually practicing it. There's probably way more interesting and deeper criticism to make about Java.
About the first steps of a newcomer, there's always going to be some level of "don't worry about this now, we'll see what this means later" for any language. I remember this to be the case for every tutorial I read to learn a language. And it's fine, as long as you can try stuff and it doesn't get in the way.
I'd say it's more important for a language and its vocabulary to be well structured and well documented for a newcomer and Java does quite good on this front.
GP is right. If the words public static keep you from learning how to program you were never going to learn anyway. If I introduce someone to soccer and they quit because they couldn't figure out how to put their shoes on, chances are they werent going to learn how to play no matter what.
I think the converse(?) to this though is that the words public static are inconsequential.
Sure, if you are incapable of learning what a couple adjectives mean you won't go far, but that holds for much more than software.
Rather it's not important that the ball is big and blue so much as that you can kick it across the field - learning what the ball means can come later, but it's just unimportant noise (to start).
Java is pretty bad at this, though, insisting on specifying unimportant details up front rather than allowing for qualification. This is OK for a large monolithic application with complex requirements and a litany of edge cases, but inappropriate for many smaller use cases.
Did you know that the first thing John Wooden did with incoming freshman collegiate basketball players at the most prestigious program in the country was teach them how to properly put on their socks?
Agreed.
I still feel like the author is missing the forest for the trees. Bash is not great to write e.g. a red black tree in or do complex image processing, but you don't have to maintain a Java install, download libraries, or setup an editor with an LSP (and really, calling java script.java 10 asdf? Why do I need to invoke Java at all? At that point, I'm probably going to wrap it in a script anyways...)
Python has its own issues but it's a small, embedded install that you don't have to accept 5 different licenses for and worry about it being able to get the same version...
And bash? That's what pacman -S jq is for - anything I can't easily do in bash or batch I just offload to a small utility written in python or rust.
Java is, at it's core, just too heavy, IMO.
pacman -S jdk-openjdk, this is the reference java implementation and it has the exact same license as the Linux kernel. And java has never done such a breaking change as python did.
There is also jre-openjdk-headless, for 140 MB. How is that any different than Python?
This "java too heavy" is like 30 years out of date, if it has ever been true.
Quick check via msys2:
"error: target not found: jdk-openjdk"
> And java has never done such a breaking change as python did.
I'm not really sure that's true? Java 8 to afterwards, there are breaking changes that mean a lot of old enterprise stuff can't easily move forward, or worse, bytecode incompatibilities mean source-code-less stuff can't be used anymore...
The whole thing about Graal is mentioned almost as an afterthought, my point is that the language etc. is so poorly designed as to be prohibitive to interface with unless...well you're on Java. Yes there are bridges etc, but a big point of the shell, bash, etc. is easy interoperability between many programs, etc.
Java is still today stuck in a mentality of "it doesn't exist if its not in Java", which is why yes, 30 years later, it is still "too heavy". Assuming you are the effective Operating System is an extremely heavy assumption.
> and it has the exact same license as the Linux kernel
Also, I neglected to touch on this point more, perhaps license is not the right word, as much as distribution - I don't know if you have ever tried building the JDK (not simple), or worked with the official JDK vs the open one (not the same functionality), or tried to access old versions of the SDK on the official websites, or had to deal with their installers, etc.
Giant headache and all around.
Not to mention, even if your pacman command works, this is still simply not comparable, the example I used was for installing a jq binary because JRE simply doesn't include this functionality by default...
And now you need the overweight pom/gradle mess to interface with the Java libraries because <insert technical debt reasons here>
The official JDK is the OpenJDK for several years.
Under Sun there were differences, but Oracle open-sourced every last difference and now there is only some Oracle branding logo as the only difference (and maybe some tiny proprietary codec, but your code will run on both the same way).
Some people write programs that do more than simple scripts
> If you want to introduce someone to programming, you probably don't want them to worry about what all those 'magic words' do.
I learned Java when i was 15 or 16, reading some random book first and then I stole 35 euros from my mother's purse and bought a copy of "Java how to program" by deitel and deitel[1]. The recommended version at the time was Java 5, and the SJCP certification was still issued by Sun Microsystems.
I can tell you, "public static void main" is not going to be the problem.
[1]: looking back (i'm in my 30ies now) sometimes I wonder if i would have been better off buying weed or alcohol (or both)
So you believe that your experience is a universal one for all learners? Cognitive load is a real consideration in teaching, and having to ignore and filter text is challenging for some folks.
The boilerplate around a main function is 10 units of load. Everything else you have to know to write a simple program is 500-600 units of load. The boilerplate is a rounding error, and just does not matter.
Yes, all people are different, and some are smarter than others. Education funding won’t change that.
Way to disparage a random person on the internet.
Mostly yes. Given how much there is to learn, public static void main is not going to be “the problem”.
As mongol said in a top-level comment, Java isn't a great language for programming "in the small". It's not a great language for a casual user - or rather, there are languages that are significantly better. If you want to introduce someone to programming in a semester course, "public static void main" isn't going to be a significant hurdle. If you want to introduce someone to programming as a 14-year-old, maybe don't start with Java.
Why? Java is a small language with not many concepts, that is typed so many of your first attempts will be caught with helpful messages at compile time, and it fails safely at runtime with error messages pointing to an exact line number. Besides, it is a language that is actually used in the industry and has one of the biggest ecosystems. It's also plenty performant, can be low-level enough for learning about all kinds of data structures/algorithms, including concurrent ones and has probably the best tooling (IDE, debugger, etc)
What other language would you start with?
And isn't it easier to introduce concepts one at a time? For that reason implicit classes makes sense, and also for the occasional scripting, as in doing something one-off, but it is not as trivial that I can do it with piping 3 commands together.
> Java is a small language with not many concepts
True. But you need to know too many of them to get anything to run.
> that is typed so many of your first attempts will be caught with helpful messages at compile time
But it doesn't feel that way. It doesn't feel "helpful", it feels nitpicky. It feels like I have to get everything exactly right before anything will run. For a raw beginner, that's very frustrating. It is (emotionally) better to have something run as far as it can run, and then crash. (I agree that exceptions pointing to the line number are very nice.)
Again, for a semester class, the startup overhead for learning Java is too small to worry about - it's maybe a day or two. But for someone on their own, not in a class, a day or two is a huge investment to put in before they can get something running!
What would I start with? Something with a REPL. (It could be a language that is normally compiled, but there needs to be a REPL. Raw beginners need to be able to get something, anything, to work as quickly as possible.)
Yeah, you want to start with basic imperative programming with as little cargo-cult nonsense in the way as possible.
Your csci 101 kids will not benefit from unpacking what it means to compile an object-oriented language down to bytecode to run on a virtual machine. It's not that it's not valuable knowledge, they just won't have the context to make meaningful heads or tails of it.
related: I still puke a little remembering the requirement that students work on assignments in emacs on terminal machines rather than their text processor of choice(which was fine for me, but why on god's green earth would you put usability warcrimes like 'hjkl' in the way of someone just starting to learn? No wonder nobody in the early naughts wanted to learn to program...).
A lot of people learn to program from declarative languages like spread sheets. We should all be happy we have access to defective versions of assembly at too high a level but be angry that we had to use too low level an editor?
You shouldn’t try any modern programming language until you’ve mastered GOTO and GOSUB and know why lines are numbered in multiples of 10.
I think this is a really important point, even though I suspect you're joking.
Along with all the BS boilerplate text this specific post talks about eliminating, which is great, we simply forget how much legacy tech BS we just assume.
Beginner programmers should not have to know what a "file" is, or what an "editor" is, or that they need an "editor" to "edit" a "file". This is technical debt: these are implementation details which should be invisible.
This goes double for "compilers" versus "interpreters" and "source code" versus "binary code". FFS the noun _code_ means _a system for rendered text unreadable_.
You have a computer. You are talking to it in words, words which resemble English because that is one of the simplest world languages when you think about scripts -- writing systems -- as well as sounds. Hangeul is easier but only works for Korean which is harder than English. Grammatically Chinese is simpler, but spoken Chinese has tones which are very hard, and written Chinese is insane. Typed Cyrillic is no harder but handwritten gets weird and complicated and Russian is much harder than English. And so on.
English wins and so we talk to computers mostly in English.
So, you have a computer, and you type on it in English. That is all you should need to know: how to enter text, how to correct it when you get it wrong, and that is it.
BASIC has a great virtue which all the Unix and even the Lisp fans forget:
It's designed to work at a command prompt. Type a command, the computer does it. Give it a number, it remembers it for later.
This is a profound and important metaphor. It eliminates all the 1960s/1970s legacy BS about "files" and "folders" and "editors" and "compilers". Beginners don't need that. Let them learn that later if they prove to have real aptitude and want to pursue this.
Type a bare expression, the computer does it. Number it, the computer remembers it for later. That is all you need to get writing software.
And just like Python sorted out the problem of spoiled whiny little baby C programmers whinging about their pathetic obsessions with indentation patterns by making indentation semantic so everyone has to comply, line numbers in BASIC are a vital simplifying unifying mechanism, so lean on them: for beginners, make line numbers syntactic.
Don't force kids to learn pro tools like nomenclature and hierarchies. Give them a toy that they can get started with.
At first, they can structure their programs with line numbers, and they learn about leaving space, about RENUMBER commands, about LIST x TO y ranges, and stuff like that, because we are not using a bloody Commodore 64 any more.
But give them IF...THEN...ELSE and WHILE...WEND and REPEAT...UNTIL and named procedures so they can learn structure and not GOTO.
All the rest is baggage and should be deferred as late as reasonably possible.
The reason we use the words "code" and "coding" how's the history behind it. Originally it referred to translating an algorithm description into machine code. Indeed something less readable. The first compilers were called "automatic coders". I.e. their input wasn't code, their output was. Originally the word "compiling" referred to putting together an image of library functions. Something more similar to linking. The terminology shifted and drifted as time went on.
Emacs doesn't use 'hjkl'. I think you weren't there in the 90's...
Evil mode ;-)
I'm of two minds about it. On the one hand, the verbosity is arguably noise if you are first learning to program, or just want to write something quick, so I can see why people dislike it.
On the other hand, viability, classes, and "staticness" are all fundamental structural concepts in Java. Hiding them for a special case is sort of like lying, and, in the long term, I can actually see this special case causing more confusion for new learners. It's sometimes better to be upfront and transparent and force your users to work with the paradigm and semantics they chose to adopt, rather than pretend it doesn't exist. If Java had been designed to allow for top-level functions from the start, it'd be a different story. I think special casing is a generally bad way to evolve a programming language.
But there is absolutely no hiding, the design is very smart!
You simply get an unnamed implicit class like `class Tmp367 {` written at the top, and the runtime loader has been modified to be more accepting of main methods. There was basically a tiny language change, and no bytecode change, java semantics are just like they always were.
The Main loader just simply will accept an instance method named 'main' for a class with an empty constructor with no args, instead of psvm.
It's hidden from the newbie who never used java before, that's what we're talking about. As you said, all that stuff is done implicitly. The user never sees any of that unless they go digging.
> Removing the public static void main(String[] args) business seems like pandering to a non-existent audience, or at least a miniscule and insignificant one.
Perhaps the audience doesn't exist because of that business. There are many times when I would have used Java over Python to write simple programs for no other reason than having the ability to create a GUI without resorting to third-party libraries. Yeah, Python has tk but it has never clicked with me in the sense that Swing does. Unfortunately, cramming every last thing into an OOP model means that simplicity is rapidly lost. (The same can be said of Python, except Python has not forced it. Java, historically did.)
I agree that for experienced programmers working on large projects a little bit of verbosity around `main` is insignificant. But first impressions matter, especially when there is pervasive word of mouth about "verbosity".
Pretend you are a college student and you are taking your first programming class (e.g. CS 1) and your friends have told you that Java is "verbose". You start with "hello world" and you have to type `public static void` etc. One of your friends shows you the same code as a Python 1-liner.
Or similarly you're a beginning programmer in the workforce and your employer asks you to solve a problem using Java. You've heard Java is verbose and when you start with "hello world" you find that what you heard was true.
This is not a non-existent/minuscule audience. They should have fixed this decades ago. Better late than never.
I've been impressed with the modernization of Java over the last 10+ years. Simplifying "hello world" is a minor change relative to the others, but still an important one.
> But first impressions matter, especially when there is pervasive word of mouth about "verbosity"
I watched most of my comp sci 101/102/201 classmates fail out because they didn’t want to understand how things worked, they just wanted to make a lot of money.
Edit: hn even helped me prove the point: https://news.ycombinator.com/item?id=42457515
Teach that to a 10 year old, where their primary keyboard experience is a phone.
More than that, if rust is the future, which I have seen espoused before, picking on the Java keywords and syntax is highly amusing.
I did cut my teeth on Java back in middle school-ish. It never bothered me at all, I was too busy having fun learning to program. I agree with GP, the mandatory class is a completely overblown complaint about Java.
If you think Java boilerplate is bad, wait until you have to learn about ELF file formats in order to execute almost everything else.
The problem is when java is used as a first language, this is needless complexity that gets in the way of the actual fundamentals of programming. Access privileges are irrelevant for a beginner who is hopefully not writing libraries that will be consumed by anyone. The distinction between an instance and static method is also confusing and irrelevant. It's just pointless ritual for the student.
Now, if one is learning java as a second language, that's a different story.
javax.json has been around since Java 7.
It's part of Java/Jakarta EE.
https://jakarta.ee/specifications/platform/10/apidocs/jakart...
> It's part of Java/Jakarta EE.
If it's part of J2EE, it's in practice "part of Java" since the JDK comes with the J2EE packages built-in...
That is, it came with the J2EE packages built-in, until Java 11 decided to break everything, and force people to get each piece of J2EE from a separate upstream project, with AFAIK no "all of J2EE" uber-jar you could simply copy into your project to restore the status quo. It's no wonder so many projects are to this day stuck on Java 8.
> since the JDK comes with the J2EE packages built-in...
Are you sure about that? I just downloaded the Java 8 JDK, and javax.json is not there. And the documentation doesn't mention it either. What am I missing?
The JDK doesn't include all the J2EE goodness: https://docs.oracle.com/javaee/7/api/javax/json/package-summ...
Remembers me the “Java for Everything” blog post: https://www.teamten.com/lawrence/writings/java-for-everythin...
Also, thank you for sharing! I do appreciate Java and I’m glad to see it can be used for scripting nowadays.
Still long way to go before it reaches .NET levels of productivity (especially so if you use F# for scripting, but C# is no slouch either) :P
Depends on the platform, some don't have any .NET productivity available. :)
These platforms are not being considered here nor have much relevance anymore. You cannot seriously claim this unless you don't use the tooling side by side at all.
The difference is so stark it's no longer amusing. Performing setup for complex Java projects has me go through similar steps as if they were written in C++. Performing setup for C# projects of comparable complexity usually requires just cloning and hitting 'dotnet run', much like it usually happens with Rust or Go (some may claim I undeservedly bash it but credit is where credit is due).
Like my phone, or those used by US military?
I love your identity with .NET ecosystem, to the point of nothing else being in the way.
See latest JetBrains Developer Surrey about platforms.
.NET is a great ecosystem, but lets be real where it stands outside Microsoft shops, across everything that has a CPU on them, and the various ecosystem where sadly it doesn't even get a tier 1 support.
Programming languages are tools, a toolbox has space for plenty of them.
Are you comparing "complex Java projects" against just ordinary "C# projects"? Because ordinary Java projects will also be "./gradlew run" or the Maven equivalent, that's nothing special.
And in many other situations "./gradlew run" just doesn't work. Hell, Gradle does not even let you quickly scaffold it without pre-existing knowledge and community advice! (if you want to avoid pitfalls and have the best "streamlined" experience) Something that is not an issue in Rust or Go. Maven is even worse.
Meanwhile I can 'git clone https://github.com/ryujinx-mirror/ryujinx && cd ryujinx/src/Ryujinx && dotnet run -c Release' and it works on the first attempt (though takes a moment to pull nuget packages, it's a big project).
The Java ecosystem has incredible projects from the technical point of view (GC implementations, OpenJDK's JIT compiler), but the tooling and application packaging and distribution seem like the painful parts.
It usually works in my experience, since the toolchains feature was added, as that takes the Java version mostly out of the equation.
There is "gradle init" to scaffold a project, or of course IDEs offer a GUI over that.
Additionally, your "dotnet run" does require the dotnet tool to be installed and of the right version. The Gradle/Maven equivalents now no longer do, because they bundle scripts into your repository that will download and run the build tool itself of the right version. They just need some moderately modern Java installed. Everything the project needs including possibly a newer Java will then be downloaded.
I'm not sure what the point of naming individual projects is. I can point at dozens of projects off the top of my head where you can just clone and run them without incident.
There are painful parts of both Gradle and Maven. Absolutely. They are very far from perfect build systems. But this is partly because they do a lot more than tools like cargo does.
> "dotnet run" does require the dotnet tool to be installed and of the right version
It only needs an SDK installed on the system. If you have the necessary framework dependency, it will just work. If it's missing - the command output will specify this, which is solved by doing `sudo apt install dotnet-sdk-{version}` or just `dotnet-runtime-{version}` (because newer SDKs can build most older targets). You can also usually roll-forward the applications without retargeting them or installing older runtime (which is trivial still). It's a reliable and streamlined process.
Probably one of the best approaches to managing the SDK and framework dependencies that does not rely on any form of help from external tooling or IDEs.
Gradle and Maven need JDK installed in either case. I had Gradle that shipped with the code crash on me because it was sufficiently old to have issues on newer OpenJDK versions. Solved it by installing properly, but you can see how it can be an error-prone process.
------------------------------------
Ultimately, if you're an expert and it's a long-term project - none of this matters, solving odd breaks and tooling issues is part of the job. It's nice when things work, it's not unexpected when they don't. Some languages have more of this and some less, but at the end of the day due to business constraints and company environment none of this is a showstopper per se - you just deal with it.
Do I think the CLI tooling, dependency management, packaging and distribution is painful in Java or Kotlin? Yes, it's what also precludes either from being productive scripting languages unless you have nailed the setup that works around all of these. Does it matter for writing complex applications? Not really, project and environment setup for such is mostly one-time thing. It's coincidentally where Java ecosystem shows its strength. My rant here is posted because I believe we can discuss pros and cons without stating that specific issues don't exist when they do or vice versa.
Among everything I tried Cargo, .NET CLI and Go had the smoothest experience of things mostly working and when they weren't - not requiring to dig through heaps of documentation, possibly dumped into a language model to catch the exact specific piece that would help to solve the puzzle. I heard good things about Python's uv. If actively maintained, Node.js projects also work reliably, not so much when they aren't though. Some C++ projects are kind enough to offer build scripting that works out of box on Unix systems - props to the maintainers, which is also the case with Java projects. But whenever either of the last two didn't work, it often took me the most effort and swearing to get either working, unlike other languages.
I don't share your experience. I have no problems importing a Maven project
I’d be curious to hear more about how you’re using F#? I’ve previously used Python for scripting, but just started developing for a company pretty deeply entrenched in .NET. Currently they’re migrating a lot from VB to C#, but I’ve missed having a handy scripting language like Python for small tools or test applications. Do you think F# could fill that roll?
Powershell is probably best fit for that role. You have to learn a new scripting language but since it runs on .Net you can actually bring in .Net Classes if you need a little more power.
https://learn.microsoft.com/en-us/powershell/module/microsof...
It should be able to! F# has "gradual typing" and full type inference which means you often do not need to specify the types at all, and it also happens to be whitespace-sensitive language much like Python is. Both of these aspects should make it feel quite familiar while also giving you full advantages of static typing.
One thing to note is I find `dotnet fsi {some script name}.fsx` taking more time to start than ideal - up to 800ms is just too much, normal .NET applications usually start in a fraction of this.
I recently posted a submission here for "FSharpPacker" written by my friend that lets you compile F# scripts to standalone applications (either runtime-dependent, self-contained or fully native binaries, much like Go), it also has some comments on getting the best mileage out of it: https://news.ycombinator.com/item?id=42304835
Probably the best feature that also comes with scripting (both C# and F#) is "inline" nuget references e.g. #r "nuget: FSharp.Control.TaskSeq" which will automatically pull the dependency from nuget without ever dealing with manually installing it or tinkering with build system in any other way.
Some additional links:
https://learn.microsoft.com/en-us/dotnet/fsharp/tools/fsharp...
https://github.com/dotnet-script/dotnet-script (C# is also a quite productive language for scripting and small programs because of top-level statements, record types, pattern matching and many other functional features though perhaps not as strongly represented as in F#, it's just very unfortunately abused in enterprise world, with teams often going out of their way to make the code far more bloated than necessary, against the language design intentions)
https://github.com/waf/CSharpRepl
F# is a leap if it's your first functional / ML style language (but worthwhile). Modern C# is good for small tools and scripting, there is the dotnet-script tool for running single .csx files
> Java has many features that make it well suited for large, long-lasting projects
Already disagree, haha
There are reasons to not like Java, but this isn't one of them. Java is a fantastic language for large long lasting projects. I can't think of a more suited language for large long lasting projects in fact.
I'll bite. Explain, please.
[flagged]
Why?
Because it's a powerhouse, has top notch performance, observability, a vast ecosystem and developer pool, etc. Root commenter is just a troll.
Agree: why? I don't know a single reason, and I've worked on Java docs in the Java industry. This was literally my day job for a while, and I have no clue what they're trying to hint at here.
I remember reading horstmann's books in college and it doesn't surprise me at all that java is the hammer he reaches for given a particular nail.
I have to say I find it an odd choice for small replacements for bash scripts. I think python or golang are probably better choices for that purpose. Java is one of those 'enterprise' backend languages which lend itself to making a team productive over making the individual productive, and I say this as a java / go dev who's most comfortable with java for most backend work.
Dr. Horstmann was my advisor in college, San Jose State.
I just loved his lectures, very dry sense of humor, and extremely funny.
He was just getting started writing books in the early 90s. He has this awesome way of thinking about programming, that I imparted to my own students when it came my turn to teach programming. I wish there some videos of his classes that I could go back to and share with people.
Good website also, https://horstmann.com/
The picture on the website with him in the row boat has a funny story with it. When asked why he is in a row boat, he would reply, "Students are in the row boat with me, learning to program. At some point I push them out of the boat into the eel infested lake. The ones who are clever enough to make it back to the shore will be good programmers." All of this said with a faint hint of a German accent and a sly smile.
If you happen to read this, Dr. Horstman. I made it to shore. Thanks! It has been an awesome journey!
That's an interesting experience. I also had him as a professor a while back and he was awful. He was always looking at his phone or computer especially during project presentations and completely ignoring what anyone said. He also didn't seem to know much course material in general.
What I remember most is his obsession with Emacs.
There was one time that I was grateful though, I had to buy a few of his books and one of them had a defect from the printer, so he helped me get a new copy from the publisher for free.
> lend itself to making a team productive over making the individual productive
That's a really insightful way of presenting this. I think if that's how I'd been introduced to Java I might even have tolerated it.