Hacker News new | past | comments | ask | show | jobs | submit login
Paving the on-ramp to Java (openjdk.org)
159 points by denisw on Sept 29, 2022 | hide | past | favorite | 152 comments

I am amused by this:

> Worse, the early exposure to static methods will turn out to be a bad habit that must be later unlearned.

I have been using Java since 1.0 and it is my default language. I am writing Java code today.

I write functions using ‘static’ all the time. I prefer that a function be static. It shows that it does not depend on state of the enclosing object. Using ‘static’ on methods is not a bad habit. Having static fields and state is to be avoided.

It's worth keeping in mind that the primary audience that the article discusses is not folks at your skill level.

By far one of the most common errors I see as a CS instructor is aggressive over-reliance on global state. Not global methods, but state. However, usually what causes the student to reach for global state is the fact that it can be accessed from a static method like main; In lieu of figuring out how to structure a class, they reach for the easier "just make the variable static" approach. Allowing the initialization sequence to call main on an instance via the class's zero-arg constructor seems like it would address the point.

I agree that this is a good way to simplify Java. I just don’t want newbie Java programmers told that static methods are bad because that is our only way to express pure functions and we need them to learn to default to immutable data and minimizing stateful objects.

That's a fair long term view, and a sense for this type of nuanced choice is one of the marks of a good programmer. Not all students have the experience to judge the tradeoff, and so my goal as an educator is to default them to the safer of the two options. In this example, it's usually trivial to just add the static keyword to an already pure method. In my experience it's considerably harder to refactor a series of stateful methods that were incorrectly declared static, since you usually also have to plumb the state object through all of the callers, and their callers, and their callers, etc...

Most IDE's and static analysis tools will actually provide a warning when an instance method could have been declared static.

Couldn't agree more.

Recently I tried another take on the same idea: using default methods in Interfaces. It has nice properties, as it enables ~mixins in a way not dissimilar to what Scala provides, and enables overriding (e.g. for tests). Give it a try, it's ... refreshing :)

I am also getting a lot of value from default methods in interfaces. Java really could benefit from a new syntax on top of the modern VM that favors immutability.

Like… kotlin?

Like Kotlin, but Kotlin also makes a lot of compromises to accommodate Java’s warts.

You can't really mock static methods though, so it makes testing harder.

Static methods should be treated like functions. Same input same output. Keep them small. If you find yourself needing to mock them they are doing too much.

But even here you’re testing the same code twice. One directly with the tests for the class the static methods live in and once accidentally when you test the class that includes the util class as a dependency. This situation can be fixed by replacing the static methods with Functions.

> But even here you’re testing the same code twice

So what? How is that a problem?

Because you have to address the separate permutations and concerns of multiple stack levels versus just declaring what you want the result to be from those additional stack levels. In essence, your "unit" test is a lie because your testing other units accidentally. I see a similar problems a lot with classes that have a small number of public methods which delegate to a larger stack of internal private calls. Unit tests are about testing the unit explicitly and forcing/contriving results of dependencies which you shouldn't be concerned with because those dependencies have been explicitly tested elsewhere. When these situations arise what you have are in memory integration tests, not unit tests.

As an example, suppose I have a static method that takes in an object and returns a result based on the state of that object. Now I have conditional logic in the unit under test that has a dependency on the result of that static method. In my unit test, I don't want to go to the trouble of creating the state of the object to force the delegate call to return a certain result that determines the execution flow, I want to just force that call to return the result I need.

This is an extremely bad habit, people over use it to make testing easier by mocking all over the place. They end up writing tests that are coupled to the implementation, and any small change breaks them. Please go through the trouble to create the state of your object that you want to test, don't just mock other dependencies, which dependencies your method uses or how it does it's job is no concern of the test at all.

I agree, mocks should not be used and if you remove mock dependencies it usually exposes glaring problems in the code organization and design because the tests become overly complex. The dependencies should be coded to an interface so that you can inject whatever implementation you want.

It's a good habit. The dependencies your method calls are externally facing, the same as it's API is.

Your method should follow a well rested interface for any touches that are outside of it:

* The input

* The return

* The side effects

You should be able to test that you've fixed a bug where you're calling a dependency 100 times instead of 1 time

> I don't want to go to the trouble of creating the state of the object to force the delegate call to return a certain result that determines the execution flow, I want to just force that call to return the result I need.

It certainly sounds like your stateful object is too complex. Simplify your state.

That will only work when your functions don't have side effects. So no database access, no network calls etc. You could work around that by passing in your database client as parameter but when you have a DAO class with lots of functions this quickly becomes tedious.

OP said:

> I write functions using ‘static’ all the time. I prefer that a function be static.

This, however, does not mean that all the functions should be static. IMHO, the more static stuff you can extract from your instance methods while keeping parameters count to max 3 (magic number that works for me), the better.

> You could work around that by passing in your database client as parameter

Yes this is the way. [1]

[1] https://en.wikipedia.org/wiki/Hexagonal_architecture_(softwa...

>like pure functions

Small nitpick :p

I think they meant "functions" in the mathematical sense.

Although due to the ambiguity of the word "function", your nitpick still has its place.

Yes, I definitely meant in the mathematical sense.

I would really like a way to express pure functions in Java. There would need to some way to mark objects as immutable at the language level.

I switched from being a C++ developer to Java a while back and the lack of const (i.e. immutability) annoys me.

