Table of Contents

Structure of a Java program

  • Last Updated: July 1, 2023
  • By: javahandson
  • Series
img

Structure of a Java program

The structure of a java program follows a fixed layout that every file must respect. First comes the package statement, then the imports, then the class, and inside the class sits the main method. Learn each block, the strict order, and the reason behind every keyword in this beginner-friendly guide.

1. Introduction

Java does not let you scatter code anywhere in a file. The language fixes a layout, and your file must follow it. Miss the layout and the compiler stops you before the program ever runs.

Think of a business letter. The address sits at the top, the greeting comes next, then the body, and the signature closes it. Nobody signs a letter in the middle. Java files carry the same kind of discipline.

So what does that layout look like? A package line first. Import lines after that. Then a class, and inside the class, your data and your methods. One of those methods carries a very special name: main.

Beginners often treat this skeleton as magic words to copy. That habit hurts later. Once you know why each line sits where it sits, errors like “main method not found” stop feeling random.

1.1 What This Article Covers

We will walk through the skeleton block by block. Every block gets a small example you can type and run. Here is the plan:

  • The standard skeleton, and why the order of blocks never changes
  • Packages, imports, wildcards, and fully qualified names
  • Class declarations, data members, and methods
  • The one-public-class rule and the filename rule
  • A full breakdown of public static void main(String[] args)
  • Command-line arguments and the args array
  • Printing output with print, println, and printf
  • Comments, compiling, running, and the classpath
  • Static blocks, class loading order, and common mistakes

No prior Java knowledge required. If you can open a text editor and a terminal, you can follow along.

2. Java Program Blocks

2.1 The Standard Skeleton

Every Java source file boils down to the same shape. Strip away the details and you land here.

package com.javahandson.demo;   // 1. package statement (optional, at most one)

import java.util.List;          // 2. import statements (optional, any number)

public class Demo {             // 3. class declaration

    int count;                  // 4. data members

    void greet() {              // 5. methods
        System.out.println("Hello");
    }

    public static void main(String[] args) {   // 6. entry point
        // Block of statements
    }
}

Six pieces, and only two of them are truly mandatory. You always need a class. You need a main method if you want to run the file directly.

The package line is optional. Imports are optional too. Data members and extra methods are optional. Yet whenever they do appear, they must appear in that order.

2.2 The Order Is Strict

Java cares about sequence here, not just presence. Package first. Imports second. Class third. That rule has no exceptions.

Why so rigid? The compiler reads your file top to bottom. It wants to know your address before it looks at your neighbours. The package line tells it where the class lives, so it must come first.

Flip two lines and the build breaks immediately.

import java.util.List;          // WRONG: import before package
package com.javahandson.demo;

public class Demo { }

// Output: error: class, interface, enum, or record expected

Keep this checklist in your head:

  • At most one package statement, and it sits on the very first code line
  • Any number of import statements, all of them below the package line
  • Comments may sit anywhere, even above the package line
  • Blank lines never change anything, so format freely

Comments are the one thing allowed above the package statement. A license header at the top of a file is perfectly legal.

2.3 The Package Statement

A package groups related classes, interfaces, and sub-packages under one roof. Think of folders on your laptop. You do not dump every file on the desktop, right? You sort them.

Java does the same with classes. Security classes live in java.security. Language basics live in java.lang. Collections live in java.util. Each package is a labelled drawer.

Packages solve two problems at once:

  • Naming clashes disappear, because two classes can share a simple name in different packages
  • Related code stays together, so a big project stays navigable
  • Access control improves, since package-private members stay hidden outside the package

Here is a small class that declares its package.

package com.javahandson.pkg1;

public class Student {
    String studentName;

    public String getStudentName() {
        return studentName;
    }

    public void setStudentName(String studentName) {
        this.studentName = studentName;
    }
}

That Student class now belongs to com.javahandson.pkg1. Its full identity becomes com.javahandson.pkg1.Student, and no other class can claim that exact name.

One detail trips people up. The package name must mirror the folder path on disk. A class in com.javahandson.pkg1 lives in a folder chain com/javahandson/pkg1. Skip that and the JVM cannot find your class at runtime.

What if you skip the package line entirely? Your class lands in the default package. That works for quick practice files. Real projects avoid it, because you cannot import a class out of the default package.

2.4 Import Statements

An import statement pulls a class from another package into your file. After that, you refer to it by its short name.

Our School class wants to use Student. Student sits in a different package, so School imports it.

package com.javahandson.pkg;

import com.javahandson.pkg1.Student;

public class School {
    public static void main(String[] args) {
        Student student = new Student();
        student.setStudentName("Rohit");
        System.out.println(student.getStudentName());
        // Output: Rohit
    }
}

