JEP draft: Anonymous Main Classes and Enhanced Main Methods (Preview)

OwnerRon Pressler
Componentspecification / language
Reviewed byAlex Buckley, Brian Goetz
Created2023/02/13 13:58
Updated2023/03/27 22:47


Evolve the Java language so that students can write their first programs without needing to understand language features designed for large programs. Far from using a separate dialect of Java, these first programs utilize streamlined declarations for single-class programs, and can be seamlessly expanded to incorporate more advanced features as needed. This is a preview language feature.




Java is a multi-paradigm language that excels at writing large, complex applications developed and maintained over many years by large teams. It has rich features for data hiding, reuse, access control, namespace management, and modularity to allow components to be cleanly composed while being developed and maintained independently. Those features expose well-defined interfaces for their interaction with other components and hide internal implementation details to allow the independent evolution of each. Indeed, the object-oriented paradigm itself is designed for plugging together pieces that interact through well-defined protocols and abstract away implementation details. This composition of large components is called programming in the large. Java also offers many constructs useful for programming in the small – everything that is internal to a component. In recent years, Java has enhanced both its programming-in-the-large capabilities with modules as well as its programming-in-the-small capabilities with data-oriented programming.

But Java is also intended to be a first programming language. When programmers first start out they don't write large programs in a team but small programs alone. They have no need for encapsulation and namespaces, useful to separately evolve components written by different people. Moreover, when teaching programming, many instructors prefer starting with programming-in-the-small and first teach basic constructs such as variables, control flow, and subroutines. At that stage, there is no need for the larger class units or any other programming-in-the-large construct. While making the language more welcoming to newcomers is, in itself, in the best interest of Java veterans they, too, may find it pleasurable to write simple programs more concisely. Even expert programmers write small programs that don't require the programming-in-the-large scaffolding.

The classic “Hello World” program that is often used as the first program for Java learners, looks like this:

public class HelloWorld { 
    public static void main(String[] args) { 
        System.out.println("Hello, World!");

Most educators feel that there's too much clutter here – too much code, too many concepts, too many constructs – for what the program does. The clutter falls into two categories:

The new programmer encounters these concepts at the worst time: before even learning about control flow and variables, and when they certainly cannot appreciate the utility that programming-in-the-large constructs have for keeping a large program well-organized. Java foists those concepts on the learner in an order that is not conducive to learning how to program. Educators often offer the admonition "Don’t worry about that, you’ll understand it later”, but this is unsatisfying to them and their students alike, and gives the students the impression that Java is complicated.

The motivation for this JEP is not merely to reduce ceremony but to help programmers that are new to Java or to programming in general learn Java in a manner that introduces concepts in the right order: advanced concepts intended for programming in the large can be postponed until they are actually beneficial and can be more easily grasped, after learning the more basic, programming in the small, concepts. This is done not by changing Java's internal structure -- code still lives inside methods that, in turn, live inside classes -- but by hiding these details until they are useful in larger programs. We offer an on-ramp, a gradual incline that gracefully merges onto the highway: When learners move on to larger programs, they need not discard what they learned in the early stages, but rather they see how it all fits within the larger picture.

The changes offered here are just a step in making Java easier to learn. They don't even address all the speed bumps in the Hello, World program: The beginner may still be puzzled by the mysterious System.out.println, and still needs to import basic utility classes and methods for essential functionality even in first-week programs. Alleviating these pains may be the subject of a separate JEP.


This is preview language feature, disabled by default

To try the the examples below in JDK 21 you must enable preview features as follows:

An Enhanced Launch Protocol

New programmers want to write and run a computer program, but the Java Language Specification focuses on defining the core Java unit of the class and the basic compilation unit -- a source file comprised of a package declaration, followed by some import declarations, followed by one or more class declarations. All it has to say about a Java program is this:

The Java Virtual Machine starts execution by invoking the method main of some specified class or interface, passing it a single argument which is an array of strings.

The JLS further says:

The manner in which the initial class or interface is specified to the Java Virtual Machine is beyond the scope of this specification, but it is typical, in host environments that use command lines, for the fully qualified name of the class or interface to be specified as a command line argument and for following command line arguments to be used as strings to be provided as the argument to the method main.

The role of picking the class whose main method is invoked, as well as assembling its dependencies in the form of a module path and/or a class path is assigned to the Java launcher, the JDK's javaexecutable. It operates by loading a specified class, initializing it, and invoking a particular main method of that class.

Omitting the String[] parameter and public modifier

We enhance the protocol so that if the main method of the specified class has default (package) access it will also be invoked. Furthermore, if no method with a String[] parameter exists and, instead, a main method that takes no arguments is present, then it will be invoked. This allows postponing the introduction of access modifiers, as well as the processing of command-line arguments, until they are needed.

Instance main

The aforementioned enhancement of the launch protocol still leaves us with the problem of a static main, which further infects methods called from main with "staticness", or requires object instantiation.

We, therefore, further enhance the launch protocol so that if no static main method is present but the launched class has a non-private zero-args constructor (i.e. of public, protected or package access), and a non-private instance main method, then the launcher will construct an instance of the class and invoke the main method on that instance. As before, if no static main method is present, the launcher will first look for an instance main method that has a String[] parameter, and if one isn't present, it will invoke one that takes no arguments.

This would allow Hello, World to be written as follows, with no access and static modifiers, and without the String[] parameter:

class HelloWorld { 
    void main() { 
        System.out.println("Hello, World!");

main method selection

When multiple main methods are available, the launch protocol chooses among them according to a fixed priority, picking one with a higher priority over those with a lower priority. Selecting the one that would have been picked before the change first ensures that the extension of the launch protocol preserves the behavior of existing "launchable" classes.

The order by which the main method is selected among available candidates is as follows:

  1. public static void main(String[] args) declared in the launched class or a superclass, this is the standard behavior before the change

  2. static void main(String[] args) of protected or package access declared in the launched class

  3. static void main() of any non-private access (i.e. public, protected or package) declared in the launched class

  4. void main(String[] args) of any non-private access declared in the launched class or inherited from a superclass

  5. void main() of any non-private access declared in the launched class or inherited from a superclass

Additionally, we deprecate the existing behavior that a public static void main(String[] args) is searched in a superclass if not found in the launched class by issuing a runtime warning when such a class is launched.

Anonymous Main Classes

Every Java class resides in a package and every package resides in a module -- they serve as namespacing or encapsulation constructs that the Java platform applies to all code. But Java allows smaller programs that don't need class namespaces to omit the package statement, making the class an implicit member of the unnamed package (similarly for module encapsulation). Classes in the unnamed package cannot be explicitly referenced by classes in named packages.

Before classes serve their main purpose of templates for the construction of objects, they serve only as namespaces for methods and fields. But we should not require learners to confront the class concept before they are comfortable with the more basic building blocks of variables, control flow, and subroutines, before they embark on learning object-orientation, and when they are still writing simple, single-file programs. Even though every Java method resides in a class, we can stop requiring an explicit class declaration for code that doesn't need it just as we don't require an explicit package declaration for code that doesn't need it.

When the Java compiler encounters a source file with a method that isn't enclosed in a class declaration, it will implicitly consider such methods, as well as any unenclosed field and any class declared in the file, as members of an anonymous main class.

An anonymous main class is always a member of the unnamed package. It is also final and cannot implement any interface nor subclass any class (other than Object). Note that because an anonymous main class cannot be referenced by name, it cannot use method references to its static methods; this can still be used, and so can method references to instance methods.

Because no class can refer to an anonymous class by name, instances of the anonymous main class cannot be directly constructed; they are useful only as standalone programs or as entry points to a program. Therefore, anonymous main classes must have a main method that can be launched as described above.

Even though an anonymous main class must reside in an unnamed package and an unnamed package must reside in the unnamed module, note that while there can only be one unnamed package (barring multiple classloaders) and only one unnamed module, there can be multiple anonymous main classes in the unnamed module. Because every anonymous main class must contain a main method and so represents a program, multiple such classes in the unnamed package correspond to multiple programs.

The members of an anonymous main class support the same modifiers as in ordinary named classes (such as static or volatile) and with the same defaults (e.g. default package access and instance membership), and generally behave in the same way as members of an explicitly-declared class. An anonymous main class has the default no-args constructor, and can have no other. It may, however, have a static initializer as well as an instance initializer.

Hello, World can now be written as:

void main() {
    System.out.println("Hello, World!");

As "top-level members" are interpreted as members of the anonymous class, we can also write the program as:

String greeting() { return "Hello, World!"; }

void main() {

or, using a field, as:

String greeting = "Hello, World!";

void main() {

If an anonymous main class has an instance main entry point (rather than a static main) -- say, void main() -- launching such a file is equivalent to the following (which employs the existing anonymous class expression):

new Object() {
    // contents of the file, not including import statements

A source file named containing an anonymous main class could be launched with the source-code launcher, like so: java javac will compile that file source file to the launchable classfile HelloWorld.class (in this case javac chooses HelloWorld for the class name as an implementation detail, but that name still cannot be directly referenced by Java source code).

A Hello, World program written in this manner is much more focused on what the program actually does, and doesn't carry concepts and constructs it doesn't need. Eliminating the main method altogether may seem like a natural next step, but it would work against the goal of gracefully evolving a first Java program to a larger one and would impose some non-obvious restrictions (see Alternatives). Dropping the void would similarly create a distinct Java dialect.

Growing a program

Because all members are interpreted just as they are in ordinary classes, if the need arises to evolve an explicit class to an ordinary class, all we need to do wrap everything in the file (except importstatements) inside a class declaration.