It annoys me even more than no-one seems to care about it much, and most think final is good enough - you can still mutate final objects. In C++ all references are final (in a Java sense) since they can't be reseated, but const actually gets you immutability (modulo const_cast).

I just want everything to be immutable unless mutability is really truly needed!

Clojure may be what you want then, if you can stomach the lisp-y syntax.

Otherwise Scala is pretty pragmatic about it (val vs var).

In both cases you still get only shallow immutability, but if nigh every object itself is immutable, shallow immutability may not be so shallow.

> I just want everything to be immutable unless mutability is really truly needed

Have you tried Rust? It’s nice in this regard; variables are immutable unless explicitly declared mutable.

Yeah, and I really like Rust. Rust has its mistakes but having RAII and immutability by default goes a very long way.

But it's not terribly easy or even a good idea to switch from Java to Rust at a real company that has no time to rewrite libraries, has to hire developers, and so on.

What about Records and immutable Collections, Sets, Maps? Isn't it possible to achieve what you want with that?

Not really. In Java you can do all kinds of things with immutable wrappers, but the language doesn't help you and it doesn't come for free for your own types.

In C++ you can write a class with some methods that mutate, and some const methods that can't mutate anything, and you can pass around const references through which mutation is guaranteed to be impossible. This all comes for free.

This was added a couple of years ago https://docs.oracle.com/en/java/javase/17/docs/api/java.base....

I was thinking that would make it possible to create unmodifiable data objects using that (and not Object as base class).

Static methods are a pain for unit testing though. I have started avoiding them for that reason alone. For stateless objects, you can always have a singleton and invoke all the methods on that object giving effectively static behavior without the baggage.

They are hard to test only if they are not functions and access some global mutable state. In vast majority of cases static methods are functions and are easier to test than normal methods, because you do not need to initialize object.

I suspect that the issue that the GP is discussing is not that they are hard to unit test, but that they are hard to mock: If you don't want to call the static method, either some relatively unfortunate thing has to be done to your mocking classes, or you have to inject it, which is uncommon in this kind of situation in your typical Spring application.

Now, what I'd say to that kind of argument is that a good static method, which could really live on its own as a function out there in the world, is something simple enough you'd never want to mock, or is something that is so specific that you'd always want to inject. This is easier to understand if you are used to functional programming styles, and therefore also realize that automated dependency injection was a misstep. It's also easier to get if, like in Scala, there is no such thing as a static method, just classes that have only static methods, and classes that have none.

But until one has 'seen the light' of FP, static methods that are complicated and are not injected will be annoying when someone is trying to not call them in a unit test. Therefore, in some places, static methods are always avoided.

But Java has a solution for the “good static methods” you’ve described in the Function interface. I’d submit that a FooLib class with these static methods could be replaced with a foo.lib package with the corresponding Function implementations. This allows great flexibility with unit testing because you don’t even need a mocking framework. Instead, you just provide a dummy implementation of the Function the unit under test depends on.

In fact, dependence on mocking frameworks is a huge code smell because it covers over the common practice of not coding to an interface. If you want to expose poor design in a code base, remove the mocking decencies and see how hard it is to rewrite the tests.

If static method behaves like a function I see very little reason for mocking. Do people mock also calls to complex constructors (which are too static methods)?

I think some people put unit testing to the extreme by not wanting to test logic outside of the unit tested (which is not possible to do fully). They write additional code for mocks so their tests could be less useful but more "pure".

If they're not modifying or accessing some state, then the occasions that one would need to mock them is drastically cut down.

The thing I have been bitten by is when requirements change, and a static method used in 100 places no longer should be static.

> It shows that it does not depend on state of the enclosing object.

Fine, but that's not relevant to the subject of this document, the "first program", as it would make all fields static, too, and so methods would still be dependent on state. It will just make moving to the next step harder; as the article says: this is probably not the direction we want to go when we scale up from a handful of statements and declarations to a simple class — we probably want to start using classes as classes, not just as containers for static members.

They’re fine if they are self contained, which is your point, and which is the author’s point (and generally everyone else). He didn’t go into detail of the why, but that is east is usually meant.

A good clean programming have it is to wrap all of your stream()/lambdas chains in a static method that describes what they do. This make sure they don’t have access to the local scope and can mutate data.

So many creational design patterns rely on static factory methods too.

I'm not really sure what they're on about.

> Having static fields and state is to be avoided.

This is totally false. It will always depend on the use case.

State, while necessary, is the enemy of sanity. It should be kept to a minimum and be well marked as dangerous.

The change I would most like to see in a new Java syntax would be to make final and immutable the default. Force the programmer to make clear where state lives.

static should be removed from Java. static is bad, not always but because it can be misused and because it makes debugging more difficult and most important of all, it makes testing impossible and PowerMock is to be avoided at all costs.

Right, methods that call Math.min() are literally untestable without PowerMock.

I always love seeing Brian Goetz’s articles, they explain the problem succinctly and they do a great job of validating their approach, showing the shortcomings of alternatives.

I especially like the language team’s very conservative attitude, valuing backwards compatibility greatly. Here, they again solve most of the problem by simply changing the “program launcher” code, without any modifications/special casing done on the language level. For the rest, we will see. I think foregoing public requirements is a no-brainer, the rest may need a bit further thought based on further comments, here is the mailing list: https://mail.openjdk.org/pipermail/amber-spec-observers/2022...

Succinctly? You must be a Java programmer. This essay is at least 3-4x longer than I’d like.

