The Classic main Method
Read and write public static void main(String[] args), know what each word means, and how Java 25 decides which main method to run.
public class Main {
public static void main(String[] args) {
// args holds the command-line arguments, never null
}
}Before Java 25 every program started from this method, and you will see it in nearly every existing codebase, framework guide and Spring Boot application:
public class Main {
public static void main(String[] args) {
System.out.println("started");
}
}
Word by word
public class Main: a top-level public class must live in a file with the same name,Main.java.public: the launcher, which is outside your class, can call it.static: it belongs to the class, so the JVM can call it without creating an object first.void: it returns nothing. The process exit code is 0 whenmainreturns normally, 1 when an exception escapes it, or whatever you pass toSystem.exit.String[] args: the command-line arguments after the class or file name. It is nevernull; with no arguments it is an empty array.
The static trap
Inside a static method there is no this. Calling an instance method or reading an instance field directly gives "non-static method cannot be referenced from a static context". The fix is not to mark everything static; create an object and call it, which is what frameworks do when they build your application objects.
How Java 25 chooses
Java 25 relaxed the rules for all classes, not only compact source files. main no longer has to be public or static (it only must not be private), and it may take no parameters. When several candidates exist, a main with a String[] parameter is chosen over one without. If the chosen method is an instance method, the launcher creates the object with the class's non-private no-argument constructor first. Existing public static void main(String[] args) methods behave exactly as before.
Back-end use
In a service, main should do very little: read configuration, build the objects, start the server and return or block. Keep argument parsing in its own method so it can be tested with a plain array. Exit with a non-zero code when configuration is invalid, because process managers, Kubernetes and CI all read the exit code, not your log line.
A Spring Boot application's entry point is exactly this method calling SpringApplication.run(App.class, args).
Example
When to use it
- Read --port and --env options from the command line of a small service or batch job.
- Exit with a non-zero code when configuration is invalid so Kubernetes or systemd marks the start as failed.
- Recognise the SpringApplication.run(App.class, args) entry point of an existing Spring Boot service.
More examples
Calling instance code from a static main
static main runs without an instance, so it creates one and calls the method on it. Most real applications do the same: main builds the objects and hands over control.
public class Main {
private final String greeting = "Hello";
String greet(String name) {
return greeting + ", " + name;
}
public static void main(String[] args) {
// greet("ada"); would not compile: non-static method greet(String)
// cannot be referenced from a static context
Main app = new Main();
System.out.println(app.greet("ada"));
}
}
// Output:
// Hello, adaArgument parsing you can test
Because parse takes a plain String[], tests and this demo can call it with any arguments. In main itself, an unknown option would end the process with exit code 1, which is what a deployment should see for bad configuration.
public class Main {
record Config(int port, String env) {}
static Config parse(String[] args) {
int port = 8080;
String env = "dev";
for (String arg : args) {
String[] pair = arg.split("=", 2);
switch (pair[0]) {
case "--port" -> port = Integer.parseInt(pair[1]);
case "--env" -> env = pair[1];
default -> throw new IllegalArgumentException("unknown option: " + pair[0]);
}
}
return new Config(port, env);
}
public static void main(String[] args) {
System.out.println(parse(args));
System.out.println(parse(new String[] {"--port=9090", "--env=prod"}));
try {
parse(new String[] {"--debug"});
} catch (IllegalArgumentException e) {
System.out.println("rejected: " + e.getMessage());
}
}
}
// Output:
// Config[port=8080, env=dev]
// Config[port=9090, env=prod]
// rejected: unknown option: --debugAn instance main in a named class (Java 25)
The launcher finds no main with a String[] parameter, so it picks main(), creates a Main with the default constructor and calls the method. Note the import: only compact source files get java.base imported automatically.
import java.util.List;
public class Main {
private final List<String> routes = List.of("/health", "/users");
// Java 25: main can be an instance method with no parameters, in any class.
void main() {
for (String route : routes) {
System.out.println("registered GET " + route);
}
System.out.println("launched as " + getClass().getSimpleName());
}
}
// Output:
// registered GET /health
// registered GET /users
// launched as Main
Discussion