Java Projects
›Short versionOpen if you only have a minute.
- Production code lives under src/main/java. That directory is the source root: packages begin beneath it, and the folder named java is not part of the package.
- One class holds public static void main. Name it in the build file so the IDE and java -jar start the same entry point.
- Tests live under src/test/java and mirror the package they exercise. They compile against main code and stay out of the application jar.
- pom.xml or build.gradle is the project. It declares the Java version, dependencies, source roots, and the main class.
- Commit sources, the build file, and a .gitignore. Leave out target/, build/, *.class, IDE caches, and any file that holds a password.
A zip of .java files in one folder, sitting next to a pile of .class files, compiles on the machine that made it. It does not say which file is the program, which files are experiments, or which directory the next person should open. A small project answers those questions with directories and one before the first feature is interesting.
Where the source actually sits
The is a convention you can rely on because Maven and Gradle already agreed on it. Production code goes in src/main/java. Tests go in a separate . Resources that are not Java, such as a properties file the program reads at runtime, go in src/main/resources and are copied onto the classpath beside the classes. Nothing about the ledger itself has to change for that split to be worth making.
ledger/pom.xmlsrc/main/java/ledger/Ledger.javasrc/main/java/ledger/App.javasrc/main/resources/application.propertiessrc/test/java/ledger/LedgerTest.java
Ledger is the type the rest of the program calls. App is the . Keeping them apart means a test can construct a Ledger without starting the program, and the program can stay a thin place that reads arguments and calls in.
package ledger;
public final class App {
public static void main(String[] args) {
Ledger ledger = new Ledger();
System.out.println(ledger.balance());
}
}The matching test uses the same package name and a different tree. JUnit sees Ledger because the test compile depends on main. Main never imports LedgerTest.
package ledger;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class LedgerTest {
@Test
void aNewLedgerStartsAtZero() {
assertEquals(0, new Ledger().balance());
}
}The file that makes the layout real
Directories alone are a suggestion. The build file is what a new clone and a CI job both execute. A short Maven POM is enough for this project: Java 17, JUnit on the test classpath only, and the main class written into the jar manifest. Gradle's application plugin records the same fact as mainClass. Pick one tool and let the IDE import it.
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>edu.example</groupId>
<artifactId>ledger</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.release>17</maven.compiler.release>
</properties>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.11.4</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.4.2</version>
<configuration>
<archive>
<manifest>
<mainClass>ledger.App</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>mvn test compiles both source roots and runs LedgerTest. mvn package writes target/ledger-1.0.0.jar. That target directory is an output. Gradle's equivalent is build. Either one can be deleted and recreated from the sources and the build file. Class files dropped beside the .java files are the same output, just harder to see.
What stays out of the repository
Commit the source roots, the resources, the build file, and a .gitignore that already knows the outputs. Leave the outputs themselves. Also leave editor caches and anything that contains a real password. A sample properties file with empty placeholders is fine. A file that opens your database is not.
target/build/*.class.idea/*.iml.classpath.settings/.DS_Store
The first clone should be boring. Open the directory, run the build, read src/main/java. If that works, the layout has done its job and the interesting code can live inside it.
A teammate clones the repository and target/ is missing. What do they run, and why was that directory absent on purpose?
They run the build, mvn test or the Gradle equivalent. target/ and build/ are compiler output. Committing them stores one machine's class files and fights the next compile. The sources and the build file are enough to recreate them.