-
JEP
-
Resolution: Unresolved
-
P4
-
None
-
None
-
Ron Pressler, Jim Laskey, & Gavin Bierman
-
Feature
-
Open
-
SE
-
Summary
Evolve the Java programming language so that beginners can write their first programs without needing to understand language features designed for large programs. Far from using a separate dialect of the language, beginners can write streamlined declarations for single-class programs and then seamlessly expand their programs to use more advanced features as their skills grow. Experienced developers can likewise enjoy writing small programs succinctly, without the need for constructs intended for programming in the large.
History
This feature was first proposed for preview by JEP 445 (JDK 21) and subsequently improved and refined by JEP 463 (JDK 22), JEP 477 (JDK 23), and JEP 495 (JDK 24). Here we propose to finalize the feature in JDK 25 with a slight change to the title and the following minor improvements following experience and feedback:
The new
IO
class to be moved from thejava.io
package to thejava.lang
package.Change the implementation of the
IO
class so that printing and reading are based onSystem.out
andSystem.in
, respectively. (In the JDK 24 preview, the methods of theIO
class were thin wrappers around their equivalents in theConsole
class.)Compact source files to no longer implicitly import the
static
methods from theIO
class.
Goals
Offer a smooth on-ramp to Java programming, so that instructors can introduce concepts in a gradual manner.
Help students write basic programs in a concise manner, and grow their code gracefully as their skills grow.
Reduce the ceremony of writing other kinds of small programs, such as scripts and command-line utilities.
Do not introduce a separate dialect of the Java language.
Do not introduce a separate toolchain; small Java programs should be compiled and run with the same tools as large programs.
Motivation
The Java programming language excels for 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 which allow components to be cleanly composed while being developed and maintained independently. With these features, components can expose well-defined interfaces for their interaction with other components while hiding internal implementation details so as to permit 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.
The Java programming language is also, however, intended to be a first language. When programmers first start out they do not write large programs, in a team — they write small programs, alone. They have no need for encapsulation and namespaces, useful to separately evolve components written by different people. When teaching programming, instructors start with the basic programming in the small concepts of variables, control flow, and subroutines. At that stage there is no need for the programming-in-the-large concepts of classes, packages, and modules. Making the language more welcoming to newcomers is in the interest of Java veterans but they, too, may enjoy writing small programs more concisely, without any programming-in-the-large constructs.
Consider the classic [Hello, World!|https://en.wikipedia.org/wiki/%22Hello,_World!%22_program] example that is often a beginner's first program:
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
There is too much clutter here — too much code, too many concepts, and too many constructs — for what the program does.
The
class
declaration and the mandatorypublic
access modifier are programming-in-the-large constructs. They are useful when encapsulating a code unit with a well-defined interface to external components, but pointless in this little example.The
String[] args
parameter also exists to interface the code with an external component, in this case the operating system's shell. It is mysterious and unhelpful here, especially since it is not used in small programs such asHelloWorld
.The
static
modifier is part of the language's class-and-object model. For the beginner,static
is not just mysterious, but harmful: To add more methods or fields to this program, the beginner must either declare them all asstatic
— thereby propagating an idiom which is neither common nor a good habit — or else confront the difference between static and instance members and learn how to instantiate an object.The beginner may be further puzzled by the mysterious incantation
System.out.println
, and wonder why a simple function call does not suffice. Even in first-week programs, the beginner may be forced to learn how to import basic utility classes for essential functionality, and wonder why they could not be provided automatically.
The new programmer encounters these concepts at the worst possible time, before they learn about variables and control flow, and when they cannot appreciate the utility of programming-in-the-large constructs for keeping a large program well organized. Instructors often offer the admonition, "don't worry about that, you'll understand it later." This is unsatisfying to they and their students alike, and leaves students with the enduring impression that the language is complicated.
The motivation for this work is not merely to reduce ceremony. We aim to help
programmers that are new to the Java language, or to programming in general,
learn the language in a manner that introduces concepts in the right order:
Start with the fundamental programming-in-the-small concepts, such as doing
simple textual I/O and processing arrays with for
loops, and proceed to
advanced programming-in-the-large concepts only when they are actually
beneficial and can be more easily grasped.
The motivation for this work is, moreover, not only to help beginning programmers. We aim to help everyone who writes small programs, whether they be students, system administrators writing command-line utilities, or domain experts prototyping core algorithms that will eventually be used in the heart of an enterprise-scale software system.
We propose to make it easier to write small programs not by changing the structure of the Java language — code is still enclosed in methods, which are enclosed in classes, which are enclosed in packages, which are enclosed in modules — but by hiding such details until they are useful.
Description
First, we allow main
methods to omit the infamous boilerplate of public
static void main(String[] args)
, which simplifies the Hello, World! program
to:
class HelloWorld {
void main() {
System.out.println("Hello, World!");
}
}
Second, we introduce a compact form of source file that lets developers get straight to the code, without a superfluous class declaration:
void main() {
System.out.println("Hello, World!");
}
Third, we add a new class in the java.lang
package that provides the most
basic line-oriented I/O methods for beginners, thereby replacing the mysterious
System.out.println
with a simpler form:
void main() {
IO.println("Hello, World!");
}
Finally, for programs that go beyond Hello, World! and need, for example,
basic data structures or file I/O, in compact source files we automatically
import a range of standard APIs beyond just the java.lang
package.
These changes combine to offer an on-ramp, that is, a gradual incline that merges gracefully onto the highway. As beginners 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. As experienced developers proceed from prototype to production, they can smoothly grow their code into components of larger programs.
Instance main
methods
In order to write and run programs, beginners will learn about the entry point
of a program. The Java SE 24 Edition of the Java Language Specification
(JLS 24, §12.1) explains that the entry point of a Java program is a
method called main
:
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.
JLS 24 §12.1.4 further states:
The method
main
must be declaredpublic
,static
, andvoid
. It must specify a formal parameter whose declared type is array ofString
.
These requirements on the declaration of main
are historical and unnecessary.
We can streamline the entry point of a Java program in two ways: Allow main
to
be non-static
, and drop the requirements for public
and an array parameter.
These changes allow us to write Hello, World! with no public
modifier, no
static
modifier, and no String[]
parameter, postponing the introduction of
those constructs until they are needed:
class HelloWorld {
void main() {
System.out.println("Hello, World!");
}
}
Assuming this program is in the file HelloWorld.java
, we can run it directly
with either the source code launcher, or compile and run it directly
as follows:
$ javac HelloWorld.java
$ java HelloWorld
This starts a Java Virtual Machine by choosing and invoking a method main
as
follows:
If the
HelloWorld
class contains amain
method with aString[]
parameter then this method is chosen.Otherwise, if the class contains a
main
method with no parameters then this method is chosen.Otherwise, an error is thrown.
If the chosen method is
static
then it is invoked.Otherwise, the chosen method is an instance
main
method. The class must have a non-private constructor with no parameters. That constructor is invoked, and then themain
method of the resulting object is invoked. If there is no such constructor then an error is thrown.
Any main
method that can be chosen and invoked under this protocol is known as
a launchable main
method. For example, the HelloWorld
class has one
launchable main
method, namely void main()
.
Compact source files
In the Java language, every class resides in a package and every package resides in a module. Modules and packages provide namespacing and encapsulation for classes, but small programs that consist of a few classes do not need these concepts. Accordingly, developers can omit package and module declarations, and their classes will reside in an unnamed package of an unnamed module.
Classes provide namespacing and encapsulation for fields and methods, but small programs built from just a few fields and methods do not need these concepts. We should not require beginners to understand these concepts before they are comfortable with the basic building blocks of variables, control flow, and subroutines. Accordingly, we can stop requiring class declarations for small programs that consist of a few fields and methods, just as we do not require package or module declarations.
Henceforth, if the Java compiler encounters a source file with fields and methods that are not enclosed in a class declaration, it will consider the source file to implicitly declare a class whose members are the unenclosed fields and methods. Such a source file is called a compact source file.
With this change, we can write Hello, World! as a compact source file:
void main() {
System.out.println("Hello, World!");
}
The implicitly declared class of a compact source file
- Is a
final
top level class in the unnamed package; - Extends
java.lang.Object
and does not implement any interfaces; - Has a default constructor with no parameters, and no other constructors;
- Has, as its members, the fields and methods in the compact source file; and
- Must have a launchable
main
method; if it does not, a compile-time error is reported.
Since the fields and methods declared in a compact source file are interpreted as members of the implicitly declared class, we can write Hello, World! by calling a method declared nearby:
String greeting() { return "Hello, World!"; }
void main() {
System.out.println(greeting());
}
or by accessing a field:
String greeting = "Hello, World!";
void main() {
System.out.println(greeting);
}
Since a compact source file declares a class implicitly, the class does not have a name that can be used in source code. The class name that a compiler generates when compiling a compact source file is implementation-specific and should not be relied upon in any source code, including in the compact source file itself.
We can refer to the current instance of the class via this
, either explicitly
or, as above, implicitly, but we cannot instantiate the class with new
. This
reflects an important tradeoff: If beginners have not yet learned
object-oriented concepts such as classes, then writing code in a compact source
file should not require a class declaration — which is what would give the class
a name usable with new
.
Assuming our compact source file is called HelloWorld.java
, it can be either
compiled and run, or run directly with the source-code launcher as follows:
$ java HelloWorld.java
The launcher compiles HelloWorld.java
in memory, treating its fields and
methods as if they are members of a class. The launcher then finds and invokes a
main
method of this class, as described earlier.
If a compact source file has a launchable main
method that is an instance
method, then running that file is equivalent to embedding it in an anonymous
class declaration, instantiating the anonymous class, and invoking the
launchable main
method:
new Object() {
String greeting = "Hello, World!";
void main() {
System.out.println(greeting);
}
}.main();
The javadoc
tool can generate documentation from a compact source file, even
though the implicitly declared class should not be referenced by other classes and
thus cannot be used to define an API. Documenting the members of the implicitly
declared class may be useful for beginners learning about javadoc
, and for
experienced developers prototyping code intended to be used in a larger program.
Interacting with the console
Beginners frequently write programs that need to interact with the console.
Writing to the console ought to be straightforward, but traditionally it
requires calling the inscrutable System.out.println
method. This is
deeply mysterious for beginners: What is System
? What is out
?
Even worse is reading from the console which, again, ought to be a straightforward
method call. Since writing to the console uses System.out
, it seems
reasonable that reading would involve System.in
, but getting a String
from
System.in
can be quite involved; for example:
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String line = reader.readLine();
...
} catch (IOException ioe) {
...
}
Experienced developers are used to this sort of boilerplate, but for
beginners this code contains yet more mysterious concepts, leading to a plethora
of questions: What are try
and catch
, what is a BufferedReader
, what is an
InputStreamReader
, and whatever is an IOException
? There are other
approaches, but none is significantly better, especially for the beginner.
To simplify the writing of interactive programs, we add a class IO
to the java.lang
package, declaring five static
methods:
public static void println(Object obj);
public static void println();
public static void print(Object obj);
public static String readln(String prompt);
public static String readln();
A beginner can now write Hello, World! as:
void main() {
IO.println("Hello, World!");
}
They can then easily move on to the simplest of interactive programs:
void main() {
String name = IO.readln("Please enter your name: ");
IO.print("Pleased to meet you, ");
IO.println(name);
}
Thus a beginner need only learn that the qualifier IO
is used for the basic
line-oriented I/O methods, much like they learn that the qualifier Math
is
used for the mathematical functions, e.g. Math.sin
.
Since the IO
class resides in java.lang
, it can be used without an import
in any Java program. This applies to all programs, not just those in
compact source files or those that use instance main
methods, e.g.
class Hello {
public static void main(String[] args) {
String name = IO.readln("Please enter your name: ");
IO.print("Pleased to meet you, ");
IO.println(name);
}
}
Automatic import of the java.base
module
Many other classes in the Java Platform API are useful in small programs. They can be imported explicitly at the start of a compact source file:
import java.util.List;
void main() {
var authors = List.of("James", "Bill", "Guy", "Alex", "Dan", "Gavin");
for (var name : authors) {
IO.println(name + ": " + name.length());
}
}
Experienced developers will find this natural, though for convenience some might
be inclined to use import-on-demand declarations (i.e., import java.util.*
).
For beginners, however, any form of import
is another source of mystery,
requiring an understanding of the package hierarchy of the Java API.
To further simplify the writing of small programs, we make all of the public top
level classes and interfaces of the packages exported by the java.base
module
available for use in compact source files, as if they were imported on demand.
Popular classes and interfaces in commonly used packages such as java.io
,
java.math
, and java.util
are thus immediately usable. In the example above,
import java.util.List
can be removed since List
will be imported
automatically.
A companion JEP proposes a new kind of
import declaration, import module M
, which imports, on demand, all of the
public top level class and interfaces of the packages exported by
module M
. Every compact source file is considered to import the
java.base
module automatically, as if the declaration
import module java.base;
appears at the start of every compact source file.
Growing a program
A small program in a compact source file is focused on what the program does,
omitting concepts and constructs it does not need. Even so, all members are
interpreted as in an ordinary class. To evolve a compact source file into a
ordinary source file, all we need to do is wrap its fields and methods in an
explicit class
declaration and add some imports. For example, this
compact source file:
void main() {
var authors = List.of("James", "Bill", "Guy", "Alex", "Dan", "Gavin");
for (var name : authors) {
IO.println(name + ": " + name.length());
}
}
can be evolved into an ordinary source file that declares a single class:
import module java.base;
class NameLengths {
void main() {
var authors = List.of("James", "Bill", "Guy", "Alex", "Dan", "Gavin");
for (var name : authors) {
IO.println(name + ": " + name.length());
}
}
}
The main
method does not change in any way. Thus turning a small program into
a class that can serve as a component in a larger program is always
straightforward.
Alternatives
Allow top level statements in compact source files. An alternative design would allow statements to appear directly in a compact source file, removing the need to declare a
main
method. This design would interpret the entire compact source file as the body of an implicitly declaredmain
method of an implicitly declared class.Unfortunately, this design would be very limiting: It would not be possible to declare methods in a compact source file. They would be interpreted as appearing in the body of an invisible
main
method, but this would make them illegal because Java does not support methods-within-methods. Accordingly, compact source files could only represent linear programs that consist of one statement after another, without the power to abstract repeated computations into subroutines.Furthermore, in this design, all variable declarations would be interpreted as local variables of the invisible
main
method. This would be limiting because local variables can only be accessed from lambda expressions if they are effectively final, unlike fields. Accordingly, writing lambda expressions in compact source files would be error-prone and confusing.We believe that the desire to write statements directly in a compact source file is largely driven by the pain of writing
public static void main(String[] args)
. Withmain
methods no longer painful to declare, we believe it is right to focus on methods and fields rather than statements in compact source files.Import fewer packages in compact source files. Rather than automatically importing all 54 packages from the
java.base
module, we could instead import only some of them. But, which ones?Every reader will have suggestions for which packages to auto-import into every small program:
java.io
andjava.util
would be near-universal suggestions;java.util.stream
andjava.util.function
would be common; andjava.math
,java.net
, andjava.time
would each have supporters. For the JShell tool, we managed to find tenjava.*
packages which are broadly useful when experimenting with one-off Java code, but it is difficult to see which subset ofjava.*
packages deserves to be permanently and automatically imported into every compact source file. The list would, moreover, change as the Java Platform evolves; e.g.,java.util.stream
andjava.util.function
were introduced only in Java 8. Developers would likely become reliant on IDEs to remind them of which automatic imports are in effect — an undesirable outcome.Importing all of the packages exported by the
java.base
module is a consistent and reasonable choice for classes implicitly declared by compact source files.Automatically import
static
methods for I/O. In earlier previews of this feature, we explored the possibility of compact source files automatically importing thestatic
methods of the newIO
class. This meant that users could callprintln(...)
in compact source files instead ofIO.println(...)
. While this had the pleasing effect of making the methods inIO
appear built-in to the Java language, it introduced a bump in the on-ramp: When evolving a compact source file into an ordinary source file, astatic
import had to be added. Ultimately we felt this runs contrary to the second goal of the JEP: that beginners can "...grow their code gracefully."Introduce a new dialect of the Java language. A radically different design would define a different dialect of the language for use in compact source files. This would allow all sorts of things to be removed in the pursuit of brevity. For example, we could drop the requirement that
main
methods must be declaredvoid
explicitly. However, this would prevent the graceful evolution of small programs to larger ones.Extend JShell. JShell is an interactive tool for executing Java code immediately. It offers an incremental environment for programming, allowing beginners to experiment without a lot of ceremony.
An alternative design would be to extend JShell to achieve the goals of this JEP. While an attractive idea in theory, the practice is less appealing. A JShell session is not a Java program, but, rather, a series of code snippets. All declarations are interpreted as
static
members of an unspecified class, and all statements are executed in a context in which all previous declarations are in scope.This works well for experimentation — the primary use case for JShell — but is not a good basis as the starting point for writing real programs. If we interpreted a compact source file as a series of code snippets, it would mean that compact source files could only express classes whose methods and fields are
static
, in effect introducing a Java dialect. Evolving a compact source file into an ordinary source file would force users to addstatic
modifiers to methods and fields, preventing the graceful evolution of small programs to larger ones.
- relates to
-
JDK-8344700 Module Import Declarations
-
- Submitted
-
-
JDK-8351435 Change the default Console implementation back to the built-in one in `java.base` module
-
- In Progress
-