How to build a Java project with multiple main classes
Categories:
Building Java Projects 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
Main-Class
entry in the MANIFEST.MF
file. However, you can still override this default at runtime by using the -jar
option with the JAR file and then specifying the desired main class.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.