How do I declare and use variables in Java?

A variable stores a value that your program can use. For example, you can use variables to store a person’s name, age, or membership status.

In this example, you will declare variables, update a value, and print the results.

Source code

public class VariablesExample {
    public static void main(String[] args) {
        String name = "Alice";
        int age = 25;
        double heightInMeters = 1.68;
        boolean member = true;

        // Update an existing variable.
        age = age + 1;

        // Let the compiler infer the type from the initial value.
        var greeting = "Hello, " + name + "!";

        // A final variable cannot be reassigned.
        final int monthsInYear = 12;

        System.out.println(greeting);
        System.out.println("Age: " + age);
        System.out.println("Height: " + heightInMeters + " m");
        System.out.println("Member: " + member);
        System.out.println("Months in a year: " + monthsInYear);
    }
}

Explanation

To declare a variable with an explicit type, write the type, a name, and an optional initial value:

int age = 25;

Here, int is the type, age is the variable name, and 25 is its initial value. The = operator assigns the value to the variable.

The example uses these types:

Type Variable Purpose
String name Stores text
int age Stores a whole number
double heightInMeters Stores a floating-point number
boolean member Stores true or false

You can assign a new value to an existing variable without repeating its type:

age = age + 1;

This statement reads the current value of age, adds one, and stores the result back in age. Its value changes from 25 to 26.

For a local variable, you can also use var:

var greeting = "Hello, " + name + "!";

The compiler infers that greeting has the type String. Its type stays fixed after the declaration. A variable declared with var must have an initial value from which the compiler can determine its type. See the Java SE 25 specification on local variable declarations.

Use final when a variable should not be reassigned:

final int monthsInYear = 12;

Assigning another value to monthsInYear later would cause a compilation error.

Finally, System.out.println() prints each result on a separate line. The + operator joins text with the variable values.

Run the example

With JDK 25 or later installed, compile and run the file:

javac VariablesExample.java
java VariablesExample

Output

Hello, Alice!
Age: 26
Height: 1.68 m
Member: true
Months in a year: 12

All variables in this example are local variables because they are declared inside the main() method. Initialize local variables before reading their values, and choose names that make their purpose clear.

How do I Initialize Fields Before Calling super() in Java 25?

Java 25 includes JEP 513: Flexible Constructor Bodies. This feature changes a long-standing rule: before Java 22, the call to super(...) or this(...) had to be the very first statement in a constructor. Starting with Java 25, you can execute statements before the explicit constructor invocation — including initializing your own instance fields.

This is especially useful when a field’s value must be ready before the superclass constructor runs (for example, if the superclass calls an overridden method that reads that field).


The Old Restriction (Pre-Java 22)

public class Child extends Parent {
    private final int value;

    public Child(int input) {
        // Compile error before Java 22:
        // "call to super must be first statement in constructor"
        this.value = input * 2;
        super(computeSomething(input));
    }
}

The Java 25 Way: Prologue Before super(...)

With flexible constructor bodies, the constructor is split into two regions:

  1. Prologue — statements that run before super(...) or this(...).
  2. Epilogue — the usual constructor body that runs after.

Example 1: Initializing a Field Before super()

public class Parent {
    public Parent() {
        // Calls a method that subclasses may override
        describe();
    }

    protected void describe() {
        System.out.println("Parent describing");
    }
}

public class Child extends Parent {
    private final String label;

    public Child(String label) {
        // Java 25: initialize field BEFORE super()
        this.label = label;
        super();
    }

    @Override
    protected void describe() {
        // Safe: 'label' is already assigned, no NPE
        System.out.println("Child label = " + label);
    }
}

Before Java 25, label would still be null when Parent‘s constructor called the overridden describe(). Now you can assign it first.


The Prologue Rules You Must Follow

Inside the prologue (code before super(...) / this(...)), the compiler enforces strict rules to keep things safe:

Allowed Not Allowed
Assign to fields declared in this class Read fields of this
Validate / transform constructor arguments Call instance methods on this
Compute values, throw exceptions, use if, switch, loops, try Use this or super as a value
Call static methods Access inherited (superclass) fields

