17.01.2026 • Updated 24.01.2026 • 23 min read

Modern Java Basics Crash Course for Experienced Programmers

Cover Image

Introduction

This guide assumes you are an experienced programmer who understands OOP principles, has Java 25 installed, and knows how to manage a project environment. Let’s skip the fluff and dive into the syntax.


Program Structure: The Java 25 Shift

Historically, Java required significant boilerplate just to print text. As of Java 25 (September 2025), the language has streamlined its entry point.

The Evolution of “Hello World”

You likely expect the verbose, explicit structure, but modern Java allows for much leaner code.

1. The Modern Script (Java 25+)

void main() {
    IO.println("Hello World!");
}

2. The Classic Enterprise Structure

package com.louiseyousre.helloworld;

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

What Changed?

The “Modern Script” above isn’t a shortcut; it’s a first-class feature called Implicit Classes and Instance Main Methods.

  • Implicit Classes: If you don’t declare a class, the compiler wraps your code in one automatically. This is ideal for scripts but means the class cannot be referenced by name elsewhere.
  • Instance Main: The main method no longer requires static or String[] args. The JVM simply instantiates the class and runs the method.
  • The IO Class: Part of java.lang (auto-imported), IO.println() is a shorthand for System.out.println().

When to Use Which Style

Experienced devs need to know when to drop the ceremony and when to keep it.

Use CaseRecommended StyleRationale
Scripts & CLI toolsvoid main()Speed and readability.
InterviewsEither (but mention both)Shows you are current with Java 25.
Production AppsExplicit classes + packagesRequired for dependency injection and multi-file linking.
Library DevExplicit classes + packagesOthers cannot import an implicit class.

The “Package” Threshold

In Java 25, the choice of syntax determines your “Mode” of development.

1. The Unnamed Package (Script Mode)

When you use implicit classes or omit the package declaration, your code lives in the unnamed package.

  • The Limit: Classes in the unnamed package cannot be imported by any other package.
  • The Use Case: One-off utility scripts, data cleaners, or competitive programming.

2. Named Packages (Project Mode)

As soon as your project grows beyond a single file, you must transition to named packages (package com.yourname.project;).

The Decision Tree for Pros:

  1. Single, self-contained file? Use the simple void main() syntax in the unnamed package.
  2. Multiple files or external dependencies? Use an explicit class, a named package, and standard build tools (Maven/Gradle).

Introduction to The Core

Let’s strip away the “Enterprise” structure to focus on the core. By the core I mean Variables, Logic and Flow …etc.


Modern Input/Output (IO)

Before diving into anything else, let’s see the modern IO class in action. It’s the standard tool for simple console interaction in Java 25, replacing the old Scanner and System.out ceremony for many tasks.

To keep our code readable, we use comments—notes written for humans that the computer ignores.

