Installing a JDK

To write Java you need a JDK (see The JDK and the JRE). Pick a free OpenJDK build of the current LTS version; as of October 2026 that is Java 25. Common ways to install one:

  • Any system: download Eclipse Temurin from adoptium.net as an installer or as an archive you unpack anywhere, also without administrator rights.
  • Windows: the installer, or a package manager such as winget.
  • macOS: the installer, or Homebrew.
  • Linux: the distribution's package manager (package names such as openjdk-25-jdk or java-25-openjdk differ per distribution, and older distributions may not offer Java 25 yet), or the vendor's own repository.

Then check the installation in a terminal:

java --version
javac --version
openjdk 25.0.4.1 2026-08-18 LTS
OpenJDK Runtime Environment Temurin-25.0.4.1+1 (build 25.0.4.1+1-LTS)
OpenJDK 64-Bit Server VM Temurin-25.0.4.1+1 (build 25.0.4.1+1-LTS, mixed mode, sharing)
javac 25.0.4.1

The first line gives the version (25.0.4.1, an extra security update released in August 2026) and the "LTS" mark; the other lines name the vendor's build. "Mixed mode" means the JVM both interprets and JIT-compiles.

PATH and JAVA_HOME

The terminal finds java through the PATH environment variable: a list of folders that are searched for commands. Installers usually set it; with an unpacked archive you add the JDK's bin folder yourself. Many tools (Maven, Gradle, application servers, IDEs) look at a second variable, JAVA_HOME, which points to the JDK folder itself, not to its bin:

# Linux or macOS, for example in ~/.bashrc
export JAVA_HOME="$HOME/.jdk/jdk-25.0.4.1+1"
export PATH="$JAVA_HOME/bin:$PATH"

Compiling with javac, running with java

The classic first program is a class with a main method. Save it as Hello.java; the file name must match the class name, including capital letters:

public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello from Kestrel Energy!");
    }
}
Hello from Kestrel Energy!

Building and running it takes two commands. First go to the folder that contains the file (your working directory):

cd ~/projects/java       # the folder that contains Hello.java
javac Hello.java         # compile: writes Hello.class
ls
java Hello               # run: give the class name, not a file name
Hello.class  Hello.java
Hello from Kestrel Energy!

javac is the Java compiler. It checks the source code and writes one class file per class: a .class file with the class's Java bytecode, its fields and methods, and the Java version that compiled it. Class files start with the four bytes CA FE BA BE ("cafe babe", a joke from Java's early days), followed by that version: 69 for Java 25. You never edit a class file; you recompile.

java is the launcher. It starts a JVM, loads the class you name, and calls its main method. It looks for classes in the current folder, or in the folders and .jar files listed in the classpath (see Packages, Modules and Build Tools).

The main method

Every program needs an entry point, the place where execution starts. In Java that is the main method. In the classic form, every word of its heading has a meaning:

Part Meaning
public Visible from outside the class, so the launcher can call it
static Belongs to the class itself; no object has to be created first
void Returns nothing
main The name the launcher looks for
String[] args The command-line arguments, as an array of strings

For many years this heading was required exactly like this, and it is still what you will see in almost all existing code. The arguments are whatever follows the class name on the command line:

public class Main {
    public static void main(String[] args) {
        System.out.println("Arguments given: " + args.length);
        for (String arg : args) {
            System.out.println("Region: " + arg);
        }
    }
}
Arguments given: 0

Run without arguments, as here, the array is empty (never null). Run as java Main North East, the same program prints Arguments given: 2 and two Region lines. Small mistakes are reported by the compiler with file and line. A forgotten semicolon is the classic one:

public class Main {
    public static void main(String[] args) {
        System.out.println("Meter report")
        System.out.println("Readings processed: 3");
    }
}
Main.java:3: error: ';' expected

The compiler reports the line where it noticed the problem, and under it the source line with a ^ right after the closing parenthesis. With several errors, fix the first one and compile again: later messages are often consequences of the first.

Running a single file directly

Since Java 11 you can skip the separate compile step. Give the launcher a file name ending in .java and it runs in source-file mode: it compiles the file in memory and runs it straight away, without writing any .class file.

java Hello.java
Hello from Kestrel Energy!

This is ideal for learning, small tools and scripts, and it is how every example on these pages is checked: each one is saved as Main.java and run with java Main.java on Java 25. That is why error messages here say Main.java. Some details of source-file mode:

  • The file is compiled every time, which takes about a second on a small machine. For a real application you compile once and run the class files, usually packed in a .jar.
  • Since Java 22 the program may use other .java files in the same folder: java Main.java also finds a class Tariff in Tariff.java next to it.
  • Compiler warnings are printed too, but the program still runs.
  • On Linux and macOS a file can run as a script with a shebang line such as #!/usr/bin/env -S java --source 25. Such a script file has no .java extension. If its name is not a valid Java identifier, such as check-meters, the compact form below fails (error: bad file name), so put the code inside a named class.