Example 2: Combining Validation and Field Init

public class Rectangle {
    protected final int area;

    public Rectangle(int area) {
        this.area = area;
    }
}

public class Square extends Rectangle {
    private final int side;

    public Square(int side) {
        // Prologue: validate, then initialize own field, then call super
        if (side <= 0) {
            throw new IllegalArgumentException("side must be positive");
        }
        this.side = side;          // assigning own field is allowed
        super(side * side);        // explicit constructor invocation
        // Epilogue starts here — 'this' is fully available
        System.out.println("Created Square with side=" + side + ", area=" + area);
    }
}

Example 3: Why This Matters — Fixing the “Leaked this” Problem

public abstract class Shape {
    public Shape() {
        render(); // calls subclass override during construction
    }
    protected abstract void render();
}

public class Circle extends Shape {
    private final double radius;

    public Circle(double radius) {
        this.radius = radius; // set before super() runs render()
        super();
    }

    @Override
    protected void render() {
        // Now 'radius' has its intended value, not 0.0
        System.out.println("Rendering circle with radius " + radius);
    }
}

Compiling and Running on Java 25

Since JEP 513 is a final feature in Java 25, no --enable-preview flag is required:

javac --release 25 Child.java Parent.java
java Child

Key Takeaways

  • You can now place statements — including assignments to your own fields — before super(...) or this(...).
  • The prologue cannot read this or call instance methods; it can only write to fields of the current class.
  • Exactly one explicit constructor invocation (super(...) or this(...)) is still required (implicitly or explicitly).
  • This feature is final in Java 25 — no preview flags needed.

This cleanly resolves cases where a subclass field needed to exist before the parent constructor ran, without resorting to static helpers or awkward factory methods.

How do I Validate Arguments Before Calling super() in Java 25?

Java 25 includes JEP 513: Flexible Constructor Bodies (finalized in Java 25 after being previewed in JEP 447, 482, and 492). This feature allows you to execute statements — including argument validation — before invoking super(...) or this(...), as long as those statements don’t access the instance being constructed.

The Old Problem (Pre-Java 22)

Before flexible constructor bodies, super(...) or this(...) had to be the first statement in a constructor. This forced awkward workarounds:

public class PositiveBigInteger extends BigInteger {
    public PositiveBigInteger(long value) {
        super(String.valueOf(value)); // must be first!
        if (value <= 0) {
            throw new IllegalArgumentException("Value must be positive");
        }
        // Superclass was already constructed with invalid data
    }
}

Workarounds required static helper methods:

public PositiveBigInteger(long value) {
    super(validate(value));
}
private static String validate(long value) {
    if (value <= 0) throw new IllegalArgumentException("Value must be positive");
    return String.valueOf(value);
}

The Java 25 Way — Prologue Code

You can now write a prologue — statements before super(...) — that validates, transforms, or prepares arguments:

public class PositiveBigInteger extends BigInteger {
    public PositiveBigInteger(long value) {
        // Prologue: fail fast BEFORE constructing the superclass
        if (value <= 0) {
            throw new IllegalArgumentException("Value must be positive, got: " + value);
        }
        super(String.valueOf(value));
        // Epilogue: normal constructor body
    }
}

Practical Examples

1. Validation with Transformation

public class EmailAddress {
    private final String normalized;

    public EmailAddress(String raw) {
        // Validate & normalize BEFORE any field assignment or super() call
        Objects.requireNonNull(raw, "raw email must not be null");
        String trimmed = raw.trim().toLowerCase();
        if (!trimmed.contains("@")) {
            throw new IllegalArgumentException("Invalid email: " + raw);
        }
        this.normalized = trimmed; // field assignment is allowed in prologue
    }
}

2. Sharing Expensive Computation Between this() Chains

public class Matrix {
    public Matrix(double[][] data) {
        // Validate once, use twice
        if (data == null || data.length == 0) {
            throw new IllegalArgumentException("Matrix cannot be empty");
        }
        int rows = data.length;
        int cols = data[0].length;
        this(rows, cols, flatten(data));
    }