Notice what the import does not do. It copies nothing. It loads nothing. An import is just a nickname, a shortcut so you can type Student instead of the long name.

Now a fair question. Why do you never import String or System? Both live in java.lang, and Java imports java.lang automatically into every file. String, System, Integer, Math, Object, Exception all ride along for free.

Two more rules worth remembering:

  • Classes in your own package need no import at all
  • Unused imports cost nothing at runtime, though they clutter the file

2.5 Single-Type vs Wildcard Imports

Java gives you two import flavours. A single-type import names exactly one class. A wildcard import opens a whole package.

import java.util.List;   // single-type: only List
import java.util.*;      // wildcard: every class directly in java.util

Which should you pick? Single-type imports win most of the time. They document exactly what your file depends on, and they dodge the ambiguity trap you will meet in a moment.

A wildcard has one more limit that surprises beginners. It reaches only one level deep. So java.util.* gives you ArrayList and HashMap, but it never gives you java.util.concurrent classes.

import java.util.*;

public class WildcardDemo {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();   // fine, both live in java.util
        names.add("Rohit");
        System.out.println(names);
        // Output: [Rohit]

        // ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
        // Above line fails: it lives in java.util.concurrent, not java.util
    }
}

2.6 Two Classes With the Same Name

Suppose a second Student class exists in another package. Same simple name, different drawer.

package com.javahandson.pkg2;

public class Student {
    int studentId;

    public int getStudentId() {
        return studentId;
    }

    public void setStudentId(int studentId) {
        this.studentId = studentId;
    }
}

Your import line now decides which one you get. Point it at pkg2 and you pick up the id-based Student.

package com.javahandson.pkg;

import com.javahandson.pkg2.Student;

public class School {
    public static void main(String[] args) {
        Student student = new Student();
        student.setStudentId(101);
        System.out.println(student.getStudentId());
        // Output: 101
    }
}

Can you import both at once? No. The compiler refuses, because the name Student would then mean two different things in one file.

package com.javahandson.pkg;

import com.javahandson.pkg2.Student;
import com.javahandson.pkg1.Student;   // compile-time error here

public class School {
    public static void main(String[] args) {
        Student student = new Student();
    }
}

// Output: error: a type with the same simple name is already defined
//         by the single-type-import of com.javahandson.pkg2.Student

2.7 Fully Qualified Names

So how do you use both Student classes in one file? Drop the import and spell out the full name instead.

package com.javahandson.pkg;

public class School {
    public static void main(String[] args) {
        com.javahandson.pkg1.Student byName = new com.javahandson.pkg1.Student();
        byName.setStudentName("Rohit");

        com.javahandson.pkg2.Student byId = new com.javahandson.pkg2.Student();
        byId.setStudentId(101);

        System.out.println(byName.getStudentName() + " - " + byId.getStudentId());
        // Output: Rohit - 101
    }
}

Wordy? Very. Correct? Absolutely. A fully qualified name is the class’s true address, and it always works, import or no import.

You can also mix the two styles. Import the class you use often, and fully qualify the rare one. Java allows exactly that, and real codebases lean on the trick when java.util.Date meets java.sql.Date.

2.8 The Class Declaration

Everything in Java lives inside a class. There is no loose code floating at file level, unlike Python or JavaScript.

A class wraps data and behaviour together. That bundling is exactly what encapsulation means, and it forms the backbone of object-oriented programming.

The declaration itself is short:

public class Student {   // access modifier, keyword, name, then the body
    // data members and methods go here
}

Break that line into parts:

  • public — any class anywhere may use it; drop it and only the same package may
  • class — the keyword itself, always lowercase
  • Student — the name, by convention starting with a capital letter
  • { } — the body, holding every member of the class

2.9 Data Members and Methods

Data members hold values. Methods do work. Together they make a class useful.

A data member is simply a variable declared inside the class. It can hold a number, a piece of text, or another object. Read more in our guide to variables and constants.

Methods come in a few common shapes:

  • Getters hand back a stored value
  • Setters change a stored value
  • Business methods run real logic, like calculating a fee
  • Static methods belong to the class, not to any single object

Here they all appear in one file.

public class Student {
    int studentId;              // data member (instance)
    static int totalStudents;   // data member (static, shared by all objects)

    public int getStudentId() { // getter
        return studentId;
    }

    public void setStudentId(int studentId) {  // setter
        this.studentId = studentId;
    }

    public void study() {       // business method
        System.out.println("Student is busy studying");
    }

    public static void main(String[] args) {
        Student student = new Student();
        student.setStudentId(101);
        System.out.println("Student id is: " + student.getStudentId());
        student.study();
        // Output: Student id is: 101
        // Output: Student is busy studying
    }
}