As a (mostly) python and C++ programmer, Java feels like someone is afraid they’re speaking to an imbecile, so they constantly repeat themselves and over-explain every point.

CDI and modern Java are both great, extraordinarily productive, run an order of magnitude faster than dynamic languages like python JavaScript and ruby, and are succinct compared to the mass of footprint of dynamic languages on a system. Your program runs at near c++ speed and does not need a gaggle of various system chances to run.

Jvm developers also take backwards compatibility seriously which is a concern for companies that will be around for more than a year or two.

While I reach for Python for one-off and random small stuff, the lack of types, it’s lack of performance, and the significant spacing make it challenging to share code and maintain.

Java certainly has its place and the changes in this article certainly expand its footprint.

Yeah and when you ask a question to your C++ colleague in that mysterious legacy team, not even on the language, you're convinced they think they're speaking to an imbecile.

C++ devs seem to live in a bubble of self admiration, spending hours over engineering under-performing applications they never profile - after all, profiling is like debugging: it's for people who cant code :p

> over engineering under-performing applications they never profile

This is how you convince everyone they're speaking to an imbecile.

Java has made progress in reducing unnecessary incantations, and it's not more verbose than C++. On the other hand, Java has probably been the most successful language in history in maintaining large codebases over years (although I guess some would say that's not a good thing).

As a java developer I would challenge you to a code golf session. You supply two problems which demonstrate the conciseness of your preferred language and I'll supply two problems which demonstrate the conciseness of Java. Then we'll see which language can implement solutions in the least amount of lines or semicolons.

I prefer to use number of words, because I used to believe haskell and alia were shorter than a corresponding imperative program, but it turned out it just took up more horizontal space, and was roughly comparable in this metric.

Applause! However, it's not always about "saving keystrokes"... it's about lowering accidental complexity and making code easier to reason about by requiring less cognitive load on the reader.

Another example: the lack of literals for collections in Java. Imagine this:

  Map x = {"a": "A", "b": "B"}; // not Java

  Map x = new java.util.HashMap();
  x.put("a", "A");
  x.put("b", "B");
The first case is more declarative, and thus easier to grasp. In the second case you have to read, parse and "execute" line by line in your head... (Does ordering matter? Does line 3 depend on line 2?, etc).

When things like these compound, you end up with code-bases many times bigger and harder to grasp than in other languages.

Granted: you can write shitty code in any language, nothing will save you from that... But I think Java makes it harder to write simple and concise code, even to experienced coders.

Then there's also the "cultural" thing many already mentioned ("enterprise Java")... which is very real, but no JEP/JSR will fix that ;-).

Brian shows brilliance, even at "trivial" issues. I trust his judgement :-).

    var ages = Map.of("sarah",19, "dan",35);   //valid java

Nice, glad to see that added to the standard API (even if a bit too late).

Stressing the point: this welcomed improvement on the API is a lot more than "saving keystrokes".

Map.of came out in JDK 9. I think there are still a lot of companies using JDK 8.... uhg. Maybe OP is one of the poor souls stuck in JDK 8.

For those of us still using Java 8, ImmutableMap.of() (from Guava) is similar (and probably the inspiration for Map.of()), as long as you don't need your Map to be modified later.

We’re still using Java 8 in prod, because Java 9 broke our Hive version and a lot of other use cases for reflection (in return for modularizing stdlib, which we never cared about). I think some projects gave up on Java 9 as it neared EOL, and Java 11 compatibility still isn’t finished.

Nice touch using "var" to show that off too.

var and static must die.

Java is deliberately nudging you not to do things like that. It would rather you create a class with fields a and b -- which has its own verbosity, though they've improved it.

I find that I use structures like that primarily when interfacing with other loosely-typed languages, like Javascript, which do encourage you to create structures on the fly like that. Impedance mismatch is always a pain, no matter where it occurs.

That's not to say Java doesn't want you to use Maps. They do -- but only when the content is dynamic. They don't want to add literals for it because that's the opposite of their goal.

There are days when you want it anyway, and then you grumble that the language has forced you to do something ugly. But every language is a set of tradeoffs where some things are ugly, other things are pretty, and the language spec doesn't require a shelf of its own.

Unit tests very frequently need Map declarations like that.

Unit tests are exactly one of those situations I had in mind. Unit tests are always kind of painful that way: you need to construct things by hand, using interfaces that were designed for automated use.

The same fact is what makes unit tests so brittle. Ideally, you do as little of that as possible: you less fake stuff you make, the more robust the test will be.

There are days you can't, and that sucks. But the language is giving you a nudge in the direction of how to do that, by making it easier to construct actual POJOs than unstructured intermediate data.

I like the idea of simplifying the main method, but it's honestly not a huge barrier as a high school teacher. Most IDEs will fill in the declaration of main for you, and I think it's perfectly fine to tell students "we'll worry about that later, write your code in the braces" without diving in to all the details. To me, what would be more helpful is an easier way to read input from stdin during a program. I usually give my intro students a static class that wraps Scanner, but it's a bit of a pain.

The article briefly mentions a hypothetical static method "readln", which would help with reading input.

Agreed, and those "get to it later" aspects are a good tease that there really is more going on. Hiding them isn't necessarily better.

When i read the title, I thought they were finally going to fix gradle or improve maven. Those are the real on-ramps and they are a much bigger barrier to entry than main().