    public Matrix(int rows, int cols, double[] flat) { /* ... */ }
}

3. Conditional Superclass Arguments

public class LoggingList<E> extends ArrayList<E> {
    public LoggingList(Collection<? extends E> source) {
        // Compute capacity hint before calling super
        int capacity = (source == null) ? 10 : Math.max(16, source.size() * 2);
        super(capacity);
        if (source != null) addAll(source);
    }
}

Rules You Must Follow

The prologue code runs before the instance is fully initialized, so the compiler enforces strict rules:

Allowed in prologue Not allowed in prologue
Reading/writing local variables Reading this.field
Assigning to own instance fields (this.x = ...) Calling instance methods on this
Throwing exceptions Using this as an expression
Calling static methods Referring to super.field
Using constructor parameters Reading fields declared in the superclass
public class Example extends Parent {
    private final int value;

    public Example(int input) {
        if (input < 0) throw new IllegalArgumentException(); // OK
        this.value = input * 2;                              // OK (own field write)
        // System.out.println(this.value);                   // Can't READ own field
        // someInstanceMethod();                             // Can't call instance method
        super(computeParentArg(input));                      // static call is fine
    }

    private static int computeParentArg(int x) { return x + 1; }
}

Key Benefits

  1. Fail fast — reject invalid arguments before allocating superclass state
  2. No more static helper methods just to satisfy the “super must be first” rule
  3. Cleaner code — validation lives next to the constructor that needs it
  4. Better performance — avoid partially constructing objects that will be discarded
  5. DRY — share computation between multiple this(...) delegations

How do I Turn a Small Java 25 Program into a Regular Class?

Java 25’s compact source files and instance main methods (JEP 512) are fantastic for scripts and quick experiments. But as your program grows — adding tests, multiple types, or reuse from other files — it’s time to graduate to a regular class. The good news: the migration is almost entirely mechanical.

Let me walk you through it step by step.


1. Recognize When It’s Time to Migrate

Convert a compact file into a regular class when you notice any of these signs:

  • The file exceeds ~100 lines or handles multiple concerns
  • You need to reuse methods from other files
  • You want to write unit tests targeting individual methods
  • You’re introducing multiple top-level types (records, enums, nested classes)
  • You need public API for other packages to consume
  • You want to package the code into a JAR or deploy it

2. Start With a Compact Source File

Here’s a typical compact program we’ll migrate:

final String title = "Tip Calculator";

void main() {
    banner();
    double bill = 84.50;
    double tipPct = 0.18;

    double tip = tipAmount(bill, tipPct);
    double total = bill + tip;

    printLine("Bill",  bill);
    printLine("Tip",   tip);
    printLine("Total", total);
}

double tipAmount(double bill, double pct) {
    return round2(bill * pct);
}

double round2(double v) {
    return Math.round(v * 100.0) / 100.0;
}

void banner() {
    println("=== " + title + " ===");
}

void printLine(String label, double value) {
    println(String.format("%-6s: %8.2f", label, value));
}

Save it as TipCalculator.java and run with java TipCalculator.java.


3. The Mechanical Migration Steps

Follow these steps in order — none of them require rethinking logic:

Step 1: Wrap Everything in a Class Declaration

Add a public class ClassName { ... } around all your code. The file name must match the class name (TipCalculator.java → class TipCalculator).

Step 2: Restore Explicit Imports

Compact files auto-import java.base and expose top-level IO methods (println, print, readln). A regular class needs explicit imports or fully qualified calls.

Replace:

  • println(...) → System.out.println(...)
  • print(...) → System.out.print(...)
  • readln(...) → use java.util.Scanner or java.io.Console

Step 3: Promote main to the Traditional Form

The classic entry point signature is:

public static void main(String[] args)

You have two options:

  • Simplest: make main public static and add the String[] args parameter.
  • Keep instance style: leave main as an instance method (still valid in Java 25) — but the traditional form is more familiar to most Java developers.