Types of Comments in Java:

  1. Single-line (//): Everything from the // to the end of that line is ignored. This is the most common way to add quick notes or labels to your steps.
  2. Multi-line (/* ... */): Everything between the opening /* and closing */ is ignored. This is perfect for longer explanations or temporarily “turning off” a block of code.

Note: There is a third type called Javadoc used for professional documentation. Because it has its own special syntax and power, we will cover it in its own dedicated article later.

Here’s a complete script demonstrating its key methods and examples of how to write comments:

void main() {
    /* The IO class is the modern way to handle console interaction.
       It is simpler and cleaner than the older "Scanner" approach.
    */

    // [1] Print "Hello, World" followed by a new line.
    IO.println("Hello World!");

    // [2] Print a new line; just to add more spacing.
    IO.println();

    // [3] Print a prompt without a new line at the end.
    IO.print("Enter Your First Name: ");

    // [4] Read the user's input as a String and assign it to a variable.
    final var firstName = IO.readln();

    // [5] Print a customized message using string concatenation.
    IO.println("Nice to meet you, " + firstName + ".");

    // [6] A more concise pattern: prompt and read in one line.
    final var lastName = IO.readln("Enter Your Last Name: ");

    // [7] Print the result.
    IO.println("So your full name is " + firstName + " " + lastName + ".");
}

Key Methods of IO:

  • IO.print(x): Prints x without a trailing newline.
  • IO.println(x) / IO.println(): Prints x with a trailing newline, or just a newline if called without an argument.
  • IO.readln(): Reads a full line of input from the console, returning it as a String.
  • IO.readln(prompt): A convenience method that performs IO.print(prompt); and then returns IO.readln().

What you should note (will be explained later in more details):

  1. final var: This declares a variable (firstName, lastName) whose type is inferred by the compiler (String in this case) and whose value cannot be changed after assignment (final). We’ll explore this next.
  2. No Imports: The IO class is part of java.lang and is automatically available, keeping script code clean.
  3. Flow: The program executes sequentially, waiting at each readln() for user input.

This example gives you a feel for modern Java’s streamlined syntax and sets the stage for understanding its core better, which we’ll continue to cover next.


Variables, Constants and the Type System

Java is a statically typed language, which ensures catch-at-compile-time safety as you know. However, it has evolved to reduce verbosity.

Declaring Variables

A variable declaration in Java consists of a type, a name, and optionally an initializer.

int count; // `int` is the type, `count` is the name and there's no initializer.
int total = 10; // here `= 10` is the initializer.

Java also supports local variable type inference using var (introduced in Java 10):

var message = "Hello";   // inferred as String
var sum = 42;            // inferred as int

Type inference does not make Java dynamically typed—the inferred type is still fixed at compile time.

Variable Naming Rules & Conventions

Rules (enforced by the compiler)

  • Must begin with a letter, _, or $.
  • Cannot start with a digit.
  • Cannot be a reserved keyword.
  • Case-sensitive.

Conventions (by community standards)

  • Use camelCase.
  • Use meaningful, descriptive names.
  • Avoid abbreviations unless widely understood.
int userAge;
String accountStatus;

Immutability (final)

In Java, immutability at the variable level is expressed using the final keyword. A final variable cannot be reassigned after it has been initialized.

final int retries = 3;
final String name = "Alice";

This guarantees that the variable will always refer to the same value or object reference.


Data Types

Java is considered:

  • Statically typed: types are known and checked at compile time.
  • Strongly typed: incompatible types cannot be mixed implicitly.

This strictness:

  • Prevents many runtime errors
  • Improves readability and maintainability
  • Enables powerful compiler optimizations

Data types in Java fall into two main categories: primitive types and reference types.

Primitive (Basic) Types

Numeric Types

// Integer types (whole numbers):
byte b = 10;      // 8-bit
short s = 300;    // 16-bit
int i = 42;       // 32-bit (most commonly used)
long l = 100000L; // 64-bit

// Floating-point types (decimal numbers):
float f = 3.14f;  // 32-bit (requires `f` suffix)
double d = 3.14;  // 64-bit (default for decimals)

int and double are the default choices for most numeric operations unless memory or precision constraints apply.

NOTE: In Java, numeric literals are int by default and decimal literals are double by default.

  • Use the f suffix to indicate a float literal (e.g., 3.14f).
  • Use the L suffix to indicate a long literal (e.g., 100000L).
  • Omitting these suffixes may lead to compilation errors or unintended type conversions.

Boolean Type

The boolean type represents logical truth values:

boolean isActive = true;
boolean hasAccess = false;

Java booleans are not interchangeable with integers (unlike some other languages).

Character Type

The char type represents a single Unicode character and uses single quotes:

char letter = 'A';
char symbol = '@';

Internally, char is a 16-bit unsigned value representing a UTF-16 code unit.

Reference Types (like String)

Reference types store references to objects, not the actual value. It could be not referencing to anything too (at any time) by holding the value null.

Primitive types cannot be null.

int x = null; // ❌ compile-time error

String (as an example):

String is a reference type, not a primitive, but it is used frequently and treated specially by the language.

String greeting = "Hello";

Key characteristics of String:

  • Immutable
  • Stored as an object
  • Supports many built-in methods (length(), substring(), etc.)
String name = null;

Note: String is just an example of a reference type. The concept of objects and how reference types work in general will be explained in more detail later.

The Object Type (Base of All Reference Types)

In Java, every reference type ultimately inherits from Object. This means that any object — a String, for example, can be stored in a variable of type Object.

Object o = "Hello";        // String stored as Object

This works because of the magic of OOP:

  • String, and all classes extend Object. (More about OOP later…)
  • Java allows upcasting (storing a subclass instance in a superclass reference). ⏳🫠🤌🏻💗

Arrays

An array in Java is a reference type that stores a fixed-size sequence of elements of the same type.

  • Arrays are objects
  • Their size is fixed once created
  • Elements are stored in contiguous memory
  • Indexing is zero-based

Declaring Arrays

int[] numbers;
String[] names;
boolean[] flags;

There’s an alternative syntax (C-Style) although the type[] name style is the preferred one for clarity. and it is:

int numbers[];

Creating Arrays

You can create an array filled with the default values (which is 0 for numbers). You must specify the size when creating an array:

int[] numbers = new int[5]; // zero(es)
String[] names = new String[3]; // null(s)

The default values are:

TypeDefault Value
int0
double0.0
booleanfalse
char'\u0000'
Reference typesnull

For example:

int[] arr = new int[3];
IO.println(arr[0]); // 0

Array Initialization

Both the following ways are nearly the same just syntax differences. As it will create the exact same array in memory.

Inline:
int[] nums = {1, 2, 3, 4, 5};
String[] fruits = {"Apple", "Banana", "Orange"};
With new:
int[] nums = new int[] {1, 2, 3}; // You don't put the length this way.

Java requires the type information when an array is created outside a declaration. Here’s just an example:

// Will work...
for (int i : new int[] {1,2,3}){
    IO.println(i);
}

// Won't work... Java 25 (It may get changed later ..  who knows?!).
for (int i : {1,2,3}){
    IO.println(i);
}

Knowing the length .length

int[] nums = {1, 2, 3};
IO.println(nums.length); // 3

Accessing & Modifying Elements:

int[] nums = {10, 20, 30};

int first = nums[0];   // 10
nums[1] = 99;          // modify element

NOTE: Accessing an invalid index causes a runtime error:

nums[3]; // ❌ ArrayIndexOutOfBoundsException

Multidimensional Arrays:

int[][] matrix = new int[2][3];

// or inline initialization

int[][] matrix = {
    {1, 2, 3},
    {4, 5, 6}
};

IO.println(matrix[1][2]) // 6

NOTE: Java multidimensional arrays are not true matrices; each row can have a different length.

We will discuss more and more about arrays later for example java.util.Arrays and sorting …etc.

Type Inference (Java 10+)

Java supports local variable type inference using var (local made you curious right 😂 waiiit).

var count = 10;          // int
var name = "Alice";      // String

Important notes:

  • var can only be used for local variables.
  • The inferred type is fixed at compile time.
  • var requires an initializer.
var x;        // ❌ invalid
var y = null; // ❌ invalid (type cannot be inferred)

Type Conversion

Java distinguishes between implicit (widening) and explicit (narrowing) conversions.

Implicit Conversion (Widening)

Safe conversions where data loss cannot occur:

byte b = 10;
int i = b;        // byte → int, safe

char c = 'A';
int code = c;     // char → int, safe

float f = 3.14f;
double d = f;     // float → double, safe

long l = 1000L;
float g = l;      // long → float, safe (may lose precision for very large values, but no truncation)
FromTo (Widening)
byteshort, int, long, float, double
shortint, long, float, double
charint, long, float, double
intlong, float, double
longfloat, double
floatdouble

Note: Widening does not truncate the value, so you won’t lose magnitude. Floating-point conversions (intfloat) are considered widening, though very large integers may lose precision in the least significant digits—but the language still treats it as safe.

Explicit Conversion (Casting)

Required when converting to a smaller or less precise type.

double d = 9.7;
int i = (int) d; // result: 9

Casting may result in:

  • Loss of precision
  • Overflow

Operators

Operators in Java are symbols used to perform operations on values or variables. They are grouped into different categories:

Arithmetic Operators

int a = 10;
int b = 3;

int sum  = a + b;        // Addition: 10 + 3 = 13
int diff = a - b;        // Subtraction: 10 - 3 = 7
int prod = a * b;        // Multiplication: 10 * 3 = 30
int div  = a / b;        // Integer division: 10 / 3 = 3
int rem  = a % b;        // Modulus (remainder): 10 % 3 = 1

double preciseDiv = 10.0 / 3.0; // Floating-point division: 3.3333

Notes:

  • Integer division truncates the decimal.
  • Modulus % is commonly used to check divisibility or cycle through values.

Assignment Operators

int x = 5;

x += 3; // x = x + 3 -> 8
x -= 2; // x = x - 2 -> 6
x *= 2; // x = x * 2 -> 12
x /= 3; // x = x / 3 -> 4
x %= 3; // x = x % 3 -> 1
int x = 5; // 0101

x &= 3;  // x = x & 3
x |= 2;  // x = x | 2
x ^= 1;  // x = x ^ 1
x <<= 1; // x = x << 1
x >>= 1; // x = x >> 1
x >>>= 1;

Notes:

  • Assignment operators store a value in a variable.
  • Compound assignment (+=, -=, etc.) is shorthand for x = x <op> y.

Comparison Operators

int a = 10;
int b = 3;

boolean eq  = (a == b);   // false
boolean neq = (a != b);   // true
boolean gt  = (a > b);    // true
boolean lt  = (a < b);    // false
boolean gte = (a >= b);   // true
boolean lte = (a <= b);   // false

Notes:

  • Comparison operators always return a boolean (true/false).
  • For objects (like String), use .equals() instead of ==.

Logical Operators

int a = 10;
int b = 3;

boolean andOp = (a > 5 && b < 5); // true (both conditions true)
boolean orOp  = (a > 5 || b > 5); // true (at least one condition true)
boolean notOp = !(a > b);          // false (negates true)

Notes:

  • && evaluates to true only if both expressions are true.
  • || evaluates to true if at least one expression is true.
  • ! inverts the boolean value.
  • && and || are Short-Circuit which means they don’t evaluate all sides if not needed to determine the final result.

Ternary Operator

int a = 10;

String result = (a % 2 == 0) ? "Even" : "Odd"; // "Even"

Notes:

  • Syntax: condition ? valueIfTrue : valueIfFalse;
  • It’s a shorthand for simple if-else statements.

Bitwise Operators:

int bitA = 5;  // 0101 in binary
int bitB = 3;  // 0011 in binary

int andBits            = bitA & bitB;  // 0101 & 0011 = 0001 -> 1
int orBits             = bitA | bitB;  // 0101 | 0011 = 0111 -> 7
int xorBits            = bitA ^ bitB;  // 0101 ^ 0011 = 0110 -> 6
int notBits            = ~bitA;        // ~0101 = 1010 (two's complement) -> -6
int leftShift          = bitA << 1;    // 0101 << 1 = 1010 -> 10
int rightShift         = bitA >> 1;    // 0101 >> 1 = 0010 -> 2
int unsignedRightShift = bitA >>> 1;   // 0101 >>> 1 = 0010 -> 2

Notes:

  • Bitwise operators work on binary representations of integers.
  • Used in flags, low-level programming, or optimization tasks.
  • Non-Short-Circuit Logical Operators (&, |) also work with booleans, but the difference is that they do evaluate both sides.

Unary Operators

int x = 5;

IO.println(x);    // 5

IO.println(x++);  // 5  (post-increment: use first, then increment)
IO.println(x);    // 6

IO.println(++x);  // 7  (pre-increment: increment first, then use)
IO.println(x);    // 7

IO.println(x--);  // 7  (post-decrement: use first, then decrement)
IO.println(x);    // 6

IO.println(--x);  // 5  (pre-decrement: decrement first, then use)
IO.println(x);    // 5

Notes:

  • ++x and x++ are not the same in expressions.
  • Common source of bugs in loops and expressions.

String Concatenation Operator (+):

+ is overloaded in Java for String concatenation**.

String name = "Java";
int version = 21;

String result = name + " " + version; // "Java 21"

Notes:

  • If either operand is a String, Java converts the other to String.
  • Evaluation is left to right.

instanceof Operator

Checks if an object belongs to a type.

Object obj = "Hello";

boolean isString = obj instanceof String; // true

Operator Precedence

Operators are evaluated in a specific order, unless parentheses override it. Understanding precedence avoids unexpected results.

Java operator precedence (simplified, highest → lowest):

  1. Parentheses: ()
  2. Unary operators: +, -, ++, --, !, ~
  3. Multiplication, division, modulus: *, /, %
  4. Addition, subtraction: +, -
  5. Bitwise shift: <<, >>, >>>
  6. Relational: <, >, <=, >=, instanceof
  7. Equality: ==, !=
  8. Bitwise AND: &
  9. Bitwise XOR: ^
  10. Bitwise OR: |
  11. Logical AND: &&
  12. Logical OR: ||
  13. Ternary: ? :
  14. Assignment: =, +=, -=, *=, /=, %=

Example:

int result1 = 5 + 3 * 2;        // 5 + (3*2) = 11
int result2 = (5 + 3) * 2;      // (5+3) * 2 = 16
int result3 = 10 > 5 && 3 < 2;  // true && false -> false

Tip: Always use parentheses to clarify intent and avoid mistakes.

Special Notes

  • Integer division truncates decimals; use double for precise division.
  • Use parentheses for clarity when combining multiple operator types.
// Object comparison: use .equals() for Strings, not ==
String s1 = "Hello";
String s2 = "Hello";

boolean stringEq    = s1.equals(s2);   // true (content)
boolean stringRefEq = (s1 == s2);      // true due to string interning, but not reliable

Control Flow (Decision Making)

Control flow statements allow a program to make decisions and execute different code paths based on conditions.

if / else

The if statement executes a block only if a condition is true.

int age = 20;

if (age >= 18) {
    IO.println("Adult");
}

Use else to handle the false case:

if (age >= 18) {
    IO.println("Adult");
} else {
    IO.println("Minor");
}

Notes:

  • Conditions must evaluate to a boolean.
  • Curly braces {} are optional for single statements but strongly recommended.

else if

Used to check one or more alternative conditions in sequence.

int score = 85;

if (score >= 90) {
    IO.println("Excellent");
} else if (score >= 75) {
    IO.println("Good");
} else if (score >= 60) {
    IO.println("Pass");
} else {
    IO.println("Fail");
}

Notes:

  • Conditions are evaluated top to bottom.
  • The first matching condition stops further checks.

switch Statement (Classic)

Traditional switch compares a value against fixed constants.

int day = 3;

switch (day) {
    case 1:
        IO.println("Monday");
        break;
    case 2:
        IO.println("Tuesday");
        break;
    case 3:
        IO.println("Wednesday");
        break;
    default:
        IO.println("Unknown day");
}

Notes:

  • break is required to prevent fall-through.
  • Works with int, enum, String, etc.

Modern switch Expressions (Java 14+)

switch can now return a value and use arrow syntax.

int day = 3;

String dayName = switch (day) {
    case 1 -> "Monday";
    case 2 -> "Tuesday";
    case 3 -> "Wednesday";
    default -> "Unknown";
};

Notes:

  • No break needed.
  • Safer and more expressive than classic switch.

switch with yield (Multi-statement cases)

When a case needs multiple statements, use yield.

int score = 85;

String grade = switch (score / 10) {
    case 10, 9 -> "A";
    case 8 -> {
        IO.println("Good performance");
        yield "B";
    }
    case 7 -> "C";
    default -> "F";
};

Pattern Matching in switch (Java 21+)

You can switch on types, not just values.

Object obj = "Hello";

String result = switch (obj) {
    case String s -> "String of length " + s.length();
    case Integer i -> "Integer: " + i;
    case null -> "Null value";
    default -> "Unknown type";
};

Notes:

  • Eliminates manual casting.
  • null must be handled explicitly.

Guarded Patterns (when clauses)

Add extra conditions to pattern matches.

Object obj = 42;

String description = switch (obj) {
    case Integer i when i > 0 -> "Positive integer";
    case Integer i when i < 0 -> "Negative integer";
    case Integer i -> "Zero";
    default -> "Not an integer";
};

Notes:

  • Guards make switch as powerful as complex if-else chains.
  • Evaluated only after the pattern matches.

Nested Conditions

Conditions can be nested, but readability can suffer.

int age = 25;
boolean hasLicense = true;

if (age >= 18) {
    if (hasLicense) {
        IO.println("Can drive");
    } else {
        IO.println("Needs license");
    }
}

Tip: Prefer early returns or guards to reduce nesting.

Short-Circuit Evaluation

Logical operators && and || stop evaluating as soon as the result is known.

int x = 0;

if (x != 0 && 10 / x > 1) {
    IO.println("Safe");
}

Notes:

  • 10 / x is never evaluated if x != 0 is false.
  • Prevents errors like division by zero.
  • Improves performance.

instanceof with Pattern Matching (Java 16+)

Simplifies type checks and casting.

Object obj = "Java";

if (obj instanceof String s) {
    IO.println(s.toUpperCase());
}

Notes: Variable s is only available inside the if block.

Summary Tips

  • Prefer switch expressions over classic switch.
  • Use pattern matching to avoid casting.
  • Use guard clauses to flatten logic.
  • Rely on short-circuit evaluation for safety.
  • Avoid deep nesting whenever possible.

Loops & Iteration

Loops allow a program to repeat execution of a block of code while a condition holds or while elements remain to be processed.

for Loop (Classic)

Used when the number of iterations is known in advance.

for (int i = 0; i < 5; i++) {
    IO.println(i);
}

Structure:

for (initialization; condition; update) { }

Notes:

  • Initialization runs once.
  • Condition is checked before every iteration.
  • Update runs after each iteration.

for Loop with Multiple Variables

for (int i = 0, j = 10; i < j; i++, j--) {
    IO.println(i + ", " + j);
}

Notes:

  • Useful for two-pointer techniques.
  • Variables must be of the same type.

Enhanced for Loop (foreach)

Used to iterate over arrays and collections.

int[] numbers = {1, 2, 3};

for (int n : numbers) {
    IO.println(n);
}

Notes:

  • Read-only access (no index).
  • Safer and cleaner than classic for.

var in Enhanced for (Java 10+)

Type inference improves readability.

for (var n : numbers) {
    IO.println(n);
}

Unnamed Loop Variables (Java 21+)

Use _ when the loop variable is intentionally unused.

for (var _ : numbers) {
    IO.println("Hello");
}

Notes:

  • Improves intent clarity.
  • Prevents unused-variable warnings.

while Loop

Used when the number of iterations is unknown.

int count = 3;

while (count > 0) {
    IO.println(count);
    count--;
}

Notes:

  • Condition is checked before each iteration.
  • Can execute zero times.

do-while Loop

Guarantees at least one execution.

int attempts = 0;

do {
    IO.println("Attempt " + attempts);
    attempts++;
} while (attempts < 3);

Notes:

  • Condition checked after the loop body.
  • Useful for menus, retries, user input.

Loop Control Statements

break

Exits the loop immediately.

for (int i = 0; i < 10; i++) {
    if (i == 5) break;
}

continue

Skips the current iteration.

for (int i = 0; i < 5; i++) {
    if (i == 2) continue;
    IO.println(i);
}

Labeled break and continue

Control outer loops from inner loops.

outer:
for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
        if (i == 1 && j == 1) break outer;
    }
}