Spot the difference between studentId and totalStudents. Each Student object owns a private copy of studentId. All of them share a single totalStudents. That distinction matters, and our article on types of data members digs deeper.

3. The One Public Class Rule

3.1 Only One Public Class Per File

A single .java file may declare at most one public class. Try two and the compiler refuses on the spot.

// File: School.java
public class School { }
public class Student { }   // second public class in the same file

// Output: error: class Student is public, should be declared
//         in a file named Student.java

Why does Java insist? The compiler needs a predictable way to find a public class on disk. One public type per file, named after the file, keeps that lookup dead simple.

3.2 The Filename Must Match

The public class name and the filename must match exactly, letter for letter. Case counts.

  • public class School must live in School.java
  • Writing it in school.java breaks the build on most systems
  • Windows hides this bug sometimes, because its file system ignores case
  • Linux and macOS build servers will not be so forgiving

That last point burns a lot of beginners. Code that compiles on a laptop fails on the build machine, purely over a capital letter.

3.3 Extra Classes in the Same File

Non-public classes face no such limit. Pack as many as you like into one file.

// File: School.java
public class School {
    public static void main(String[] args) {
        Teacher teacher = new Teacher();
        teacher.teach();
        // Output: Teaching Java structure
    }
}

class Teacher {          // package-private, no public keyword
    void teach() {
        System.out.println("Teaching Java structure");
    }
}

Handy for tiny helper classes and quick demos. Beyond that, keep one class per file. Your teammates will thank you when they go looking for Teacher.java.

4. Breaking Down the main Method

4.1 The Signature at a Glance

Here is the line every Java program starts from:

public static void main(String[] args) {
    // your program starts here
}

Five parts, five reasons. This table sums them up, then each one gets its own section.

Part Why it is there
public The JVM sits outside your class, so it needs open access to call the method
static No object exists when the program starts, so the JVM calls main on the class itself
void main hands nothing back; the JVM has no use for a return value
main The exact name the JVM searches for as the entry point
String[] args Command-line arguments, delivered as an array of text; empty when you pass none, never null

4.2 Why public?

The JVM lives outside your code. It belongs to no package of yours, and it certainly does not extend your class.

Java’s access rules therefore leave one option. Only a public method stays visible to a caller from anywhere. Mark main as private, and the JVM sees a locked door.

Try it and you get a clear message.

public class Test {
    private static void main(String[] args) {   // private, not public
        System.out.println("Never runs");
    }
}

// Output: Error: Main method not found in class Test

4.3 Why static?

Picture the moment your program starts. No objects exist yet. Nothing has been constructed. The heap sits empty.

An instance method needs an object to run on. So if main were an instance method, the JVM would have to create a Test object before calling main. And which constructor should it choose? What if the constructor takes arguments? What if it throws?

Java sidesteps that whole mess with one keyword. A static method belongs to the class, not to an object. The JVM loads the class, then calls Test.main directly. Clean and predictable.

One side effect follows from this. Inside main, you cannot touch instance members directly. Create an object first, then reach through it.

4.4 Why void?

Ask yourself who calls main. The JVM does. Now ask what the JVM would do with a returned value. Nothing at all.

So main returns void. Its job finishes when the last statement finishes.

Do you want to signal success or failure to the operating system? Java gives you a separate tool for that.

public class ExitDemo {
    public static void main(String[] args) {
        System.out.println("Checking input...");
        if (args.length == 0) {
            System.exit(1);   // non-zero status tells the shell something went wrong
        }
        System.out.println("Got " + args.length + " argument(s)");
    }
}

// Output: Checking input...
// (exits with status 1 when you pass no arguments)

4.5 Why the Name main?

The name carries no magic meaning. Java’s designers simply picked a word and wrote it into the specification, following C before it.

What matters is the agreement. The JVM promises to call a method named main. Your class promises to provide one. Rename it to Main or begin, and the contract breaks.

Remember, Java is case sensitive. Main and main are two different names.

4.6 Why String[] args?

Your program often needs input from the outside. Maybe a filename. Maybe a port number. The shell passes such values as plain text, and Java hands them to you in an array.

The parameter name args is convention only. Call it arguments or even a, and the program still runs. The type is what the JVM checks.

Two spellings of that type both work:

public static void main(String[] args)    // classic form
public static void main(String args[])    // legacy C-style, same thing
public static void main(String... args)   // varargs, also accepted by the JVM

All three compile to the same signature under the hood. Stick with the first. Everyone reads it instantly.

4.7 What the JVM Actually Looks For

When you run java Test, the JVM performs a very literal search inside class Test. It wants a method that ticks every box:

  • Named exactly main, lowercase
  • Marked public
  • Marked static
  • Returning void
  • Taking one parameter of type String[]