Step 4: Decide Which Members Stay Instance vs. Become Static

  • Instance fields/methods → keep as instance members and create the object from main.
  • Pure utility methods (no shared state) → mark static and call them directly from main.

Step 5: Add Access Modifiers Where Appropriate

  • public for the class and main.
  • private for internal helpers.
  • Package-private (no modifier) for methods you may want to unit-test from the same package.

4. Two Migration Styles

Style A: Static-Method Class (Best for Utilities)

If your program is essentially a collection of pure functions, make everything static:

public class TipCalculator {

    private static final String TITLE = "Tip Calculator";

    public static void main(String[] args) {
        banner();
        double bill = 84.50;
        double tipPct = 0.18;

        double tip = tipAmount(bill, tipPct);
        double total = bill + tip;

        printLine("Bill",  bill);
        printLine("Tip",   tip);
        printLine("Total", total);
    }

    // ---------- domain logic ----------

    private static double tipAmount(double bill, double pct) {
        return round2(bill * pct);
    }

    private static double round2(double v) {
        return Math.round(v * 100.0) / 100.0;
    }

    // ---------- output helpers ----------

    private static void banner() {
        System.out.println("=== " + TITLE + " ===");
    }

    private static void printLine(String label, double value) {
        System.out.println(String.format("%-6s: %8.2f", label, value));
    }
}

Notes:

  • The final field became private static final (a constant, conventionally uppercase).
  • println(...) became System.out.println(...).
  • Helper methods became private static.
  • Compile with javac TipCalculator.java and run with java TipCalculator.

Style B: Instance-Based Class (Best for Stateful Programs)

If your program has meaningful state or you plan to inject configuration, prefer an instance-based design:

public class TipCalculator {

    private final String title;

    public TipCalculator(String title) {
        this.title = title;
    }

    public static void main(String[] args) {
        new TipCalculator("Tip Calculator").run();
    }

    public void run() {
        banner();
        double bill = 84.50;
        double tipPct = 0.18;

        double tip = tipAmount(bill, tipPct);
        double total = bill + tip;

        printLine("Bill",  bill);
        printLine("Tip",   tip);
        printLine("Total", total);
    }

    // ---------- domain logic ----------

    double tipAmount(double bill, double pct) {
        return round2(bill * pct);
    }

    double round2(double v) {
        return Math.round(v * 100.0) / 100.0;
    }

    // ---------- output helpers ----------

    private void banner() {
        System.out.println("=== " + title + " ===");
    }

    private void printLine(String label, double value) {
        System.out.println(String.format("%-6s: %8.2f", label, value));
    }
}

Notes:

  • A constructor accepts title, enabling multiple configurations.
  • main is a tiny bootstrap that instantiates the class and calls run().
  • The domain methods have package-private access, making them easy to unit-test.

5. A Side-by-Side Comparison

Aspect Compact source file Regular class
Class declaration Implicit Explicit public class Name { ... }
File name Any (java Foo.java) Must match class name
main signature void main() public static void main(String[] args)
Imports Auto (java.base) Explicit import statements
I/O helpers println, print, readln System.out.println, Scanner, etc.
Access modifiers Optional Explicit (public, private, …)
How to run java Foo.java javac Foo.java then java Foo
Testability Limited Full JUnit / Mockito support
Multiple types Discouraged Fully supported

6. Final Checklist

Before you consider the migration complete:

  • File name matches the public class name.
  • main method is public static void main(String[] args).
  • All println / readln calls use standard System.out / Scanner APIs (or explicit import java.lang.IO; if you want to keep them).
  • Access modifiers reviewed (public, private, package-private).
  • Constants renamed to UPPER_SNAKE_CASE and marked private static final.
  • Compiles cleanly with javac — no more relying on the source-launcher.
  • Consider extracting records, enums, or helper classes into their own files.
  • Add unit tests for the newly exposed methods.

Summary

Migrating from a compact source file to a regular class in Java 25 is a mechanical, low-risk refactor:

  1. Wrap the code in public class Name { ... }.
  2. Restore explicit imports and System.out calls.
  3. Upgrade main to the traditional public static void main(String[] args) signature.
  4. Decide between a static-utility style or an instance-based design.
  5. Add proper access modifiers and constants.