Notes:

  • Powerful but should be used sparingly.
  • Often replaceable with refactoring or early returns.

Nested Loops

Loops inside loops.

for (int i = 1; i <= 3; i++) {
    for (int j = 1; j <= 3; j++) {
        IO.println(i + ", " + j);
    }
}

Notes:

  • Time complexity multiplies (O(n²)).
  • Be careful with performance.

Infinite Loops

A loop that never ends unless explicitly stopped.

while (true) {
    // runs forever
}
for (;;) {
    // infinite loop
}

Common causes:

  • Missing update
  • Always-true condition
  • Logic errors

How to avoid:

  • Ensure termination conditions
  • Use timeouts or counters
  • Prefer bounded loops when possible

Summary Tips

  • Use classic for when you need indexes.
  • Prefer enhanced for for collections.
  • Use while / do-while when conditions are dynamic.
  • Avoid deep nesting and accidental infinite loops.
  • Use unnamed variables (_) to express intent clearly.

Functions / Methods

Functions (also called methods in Java) are reusable blocks of code that perform a specific task. They help organize programs, avoid repetition, and make code more readable.

Defining a Method

int add(int a, int b) {
    return a + b;
}

Notes:

  • int is the return type (void if no value is returned).
  • add is the method name.
  • (int a, int b) are parameters.

