How to run Java without Maven, Gradle, or IDE
Tags: java, gradle, commandline
Published: 2022-08-10 13:51:43

---
title: How to run Java without Maven, Gradle, or IDE
published: true
description: How to run Java using command line
tags: #java #gradle #commandline
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3tjckydvghjj30h85mfz.jpg
# Use a ratio of 100:42 for best results.
---

When you want to start programming using Java, you're most likely use Maven, Gradle, or an Integrated Development Environment (IDE) like IntelliJ or NetBeans. These tools has one thing in common, they're slow!

To start an empty Java project, Gradle takes more than 2 minutes.

```
BUILD SUCCESSFUL in 2m 38s
3 actionable tasks: 3 executed
```

Maven is better, although 57 seconds isn't exactly fast either.

```
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  57.824 s
[INFO] ------------------------------------------------------------------------
```

In a complex Java project, we could argue this overhead is worth it. But, what if you don't want to start a complex Java project? What if you just want to try a Java library, test your programming logic, or trying out a new Java syntax?

For a simple project, Java's build-in program like `javac` and `java` is enough. You can start write a Java program with a text editor and a terminal. That's it!

In this article, I'll show three examples of using JDK tools and a terminal to run a Java program.



#### 1. Running a self-contained Java program

The first example is a class that takes a String as an input and produce hash as an output. This class doesn't need any third-party library. This class only use Java's build-in classes.

```java
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class Md5Util1 {
    public static void main(String[] args) throws NoSuchAlgorithmException {
        String plainText = args[0];

        MessageDigest md = MessageDigest.getInstance("MD5");
        md.update(plainText.getBytes());
        byte[] digest = md.digest();

        // Convert byte array to a hexadecimal String
        StringBuilder sb = new StringBuilder(2 * digest.length);
        for (byte b : digest) {
            sb.append(String.format("%02x", b));
        }

        System.out.println("MD5 Hash: " + sb);
    }
}
```

Open a terminal to compile and run this program.

```
$ javac Md5Util1.java
$ java Md5Util1 "Hello, world!"
MD5 Hash: 6cd3556deb0da54bca060b4c39479839
```

The compilation takes 0.7 second. No need to run an IDE that takes a lot of memory. Nor does waiting the build system finished configuring something. Just write the code!

As a refresher, `javac` is JDK's program that compile `java` code to bytecode.

```
$ javac Md5Util1.java
$ ls
Md5Util1.java Md5Util1.class
```

`java` is JDK's program that run a Java class.

```
$ man java
NAME
       java - Java application launcher

SYNOPSIS
       java [ options ] class [ argument...  ]
```

Note that we don't use `.class` extension, we just use the class name. Instead of `java Md5Util1.class`, we use `java Md5Util1`.

The argument(s) is passed to the Class's main method (`public static void main(String[] args)`). In this case, "Hello, world!" is passed on `Md5Util1` main method.

We'll explore the `java` options in the next example.

Update:
Since Java 11, you can run java directly like this.

```
$ java Md5Util1 "Hello, world!"
MD5 Hash: 6cd3556deb0da54bca060b4c39479839
```

#### 2. Running a JUnit test

Even in a simple project, you need to use a third-party library. Like JUnit. JUnit is the de-facto library for testing Java code. Let's modify the Md5Util1 to make it easier to test.

```java
import java.security.MessageDigest;

public class Md5Util2 {
    public static void main(String[] args) throws Exception {
        String plainText = args[0];
        String output = stringToMd5(plainText);

        System.out.println("MD5 Hash: " + output);
    }

    public static String stringToMd5(String input) throws Exception {
        MessageDigest md = MessageDigest.getInstance("MD5");
        md.update(input.getBytes());
        byte[] digest = md.digest();

        // Convert byte array to a hexadecimal String
        StringBuilder sb = new StringBuilder(2 * digest.length);
        for (byte b : digest) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }
}
```

We've extracted the code for hashing in `stringToMd5` method. Now, let's add the test class.

```java
import org.junit.Test;

import static org.junit.Assert.assertEquals;

public class Md5Util2Test {

    @Test
    public void helloWorld() throws Exception {
        // Given
        String input = "Hello, world!";
        String expectedOutput = "6cd3556deb0da54bca060b4c39479839";

        // When
        String actualOutput = Md5Util2.stringToMd5(input);

        // Then
        assertEquals(expectedOutput, actualOutput);
    }
}
```

Again, open a terminal to compile and run this program.

```
$ javac Md5Util2.java Md5Util2Test.java
Md5Util2Test.java:1: error: package org.junit does not exist
import org.junit.Test;
```

