PrepZone Logo
PrepZone

Anatomy of a Java Program

main(), packages, access modifiers, static blocks, and the order in which everything initialises.

Read these first

Why this matters

  • A single character wrong in the main signature and the JVM refuses to start, with an error that does not explain why.
  • Access modifiers are the only tool the language gives you for enforcing boundaries, and the default one is the one most people cannot name.
  • Initialisation order decides whether a field is set before you read it, and getting it wrong produces nulls that look impossible.
Hello.javaSource you write
Hello.classBytecode
JVMWindows / Linux / macOS
Machine codeRuns on the CPU
One compile step produces bytecode. Every operating system then runs that same bytecode through its own JVM.

The entry point

Java
public static void main(String[] args) { }

Each word is required, and for a reason.

  • public — the JVM calls this from outside your class, so it must be visible everywhere.
  • static — it runs before any object exists, so it cannot belong to an instance.
  • void — the JVM ignores return values; the exit code comes from System.exit instead.
  • main — the name the launcher looks for. It is not a keyword, just a convention the JVM enforces.
  • String[] args — command-line arguments. The array is empty, never null, when none are passed.

Packages and the file layout

A package is a namespace that also maps to a directory. The declaration must be the first statement in the file.

Java
package com.prepzone.billing;

import java.util.List;              // one class
import java.util.concurrent.*;      // every class in that package
import static java.lang.Math.max;   // a static member, usable unqualified

public class Invoice {
    public static void main(String[] args) {
        System.out.println(max(10, 20));
    }
}

Rules worth knowing

  • The file must be named after its public class, and that class must sit in a directory path matching its package.
  • A file may contain several classes but only one public one.
  • java.lang is imported automatically, which is why String and System need no import.
  • A wildcard import does not include sub-packages — java.util.* does not bring in java.util.concurrent.
  • Importing two classes with the same simple name from different packages forces you to write one of them fully qualified.

Access modifiers

Four levels, and the one with no keyword is the one people forget.

AspectModifierVisible to
privateOnly the declaring classNothing outside, not even a subclass
(none) — package-privateEvery class in the same packageNot subclasses in other packages
protectedSame package, plus subclasses anywhereNot unrelated classes elsewhere
publicEverywhereEverywhere
  • private

    ModifierOnly the declaring class
    Visible toNothing outside, not even a subclass
  • (none) — package-private

    ModifierEvery class in the same package
    Visible toNot subclasses in other packages
  • protected

    ModifierSame package, plus subclasses anywhere
    Visible toNot unrelated classes elsewhere
  • public

    ModifierEverywhere
    Visible toEverywhere

Omitting a modifier is a real choice called package-private, not an absence of one.

The practical guidance is to start every field private and every method as narrow as it can be, then widen only when something genuinely needs access. Widening later is easy; narrowing a published API is not.

Initialisation order

This is the part that causes surprising nulls. Static members initialise once when the class is first used; instance members initialise on every construction.

Java
public class Order {
    static String region = log("1. static field");
    static { log("2. static block"); }

    String id = log("3. instance field");
    { log("4. instance block"); }

    Order() { log("5. constructor"); }

    static String log(String step) {
        System.out.println(step);
        return step;
    }

    public static void main(String[] args) {
        new Order();
        System.out.println("---");
        new Order();
    }
}
Java
1. static field
2. static block
3. instance field
4. instance block
5. constructor
---
3. instance field
4. instance block
5. constructor

The order, stated plainly

  • Static field initialisers and static blocks run once, in source order, when the class is first loaded and initialised.
  • For each new object: instance field initialisers and instance initialiser blocks run in source order, then the constructor body.
  • With inheritance, the parent's static work happens before the child's, and the parent's constructor completes before the child's body begins.

Common misreadings

  • "static void main is fine." It compiles, but the JVM cannot call it. The launcher requires public.
  • "protected means subclasses only." It also grants access to every class in the same package. For subclass-only access there is no modifier.
  • "args is null when no arguments are given." It is a zero-length array, so args.length is safe without a null check.
  • "The constructor runs first." Field initialisers and instance blocks run before the constructor body.
  • "A class with no modifier is public." It is package-private, invisible outside its package.

Quick recall

Everything you need if you only revisit this box.

  • public static void main(String[] args) — public so the JVM can reach it, static so no object is needed, and the parameter type is fixed even though the name is not.
  • args is always a real array; an empty command line gives length 0.
  • A package declaration must be the first statement, and the directory path must match it.
  • Four access levels: private, package-private (no keyword), protected, public. protected includes the whole package.
  • Order per object: instance field initialisers → instance blocks → constructor body. Statics run once, before all of it.
  • Never call an overridable method from a constructor — the override runs before the subclass fields exist.

Test yourself

Answer these before moving on — recall is what makes it stick.