When inside a class , public and other access modifiers (visibility) are required.

public int add(int a, int b) {
    return a + b;
}

Calling a Method

int sum = add(3, 5);
IO.println(sum); // 8

Notes:

  • Pass arguments matching parameter types.
  • Return value can be stored or used directly.

Parameters & Arguments

  • Parameters: Variables defined in the method signature.
  • Arguments: Actual values passed during the call.
void greet(String name) {
    IO.println("Hello, " + name);
}

void main() {
	greet("Louise"); // Hello, Louise
}

Return Values

  • Use return to send a value back.
  • Methods with void do not return a value.
double square(double x) {
    return x * x;
}

void main() {
	IO.println(square(4)); // 16
}

Notes:

  • Returning a value ends the method immediately.
  • Multiple return statements can exist in a method.

Method overloading and Default Parameters (Java does not support them natively)

Method overloading means multiple methods with same name but different parameters. Java requires method overloading to simulate default parameters.

void greet() {
    greet("Guest");
}

void greet(String name) {
    IO.println("Hello, " + name);
}

void main() {
	greet(); // Hello, Guest
	greet("Louise"); // Hello, Louise
}

Return type alone cannot differentiate methods. So the following wouldn’t compile.

int getSomething() {
    return 1;
}

String getSomething() {
    return "Hello";
}

