Java has evolved significantly in how we can define main methods and application entry points. This guide covers traditional approaches, modern compact syntax, and emerging alternatives for Java application startup.
Traditional Main Method
Classic Approach
public class TraditionalMain { public static void main(String[] args) { System.out.println("Hello, World!"); for (String arg : args) { System.out.println("Argument: " + arg); } } } Compilation and Execution:
javac TraditionalMain.java java TraditionalMain arg1 arg2
Modern Compact Approaches
1. Implicit Classes (Java 21+ Preview)
// No class declaration needed! void main() { System.out.println("Hello from implicit class!"); } 2. Compact Main Method (Java 21+)
// No public access modifier, no String[] parameter required class CompactMain { static void main() { System.out.println("Compact main method!"); } } 3. String[] Parameter Optional
class FlexibleMain { // Both forms are valid static void main() { System.out.println("No args needed"); } static void main(String[] args) { System.out.println("With args: " + args.length); } } Complete Examples by Java Version
Java 21+ Unnamed Classes and Instance Main Methods
// SimpleMain.java - Run directly with: java SimpleMain.java void main() { System.out.println("🎉 No class declaration needed!"); System.out.println("Running in " + java.time.LocalTime.now()); } // With command-line arguments void main(String[] args) { System.out.println("Received " + args.length + " arguments:"); for (int i = 0; i < args.length; i++) { System.out.println((i + 1) + ": " + args[i]); } } // Using modern Java features void main() { var message = """ 🚀 Compact Main Method Features: • No explicit class declaration • No public modifier required • String[] parameter optional • Perfect for scripting and simple apps """; System.out.println(message); } Single-File Source-Code Programs
// Calculator.java - Run with: java Calculator.java 5 + 3 void main(String[] args) { if (args.length != 3) { System.out.println("Usage: java Calculator.java <num1> <operator> <num2>"); return; } double num1 = Double.parseDouble(args[0]); String operator = args[1]; double num2 = Double.parseDouble(args[2]); double result = switch (operator) { case "+" -> num1 + num2; case "-" -> num1 - num2; case "*" -> num1 * num2; case "/" -> num1 / num2; default -> throw new IllegalArgumentException("Unknown operator: " + operator); }; System.out.printf("%s %s %s = %.2f%n", num1, operator, num2, result); } Practical Use Cases
1. Quick Scripts and Utilities
// FileConverter.java void main(String[] args) throws IOException { if (args.length < 2) { System.out.println("Usage: java FileConverter.java <input> <output>"); return; } String inputFile = args[0]; String outputFile = args[1]; String content = Files.readString(Path.of(inputFile)); String converted = content.toUpperCase(); Files.writeString(Path.of(outputFile), converted); System.out.println("Converted " + inputFile + " to " + outputFile); } 2. API Testing Client
// ApiTester.java void main(String[] args) throws Exception { String url = args.length > 0 ? args[0] : "https://api.github.com/users/java"; HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(url)) .header("Accept", "application/json") .build(); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println("Status: " + response.statusCode()); System.out.println("Body: " + response.body()); } 3. Data Processing Script
// DataProcessor.java void main(String[] args) { var data = """ Alice,25,Engineer Bob,30,Designer Charlie,35,Manager """; records Person(String name, int age, String job) {} var people = data.lines() .map(line -> line.split(",")) .map(parts -> new Person(parts[0], Integer.parseInt(parts[1]), parts[2])) .toList(); // Average age double avgAge = people.stream() .mapToInt(Person::age) .average() .orElse(0); System.out.printf("Average age: %.1f%n", avgAge); people.forEach(p -> System.out.println(p.name() + " - " + p.job())); } Advanced Compact Patterns
1. Modular Compact Applications
// App.java - Self-contained modular application void main() { var app = new App(); app.start(); } class App { private final List<String> messages = new ArrayList<>(); void start() { messages.add("Application started at " + java.time.LocalDateTime.now()); processData(); displayResults(); } private void processData() { // Simulate some processing messages.add("Processed " + (int)(Math.random() * 100) + " items"); } private void displayResults() { System.out.println("=== APPLICATION RESULTS ==="); messages.forEach(System.out::println); } } 2. Interactive Console Application
// InteractiveApp.java void main() { var scanner = new java.util.Scanner(System.in); System.out.println("🔧 Interactive Configuration"); System.out.print("Enter your name: "); String name = scanner.nextLine(); System.out.print("Enter your age: "); int age = scanner.nextInt(); var user = new User(name, age); displayWelcome(user); } record User(String name, int age) {} void displayWelcome(User user) { var message = """ 🎊 WELCOME %s! Profile Summary: • Name: %s • Age: %d • Birth Year: %d Enjoy using the application! """.formatted(user.name(), user.name(), user.age(), java.time.Year.now().getValue() - user.age()); System.out.println(message); } 3. HTTP Server Example
// SimpleServer.java void main() throws IOException { int port = 8080; var server = com.sun.net.httpserver.HttpServer.create( new java.net.InetSocketAddress(port), 0); server.createContext("/", exchange -> { String response = """ { "message": "Hello from compact Java!", "timestamp": "%s", "version": "Java %s" } """.formatted(java.time.Instant.now(), System.getProperty("java.version")); exchange.getResponseHeaders().set("Content-Type", "application/json"); exchange.sendResponseHeaders(200, response.getBytes().length); try (var os = exchange.getResponseBody()) { os.write(response.getBytes()); } }); server.start(); System.out.println("🚀 Server running on http://localhost:" + port); System.out.println("Press Ctrl+C to stop..."); } Build Tool Integration
Maven with Exec Plugin
<!-- pom.xml --> <build> <plugins> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>3.1.0</version> <configuration> <mainClass>CompactMain</mainClass> <commandlineArgs>arg1 arg2</commandlineArgs> </configuration> </plugin> </plugins> </build>
mvn compile exec:java
Gradle Application Plugin
// build.gradle plugins { id 'application' } application { mainClass = 'CompactMain' } run { args = ['arg1', 'arg2'] } gradle run
Migration Strategies
From Traditional to Compact
// BEFORE: Traditional approach public class TraditionalApp { public static void main(String[] args) { if (args.length > 0) { System.out.println("Arguments: " + String.join(", ", args)); } else { System.out.println("No arguments provided"); } } } // AFTER: Compact approach class CompactApp { static void main(String[] args) { var message = args.length > 0 ? "Arguments: " + String.join(", ", args) : "No arguments provided"; System.out.println(message); } // Or even more compact: static void main() { System.out.println("Running without arguments"); } } Best Practices and Patterns
1. Error Handling in Compact Mains
// RobustMain.java void main(String[] args) { try { if (args.length == 0) { throw new IllegalArgumentException("No arguments provided"); } int result = processArguments(args); System.out.println("Result: " + result); } catch (NumberFormatException e) { System.err.println("Error: Invalid number format"); System.exit(1); } catch (IllegalArgumentException e) { System.err.println("Error: " + e.getMessage()); System.exit(1); } catch (Exception e) { System.err.println("Unexpected error: " + e.getMessage()); e.printStackTrace(); System.exit(2); } } int processArguments(String[] args) { return Arrays.stream(args) .mapToInt(Integer::parseInt) .sum(); } 2. Configuration Pattern
// ConfigurableApp.java void main(String[] args) { var config = loadConfig(args); runApplication(config); } record AppConfig(String inputFile, String outputFile, boolean verbose) {} AppConfig loadConfig(String[] args) { String inputFile = "input.txt"; String outputFile = "output.txt"; boolean verbose = false; for (int i = 0; i < args.length; i++) { switch (args[i]) { case "-i" -> inputFile = args[++i]; case "-o" -> outputFile = args[++i]; case "-v" -> verbose = true; } } return new AppConfig(inputFile, outputFile, verbose); } void runApplication(AppConfig config) { if (config.verbose()) { System.out.println("Starting application with config: " + config); } // Application logic here System.out.println("Processing " + config.inputFile() + " to " + config.outputFile()); } 3. Command Pattern for Complex Applications
// CommandApp.java void main(String[] args) { if (args.length == 0) { showHelp(); return; } var command = args[0]; var commandArgs = Arrays.copyOfRange(args, 1, args.length); switch (command) { case "greet" -> greet(commandArgs); case "calculate" -> calculate(commandArgs); case "help" -> showHelp(); default -> System.out.println("Unknown command: " + command); } } void greet(String[] args) { String name = args.length > 0 ? args[0] : "World"; System.out.println("Hello, " + name + "!"); } void calculate(String[] args) { if (args.length != 3) { System.out.println("Usage: calculate <num1> <operator> <num2>"); return; } double a = Double.parseDouble(args[0]); String op = args[1]; double b = Double.parseDouble(args[2]); double result = switch (op) { case "+" -> a + b; case "-" -> a - b; case "*" -> a * b; case "/" -> a / b; default -> throw new IllegalArgumentException("Unknown operator: " + op); }; System.out.println(a + " " + op + " " + b + " = " + result); } void showHelp() { System.out.println(""" Available commands: greet [name] - Greet someone calculate a op b - Perform calculation help - Show this help """); } Testing Compact Main Methods
JUnit Testing
// Test for compact main methods class CompactMainTest { @Test void testMainWithoutArgs() { // Capture output var outputStream = new ByteArrayOutputStream(); System.setOut(new PrintStream(outputStream)); // Run main method CompactMain.main(); assertThat(outputStream.toString()).contains("Compact main method"); } @Test void testMainWithArgs() { var outputStream = new ByteArrayOutputStream(); System.setOut(new PrintStream(outputStream)); CompactMain.main(new String[]{"test", "args"}); assertThat(outputStream.toString()).contains("With args: 2"); } } Performance Considerations
Startup Time Optimization
// Optimized for fast startup class FastStartupApp { static void main() { // Minimal class loading var startTime = System.nanoTime(); // Core logic performTask(); var duration = (System.nanoTime() - startTime) / 1_000_000.0; System.out.printf("Task completed in %.2f ms%n", duration); } private static void performTask() { // Use primitives and avoid heavy initialization int result = 0; for (int i = 0; i < 1000; i++) { result += i; } System.out.println("Result: " + result); } } Conclusion
Compact main methods in Java provide:
- Reduced boilerplate for simple applications and scripts
- Improved readability with less ceremonial code
- Faster prototyping and experimentation
- Better scripting support for single-file programs
When to use compact main methods:
- Simple utilities and scripts
- Prototyping and experimentation
- Educational examples
- Single-file applications
When to stick with traditional main methods:
- Large, complex applications
- Enterprise applications with frameworks
- Libraries and APIs
- Applications requiring specific access modifiers
The compact main method syntax represents Java's evolution towards more developer-friendly syntax while maintaining backward compatibility and enterprise readiness.