Miss any single box and the search fails. The JVM does not guess. It does not fall back to some other method. It simply reports failure and stops.

Notice something interesting. Your class may hold other methods called main, as long as their parameters differ. Those are ordinary overloaded methods, and the JVM ignores them at startup.

public class Overloaded {
    public static void main(String[] args) {   // the JVM starts here
        System.out.println("Real entry point");
        main(5);
    }

    public static void main(int number) {      // just a normal overload
        System.out.println("Called with " + number);
    }
}

// Output: Real entry point
// Output: Called with 5

4.8 When main Is Missing or Wrong

Here is the part that confuses newcomers. A broken main still compiles.

Think about why. Your file might be a library class that nobody runs directly. The compiler has no business demanding an entry point, so it stays quiet. Trouble arrives only at runtime.

public class Broken {
    public void main(String[] args) {   // missing static: compiles fine
        System.out.println("Hello");
    }
}

// javac Broken.java   -> compiles with no error
// java Broken         -> Error: Main method is not static in class Broken

// Output: Error: Main method is not static in class Broken,
//         please define the main method as:
//            public static void main(String[] args)

Change the parameter type and you get a different flavour of the same failure.

public class Broken2 {
    public static void main(String args) {   // String, not String[]
        System.out.println("Hello");
    }
}

// Output: Error: Main method not found in class Broken2,
//         please define the main method as:
//            public static void main(String[] args)

Whenever that message appears, run down the five-box checklist above. One of the boxes is empty, guaranteed.

5. Command-Line Arguments

5.1 Reading the args Array

Whatever you type after the class name lands in args. The shell splits on spaces, and each piece becomes one element.

public class Args {
    public static void main(String[] args) {
        System.out.println("Argument count: " + args.length);
        for (int i = 0; i < args.length; i++) {
            System.out.println("args[" + i + "] = " + args[i]);
        }
    }
}

// > java Args Hello Developer
// Output: Argument count: 2
// Output: args[0] = Hello
// Output: args[1] = Developer

Want a value with a space inside it? Wrap it in quotes, and the shell keeps it as one argument.

// > java Args "Hello Developer"
// Output: Argument count: 1
// Output: args[0] = Hello Developer

5.2 args Is Empty, Never null

Run the program with no arguments and args still exists. The JVM hands you an array of length zero.

public class Args {
    public static void main(String[] args) {
        System.out.println("Argument length - " + args.length);
    }
}

// > javac Args.java
// > java Args
// Output: Argument length - 0

This guarantee is worth memorising, and interviewers love asking about it. Because args never holds null, a null check is pure noise.

if (args != null && args.length > 0) {   // the null check adds nothing
    System.out.println(args[0]);
}

if (args.length > 0) {                    // cleaner, and just as safe
    System.out.println(args[0]);
}

What still bites you is an out-of-range index. Reading args[0] with zero arguments throws ArrayIndexOutOfBoundsException. Check the length first, always.

5.3 Every Argument Arrives as Text

Type java Calc 10 20 and you might expect two numbers. You get two Strings instead. The array type says String[], and the JVM keeps that promise literally.

Convert them yourself before doing any maths.

public class Calc {
    public static void main(String[] args) {
        if (args.length < 2) {
            System.out.println("Usage: java Calc <num1> <num2>");
            return;
        }
        int a = Integer.parseInt(args[0]);   // text to number
        int b = Integer.parseInt(args[1]);
        System.out.println("Sum = " + (a + b));
    }
}

// > java Calc 10 20
// Output: Sum = 30

// > java Calc ten 20
// Output: Exception in thread "main" java.lang.NumberFormatException: For input string: "ten"

Notice the guard clause at the top. Three lines of defence turn a crash into a friendly usage message.

6. Printing Output From a Java Program

Section 5 showed how text gets into your program. Now look at the other direction. How does your program talk back to you?

Every example so far has called System.out.println. It sits in the skeleton, inside main, and in almost every code block above. A method you use that often deserves a proper look.

Three methods do nearly all the work: print, println, and printf. They differ mainly in one thing, and that is how they treat the end of a line.

6.1 System.out.print

The print method writes your text and then stops. Your cursor stays exactly where the text ended, still on the same line.

Call it twice and the second piece of text lands right beside the first.

public class PrintDemo {
    public static void main(String[] args) {
        System.out.print("Hello ");
        System.out.print("Java ");
        System.out.print("World");

        // Output: Hello Java World
    }
}

Everything landed on one row. No line break appeared anywhere, not even after the final word.

One small rule catches beginners out. The print method always needs an argument. No empty version exists, so System.out.print() refuses to compile.

6.2 System.out.println

The println method does everything print does, then adds a line break. Its name spells that out: print line.