A cleaner/simpler alternative was Kobalt, but it’s now abandoned. A simple, official build tool would make the ecosystem easier to learn.


Maybe you will appreciate https://github.com/sormuras/bach/ ?

Thanks! That is very interesting.

They’ve had great tools along these lines in the Clojure world where the repl/shell has been around longer (deps.edn+repl and boot).

There are a bunch of different problems better described as "off-cliffs" than on-ramps; initial syntax / conceptual complexity is one, build systems are another. Not that other ecosystems are necessarily better. Modern web dev is pretty damn confusing for beginners as well. I helped an ex-girlfriend as she went through a coding bootcamp. Very little of the material stuck and it wasn't her fault. The whole stack is just far too complicated.

The biggest issue for teaching Java is IMHO not really the syntax of hello world - as Brian says, teachers can handwave this by saying they'll explain it later. It's worth improving mostly because it helps regular programmers who want to use Java in a scripting-like way. The more fundamental difficulty that newbies have is showing other people what they've done.

In the old JRE days it was easy - just send them a fat JAR that opens up a window with Swing and does some cool stuff. Your friends/parents/teachers/etc would probably have a JRE installed already and it'd all just work. In recent years they've abandoned the idea of a JRE and JARs as an app format. You can still open up a window with Swing or JavaFX and get scribbling, but now to show anyone what you did they want you to bundle the JVM and make a more 'native' style app with it. There's a lot to recommend that approach overall, but it's unquestionably harder for learners.

Last year I was discussing this problem with Dmitry Jemerov (product manager for IntelliJ). He sees this issue directly - learners download IntelliJ, get some cool pixels on screen with Swing or JavaFX and then hit a brick wall when they want to send their program to other people. They ask around and it becomes "oh why aren't you learning web dev?" and then they balk when they realize that involves learning 3 new languages and maybe server sysadmin stuff. App stores don't help here because Apple/MS don't like their nice discovery system being gunked up with "my first code" stuff. Even if they stick with Java and try to learn jpackage they quickly realize they need Windows, and a Mac, and maybe Linux just to make packages that their friends can run at all, and then having done all that there's no way to push updates!

We were discussing this problem in the context of maybe doing integrating IntelliJ together with Conveyor [1], a tool my company has been writing that makes desktop app distribution drastically simpler. Actually if you are OK with self-signing it's no harder than compiling Markdown to HTML is, the workflow is very similar. Even if you want to sign it's still very easy; the hardest part is just the general bureaucracy involved with buying the certs. It works for any kind of app including Electron or native apps, but of course with a JVM app you don't have to cross-build anything and Java GUI toolkits are a bit easier to learn than web dev.

There's more work to do here, for instance drawing icons is tedious and when learning a distraction, so we've got a feature in development that draws icons for you. And whilst you can upload the resulting package repository to GitHub Releases, that's not automated and it could be.

I think we're pretty close now to basically abolishing this on-ramp/off-cliff, at least if your users are willing to copy/paste commands to the terminal (due to the self-signing; if you supply real code signing certs the UX is obviously much nicer). Getting rid of the code signing requirements means sandboxing but that's a bigger lift.

BTW - Conveyor is actually implemented as a parallel incremental task-caching build system, internally. We've generalized it and are currently experimenting with that for some of our own build needs. We're some way from being able to replace Maven/Gradle but totally agree that usability of existing build tools is very poor. Usability is in fact the primary ground on which this experimental build system differentiates.

[1] https://conveyor.hydraulic.dev/

> Even if they stick with Java and try to learn jpackage they quickly realize they need Windows, and a Mac, and maybe Linux just to make packages that their friends can run at all

And when they test it on Windows they'll realize that the launcher that jpackage creates does something weird on startup (possibly involving relaunching itself? [1]) that makes Windows display a busy cursor for a while after the program has launched, so they'll have to forget about jpackage and redo it all with something like Packr [2] and Wix# [3]...

[1] https://github.com/openjdk/jdk/blob/fe45835f7cebfccd4544ae19...

[2] https://github.com/libgdx/packr

[3] https://github.com/oleg-shilo/wixsharp

Confused about the criticisms now. Otherwise everybody complains about Java boilerplate. Remove some of it and make it optional, to avoid bogging down newbies in irrelevant details in the first instance, and further lead to less boilerplate to make simpler applications in the second instance (or further down the line). I'm not seeing a massive amount of effort being expended here, and I don't see how it's being wasted.

