Step 5 · Ubuntu

Java: javac, java and jshell

A current Temurin JDK, and the three commands you need to actually use it: javac to compile, java to run, and jshell to try an idea without writing a file at all.

Run this

$ curl -fsSL https://aikaryashala.com/system_setup/scripts/install_java.sh | bash

Then reload your shell so JAVA_HOME is set:

$ source ~/.bashrc
$ java -version

It also writes two sample files into ~/java-samples for the exercise below.

What gets installed

jar and javap come along inside the same JDK package. Maven and Gradle do not — they are step 9, installed separately when a project actually needs a build tool.

Why Temurin rather than Ubuntu's openjdk

Ubuntu packages a JDK, but which versions it offers depends on your release and they can lag well behind. Eclipse Adoptium publishes Temurin — the same OpenJDK, built and tested by the community that maintains it — through its own APT repository, so every LTS is available and upgrades are clean. The script adds that repository, and falls back to Ubuntu's package if it cannot be reached.

Try it without writing a file

jshell is the fastest way to test an idea. No class, no main, no compile step.

$ jshell
|  Welcome to JShell. For an introduction type: /help intro
jshell> int x = 21
x ==> 21
jshell> x * 2
$2 ==> 42
jshell> System.out.println("hello")
hello
jshell> "abc".toUpperCase()
$4 ==> "ABC"
jshell> int[] nums = {12, 7, 3, 21}
nums ==> int[4] { 12, 7, 3, 21 }
jshell> java.util.Arrays.stream(nums).sum()
$6 ==> 43
jshell> /exit

Useful commands inside jshell: /list shows what you have typed, /vars the variables you have defined, /imports what is already imported, /open File.java loads a file, and /exit leaves.

The two sample files

One class calling another is where javac stops being trivial, so the exercise uses two files. Both land in ~/java-samples, and are downloadable here: Report.java and Stats.java.

public class Stats {

    /** The average. Returns 0 for an empty array. */
    public static double mean(int[] numbers) {
        if (numbers.length == 0) {
            return 0;
        }
        int total = 0;
        for (int n : numbers) {
            total += n;
        }
        return (double) total / numbers.length;
    }

    /** The largest value. */
    public static int max(int[] numbers) {
        int best = numbers[0];
        for (int n : numbers) {
            if (n > best) {
                best = n;
            }
        }
        return best;
    }

    /** The smallest value. */
    public static int min(int[] numbers) {
        int worst = numbers[0];
        for (int n : numbers) {
            if (n < worst) {
                worst = n;
            }
        }
        return worst;
    }

    /** How far apart the largest and smallest values are. */
    public static int spread(int[] numbers) {
        return max(numbers) - min(numbers);
    }
}
public class Report {

    public static void main(String[] args) {
        int[] readings = parse(args);

        System.out.println("--- readings ---");
        System.out.printf("  count: %d%n", readings.length);
        System.out.printf("   mean: %.2f%n", Stats.mean(readings));
        System.out.printf("    max: %d%n", Stats.max(readings));
        System.out.printf("    min: %d%n", Stats.min(readings));
        System.out.printf(" spread: %d%n", Stats.spread(readings));
    }

    /** Use the numbers given on the command line, or a default set. */
    private static int[] parse(String[] args) {
        if (args.length == 0) {
            return new int[] {12, 7, 3, 21, 9, 15};
        }

        int[] numbers = new int[args.length];
        for (int i = 0; i < args.length; i++) {
            numbers[i] = Integer.parseInt(args[i]);
        }
        return numbers;
    }
}

Compile and run them

$ cd ~/java-samples
$ javac Report.java
$ java Report
--- readings ---
  count: 6
   mean: 11.17
    max: 21
    min: 3
 spread: 18

$ java Report 4 8 15 16 23 42
--- readings ---
  count: 6
   mean: 18.00
    max: 42
    min: 4
 spread: 38
You only compiled one file

javac Report.java produced both Report.class and Stats.class. javac follows the references it finds and compiles what it needs, as long as the source is in the same directory. Run ls *.class to see both.

Skip the compile step entirely

Modern Java runs source files directly — it compiles in memory and leaves no .class file behind. Ideal while learning:

$ java Report.java
$ java Report.java 4 8 15 16

Load them into jshell

$ jshell
jshell> /open Stats.java
jshell> int[] nums = {12, 7, 3, 21, 9, 15}
jshell> Stats.mean(nums)
$3 ==> 11.166666666666666
jshell> Stats.spread(nums)
$4 ==> 18

This is the fastest way to poke at one method without writing a main around it.

The file name must match the class name

A public class Report has to live in a file called Report.java — same spelling, same capitalisation. Getting this wrong is the first error nearly everyone hits.

JAVA_HOME

Many Java tools locate the JDK through the JAVA_HOME environment variable. The script works out the right path and adds it to ~/.bashrc:

$ echo $JAVA_HOME
/usr/lib/jvm/temurin-25-jdk-amd64

If you install more than one JDK, switch between them with:

$ sudo update-alternatives --config java
$ sudo update-alternatives --config javac

Check it worked

$ java -version
$ javac -version
$ echo $JAVA_HOME
$ ls ~/java-samples

One thing left

Ubuntu is running, and you have the core commands plus working toolchains for C, Python and Java. Now check they all really work and report it: step 5.5 tests every tool from steps 2 to 5 by actually using it, and sends the result.

$ curl -fsSL https://aikaryashala.com/system_setup/scripts/report_completion.sh | bash

After that, everything else is optional — including Maven and Gradle for when a Java project needs a real build tool.