public class PrintlnDemo {
    public static void main(String[] args) {
        System.out.println("Hello");
        System.out.println("Java");
        System.out.println();        // legal: prints a blank line
        System.out.println("World");

        // Output: Hello
        // Output: Java
        // Output:
        // Output: World
    }
}

Look at that bare println() call. An empty version does exist here, and it prints a blank line. Its cousin print has no such twin.

So what does println actually add at the end? Not a hard-coded \n. It asks the JVM for the line separator of the current platform. Windows wants \r\n, while Linux and macOS want \n. Your code never changes.

6.3 print vs println Side by Side

Which one should you reach for? Ask yourself a single question. Is this row of output finished?

Behaviour System.out.print System.out.println
Line break after the text No Yes
Where the cursor lands Same line, just after the text Start of the next line
Empty no-argument call Does not exist Allowed, and prints a blank line
Natural use Building one row piece by piece Finishing a row
Inside a loop Values stay on one row Every value gets its own row

A loop shows the split most clearly. Print each value, then close the row with a bare println.

public class LoopDemo {
    public static void main(String[] args) {
        for (int i = 1; i <= 5; i++) {
            System.out.print(i + " ");   // everything stays on one row
        }
        System.out.println();            // now close that row
        System.out.println("Done");

        // Output: 1 2 3 4 5
        // Output: Done
    }
}

Swap that print for a println and you get five separate lines instead. Same loop, completely different shape.

6.4 What System.out Really Is

System.out.println looks like one long magic name. It is really three separate things, joined by dots.

  • System — a final class in java.lang, which is why you never import it
  • out — a static field inside System, and its type is PrintStream
  • println — an ordinary instance method on that PrintStream object

Read the chain again with that in mind. You reach into the System class, grab the out object, then call a method on it. Nothing mysterious happens.

Section 2.4 explained why String needs no import. System rides along for exactly the same reason, because java.lang arrives free in every file.

public class OutDemo {
    public static void main(String[] args) {
        java.io.PrintStream stream = System.out;   // out is just an object
        stream.println("Called through a variable");

        // Output: Called through a variable
    }
}

That code proves the point. Once you hold the PrintStream in a variable, println is only a method call like any other.

6.5 Overloads for Every Type

Here is a fair question. How can println swallow an int, a double, a String, and a whole object, all under one name?

It cannot, and it does not. PrintStream simply declares many methods named println, each one taking a different type. That is plain method overloading.

Parameter type print println What lands on screen
boolean Yes Yes true or false
char Yes Yes The single character
int Yes Yes The number; byte and short widen to int
long Yes Yes The number
float Yes Yes The number
double Yes Yes The number
char[] Yes Yes The characters, not an address
String Yes Yes The text
Object Yes Yes Whatever toString returns
no argument No Yes A blank line

The char[] row is the odd one out. A char array prints its characters. Every other array falls through to the Object overload, so you see an address-like string instead.

public class ArrayPrint {
    public static void main(String[] args) {
        char[] letters = {'J', 'a', 'v', 'a'};
        int[] numbers = {1, 2, 3};

        System.out.println(letters);   // picks the char[] overload
        System.out.println(numbers);   // falls back to the Object overload

        // Output: Java
        // Output: [I@1b6d3586
    }
}

That second line is no bug. It shows Object.toString doing its job, and section 6.9 hands you the fix.

6.6 Formatted Output With printf

Sometimes you want columns that line up, or a price with exactly two decimals. String concatenation turns ugly fast when you try. The printf method handles it cleanly.

A printf call takes a format string dotted with placeholders, followed by the values to drop into them.

public class PrintfDemo {
    public static void main(String[] args) {
        System.out.printf("%-10s %5.2f x%d%n", "Coffee", 3.5, 2);
        System.out.printf("%-10s %5.2f x%d%n", "Sandwich", 12.0, 1);

        // Output: Coffee      3.50 x2
        // Output: Sandwich   12.00 x1
    }
}

Notice how the prices line up. Break the format string apart and you can see why:

  • %-10s — a String, padded out to 10 characters, aligned left
  • %5.2f — a decimal number, five characters wide, two places after the point
  • %d — a whole number
  • %n — a line break that suits the platform

Remember one rule above all others. The printf method never adds a line break on its own. Forget that %n and your next output lands on the very same row.

These specifiers cover most daily work:

  • Use %s for a String, or any object through its toString method
  • Use %d for the integer types
  • Reach for %f when you print a float or a double
  • Pick %b for a boolean and %c for a single char
  • Type %% whenever you want a literal percent sign

6.7 Escape Sequences

You can also force a line break from inside the text itself. An escape sequence is a backslash plus a letter, and the compiler swaps it for a special character.

  • Write \n for a newline
  • Write \t for a tab
  • Use \” to place a double quote inside a String
  • Type \\ when you want one real backslash
