Structure of a Java program
-
Last Updated: July 1, 2023
-
By: javahandson
-
Series
Learn Java in a easy way
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.
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.
We will walk through the skeleton block by block. Every block gets a small example you can type and run. Here is the plan:
No prior Java knowledge required. If you can open a text editor and a terminal, you can follow along.
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.
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 expectedKeep this checklist in your head:
Comments are the one thing allowed above the package statement. A license header at the top of a file is perfectly legal.
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:
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.
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:
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
}
}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.StudentSo 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.
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:
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:
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.
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.javaWhy 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.
The public class name and the filename must match exactly, letter for letter. Case counts.
That last point burns a lot of beginners. Code that compiles on a laptop fails on the build machine, purely over a capital letter.
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.
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 |
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 TestPicture 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.
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)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.
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.
When you run java Test, the JVM performs a very literal search inside class Test. It wants a method that ticks every box:
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 5Here 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.
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] = DeveloperWant 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
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 - 0This 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.
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.
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.
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.
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.
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.
System.out.println looks like one long magic name. It is really three separate things, joined by dots.
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.
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 | 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.
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:
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:
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.
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.
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.
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: 5The 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: nullCalling 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.
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.
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.
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.
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.
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:
Type java School.class and you get a confusing error. The JVM hunts for a class literally named “class” inside a package named “School”.
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: RohitLook 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.
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:
Build tools like Maven and Gradle exist mainly to manage this for you. Under the hood, they simply assemble a very long classpath.
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:
Instance members play no part here. They wait until somebody calls new.
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 = 10Read 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.
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 = 5Same 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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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].
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.
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.
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.
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.
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.
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.
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.
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.
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.