Compact source files

The classic heading asks beginners to accept five unexplained words before printing one line. Java 25 removed that hurdle. A compact source file is a .java file with methods and fields at the top level and no class around them; it needs only a main method:

void main() {
    IO.println("Hello from Kestrel Energy!");
}
Hello from Kestrel Energy!

Run it the same way, with java Hello.java, or compile it with javac Hello.java. Three things are new here:

  • No class. The compiler wraps the file in an implicitly declared class for you (it has no name you can use in code; on disk, javac names it after the file: Hello.class).
  • An instance main. No public, no static, no String[] args; add String[] args if you need the arguments.
  • The IO class (in java.lang since Java 25, so it works in every Java file) offers IO.println, IO.print and IO.readln. In addition, a compact source file automatically imports every package of the module java.base, so List, LocalDate or BigDecimal need no import line.

Helper methods, fields, records and other classes go in the same file, before or after main:

void main() {
    printHeader("Outage summary");
    IO.println(new Region("North", 1250));
    IO.println(LocalDate.of(2026, 10, 5).getDayOfWeek());
}

// A helper method, used by main
void printHeader(String title) {
    IO.println(title);
    IO.println("-".repeat(title.length()));
}

// Types can be declared below main, in the same file
record Region(String name, int customersAffected) {}
Outage summary
--------------
Region[name=North, customersAffected=1250]
MONDAY

The relaxed main also works in ordinary named classes: this class runs fine with java Report.java, because the launcher creates a Report object and calls its main:

class Report {
    void main() {
        IO.println("Instance main in a named class");
        IO.println(title());
    }

    String title() {
        return "Meter report";
    }
}
Instance main in a named class
Meter report

A compact source file is meant for small programs and for learning. It cannot be part of a named package, which every larger program uses, so the compiler refuses a package line:

package kestrel.billing;

void main() {
    IO.println("Billing run");
}
Main.java:1: error: compact source file should not have package declaration

When a program grows, you turn it into a normal class by putting class Billing { … } around the code and adding the imports it needs. Nothing else changes.

Writing output

Two families of methods write to the terminal (the standard output stream):

  • System.out.println(…) and System.out.print(…): available in every Java version and used in nearly all existing code.
  • IO.println(…) and IO.print(…): shorter, since Java 25. They write to the same System.out.

println writes its argument followed by a line break; print writes without the line break, so the next output continues on the same line. Without an argument, println() prints an empty line.

void main() {
    String ticket = "INC-20417";
    int customers = 1250;
    IO.print("Ticket: ");
    IO.println(ticket);
    IO.println();
    IO.println("Customers affected: " + customers);
    System.out.println(ticket + " affects " + customers + " customers");
    IO.println(customers / 1000.0);
}
Ticket: INC-20417

Customers affected: 1250
INC-20417 affects 1250 customers
1.25

Both accept any value and print its text form. The + operator joins strings, and converts a number to text when one side is a string. Formatted output with String.format is covered in Strings and Text. Error messages belong on the standard error stream: System.err.println("…"). That separation matters in operations: a service manager or container platform can collect the two streams separately.

The rest of this topic uses one style per page: compact source files with IO.println where the code is short, and classic classes with System.out.println where classes are the subject.

Reading input

IO.readln(prompt) prints the prompt, waits until the user types a line and presses Enter, and returns that line as a String, without the line break. In the examples below the typed input is fed in automatically and, unlike in a real terminal, does not appear in the output. Because the prompt is printed without a line break, the next output continues on the same line:

void main() {
    String name = IO.readln("Your name: ");
    IO.println("Hello, " + name + ". Welcome to the service desk.");
}
Your name: Hello, Amara. Welcome to the service desk.

At the end of the input, for example when input comes from a file that has ended, IO.readln returns null (no value) instead of a string:

void main() {
    String line = IO.readln();
    IO.println(line == null ? "No input: end of stream." : "Read: " + line);
}
No input: end of stream.

Converting input to numbers

Input is always text. To calculate with it, convert it: Integer.parseInt for whole numbers, Double.parseDouble for numbers with decimals, or new BigDecimal(text) for money (see BigDecimal for money). Unlike Python or JavaScript, Java will not let you multiply a string by a number at all; the compiler refuses.