public class EscapeDemo {
    public static void main(String[] args) {
        System.out.print("Name\tCity\n");
        System.out.print("Rohit\tPune\n");
        System.out.println("She said \"Hello\"");

        // Output: Name    City
        // Output: Rohit   Pune
        // Output: She said "Hello"
    }
}

Now for a subtle point that interviewers enjoy. The \n sequence is fixed, and it always means one newline character. Both println and %n behave differently, because they ask the JVM for the platform separator.

Does the difference show on screen? Rarely. Does it matter when you write a file that Windows tools must open? Very much. Call System.lineSeparator() whenever a file is the target.

6.8 System.err vs System.out

System.out is not your only stream. System.err sits right beside it, and it carries error messages.

Both fields hold a PrintStream, so both offer the same print, println, and printf methods. Only the destination differs.

public class ErrDemo {
    public static void main(String[] args) {
        System.out.println("Normal message");
        System.err.println("Something went wrong");

        // Output: Normal message
        // Output: Something went wrong   (most IDEs paint this one red)
    }
}

Why bother keeping two streams? Because the shell can redirect each one separately.

> java ErrDemo > output.txt      // only System.out lands in the file
> java ErrDemo 2> errors.txt     // only System.err lands in the file

Send your normal results to out, and send your problems to err. Anyone who later pipes your program into a file will thank you for it.

6.9 Output Mistakes That Bite Beginners

Four traps catch almost every learner. Meet them here rather than in an interview.

Plus means addition, until suddenly it does not. Java reads the + operator from left to right. Begin with a String and everything after it turns into text.

System.out.println("Sum: " + 2 + 3);     // Output: Sum: 23
System.out.println("Sum: " + (2 + 3));   // Output: Sum: 5

The first line concatenates twice over. Brackets in the second line force the addition to happen first, so 5 appears.

Printing an array shows an address. Only a char array prints its contents. Every other array needs Arrays.toString.

import java.util.Arrays;

public class ArrayFix {
    public static void main(String[] args) {
        int[] numbers = {1, 2, 3};

        System.out.println(numbers);                    // address-like text
        System.out.println(Arrays.toString(numbers));   // readable

        // Output: [I@1b6d3586
        // Output: [1, 2, 3]
    }
}

The literal null will not compile. Type System.out.println(null) and the compiler calls the reference ambiguous, because null fits the String, char[], and Object overloads equally well. A String variable holding null behaves perfectly well, though.

String name = null;
System.out.println(name);            // Output: null

// System.out.println(null);         // error: reference to println is ambiguous

System.out.println((String) null);   // the cast picks one overload
                                     // Output: null

Calling println in a tight loop runs slowly. Every call locks the stream and flushes it straight away. Push a million lines through it and your program crawls. Gather the text in a StringBuilder, or wrap the stream in a BufferedWriter, then write once at the end.

7. Comments in a Java Program

7.1 Single-Line Comments

Comments explain code to humans. The compiler skips them completely, so they cost nothing at runtime.

The single-line form starts with two slashes and runs to the end of the line.

int retries = 3;   // three attempts, then we give up
// The whole of this line is a comment

Use these for quick notes beside a tricky line. Do not narrate the obvious. A comment saying “increment i” above i++ helps nobody.

7.2 Multi-Line Comments

Need several lines? Open with slash-star and close with star-slash. Everything between disappears at compile time.

/*
 * Retry policy:
 *   attempt 1 - immediate
 *   attempt 2 - after 1 second
 *   attempt 3 - after 5 seconds
 */
int retries = 3;

Developers also use this form to switch off a block of code while debugging. Handy, though version control is a better place to park old code.

7.3 Javadoc Comments

The third form looks similar but starts with slash-star-star. That extra star turns it into documentation.

/**
 * Adds two numbers and returns the sum.
 *
 * @param a the first number
 * @param b the second number
 * @return the sum of a and b
 */
public int add(int a, int b) {
    return a + b;
}

Run the javadoc tool over your source and it turns these blocks into browsable HTML pages. Every page of the official Java API grew from exactly this kind of comment.

Tags like @param, @return, and @throws give the tool structure to work with. Your IDE reads them too, which is why hovering over a method shows a neat description.

8. Compiling and Running From the Command Line

8.1 javac, Then java

An IDE hides these two steps behind a green triangle. Do them by hand once and the whole picture clicks.

> javac School.java    // compiles the source into School.class (bytecode)
> java School          // the JVM loads School.class and calls its main method

// Output: Rohit

Step one belongs to the compiler. javac reads your .java text, checks the syntax, and writes bytecode into a .class file.