Named vs Positional Parameters

  • Java only supports positional arguments.
  • Arguments are assigned in order to parameters.
void printCoords(int x, int y) {
	IO.println("x=" + x + ", y=" + y);
}

void main() {
	printCoords(10, 20); // x=10, y=20
}

Method Scope

  • Variables defined inside a method are local and destroyed after the method ends.
  • Parameters are also local to the method.
void example() {
    int localVar = 5; // accessible only inside example()
}

Pure vs Impure Methods

Pure

No side effects; same inputs → same outputs.

int multiply(int a, int b) {
    return a * b; // pure
}

Impure

Modifies external state or depends on it.

int x = 0;

void increment() {
    x += 1;
}

void main() {
    IO.println("x=" + x);
    increment();
    IO.println("x=" + x);
}

Anonymous Functions / Lambdas (Java 8+)

  • Lambdas allow inline, unnamed functions.
  • Typically used with functional interfaces (more about interfaces later).
// More about lists later....
List<Integer> nums = List.of(3, 4, 5);

// More about streams later...
var squaredNums = nums.stream().map(n -> n*n).toList();

for (int squaredNum : squaredNums) {
	IO.println(squaredNum);
	// Expected Output:
	// 9
	// 16
	// 25
}

Notes:

  • n -> n * n is a lambda expression.
  • Can replace short single-method classes.
  • Often used with streams or event handlers.