The beauty of Java 25 is that you can prototype with a compact file and graduate to a full class only when the program actually needs it — no rewrites, just a scaffolding upgrade.

How do I Compile and Run a Single Java 25 Source File?

Java 25 makes running a single .java file easier than ever. Thanks to JEP 330 (Launch Single-File Source-Code Programs), combined with the new JEP 512 (Compact Source Files and Instance Main Methods), you can go from source to running program with a single command — no explicit javac step required.

Let’s walk through the options step by step.


1. Prerequisites

  • JDK 25 installed and on your PATH
  • Verify your setup:
java --version
javac --version

Both should report version 25.


2. Write a Single-File Program

Create a file called Hello.java. In Java 25, the simplest possible form is:

void main() {
    IO.println("Hello, Java 25!");
}

No class, no static, no String[] args, no imports. The IO class is auto-imported in compact source files.


3. Run It Directly (No Compilation Step)

This is the recommended approach for scripts, prototypes, and small utilities:

java Hello.java

What happens under the hood:

  1. The java launcher detects that the argument ends in .java.
  2. It invokes the compiler in memory (nothing is written to disk).
  3. The resulting bytecode is executed immediately.

Expected output:

Hello, Java 25!

Passing Arguments to Your Program

Anything after the source file name is passed as an argument to main:

java Hello.java arg1 arg2 arg3

To receive them, declare the parameter:

void main(String[] args) {
    for (String arg : args) {
        IO.println("Got: " + arg);
    }
}

4. Compile and Run Separately (Traditional Way)

If you want a reusable .class file (for example, to distribute or run repeatedly without recompiling), do it in two steps.

Step 1 — Compile

javac Hello.java

This produces Hello.class in the current directory.

Note: For a compact source file without a class declaration, the compiler generates a synthetic class name based on the file name.

Step 2 — Run

java Hello

Note there is no .java extension here — you pass the class name, not the file name.


5. Choosing a Specific Source Version

If your default javac isn’t Java 25, you can request the level explicitly:

javac --release 25 Hello.java

Or, when running a single-file program:

java --source 25 Hello.java

This is handy if multiple JDKs coexist on your system.


6. Handling Files with a Non-Matching Name

With single-file source-code execution, the file name does not have to match any class defined inside — even if you declare public class Foo inside Bar.java, the following still works:

java Bar.java

However, if you switch to the classic two-step javac + java workflow, the standard rule applies: a public class must live in a file with the same name.


7. Adding a Shebang for Script-Like Execution (Unix/macOS)

You can turn a .java file into an executable script by adding a shebang line as the very first line:

#!/usr/bin/env java --source 25

void main() {
    IO.println("Running as a script!");
}

Then make it executable and run:

chmod +x hello
./hello

The file must not have a .java extension when using the shebang trick — otherwise the launcher tries to compile the shebang line as Java source.


8. Quick Reference

Goal Command
Run a single source file java Hello.java
Run with program arguments java Hello.java foo bar
Force a specific source level java --source 25 Hello.java
Compile only javac Hello.java
Compile with an explicit release javac --release 25 Hello.java
Run compiled class java Hello

9. Common Pitfalls

Problem Cause Fix
error: unknown source file extension You passed a file without .java to java Use java Hello.java (with extension) or java Hello (without) after compiling
error: 'class', 'interface', ...' expected Older JDK doesn’t understand compact source files Upgrade to JDK 25
main method not found Wrong signature (e.g., int main()) Use void main() or void main(String[] args)
Shebang script fails to run File has .java extension Rename the file to remove the extension

Summary

  • For quick experiments: java Hello.java — one command, no .class file produced.
  • For repeated runs or distribution: javac Hello.java then java Hello.
  • Combine with Java 25’s compact source files and instance main for the shortest possible programs.

This unified workflow makes Java feel almost script-like while keeping the full power of the platform available when you need it.