How to build a Java project with multiple main classes

Learn how to build a java project with multiple main classes with practical examples, diagrams, and best practices. Covers java, class, project development techniques with visual explanations.

Building Java Projects with Multiple Main Classes

Hero image for How to build a Java project with multiple main classes

Learn how to structure, compile, and run Java applications that contain more than one entry point, enabling flexible execution paths.

In Java development, it's common to encounter projects that require multiple entry points. This could be for various reasons: having separate utilities, different application modes (e.g., GUI vs. command-line), or distinct test runners. While a typical Java application has a single public static void main(String[] args) method, the Java Virtual Machine (JVM) allows you to specify which main method to execute at runtime. This article will guide you through structuring your project, compiling it, and executing specific main classes.

Understanding Multiple Entry Points

A Java project can indeed contain multiple classes, each with its own main method. The main method serves as the program's entry point. When you run a Java application, you tell the JVM which class's main method to invoke. This flexibility is powerful for managing complex projects or providing different functionalities within a single codebase.

flowchart TD
    A[Java Project] --> B{Class A with main()}
    A --> C{Class B with main()}
    A --> D{Class C with main()}
    B -- JVM Execution --> E[Application Flow 1]
    C -- JVM Execution --> F[Application Flow 2]
    D -- JVM Execution --> G[Application Flow 3]

Conceptual flow of a Java project with multiple main classes, each leading to a different execution path.

Structuring Your Project

For clarity and maintainability, it's best practice to organize your classes into packages. This helps avoid naming conflicts and logically groups related code. Let's consider a simple project with two main classes: one for a greeting application and another for a calculator.

package com.example.app;

public class Greeter {
    public static void main(String[] args) {
        if (args.length > 0) {
            System.out.println("Hello, " + args[0] + "!");
        } else {
            System.out.println("Hello, World!");
        }
    }
}

Greeter.java: A simple class with a main method to greet a user.

package com.example.util;

public class Calculator {
    public static void main(String[] args) {
        if (args.length == 3) {
            try {
                int num1 = Integer.parseInt(args[0]);
                String operator = args[1];
                int num2 = Integer.parseInt(args[2]);

                switch (operator) {
                    case "+":
                        System.out.println("Result: " + (num1 + num2));
                        break;
                    case "-":
                        System.out.println("Result: " + (num1 - num2));
                        break;
                    default:
                        System.out.println("Unsupported operator: " + operator);
                }
            } catch (NumberFormatException e) {
                System.out.println("Invalid numbers provided.");
            }
        } else {
            System.out.println("Usage: java com.example.util.Calculator <num1> <operator> <num2>");
        }
    }
}

Calculator.java: Another class with a main method for basic arithmetic.

Compiling and Running

Once your Java files are structured, the compilation process remains largely the same. You compile all .java files into .class files. The key difference comes during execution, where you explicitly specify which main method to run.

1. Compile the Java files

Navigate to the root directory of your project (e.g., where com folder resides) in your terminal and compile all Java files. The -d flag specifies the output directory for compiled class files.

2. Run the Greeter application

To run the Greeter application, specify its fully qualified class name to the java command. You can also pass arguments to the main method.

3. Run the Calculator application

Similarly, to run the Calculator application, provide its fully qualified class name and any necessary arguments.

Compile

javac -d . com/example/app/Greeter.java com/example/util/Calculator.java

Run Greeter

java com.example.app.Greeter John

Expected Output: Hello, John!

Run Calculator

java com.example.util.Calculator 10 + 5

Expected Output: Result: 15

Using Build Tools (Maven/Gradle)

For larger projects, manually compiling and running becomes cumbersome. Build tools like Maven or Gradle simplify this process significantly. They handle dependency management, compilation, packaging, and can be configured to run specific main classes.

graph TD
    A[Developer] --> B(Build Tool: Maven/Gradle)
    B --> C{Compile Source Code}
    B --> D{Package into JAR}
    B --> E{Run Specific Main Class}
    C --> F[Class Files]
    D --> G[Executable JAR]
    E --> H[JVM Execution]

Workflow illustrating how build tools streamline the process of compiling, packaging, and running Java projects with multiple main classes.

With Maven, you can configure the maven-jar-plugin to specify the Main-Class for the generated JAR. For running different main classes without rebuilding the JAR, you can use the exec-maven-plugin.

<project>
    ...
    <build>
        <plugins>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>3.0.0</version>
                <configuration>
                    <mainClass>com.example.app.Greeter</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>
    ...
</project>

Maven pom.xml configuration to run the Greeter class using exec-maven-plugin.

# To run the Greeter class
mvn exec:java -Dexec.mainClass="com.example.app.Greeter" -Dexec.args="MavenUser"

# To run the Calculator class
mvn exec:java -Dexec.mainClass="com.example.util.Calculator" -Dexec.args="20 - 10"

Running specific main classes using Maven's exec:java goal.

Gradle offers similar flexibility. You can define multiple application plugins or use custom tasks to run different main classes.

apply plugin: 'java'
apply plugin: 'application'

mainClassName = 'com.example.app.Greeter'

task runCalculator(type: JavaExec) {
    classpath = sourceSets.main.runtimeClasspath
    main = 'com.example.util.Calculator'
    args '10', '*', '2'
}

Gradle build.gradle configuration to define a custom task for running the Calculator.

# To run the default main class (Greeter)
gradle run --args="GradleUser"

# To run the Calculator using the custom task
gradle runCalculator

Running specific main classes using Gradle tasks.