Conclusion

In this guide, we’ve covered the core essentials of modern Java:

  • Leaner program structure with implicit classes and void main() scripts.
  • Modern input/output with the IO class.
  • Variables, constants, and type system including primitives, reference types, and arrays.
  • Type inference with var and safe conversions.
  • Operators: arithmetic, assignment, comparison, logical, bitwise, unary, ternary, and instanceof.
  • Control flow: if/else, switch (classic and modern), pattern matching, and guarded conditions.
  • Loops & iteration: for, while, do-while, nested loops, enhanced for, and loop control statements.
  • Functions / methods: defining, calling, parameters, return values, overloading, scope, purity, and lambdas.

You now have a solid foundation to write clean, modern Java code and understand the core language mechanics.

What’s Next?

In future articles, we’ll dive into Object-Oriented Programming in Java and beyond, including:

  • Classes, objects, and constructors.
  • Inheritance, interfaces, and polymorphism.
  • Encapsulation and access modifiers.
  • Advanced concepts like records, sealed classes, and pattern matching.
  • Functional programming with streams, lambdas, and higher-order operations.

By mastering these fundamentals first, you’ll be ready to tackle more advanced Java topics with confidence.

Stay tuned for the next part of the Java Journey where we explore OOP, streams, functional programming, and deeper language features.