When you have such a large ecosystem (Java's user base is larger than the population of quite a few European countries or the population of the San Francisco Bay Area), no matter what you do or don't do, there will be plenty of complaints because there are few things programmers are in consensus over. Satisfying everyone is impossible. If 99% are content with some Java feature (or lack thereof) and 1% of the remaining 1% complain online, you're looking at a thousand complainers.

> Otherwise everybody complains about Java boilerplate.

Do serious people actually complain about this often? I think it's more of a drive-by comment from people who don't like Java for whatever other reasons.

I wrote a lot of Java in the 2000's and the early 2010's. The verbosity of the language was always my #1 complaint. The amount of stuff I had to type vs the productivity of those lines was always out of whack. Maybe it's better now?

Requiring fewer lines for Hello World doesn't really change that though.

Alas, the language is incredibly verbose in and of itself and the plethora of examples continue to be incredibly verbose. From _Java in a Nutshell_ to the latest documentation, the examples all tend to be of the form:

UnbelievablyLongClassName unbelievablyLongClassName = new UnbelievablyLongClassName(parameterName, anotherParameterName);

Given that nearly all existing code follows this pattern (yes, there is var which is only useful in some cases as opposed to C++'s auto keyword) Java has become hopelessly convoluted.

Before I get downvoted, you should keep on mind that I was an early adopter (1996) of "Java as a sane C++" and have been stunned by the verbosity of "modern" Java applications. Unless the standard libraries change (and the effect of that would be staggering) and the open source libraries all change, and every existing application code changed to use reasonable names, Java will always be overly verbose.

> UnbelievablyLongClassName unbelievablyLongClassName = new UnbelievablyLongClassName(parameterName, anotherParameterName);

That's verbose, but how is it convoluted? It seems extremely plain and simple and linear to me? A local variable is set to a new instance of a class with a couple of parameters. What do you see as being convoluted here?

Individually the lines are verbose but are usually readable. But once you start talking about hundreds or thousands of lines across tens or hundreds of files the sheer volume of the text is what starts leading to convolution. What pushes it over the edge then are the endless abstractions, misdirections and enterprise patterns that may or may not make sense to solve the problem at hand but generally are "best practice" or at least were when lots of legacy code was first laid down 10-20 years ago. It becomes a giant headache very quickly.

I like and professionally write Java. Unnecessary boilerplate is the worst part of language, and I'm happy that Java authors agree with me and incrementally fight it.

I agree. One dev I was working with was complaining about how much code they had to write (wrt a design pattern, and not Java specifically). But the problem was not the design patter (which I agree is hella verbose, ports-and-adapters), it was the fact that they did not know how to use their IDE. I had to explain that until you increase the proficiency of your IDE skills, every pattern is going to be slow for you. What took them several minutes to do, could be done in a few seconds by an IntelliJ expert. I know counter arguments can be made. But still. If you gonna ride a horse through tough terrain, you should really get good at using your horse, and not complain about the terrain.

Yeah, to be honest I wouldn’t be surprised if someone proficient in IntelliJ actually does fewer keystrokes for a Java program than they would given an equivalent program in a “much” terser PL, simply due to the IDE helping so much. It is especially visible with dynamic languages, whose auto-complete is simply an order of magnitude worse than what Java offers due to static typing, and that’s just a small part of the equation.

You don't think serious people can be awfully opinionated about the tools in their profession? It's not like it's something unique to Java or even programming. A lot of professionals have dogmatic beliefs about what ideas, tools, etc. they subscribe to and a lot of time is wasted in bickering about it, whether unjustified or not.

Genuinely, I found out that people who tend to be very dogmatic don't tend to be all that great when you look at their actual output - whether measured by quality or speed or ability to orient themselves is someone elses code. They do tend to sound more capable then they are to those who dont work with their code, but that is social thing not capability or seriousness thing.

Opinionated yes, dogmatic not.

I can understand them having an opinion.

I wouldn’t think a serious person would continually bring it up as an actual issue preventing them achieving things.

I agree. Ironically, Java the language doesn't have any more boilerplate than, say, C++ or Go, which people are apparently okay with?

It does however have some stuff that's plain dumb (can't have functions outside classes, public static void main(String[]), ...) some of it isn't even fixable (default visibility should be private, final should be like const in C++, or even better like const in D).

This is a change that takes very little effort and doesn't impact professional development in the slightest.

Java the language is fine, but Java has - or used to have? - so much idiomatic boilerplate - worst of all IMO was the ridiculous imperative to write getters and setters for every field, but there was also a plethora of very broad interfaces - all of which required all kinds of boilerplate and/or delegation to be able to use. Don’t even get me started on the frameworks.

Maybe Java has changed in the last 5 years, but idiomatic Go has much, much, much less boilerplate than the idiomatic Java I used to know.

To your point, I started writing my POJOs without getters/setters. It felt like I was doing something wrong and the ghosts of senior devs I have worked with were looking down at me with shame.... "how could you".

The worst part is that some libraries use getters as convention. e.g. Jackson (by default) uses getter methods to decide what to serialize.

I used to do the same thing, giving total visibility and access inside a package. I do this in my old “Java AI” book (you can get a free copy at https://leanpub.com/javaai by setting the price to free).

I had not touched Java in a long while until yesterday when I wanted to do a quick benchmark of Lucerne to compare it to a search library in another language. I was surprised how easy and quick it was to do this.

I have considered updating my old book for modern Java. It would probably only take a few days to update the examples, but updating the book text would take longer.

Me too - I absolutely recommend using public member fields and not setters/getters! In 20 years of Java development I did not once benefit from all the extra fluff. Classic YAGNI.

I don’t recall having a problem with Jackson, maybe there is an option you can use? But it’s 5 years now since I used Java.

But my point of course was that this is idiomatic boilerplate you don’t find in other languages. Along with a bunch of other ceremony and nonsense like automatic dependency injection and reflection heavy frameworks that reduce mechanical effort by creating complex, invisible and abstract conceptual systems that require far more mental gymnastics than they save on typing. (I’m looking at you, JPA)

C++ boilerplate is criticized all the time. Just being able to pass a range rather than begin and end iterators was such a huge improvement in wordiness and is a widely loved change, for example.

Another option is any classless java file is just a class with the same name as the file implementing Runnable.

E.g. Foo.java


   class Foo implements Runnable {
     void run() {
Then running java Foo.java instead just stubs a main() that looks like:

   class Main {
     void main() {
       new Foo().run();

Instantly my mind wants to "javaify" it again, that is, generalizing it to some general case by involving qualified names:

Instead of stubbornly implementing Runnable from those flat files, mix in the well-established concept of single-function-interfaces (Runnable is just one of many) and introduce a top-level "extends" that makes the file body an SFI implementation:

  extends java.lang.Runnable;
  java.lang.System.out.println("Hello world");
(and it only gets better if you also add the rule "in absence of any imports statements, an implicit import java.lang.*; is added)

Yeah, I hadn't quite figured how you'd mix this such that the arguments are passed by convention, so your point about single function interfaces is a good one there.

BTW, I think the pedantic view on this based on the (mailing list discussion) is that it becomes java.lang.StaticImports.println("Hello"), which calls System.out.println.

You'd be limited very quickly since Java does not have local methods. This would work for only very simple scripts that fit in a single method.

The context of this is entirely what is the lowest amount of ceremony that we need in order to reduce the teaching overhead of a java program. This really comes down to simple first up, with easy exits towards more complex. That very much looks like to me:

1. Single file single function imperative

2. Single file multi-function imperative (introduce multiple functions)

3. OO (introduce classes etc)

Not a Java dev here, at least in part because the amount of ceremony to do trivial things seems excessive.

My understanding is that the ceremony and redundancy is deliberate. You type the same meaning multiple times so the compiler can detect inconsistencies and tell you about them. I.e. it's a feature, not a bug. Slightly analogous to type annotations or unit tests, one wants to write the redundant information in exchange for compiler diagnostics.

Syntactic sugar to make the language less annoying to work in is opposed to that goal, hard to guess if that's better for the target market.

This seems to be a weird micro-optimisation. While I agree with some of the goals (classless files, parameterless main exist in Kotlin and are nice to have), they should, in my opinion, not be implemented for the sole purpose of making `main` as minimal as possible.

I don't think you need to understand every single character of your very first program right from the beginning. A few of the concepts can be hand waved away to be explained in a later chapter without impeding the overall understanding of programs once you left the sub-twenty-lines beginner programs.

Extra language specification rules that you need to be aware of for your whole career... in order to make the first 30 seconds easier when looking at hello-world for the first time.

The genius of this proposal is that besides the implicit unnamed class, the other changes are all purely done to the program launcher program, no changes are proposed for the language at all.

And one could argue that quickly writing up some program happens often regardless of skillset (I do make at least two psvm Main classes per month), so the code reduction there is also a welcome change.

> the other changes are all purely done to the program launcher program, no changes are proposed for the language at all

But the behaviour of the launcher is covered by the language spec isn't it? It's the language that specifies how main is selected.


Well, there is nothing in the spec that says would forbid the JVM loading a ProgramLauncher.class file with a public static void main method, which will look at the program arguments and if it has something ending in .java, it will compile that file, and load the resulting class (current mode of execution).

Also, even if it might become a spec change (a language level one, not a jvm level one), it will be an additive change, at worst some new program won’t run on older JVMs, but all old program will continue to run on new ones.

As I mention on the sibbling comment, C# 10 already went through this.

For me it seems like a reaction to JavaScripts and Pythons of this world, optimizing for Hello World just to win over them on those 30 seconds.

They are going to rewrite their applications anyway, when performance comes knocking on the door.

I think there is more value to a terse declaration of 'main()' than people think. In C#, you can declare a quite functional microservice under 90 lines of code with asp.net core's minimal api format. To this day I think the "enterprise style religious OOP+SOLID Java/C#" is something that has to go - so many applications that take 30 files and 4 projects could've been declared in 3-5 files with the most of core logic being in Program.cs that you can grasp within few seconds of looking at it.

It will never go away, before it was COBOL, xBase, C, C++, Smalltalk.

Tomorrow might be Go, Rust, Zig, or whatever they fancy using.

These are projects at scale of dozens of developers with various skill sets distributed around the world, optimizing for Hello World hardly matters.

It's also a reaction to languages like Kotlin that already have these and run on top of the JVM. With Kotlin these don't apply just to main. IIRC, other JVM languages have similar reductions in boiler-plate compared to Java.

C# just went through this for C# 10, which I find a waste of resources, optmizing for "Hello World" is not what matters in large scale engineering.

Large scale engineering is totally beside the point for this. This is about optimizing for teaching a first language (since many will stick with that).

Read the article, the "static trap" is a real thing for new students (on top of all other ceremony). To solve the issue of main being non-static some students will start to put static everywhere as a 'fix' to their initial issue, a day or two later they'll be asking why every student gets the same name (because they have one static variable to keep the student or name objects).

The verbosity of Java also introduces another more serious problem for learning, not only do the students have problems compiling, they also start putting their mind on focusing on syntax instead of logic once they're past the first few problems. The issue of de-programming students from this affliction is waaay too common to be ignored.

Back when I was a TA, first year students got along just as well with Pascal, Fortran and C++, many of which lacked a programming background.

I wasn't found of minimal APIs, global usings and implicit Program.cs for C# 10, and share a similar opinion in relation to Java.

No need to try to make it look like JavaScript and Python, each language has its place.

Anecdata - I'm self taught. Started my learning on Python.

I wanted to delve into Java. And sure enough, my very first thoughts were "wtf is public static void, why does this even matter, and why is print() so weird and long?"

Shit, I ended up finding it easier to get into F# than Java.

Then I got hired for a Java role, which is proof that the universe has a sense of humour.

And none of the listed languages enforce main to be part of a class, hence the described problem didn't exist for your students.

I've taught C to beginners after the Java class and whilst there are some related problems those students encounter, the extra confusion caused by having main in a class doesn't exist (and doesn't cause as much syntax vs logic confusion later on).

I can assure you they were using OOP in C++, creating their own standard library, leaving behind the ways of C, as any proper C++ course during the ARM C++ days.

The lecture notes that eventually became this book were their path into discovering C++.


"Top-level statements" in C# is a double edge sword: In a long run it will help make C# a bit more approachable to an absolute beginner to programming by reducing "noise" and cognitive load but right now it tends to confuse the beginner due to countless tutorials, SO answers, books, courses being written using using, namespace, class, Main structure.

Which makes the feature kind of pointless. Especially since all teaching materials (and those who have used them for teaching) were saying to just ignore the rest for now and concentrate on

  Console.WriteLine("Hello World!");
which is, as it's written, confusing to a beginner anyway ("What is Console?") until he learns about classes, after which both namespace and using get clear(er).

It's not optimising for Hello World. It's optimising for a new student's learning experience, so that concepts can be introduced in a more reasonable order.

I beg to differ, as soon as they need a second file, import a dependency or use additional helper methods, they get to face all of it anyway.

And then there is the issue with leaky abstractions when the code that gets compiled interfers with the ideal worlds that the compiler injects, and possible confusing error messages get generated.

Like it happens with all guest languages that target the JVM pretending to be Java, and one gets a confusing stack trace only understandable by those with proper Java background.

> as soon as they need a second file, import a dependency or use additional helper methods, they get to face all of it anyway.

The problem isn't the set of things students will need to learn, but the order in which they encounter them. Students write a one-file program before they write a two-file one, but because Java is optimised for large programs, today they encounter things like accessibility rules -- which exist to serve large programs -- before they even get to write a very small one. That's the idea of the "on-ramp" in the title -- you end up int the same place, but with a smoother ride.

Both students and teachers who prefer Python to Java as a first language mentioned this "on-ramp" problem as one of the reasons. There are others, and they will be addressed by other enhancements.

BTW, importing other packages and using helper methods does not require knowing about accessibility rules.

> Like it happens with all guest languages that target the JVM pretending to be Java, and one gets a confusing stack trace only understandable by those with proper Java background.

But this is not a "guest language" and we'll need to ensure that stack traces -- which we also control -- will not be confusing.

If we're talking beginners / students here couldn't this all be hidden by a specialized IDE? Why the need to put it into "real Java"?

Lets see how much better it gets over C# 10 then.

C# has this on their new ASPNET 7.0 docs. To have a single Program.cs file with all the builder and app code . incredibly accessible to a new learner of the framework.

This is still too long. I think C# already supports top-level execution (eg, your program is assumed to be main() as do almost all other languages). `println` is also a bad name for something that could just be `log` or `print`.

I’m actually very happy to see this after using Java for many years. I definitely had all of the mentioned thoughts about the keywords, and not knowing what they do when first learning.

Java needs staunchly maintain its static type nature rather than move towards a ‘everything is a string/array whatever’. The concept of types are not only powerful, performant, but they are way to grasp conceptually because the Java semantics and compile time checking.

I do wish CDI (or SpringFramewotk) was brought into CS and similar degrees. Dependency Injection brings a large number of good habits to developers, but it is a concept that is hard to teach as an employer. In our onboarding program, we have to teach people about the CDI container (a program ruining your program) managing all of your state for you (to great benefit and speed of development). During this time, we’re not getting much value out of the developer, and the teacher is also away from their normal duties as well. :/

The declaration of a class and the incantation of public static void main is pure mystery to a beginning programmer.

This article misses the point, however. The problem is not writing your first hello world. The ceremony is not so much in the language itself but in the infrastructure. The verbosity of the language is just the tip of the iceberg.

I think Java back in the 90's started out actually quite decently, although from the get-go it had the verbosity. But at least it didn't have everything else yet that you're expected to know for any non-trivial program.

The first time the word "enterprise" appeared together with Java was the moment it went all downhill.

There is literally nothing related to “enterprise Java” in this post.

Hence "misses the point": I'm arguing that in order to get up to speed on state-of-the-art Java development, there's a lot more to learn than just the core language.


Sounds like great improvements. I would wish for the ramp to be extended a little bit to also do something about packages, for example allowing a common prefix like "com.company.project.whatever" to be elided, so that we don't have to go from "src/Hello.java" straight to navigating the maze of "src/main/java/com/company/project/whatever/hello/Hello.java".

I wrote up an alternative proposal here: https://blog.joda.org/2022/10/fully-defined-entrypoints.html

My concept is much more powerful by having a new kind of class declaration alongside 'record' and 'enum' - 'entrypoint'.

I have long considered that having to have the class definition explicit in the file is redundant, especially as the 2 have to have the same name anyway. Obviously if you need to `extend` or `implement` then an explicit declaration is needed. But otherwise, just defaulting to the file base name is more than sufficient.

Except for interfaces, record classes, type parameters, class-level annotations and javadoc. There's probably more I'm not thinking of right now.

Groovy does a good job with this type of thing.

Edit: but a bad job at a lot of other things.

I remember when first learning Java in 1995, and already being familiar with OOP from C++, I found it curious that such an OOP-focused language didn’t represent a program 1:1 as an object instantiated and initialized at load time, with a main/run method invoked on it to start execution. The choice of having main() be static was made even weirder by its still having to be defined within a class. Then if you’d want to have your application be an object, you’d have to do the extra `new MyApp(args).run()` bit manually, and have run() be the “real” main method. It all seemed very backwards.

I wonder - why he has not take it a step further and propose to get rid of return type (void) also? It would look even more slick (add optional semicolons and we will get javascript out of this :)).

main() { println("Hello World"); }

I know that this notation is traditionally reserved for constructors but I'm not 100% sure why we cannot differentiate between methods and constructors just by looking at class name?

I see very little gain for quite a high price here. It would break the grammar quite a bit, potentially causing even breaking changes, for something that is usually not absent even in languages that do strong type inference.

The proposed changes don’t have these negatives, while still provide benefits.

It's not as bad as it seems because the language already have constructors. So instead of having a peculiar case for constructors, you unify constructors and methods by saying that void does not have to be specified.

And i do not buy your argument that it will break the grammar, the parser of javac consider constructors as methods.

Doesn't Groovy (https://groovy-lang.org) achieve much of this? I remember being taught with it at university for some time before they introduced Java. With Groovy you don't need the class or main method, and can have a program which is just `println "Hello world!"`.

> achieve much of this

Well no, because that on-ramps you to Groovy, not Java.

Groovy is not Java.

But it's a brilliant example of everything that could possibly go wrong if you blindly optimize for hello world

Or you get jealous of the runtime errors the JS, Rails and Python teams have.

Or invent entirely new classes of errors with static type annotations that are actually utterly ignored.

Neither is the final sample presented in the article! But I think the point is that Groovy already exists and can run most Java programs as well.

Yet another hunk of groovy finally being incorporated into Java, about a decade too late.

One additional comment: they need to make the launch protocol for jar files, much simpler as well. META-INF needs to be replaced with a simple properties file that indicates what method to run and which libraries to have on the class path.

Is there a good resource for learning modern Java?

There's a new version of Head First Java that came out this year, I expect it's reasonably up to date wrt modern language features. I haven't read it personally but it's frequently recommended.

At a more intermediate level Effective Java 3rd edition is a must read.

Kotlin's syntax is cleaner in some most places, more explicit in some places (no implicit nulls, and wrt generics) and much terser when using naked functions etc.

Did Java's papa get jealous?

I always find it funny when people bring up Kotlin in the context of Java's evolution. It's not one of Java's main competitors, and while Java has borrowed many features from other languages [1], to date it has not adopted a single feature from Kotlin [2]. In fact, almost at every turn -- from data classes, through async/await, to string interpolation (JEP 430) -- Java has opted for a different approach. Is Kotlin the only other language they know?

But I don't think you're entirely wrong to suggest that competition had something to do with this. Python is one of Java's main competitors, and when it comes to teaching a first language it is the main competitor. Both students and teachers who prefer Python as a first language mentioned this "on-ramp" problem as one of the reasons. There are others, and they will be addressed by other enhancements.

[1]: Java's "charter" calls for it being a last mover, adopting features after they've been successfully used elsewhere

[2]: Although there is one I hope it would one day -- nullability types.

> Is Kotlin the only other language they know?

On the JVM: I'd say yes.

Scala, Groovy, Jython, JRuby, and what not came and went.

I'd say Clojure is the third, and it take a nice place in the design space.

But Kotlin to me is so close to Java that it is basically Java 2.0: the Java that Java cannot be due to intended organizational slowness.

> Python is one of Java's main competitors, and when it comes to teaching a first language it is the main competitor.

Yes it probably is. The changes outlines in the article are not going to help here I'm afraid.

But I'm weird. I'd say the best lang without static typing for noobs is a LISP (so that every student is likely equally confused). When learning a language with types go for Rust or Idris. All not exactly competitors of Java :)

> the Java that Java cannot be due to intended organizational slowness.

When Java was created in the '90s, James Gosling said that it is intended to be a slow-evolving conservative language that will only pick features from other languages once they have been sufficiently proven to be worth it. We try to live up to that goal and be a last-mover. It's not organisational slowness but conservatism in design that has worked very well for Java. Java will never adopt many if not most of the features Kotlin has, because most features never prove themselves as particularly beneficial over time, and because we try to make the language as minimal as possible, because it's intended for a very wide audience, and, in general, most programmers prefer languages with fewer features to languages with more features.

I'd say Kotlin was Java 2.0 ten to five years ago, but now it's a language that's going in a different direction from Java's.

> The changes outlines in the article are not going to help here I'm afraid.

Not alone, but this is just the beginning.

> I'd say Kotlin was Java 2.0 ten to five years ago, but now it's a language that's going in a different direction from Java's.

Maybe it is, maybe it is not... We all do not know the future.

> Java will never adopt many if not most of the features Kotlin has, because most features never prove themselves as particularly beneficial over time

So what. I care (as said before) for the implicit nulls problem. Also, I dont like to my code to be littered with annotations (to introduce magic) and exceptions-as-alternative-return-values.

Almost 10y with Java, now on Kotlin for everything new and not looking back.

That's great, but for the past 15 years or so, the portion of people using the Java platform through other languages has neither increased nor decreased (around 10%). There's a segment of programmers who prefer more feature-rich languages than Java, or perhaps different programming paradigms, and we're happy to have the platform accommodate them.

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