Step two belongs to the JVM. java loads that .class file, verifies it, links it, and starts executing from main.

8.2 Why java Takes No File Extension

This asymmetry puzzles everyone at first. javac School.java needs the extension. java School must not have it. Why the difference?

Because the two commands take different kinds of input:

  • javac takes a file path, so it needs the real name on disk, extension included
  • java takes a class name, not a file name, and it appends .class internally while searching the classpath

Type java School.class and you get a confusing error. The JVM hunts for a class literally named “class” inside a package named “School”.

8.3 Running a Class Inside a Package

Packages change the commands slightly. Your folder layout must mirror the package name, and you launch with the fully qualified class name.

project/
  com/
    javahandson/
      pkg/
        School.java
      pkg1/
        Student.java

// From the project folder:
> javac com/javahandson/pkg/School.java com/javahandson/pkg1/Student.java
> java com.javahandson.pkg.School

// Output: Rohit

Look closely at that last command. Slashes become dots, and the .class extension never appears. Run it from inside the pkg folder instead, and the JVM will not find the class.

8.4 Classpath Basics

The classpath answers one question for the JVM: where do I look for .class files? By default it points at the current directory.

Point it somewhere else with the -cp flag.

> javac -d out com/javahandson/pkg/School.java com/javahandson/pkg1/Student.java
> java -cp out com.javahandson.pkg.School

// -d out  : write .class files into the out folder
// -cp out : search for classes starting from the out folder

A few rules keep classpath pain away:

  • Every dependency your program touches must sit on the classpath
  • Jar files count as classpath entries too, so list them explicitly
  • Separate entries with a semicolon on Windows and a colon on Linux or macOS
  • ClassNotFoundException almost always means a classpath problem, not a code problem

Build tools like Maven and Gradle exist mainly to manage this for you. Under the hood, they simply assemble a very long classpath.

9. Static Initialization Order

9.1 What Happens Before main

Your first line of main is not the first thing that runs. The JVM does real work before it ever reaches you.

Class loading follows a fixed sequence:

  1. The class loader finds School.class on the classpath and reads its bytecode
  2. Static data members receive their default values: 0, false, or null
  3. Static initializers and static blocks run, top to bottom, in source order
  4. Only then does the JVM call main

Instance members play no part here. They wait until somebody calls new.

9.2 A Worked Example

Nothing beats printing the order and watching it happen.

public class InitOrder {

    static int counter = init("static field");   // runs first

    static {                                     // runs second
        System.out.println("static block");
        counter = 10;
    }

    static int init(String label) {
        System.out.println(label);
        return 5;
    }

    public static void main(String[] args) {     // runs last
        System.out.println("main, counter = " + counter);
    }
}

// Output: static field
// Output: static block
// Output: main, counter = 10

Read the output from the top. The static field initializer fires, then the static block, then main. Counter ends at 10, because the block overwrote the 5.

9.3 Source Order Matters

Move that static block above the field and the result flips.

public class InitOrder2 {

    static {
        System.out.println("static block");
        counter = 10;            // legal: assignment to a later field
    }

    static int counter = 5;      // this runs after the block, so 5 wins

    public static void main(String[] args) {
        System.out.println("main, counter = " + counter);
    }
}

// Output: static block
// Output: main, counter = 5

Same two lines, opposite result. The block now runs first, sets 10, and the field initializer promptly overwrites it with 5.

Take the lesson to heart. Static blocks are a common interview topic exactly because source order decides the answer.

10. Common Mistakes

10.1 Import Before Package

The package line must sit above every import. Swap them and the compiler bails out with a cryptic message about an expected class or interface.

Fix: package first, imports next, class last. No exceptions.

10.2 A main Method That Does Not Match

Drop static, misspell the name, or take a plain String parameter, and your program compiles happily. Then it dies at launch with “Main method not found”.

Fix: copy the signature exactly, then check all five boxes from section 4.7.

10.3 Filename and Class Name Drift Apart

Rename a class in your editor and forget to rename the file. The compiler notices immediately when the class is public.

Fix: keep public class Foo inside Foo.java, capital letter and all.

10.4 Running java With the .java Extension

Typing java School.class or java School.java sends the JVM hunting for the wrong thing entirely.

> java School.class
// Output: Error: Could not find or load main class School.class

> java School
// Output: Rohit

Fix: pass a class name to java, never a file name.

10.5 Calling Instance Members From main

Beginners write a method, call it from main, and hit a wall.

public class Greeter {
    void greet() {                          // instance method
        System.out.println("Hello");
    }

    public static void main(String[] args) {
        greet();   // error: non-static method greet() cannot be
                   //        referenced from a static context
    }
}

Remember why. main is static, so no object exists yet. Build one, then call through it.

