Understanding the Java Classpath: Building a Project Manually
Tags: java, beginners, tutorial
Published: 2019-07-27 10:37:40

---
title: Understanding the Java Classpath: Building a Project Manually
cover_image: https://i.imgur.com/qbxrrs1.png
published: true
description: Even Java's official documentation doesn't like talking about the Classpath. Let's demystify it a little. 
tags: java, beginners, tutorial
---

This is the second post in a series looking to understand Java projects. Earlier, [we looked at how Java actually gets installed onto a macOS system](https://dev.to/martingaston/getting-started-with-a-java-environment-on-macos-541c), and now we're going to build a basic app. Then we'll move on to incorporating the same app with help from Gradle, a popular build tool, and we'll finish by incorporating our project into the IntelliJ IDE.

### Our directory structure

We're going to be compiling a totally jazzed up version of Hello World, and our overblown example will make use of packages, libraries and tests because it's a more indicative of a project we'd spot in the wild. This project is intended for people who have worked a little with Java and isn't going to dwell much on syntax outside of tooling.

We'll be loosely following the conventions of the Maven [standard directory layout](https://maven.apache.org/guides/introduction/introduction-to-the-standard-directory-layout.html), which is admittedly a little overkill for our tiny app - but we're also going to charge ahead with it because it's indicative of the conventions we'll commonly run into when working with Java. Here's how that looks: 
```
.
├── bin
├── lib
│   ├── jfiglet-0.0.8.jar
│   └── junit-platform-console-standalone-1.4.2.jar
└── src
    ├── main
    │   └── java
    │       └── space
    │           └── gaston
    │               ├── greeter
    │               │   └── Greeter.java
    │               ├── helloWorld
    │               │   └── HelloWorld.java
    │               └── textEffects
    │                   └── Bubble.java
    └── test
        └── java
            └── space
                └── gaston
                    ├── greeter
                    │   └── GreeterTest.java
                    └── textEffects
                        └── BubbleTest.java
```

Let's unpack what we've got here:
- `bin` will contain our compiled `.class`  files
- `lib` will hold our third-party libraries. In this case we're using JFiglet and the JUnit test runner, which we'll get back to later.
- `src` will house our `.java` source code. Within `src` you have subdirectories `main` and `test` , which both have a `java` subdirectory to denote the language, and then the [standard Java package hierarchy](https://docs.oracle.com/javase/tutorial/java/package/packages.html). Eventually, after months of digging, you finally stumble upon our actual `.java` files. 

*Just a quick caveat: I haven't checked the `lib` folder into Git so you'll need to grab the [JFiglet](https://search.maven.org/artifact/com.github.lalyos/jfiglet/0.0.8/jar) and [JUnit](https://repo1.maven.org/maven2/org/junit/platform/junit-platform-console-standalone/1.4.2/junit-platform-console-standalone-1.4.2.jar) `.jar` files from Maven if you're looking to build this project yourself. This is, partially, to expose how fiddly it is to manage dependencies manually and help us understand how, later on, how awesome it is that other tools can come to our rescue.* 

### .java, .class and .jar

Okay, so I threw around quite a few filetypes just then. I think it's entirely common to be hidden from a lot of these specifics by our IDE, so here's a quick summary of what each of those types does:

- A `.java` file is a plain-text file of human-readable Java source code - though admittedly that 'readable' designation is, ahem, rather debatable when it comes to certain aspects of Java syntax. Basically, `.java` files are the files we squishy human flesh monsters sling our code in. 
- A `.class` file is compiled Java bytecode that can be executed by the Java Virtual Machine, an admittedly snazzy tool which runs our Java programs. These cold, mechanical files are the ones the computer likes read. 
- A `.jar` file is an archive of `.class` files (and other necessary resources) all neatly zipped up for convenience. 

### javac and java

Two of our JDK binaries are responsible for compiling and running our code: `javac` and `java`. In short, `javac` is responsible for turning our `.java` files into `.class` files that `java` can run. 

If we put together a totally barebones `HelloWorld.java`, we can then feed it as an argument to `javac` and run it with `java`: 
```java
public class HelloWorld {
  public static void main(String[] args) {
    System.out.println("Oh no, not another Hello World example...");
  }
}
```

Without multiple `.java` files, external libraries or output directories to worry about, compiling and running this is as straightforward as sending the file path to `javac` and then running the output:
```bash
$ javac HelloWorld.java
$ java HelloWorld
Oh no, not another Hello World example...
```

Notice how the `.class` file will be currently be compiled into the same directory  as its companion source code, and that we call the `HelloWorld` class directly with `java` with no `.class` extension - the latter would trigger a dreaded `java.lang.ClassNotFoundException` error, but that's a story for another day. It turns out we use `java` to run Java classes, not Java `.class` files.

### Classpath

Another potential error we might see right about now is a `java.lang.NoClassDefFoundError`, which usually comes about because there's a `.class` file that was created at compile time (with `javac`) but has got lost somewhere when we're trying to run it with `java`. 

Be warned to those of a fragile constitution, the next word is enough to give you the vapours. This frightful concept has dashed the hopes and dreams of event the strongest minds, adventurer. Abandon all hope all ye who enter... the *classpath*. 

Let's zoom in our our main `.java` files:
```
.
├── greeter
│   └── Greeter.java
├── helloWorld
│   └── HelloWorld.java
└── textEffects
    └── Bubble.java
```

Our `Greeter.java`, `HelloWorld.java` and `Bubble.java` are all being stored in different packages (which, in Java, also means different directories) and have requirements of their own. `HelloWorld` includes both a `Greeter` and a `Bubble`, with `Bubble` itself an adapter to the third-party `FigletFont` class from the JFiglet `.jar` in our `lib` folder.

Over in our `test` folder we've got tests for `Greeter` and `Bubble`, which includes classes from JUnit in `lib` as well as requiring the actual `Greeter` and `Bubble` classes to test. 

Welcome, friends, to dependencies. 

Java, while pretty smart, needs to know where to go sniffing for all these requirements - hence the classpath. We can get the scoop right from the [horse's mouth](https://docs.oracle.com/javase/tutorial/essential/environment/paths.html): 

> The default value of the class path is ".", meaning that only the current directory is searched. Specifying either the CLASSPATH variable or the `-cp` command line switch overrides this value.

Also: 

> Setting the `CLASSPATH` can be tricky and should be performed with care.

Which is just charming. Essentially, our classpath needs to contain the path to our `.jar` files to the top of our package hierarchies. It can be set either via environment variable, which you shouldn't do, or with the much better option of the `-cp` flag. 

It also helps explain a common Java gotcha, where you try and run `java` without `cd`'ing into the directory first. 
```bash
$ java bin.space.gaston.helloworld.HelloWorld
Error: Could not find or load main class bin.space.gaston.helloworld.HelloWorld
Caused by: java.lang.NoClassDefFoundError: space/gaston/helloworld/HelloWorld (wrong name: bin/space/gaston/helloworld/HelloWorld)
```

`bin`, you see, isn't part of the Java package hierarchy, but does need to be on the classpath if you're outside of the folder. So both of the below would work:

```bash
$ cd bin
$ java space.gaston.helloworld.HelloWorld
```

```bash
$ java -cp bin space.gaston.helloworld.HelloWorld
```

### Bringing it all together

With all that in mind, we can start to compile our files into the `bin` directory. From the top of our project directory, we can get to work. 

#### 1. Compiling our `main` folder: 
```bash
$ javac -d bin -cp lib/jfiglet-0.0.8.jar src/main/java/space/gaston/greeter/Greeter.java src/main/java/space/gaston/helloWorld/HelloWorld.java src/main/java/space/gaston/textEffects/Bubble.java
```

We're now using the `-d` flag to specify where our compiled files should end up. We're manually feeding each of our `.java` files to `javac`, so we don't need to add them to the classpath, but we do need to add our JFiglet `.jar` file so that `Bubble` can compile. 

#### 2. Compiling our `test` folder: 
```bash
$ javac -d bin -cp lib/junit-platform-console-standalone-1.4.2.jar:lib/jfiglet-0.0.8.jar src/main/java/space/gaston/textEffects/Bubble.java src/test/java/space/gaston/textEffects/BubbleTest.java src/main/java/space/gaston/greeter/Greeter.java src/test/java/space/gaston/greeter/GreeterTest.java
```

We need to add both the JFiglet and JUnit `.jar` files to our classpath, and now we've also got to feed in each test file and the file its testing to the compiler. We could effectively consolidate steps 1 and 2, but it's good to break them up for demonstration purposes here as I think it helps illustrate what's going on.  

Our bin file will now look like this - notice that the directory structure of our `.class` files must maintain the same package hierarchy as the `.java` source files: 
```
.
├── BubbleTests.class
├── GreeterTests.class
└── space
    └── gaston
        ├── greeter
        │   └── Greeter.class
        ├── helloworld
        │   └── HelloWorld.class
        └── textEffects
            └── Bubble.class
```

#### 3. Running our tests:
```
$ java -jar lib/junit-platform-console-standalone-1.4.2.jar -cp bin:lib/jfiglet-0.0.8.jar --scan-class-path
```
```bash
╷
├─ JUnit Jupiter ✔
│  ├─ BubbleTests ✔
│  │  └─ helloReturnsAsciiHello() ✔
│  └─ GreeterTests ✔
│     ├─ greetWithArgumentSteveReturnsHelloSteve() ✔
│     └─ greetWithNoArgsReturnsHelloWorld() ✔
└─ JUnit Vintage ✔
```

Our version of JUnit can be run on the command line. We're passing in the `--scan-class-path` flag to automatically have JUnit look for all tests on our classpath, so this requires adding the `bin` folder to the classpath (because we're at the top of our project folder) as well as JFiglet, which is still required by `Bubble`. 

Also, yay, the tests pass. 

#### 4. Running our main app:
```
$ java -cp bin:lib/jfiglet-0.0.8.jar space.gaston.helloworld.HelloWorld
```
```
  _   _          _   _             ____    _
 | | | |   ___  | | | |   ___     / ___|  | |_    ___  __   __   ___
 | |_| |  / _ \ | | | |  / _ \    \___ \  | __|  / _ \ \ \ / /  / _ \
 |  _  | |  __/ | | | | | (_) |    ___) | | |_  |  __/  \ V /  |  __/
 |_| |_|  \___| |_| |_|  \___/    |____/   \__|  \___|   \_/    \___|
```

No, I have no idea either why we tasked it to output 'Hello Steve'. 

So, great. We've got that working. But, gosh, wasn't that a bit much for even a simple, totally contrived application? Can you imagine that literally every time you make a change to the app and need to recompile? I don't know about you, but if I had to work like that for over a week I'd be permanently stuck looking like I was cosplaying Edvard Munch's The Scream. 

In the next post, the cavalry will march in to bail us out of a lifetime of perpetual build horror. 

***

Has this post been useful for you? I'd really appreciate any comments and feedback on whether there's anything that could be made clearer or explained better. I'd also massively appreciate any corrections!