This time, `javac` shows an error!  `javac` doesn't know where to find the `org.junit` package. We need to inform `javac` where to find the the JUnit package and classes.

Java classes is bundled in a `jar` file. So, we should download the `junit.jar` file and attach it during compilation. We use `-cp` option to include the path of the class (hence, **c**lass **p**ath).

Download the junit's jar file in the mvnrepository.com (or the junit website). In this example, I use JUnit 4.12.

```
$ javac -cp junit-4.12.jar Md5Util2.java Md5Util2Test.java
$ ls -al | grep ".class"
Md5Util2.class
Md5Util2Test.class
```

Now, let's run the test!

```
$ java -cp .:junit-4.12.jar org.junit.runner.JUnitCore Md5Util2Test
Exception in thread "main" java.lang.NoClassDefFoundError: org/hamcrest/SelfDescribing
	at java.base/java.lang.ClassLoader.defineClass1(Native Method)
	at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1017)
	at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:174)
```

Oops, another error. This is why we should read [the manual](https://github.com/junit-team/junit4/wiki/Getting-started) :sweat_smile:.

JUnit require hamcrest.jar to run the test. So, heads over to mvnrepository.com to download the hamcrest.jar. In this example, I use version 1.3.

The class path can be used to in `java` command too. Let's try to run the test again.

```
$ java -cp .:junit-4.12.jar:hamcrest-core-1.3.jar org.junit.runner.JUnitCore Md5Util2Test
JUnit version 4.12
.
Time: 0.01

OK (1 test)
```

Use `colon` to separate classes in the class path. `.:junit-4.12.jar:hamcrest-core-1.3.jar` means we add the current path (dot is the current path) and two other jar files to run the program.

When we run a JUnit test, we're actually running `org.junit.runner.JUnitCore` class. We pass the target test class to the `JUnitCore` arguments. In this case, `Md5Util2Test` is the target test class.

At this point, you might be wondering.. Why we don't need to add `MessageDigest` in the class path? Java already add JDK's standard classes in the class path. The classes is bundled as `rt.jar` file. It's located in the `/Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home/jre/lib` directory.

You can exract the `rt.jar` by using `jar -xf rt.jar` command.

```
$ tree -L 1
.
├── META-INF
├── apple
├── com
├── java
├── javax
├── jdk
├── org
├── rt.jar
└── sun

$ cd java

$ tree -L 1
.
├── applet
├── awt
├── beans
├── io
├── lang
├── math
├── net
├── nio
├── rmi
├── security
├── sql
├── text
├── time
└── util
```

Other than java.security.MessageDigest, you can see other useful Java classes, such as:

- java.math.BigDecimal
- java.time.Duration
- java.util.ArrayList



#### 3. Running a Java program with multiple JAR dependencies

As you see in example 2, some JAR may depends on other JARs. The more JAR we use in the project, the more complicated the compilation and running command is. This is why build tools like Maven or Gradle exist.

That being said, we can still use JDK's tool to run the program. Let's use Retrofit library as example. Retrofit is a popular REST API library that uses `Interface` as the API declaration. I use the sample code from Retrofit website verbatim to simplify the example.

```java
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Path;

import java.util.List;

public interface GitHubService {
    @GET("users/{user}/repos")
    Call<List<Repo>> listRepos(@Path("user") String user);
}
```

The `Repo` object from GitHub API is contains lots of information about a repository.  In this case, we're only interested in Repo's `id` and `name`.

```java
public class Repo {
    public String id;
    public String name;
}
```

Next, create GitHubRetrofit class that build the Retrofit instance.

```java
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import java.util.List;

public class GitHubRetrofit {

    private Retrofit retrofit;
    private GitHubService service;

    public GitHubRetrofit() {
        retrofit = new Retrofit.Builder()
                .baseUrl("https://api.github.com/")
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        service = retrofit.create(GitHubService.class);
    }

    public Repo helloWorldRepo() throws Exception {
        Response<List<Repo>> response = service.listRepos("octocat").execute();
        return response.body()
                .stream()
                .filter(repo -> repo.name.equals("Hello-World"))
                .findFirst()
                .get();
    }
}

```

Finally, we create the test class that test the GitHubRetrofit class.

```java
import org.junit.Test;

import static org.junit.Assert.assertEquals;

public class GitHubRetrofitTest {

    @Test
    public void repos() throws Exception {
        // Given
        CallGitHub callGitHub = new CallGitHub();
        String expectedName = "Hello-World";

        // When
        Repo repo = callGitHub.helloWorldRepo();

        // Then
        assertEquals(repo.name, expectedName);
    }
}

```

Even with this simple API call example, there are many classes and import statements. So, there should be several JAR files involved. Of course, we need the retrofit, junit, and hamcrest JAR. But, how do we know which JAR files to download in mavenrepository?

This is why we use Maven or Gradle. They manages dependencies for us. Maven uses Project Object Model (POM) to describe the dependencies. The POM file itself is available in the [mvnrepository's Retrofit page.](https://mvnrepository.com/artifact/com.squareup.retrofit2/retrofit/2.9.0) For example:

```xml
<dependencies>
	<dependency>
		<groupId>com.squareup.retrofit2</groupId>
		<artifactId>retrofit</artifactId>
		<version>2.9.0</version>
		<scope>compile</scope>
	</dependency>
	<dependency>
		<groupId>com.google.code.gson</groupId>
		<artifactId>gson</artifactId>
		<version>2.8.5</version>
		<scope>compile</scope>
	</dependency>
</dependencies>
```

You can use Gradle to list out dependencies too, by using `./gradlew dependencies` command. 

```
+--- com.squareup.retrofit2:converter-gson:2.9.0
|    +--- com.squareup.retrofit2:retrofit:2.9.0
|    |    \--- com.squareup.okhttp3:okhttp:3.14.9
|    |         \--- com.squareup.okio:okio:1.17.2
|    \--- com.google.code.gson:gson:2.8.5
\--- com.squareup.retrofit2:retrofit:2.9.0 (*)
```

Since this article is about building and running Java without Maven or Gradle, I won't explain more about this topic.

After doing some trial and error, we know that we need the following JAR files. I put it under the `/libs` folder.

```
$ ls
GitHubRetrofit.java
GitHubRetrofitTest.java
GitHubService.java
Repo.java
libs

$ tree
.
├── converter-gson-2.9.0.jar
├── gson-2.8.5.jar
├── hamcrest-core-1.3.jar
├── junit-4.12.jar
├── okhttp-3.14.9.jar
├── okio-1.17.2.jar
└── retrofit-2.9.0.jar

0 directories, 7 files

```

That's a lot of JARs!

To compile the class, run this command.

```
$ javac -d classes -cp libs/\* *.java
```

The `-d` option describes the destination **d**irectory. Since we're dealing with multiple files, it's tidier to group the class in a separate folder.

```
$ tree -L 2
.
├── GitHubRetrofit.java
├── GitHubRetrofitTest.java
├── GitHubService.java
├── Repo.java
├── classes
│   ├── GitHubRetrofit.class
│   ├── GitHubRetrofitTest.class
│   ├── GitHubService.class
│   └── Repo.class
└── libs
    ├── converter-gson-2.9.0.jar
    ├── gson-2.8.5.jar
    ├── hamcrest-core-1.3.jar
    ├── junit-4.12.jar
    ├── okhttp-3.14.9.jar
    ├── okio-1.17.2.jar
    └── retrofit-2.9.0.jar

2 directories, 15 files
```

Finally, run the test by using this command.

```
$ java -cp classes:libs/\* org.junit.runner.JUnitCore GitHubRetrofitTest
JUnit version 4.12

Time: 1.355

OK (1 test)
```

With several JAR files, we could simplify the command by grouping the Java files, Class files, and JAR files in a separate folder. 

In this scenario, this trick works nicely. But for more complex project the build system can handle this in more elegant way. For example, Maven has build a standard directory layout for organizing a Java project. You can execute build and run with a simpler command.



#### Summary

Don't be scared by the last example. For simple use case, like solving an algorithmic problem or running a test, building and running Java manually is simple and powerful. 

Not only you're more aware of the syntax (No autocomplete to help you), you'll also appreciate the blazing fast compilation speed. **This enables you to focus more on the code rather than tweaking build system or IDE configuration**.

As a bonus, if you're a laptop user like I am, you'll recognize your laptop temperature is colder :grin: (Yay typing!). 

Use Maven/Gradle if you need few dependencies. Rather than typing and remembering Java commands, you just need to remember few build system commands like `test` or `build`.

Finally, use IDE if you need to manage many dependencies (and its transitive dependencies). IDE is also useful in some cases, like method completion (so, you don't have to find Java docs in the internet) or breakpoint for debugging (you can do it with `jdb` but it's more complicated). 

In conclusion, use the right tool for the right job!

### Credit

- Cover photo by [Emma Smith](https://unsplash.com/photos/GmEgxfWo8po)