public class Greeter {
    void greet() {
        System.out.println("Hello");
    }

    public static void main(String[] args) {
        Greeter greeter = new Greeter();   // create an object first
        greeter.greet();
        // Output: Hello
    }
}

Alternatively, mark greet as static. Both fixes work, so pick the one that suits your design.

11. Interview Questions

Q: What is the correct order of blocks in the structure of a Java program?

A: The package statement comes first, then the import statements, then the class declaration. Inside the class you place data members, methods, and the main method. Only comments may appear above the package line. Break that order and the code fails to compile.

Q: Why is the main method static in Java?

A: No object of your class exists when the program starts. A static method belongs to the class itself, so the JVM can call Test.main without building an object first. Were main an instance method, the JVM would have to pick a constructor and guess at its arguments.

Q: Why must the main method be public and void?

A: The JVM calls main from outside your class and outside your package, so only public access lets it through. The return type stays void because the JVM would simply discard any value you handed back. To report success or failure to the shell, call System.exit with a status code instead.

Q: Can a Java file contain more than one public class?

A: No. Each .java file allows at most one public class, and its name must match the file name exactly. You may add any number of non-public classes to the same file, which is handy for small helpers and quick demos.

Q: Why do we never import the String class?

A: String lives in java.lang, and Java imports java.lang into every source file automatically. System, Math, Integer, Object, and Exception come along too. Every other package, including java.util, still requires an explicit import.

Q: How do you use two classes with the same name in one file?

A: You cannot import both, since the compiler would then see one simple name meaning two types. Instead, use the fully qualified name for at least one of them, such as com.javahandson.pkg1.Student. Many teams import the common class and fully qualify the rare one.

Q: What happens if you pass no command-line arguments?

A: The args array simply has a length of 0. It never holds null, so a null check adds nothing. Guard against ArrayIndexOutOfBoundsException by checking args.length before you read args[0].

Q: Why does javac need the .java extension while java does not?

A: javac takes a file path, so it needs the exact name on disk. The java command takes a class name and appends .class itself while searching the classpath. That is why java School works and java School.class fails.

Q: What runs before the main method?

A: The JVM loads the class, gives static fields their default values, then runs static initializers and static blocks in source order. Only after all of that does it call main. Instance blocks and constructors wait until somebody calls new.

Q: Does a class without a main method still compile?

A: Yes, and that surprises many beginners. Library classes rarely carry a main method, so the compiler never demands one. The failure surfaces only when you try to launch that class with the java command.

Q: What is the difference between System.out.print and System.out.println?

A: Only println adds a line break after your text, so the next output starts on a fresh line. Its cousin print adds nothing at all, which leaves the cursor sitting on the same row. You also get a bare no-argument println that prints a blank line, and print offers no such call.

Q: What exactly is System.out in Java?

A: System is a final class in java.lang, out is a static field inside it, and the type of that field is PrintStream. So println is just an instance method on a PrintStream object. You never import System because java.lang comes free with every source file.

Q: Why does System.out.println(“Sum: ” + 2 + 3) print Sum: 23?

A: Java evaluates the + operator from left to right. The first operand is a String, so 2 gets concatenated rather than added, giving “Sum: 2”. Then 3 gets concatenated too. Wrap the numbers in brackets, as in “Sum: ” + (2 + 3), and you get Sum: 5.

Q: How does printf differ from print and println?

A: The printf method formats values into a template, which lets you fix column widths and decimal places. It never adds a line break by itself, so you must end the format string with %n. Use print to build a row, println to finish one, and printf when the output must line up.

Q: Why does printing an int array show something like [I@1b6d3586?

A: PrintStream declares an overload for char[], but not for any other array type. An int array therefore matches the Object overload, so Java prints whatever Object.toString returns. Call Arrays.toString(numbers) to see the real contents.

12. Conclusion

Let us pull the threads together. Every Java file follows one skeleton: package, imports, class, then members inside the class.

The package line names your drawer. Imports give you short nicknames for classes from other drawers, and a fully qualified name always works as the fallback. Java hands you java.lang for free.

Inside the class, data members hold state and methods do the work. One method stands apart. public static void main(String[] args) exists because the JVM must reach it from outside, call it without an object, expect nothing back, find it by an exact name, and pass along whatever the shell typed.

Compile with javac and a file name. Launch with java and a class name. Keep the classpath honest, remember that static blocks fire before main, and those startup errors stop feeling mysterious.

Your program needs a voice too. Reach for print while you are still building a row, call println once that row is done, and pick printf when the columns must line up. All three live on System.out, which is simply a PrintStream object parked in a static field.

Type out each example in this article. Break them on purpose, read the error, then fix them. That loop teaches the structure faster than any amount of reading.

Further Reading

Leave a Comment