Language selector

Shorter Java scripts thanks to JEP 445

In the past I wrote two posts about Java sripts and their portability using shebang.

As far as I can tell everything in these two installments is still valid, only now, in Java 21, thanks to JEP-445 things can be even simpler. In this post I’ll try to describe fist the changes introduced by this JEP, then go deeper on how they relate to the “scripts in Java”.

I’m going to cover how you can simplify your main method, get rid of the class surrounding it, and even show you when it won’t work.

Is main really bloated?

I started programming in Java in 2003. And Java wasn’t my first C-derived language I knew. Before Java I had learned Turbo Pascal and C++, so the concept of int main() as the entry point wasn’t entirely new to me. On the other hand I was also quite efficient PHP 3 coder (because scholarship wasn’t enough), so I was also exposed to the concept of main-less programming language. (Yes, I think PHP is a programming language and deserves respect when due.)

In fact at that time Java was the very first language to hardly anyone, we already knew some languages with main. Sure, String[] args was some ceremony, but that was okay. What was somewhat more confusing, and on the “trust me, you’ll understand that later” side of things, was the class surrounding the main. And so we went into class Klass to understand it later, unless we were already familiar with object-oriented C++.

Thing is, this was 20 years ago, when Java was perceived somewhat as a new-kid-in-the-block. These days we have more and more newcomers to Java, who come from main-less languages or even don’t have any experience at all. Therefore, I quite like what JEP-445 helped us to achieve, i.e. skip a lot of the ceremony, when you’re only about a simple, single-file programme, because you’re learning all those “variables” and “loops”, and maybe even “methods”.

If you’re okay with --enable-preview features, and you don’t need arguments, instead of:

public class PublicStaticVoidMainWithArguments {
    public static void main(String[] _ignored) {
        System.out.println("Good old public static main.");
    }
}

You can simply go for:

void main() {
    System.out.println("The new main.");
}

Now, thanks to JEP-445 we can:

  • skip the class declaration entirely,
  • skip the String[] args as well (unless you still need them, then you can still use them, of course),
  • skip static from the main method, so it can be an instance method,
  • in fact, you can also use any access modifier, including the default one (just like above), except private.

Precedence of main methods

‘Okay’ you may ask, ‘in Java I may overload the methods. What happens if I have public static void main(String args[]) and void main()?’

Well, don’t worry. If the code compiles and you have two main metods, only one will be called and the precedence is strict.

  • First the method having String[] args will have precedence over an args-less method.
  • Then static is preferred over instance.
  • Last, the more public, the higher it goes.

BTW I’d argue if having such a puzzle with more than one main method in your file (or class) is really sane approach. Don’t do that to yourself and people around you. ;-)

Is this the class-less approach?

Not really. The class is still there. It’s so-called unnamed class, generated for us automatically under the hood.

You don’t have to trust me, you can trust the code. Please try to put the following in a file that has a .java extension and run it:

void main() {
    System.out.println("New instance main in unnamed class, without arg(s).");
    System.out.println("Is the class unnamed? " + this.getClass().isUnnamedClass());
    System.out.println("The class name is " + this.getClass().getName() + "...?");
    System.out.println("And the modifiers are [" + java.lang.reflect.Modifier.toString(this.getClass().getModifiers())+"]");
    System.out.println("(The package name is [" +this.getClass().getPackage().getName()+"])");
}

If you wonder how you can run this code (e.g. because your IDE doesn’t show you the nice green “play” button), you can always run this as per JEP-330:

$ java --source 21 --enable-preview yourFileName.java

You shall see, that the class is final, it resides in the default (“nameless”) package, you can now check if it isUnnamedClass() and… it has a name! Its name is derived from the name of the file. The funny thing is, that as an “implementation detail”, the class actually has a name, derived from the name of the file…

Therefore, if you had named your file like e.g. your-file-name.java, you will see an error. Again, you don’t have to trust me, run and have some fun ;-)

In case you can’t, due to whatever reason, it looks more or less like this:

your-file-name.java:44: error: bad file name: your-file-name
    void main() {
         ^

So currently the name of the file can’t be just any name, it still needs to be a valid class name if the file name ends with .java. (Or you have to wrap it into a non-public class.) I’m not quite sure if this is very fortunate TBH, because there’s a consequence.

Consequence: no class-less scripts

After JEP-330 we were able to write “Java scripts” using #!shebang.

So we might be tempted to put the main method showed above into e.g. java.script and just on top add a line like:

#!/usr/bin/env -S java --enable-preview --source 21

Only… it doesn’t work. The file-name-to-class-name mechanism applies only if we run java --source effectivelyAKlass.java. If we’re into shebang, it won’t work. Again, because bad file name error. We need to wrap the “simplified” main into a class, so it looks e.g. like this:

#!/usr/bin/env -S java --enable-preview --source 21

// In GNU/Linux the -S can be used since env version 8.30
// for other OSes and older env versions you may need to use absolute shebang

class OwningClass {
     void main() {
        System.out.println("This is Java Script! 👿");
        System.out.println("Hello from "+ Runtime.version());
        System.out.println("New instance main in unnamed class, without arg(s).");
        System.out.println("Is the class unnamed? " + this.getClass().isUnnamedClass());
        System.out.println("The class name is " + this.getClass().getName() + "...?");
        System.out.println("And the modifiers are [" + java.lang.reflect.Modifier.toString(this.getClass().getModifiers())+"]");
        System.out.println("(The package name is [" +this.getClass().getPackage().getName()+"])");
    }
}

And apparently we can’t get rid of this OwningClass if we want things to work in the script…

Do I like this aspect of JEP-445?

Honestly, I’m not so sure if I quite like this “implementation detail”, effectively forcing us to have proper names for .java files or still declaring class Klass in shebang scripts.

I am of course well aware that this is an extreme rare case, corner of a corner, useful mostly for the connoisseurs of the recruitment interviews (when inverting binary trees isn’t enough). And it might well be that I’m missing some serious arguments making this approach the best one…

Still, maybe this “implementation detail” is too strict? Maybe the automatically generated class could have an empty name "" just like the default package, if this doesn’t require changing the JVM too much? And if this would be too much of a breaking change, then maybe at least it could have a totally random name? Or maybe asking for the name of an unnamed class should throw an exception, just like addFirst(...) on unmodifiable collection? (Although I’m really not sure if it would be a good approach.)

I’m just slightly afraid that this implementation detail not only might be too constraining, but some people might abuse it, even if the documentation says not to use the name of the UnnamedClass. (Does sun.misc.Unsafe ring a bell? ;-))

The future will show.

And it’s much better anyway

Old script

Sure, if we want to write a “decent Java script,” we still need to use a class or have the right file name, but it’s still better. At least it’s much easier to start an adventure with Java now.

I don’t have any particularly big illusions that such scripts will be popular in production in the era of native images. But they should be just right for learning, because you don’t have to understand everything right away (or take it on faith) and struggle with javac and java.
And this is not the last word in Java scripts, as there is a proposal for another JEP-458!

Language selector