void main() {
    BigDecimal tariff = new BigDecimal("0.27");   // euros per kWh
    String input = IO.readln("Meter reading this month (kWh): ");
    int kwh = Integer.parseInt(input.strip());
    BigDecimal cost = tariff.multiply(BigDecimal.valueOf(kwh));
    IO.println();
    IO.println("Usage: " + kwh + " kWh, cost: EUR " + cost);
}
Meter reading this month (kWh):
Usage: 342 kWh, cost: EUR 92.34

strip() removes spaces around the input, a common source of errors. Integer.parseInt throws an exception when the text is not a whole number, and the program stops:

void main() {
    String input = IO.readln("Meter reading (kWh): ");
    int kwh = Integer.parseInt(input);
    IO.println("Usage: " + kwh + " kWh");
}
Meter reading (kWh):
Exception in thread "main" java.lang.NumberFormatException: For input string: "twelve"

Java has no "try-parse" method that returns a success flag, as C# does. For input from people you catch the exception instead, and a loop asks again until the input is valid:

void main() {
    int kwh = -1;
    while (kwh < 0) {
        String input = IO.readln("Meter reading (kWh): ");
        try {
            kwh = Integer.parseInt(input.strip());
            if (kwh < 0) {
                IO.println("'" + input + "' is negative, try again.");
            }
        } catch (NumberFormatException e) {
            IO.println("'" + input + "' is not a whole number, try again.");
        }
    }
    IO.println("Accepted: " + kwh + " kWh");
}
Meter reading (kWh): 'abc' is not a whole number, try again.
Meter reading (kWh): '-5' is negative, try again.
Meter reading (kWh): Accepted: 120 kWh

Each prompt and the message after it end up on one line here, because the typed answers are not shown. Loops are explained in Loops and Iteration, try and catch in Exceptions and Error Handling.

The older way: Scanner

Before Java 25, the usual way to read from the keyboard was a Scanner on System.in. You will find it in most tutorials and exam questions:

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("Customer number: ");
        int customer = scanner.nextInt();
        System.out.print("Postal code: ");
        String postalCode = scanner.next();
        System.out.println();
        System.out.println("Customer " + customer + " lives in " + postalCode);
    }
}
Customer number: Postal code:
Customer 10042 lives in 2511

The output shows Scanner's classic trap: next() reads one word, so the postal code "2511 CV" lost its letters (a UK postcode such as "SW1A 1AA" would lose its second half the same way). nextLine() reads the rest of the line, but after nextInt() it first returns the empty remainder of the previous line. IO.readln plus an explicit conversion avoids both problems. Don't mix the two in one program: after the first IO.readln, other use of System.in is unpredictable.

Trying code in JShell

JShell is Java's interactive shell, a REPL included in every JDK since Java 9. Start it with jshell, type an expression or statement, and see the result at once; no class, no main, no semicolon needed at the end of a line:

$ jshell
|  Welcome to JShell -- Version 25.0.4.1
|  For an introduction type: /help intro

jshell> 7 / 2
$1 ==> 3

jshell> int kwh = 342
kwh ==> 342

jshell> kwh * 0.27
$3 ==> 92.34

jshell> LocalDate.of(2026, 10, 5).getDayOfWeek()
$4 ==> MONDAY

jshell> int x = "12"
|  Error:
|  incompatible types: java.lang.String cannot be converted to int
|  int x = "12";
|          ^--^

jshell> /exit
|  Goodbye

A result you did not store in a variable gets a scratch name such as $1, which you can use in later lines. Commands start with a slash: /vars lists your variables, /imports the imports (in Java 25 the whole java.base module, which is why LocalDate works without one), /help everything else. JShell is ideal for checking what a method returns or how an operator behaves, as in the first line: 7 / 2 is 3, because both numbers are whole numbers (see Variables, Types and Operators).

Editors and certification

Any text editor will do for single files, but an editor that understands Java shows errors while you type, completes names, reorganizes imports and lets you step through code in a debugger. The common choices are IntelliJ IDEA (by JetBrains), Eclipse IDE, Visual Studio Code with the Java extensions, and Apache NetBeans. All of them use the JDK you installed and understand Maven and Gradle projects, so a project made in one opens in the others. For finding and fixing errors in general, see Debugging and Testing.

Oracle's certification for current Java is Oracle Certified Professional: Java SE 25 Developer (exam 1Z0-831), introduced around mid-2026. It is a professional-level exam that tests detailed knowledge of the language and its core library, not a beginner certificate; as of 2026 it is reported to have 50 questions in 120 minutes with a passing score of 68%, but check Oracle's exam page for the current details. Pages in this topic that cover its subjects carry the tag OCP Java SE 25.