Core Syntax #

Java is a highly structured language — every line of code must live in a clear context: inside a class, inside a method, with an explicit type. For developers coming from Python or JavaScript, Java’s strict syntax rules feel excessive at first, but that strictness is exactly what makes Java code easy to read and predictable at scale. This article covers the Java syntax foundations you need before writing any program: how code is compiled and run, the file structure you must follow, the package and import system, access modifiers, and the naming conventions used across the entire Java ecosystem.

How Java Compiles and Runs Code #

Before looking at syntax, it’s important to understand the path from source code to a running program. Java uses a two-stage approach that sets it apart from both compiled languages like C++ and interpreted languages like Python:

flowchart LR
    A["HelloWorld.java\n(Source Code)"] -->|"javac"| B["HelloWorld.class\n(Bytecode)"]
    B -->|"java"| C["JVM\n(Java Virtual Machine)"]
    C -->|"JIT Compilation"| D["Machine Code\n(Native)"]
    D --> E("[Program Output]")

    style A color:#000,stroke:#f59e0b,stroke-width:2px
    style B color:#fff,stroke:#3b82f6,stroke-width:2px
    style C color:#fff,stroke:#7c3aed,stroke-width:2px
    style E color:#fff,stroke:#16a34a,stroke-width:2px

javac compiles .java source code into .class bytecode — an intermediate format that isn’t native machine code. The JVM then runs this bytecode on any platform (Windows, macOS, Linux) without recompiling. That’s the meaning behind Java’s slogan: “Write once, run anywhere”.

# Compile: source code → bytecode
javac HelloWorld.java      # produces HelloWorld.class

# Run the bytecode on the JVM
java HelloWorld            # note: no .class extension

Java Program Structure #

Every Java program starts from the same structure. Here’s the simplest complete program with an explanation of each part:

// 1. Package declaration — optional, but recommended
package com.example.app;

// 2. Import classes from other packages
import java.util.List;
import java.util.ArrayList;

// 3. Class declaration — the file name must match the public class name
public class HelloWorld {

    // 4. Entry point — the JVM looks for this method to start the program
    public static void main(String[] args) {

        // 5. Statements end with a semicolon
        System.out.println("Hello, World!");

        // 6. Variables must be declared with a type
        String message = "Java " + Runtime.version().feature();
        System.out.println(message);
    }
}

Rules that are mandatory and cause compile errors when violated:

✓ The file name must exactly match the public class name
  → file: HelloWorld.java, class: public class HelloWorld
  → file: UserService.java, class: public class UserService

✓ Every statement ends with a semicolon (;)

✓ All code must be inside a class

✓ One file can have only one public class
  (it may have several non-public classes)

Class Declarations #

The class is the smallest unit of code organization in Java. All code — variables, methods, logic — must be inside a class.

// Anatomy of a class declaration
[modifier] class ClassName [extends ParentClass] [implements Interface1, Interface2] {
    // fields (variables)
    // constructors
    // methods
}

Class Declaration Variations #

// Public class — accessible from other packages
public class User {
    String name;
    int age;
}

// Class that inherits from another class
public class Admin extends User {
    String accessLevel;
}

// Class that implements an interface
public class EmailService implements NotificationService {
    @Override
    public void send(String message) {
        // implementation
    }
}

// Final class — cannot be inherited
public final class Constants {
    public static final double PI = 3.14159;
}

// Abstract class — cannot be instantiated directly
public abstract class Shape {
    public abstract double calculateArea(); // abstract method
}
classDiagram
    class Shape {
        <<abstract>>
        +calculateArea() double
    }
    class Circle {
        -double radius
        +calculateArea() double
    }
    class Square {
        -double side
        +calculateArea() double
    }
    class Drawable {
        <<interface>>
        +draw() void
    }
    Shape <|-- Circle
    Shape <|-- Square
    Drawable <|.. Circle

The main Method #

The main method is the entry point of every Java program. The JVM looks for a method with this exact signature to start execution:

public static void main(String[] args) {
    // program code
}

Every keyword in this signature has a reason:

public   the JVM (from outside the class) must be able to call it
static   the JVM calls it without creating an object first
void     returns no value to the JVM
String[] args  receives arguments from the command line

Reading Command Line Arguments #

public class Greet {
    public static void main(String[] args) {
        // java Greet Budi 25
        if (args.length < 2) {
            System.out.println("Usage: java Greet <name> <age>");
            return;
        }

        String name = args[0];       // "Budi"
        int age     = Integer.parseInt(args[1]); // 25

        System.out.println("Hello, " + name + "! You are " + age + " years old.");
    }
}
Since Java 21, there’s the Unnamed Main Method and Instance Main Methods (preview) features that let you write programs without the public static void main boilerplate. For now, though, the standard signature above is still used in almost every production codebase.

Packages and Imports #

Packages are Java’s mechanism for grouping related classes while avoiding name conflicts. Think of a package like a folder in the filesystem.

Package Declaration #

// First line of the file (before any imports)
package com.example.ecommerce.service;

// Naming convention: reversed domain name + module name
// com.companyName.applicationName.moduleName

Imports #

// Import a specific class — recommended
import java.util.List;
import java.util.ArrayList;
import java.util.HashMap;

// ANTI-PATTERN: wildcard import — imports every class in the package
import java.util.*;   // ✗ unclear which classes are actually used

// Static import — use static members without the class name
import static java.lang.Math.PI;
import static java.lang.Math.sqrt;

public class Circle {
    double calculateCircumference(double r) {
        return 2 * PI * r;  // use PI directly, not Math.PI
    }
    double calculateDiagonal(double a, double b) {
        return sqrt(a*a + b*b); // use sqrt directly
    }
}

Directory Structure Matching Packages #

The package name must mirror the file’s directory structure:

src/
  └── com/
      └── example/
          └── ecommerce/
              ├── service/
              │   ├── UserService.java      → package com.example.ecommerce.service
              │   └── OrderService.java     → package com.example.ecommerce.service
              ├── model/
              │   ├── User.java             → package com.example.ecommerce.model
              │   └── Order.java            → package com.example.ecommerce.model
              └── repository/
                  └── UserRepository.java   → package com.example.ecommerce.repository

Access Modifiers #

Access modifiers control where a class, method, or field can be accessed from. Java has four access levels:

public class ModifierExample {

    public    String publicName;    // accessible from anywhere
    protected String protectedName; // package + subclasses outside the package
    String    defaultName;          // same package only (no keyword)
    private   String privateName;   // this class only

    // Methods with various modifiers
    public    void publicMethod()    { }
    protected void protectedMethod() { }
              void defaultMethod()   { }  // package-private
    private   void privateMethod()   { }
}
flowchart TD
    subgraph "Access Scope"
        A["private\nThis class only"]
        B["(default)\nClasses in the same package"]
        C["protected\nDefault + subclasses outside the package"]
        D["public\nAll classes in all packages"]
    end
    A --> B --> C --> D
    style A color:#fff,stroke:#e05252,stroke-width:2px
    style B color:#000,stroke:#f59e0b,stroke-width:2px
    style C color:#fff,stroke:#3b82f6,stroke-width:2px
    style D color:#fff,stroke:#16a34a,stroke-width:2px

The Encapsulation Principle #

Use the narrowest access modifier possible — this is the least privilege principle that makes code safer and easier to refactor:

// ANTI-PATTERN: all fields public — the class has no control over its data
public class Account {
    public double balance;       // ✗ anyone can change it directly
    public String accountNumber; // ✗ no validation
}

// CORRECT: private fields, access via methods that control validation
public class Account {
    private double balance;
    private String accountNumber;

    public Account(String accountNumber, double initialBalance) {
        if (initialBalance < 0) throw new IllegalArgumentException("Balance cannot be negative");
        this.accountNumber = accountNumber;
        this.balance       = initialBalance;
    }

    public double getBalance()       { return balance; }
    public String getAccountNumber() { return accountNumber; }

    public void withdraw(double amount) {
        if (amount > balance) throw new IllegalStateException("Insufficient balance");
        balance -= amount;
    }

    public void deposit(double amount) {
        if (amount <= 0) throw new IllegalArgumentException("Deposit amount must be positive");
        balance += amount;
    }
}

Primitive vs Reference Types #

Java distinguishes two categories of types with very different behavior:

// PRIMITIVE types — stored directly on the stack, not objects
byte    b = 127;              // 8-bit, -128 to 127
short   s = 32767;            // 16-bit
int     i = 2_147_483_647;    // 32-bit (most common)
long    l = 9_223_372_036L;   // 64-bit, add L at the end
float   f = 3.14f;            // 32-bit, add f at the end
double  d = 3.14159265358979; // 64-bit (most common for decimals)
char    c = 'A';              // 16-bit Unicode
boolean flag = true;

// REFERENCE types — store a memory address pointing to an object on the heap
String     name   = "Budi";          // String object
int[]      numbers  = {1, 2, 3};       // array
List<String> list = new ArrayList<>(); // Collection

Critical Differences: Assignment and Comparison #

// PRIMITIVE: assignment copies the value
int a = 10;
int b = a;
b = 20;
System.out.println(a); // 10 — a is unchanged

// REFERENCE: assignment copies the address (not the value)
int[] arrA = {1, 2, 3};
int[] arrB = arrA;    // arrB points to the SAME object
arrB[0] = 99;
System.out.println(arrA[0]); // 99 — arrA changed too!

// Reference comparison
String s1 = new String("hello");
String s2 = new String("hello");

// ANTI-PATTERN: comparing references with ==
if (s1 == s2) { }          // ✗ false — different addresses even though contents match

// CORRECT: compare values with .equals()
if (s1.equals(s2)) { }     // ✓ true
Comparing String with == instead of .equals() is one of the most common bugs in Java. The == operator on reference types compares memory addresses, not values. Always use .equals() to compare object contents.

Naming Conventions #

Java has naming conventions followed by nearly the entire ecosystem. Breaking them doesn’t cause errors, but it makes your code look foreign to other Java developers:

// CLASSES & INTERFACES: PascalCase
public class UserService      { }
public class HttpClient       { }
public interface Serializable { }
public @interface Override    { }  // annotation

// METHODS & VARIABLES: camelCase
public void calculateTotalPrice() { }
int productCount = 0;
String fullName   = "";
boolean isLoggedIn = false;

// CONSTANTS: UPPER_SNAKE_CASE
public static final int    MAX_RETRY_COUNT = 3;
public static final String DEFAULT_CHARSET = "UTF-8";
public static final double DISCOUNT_LIMIT  = 0.5;

// PACKAGES: all lowercase, no underscores
package com.example.userservice;
package org.apache.commons.lang3;

// GENERIC TYPE PARAMETERS: single capital letter
class Box<T>          { }  // T = Type
class Pair<K, V>      { }  // K = Key, V = Value
class Repository<E>   { }  // E = Entity

Statements, Blocks, and Expressions #

Understanding these three concepts helps you read Java code structure better:

public class BasicConcepts {
    public static void main(String[] args) {

        // STATEMENT: a single instruction, ending with a semicolon
        int x = 10;
        System.out.println(x);
        x++;

        // BLOCK: a group of statements inside curly braces
        {
            int y = 20;  // y only exists inside this block
            System.out.println(x + y);
        }
        // System.out.println(y); // ERROR: y is out of scope

        // EXPRESSION: a combination of values and operators that produces a value
        int result  = x * 2 + 5;        // arithmetic expression
        boolean ok  = result > 10;        // boolean expression
        String  s   = "value: " + result; // string concatenation expression

        // An expression can stand as a statement if it has side effects
        x = x + 1;   // assignment
        x++;          // increment
        method();     // method call
    }

    static void method() { }
}

Console Output #

Java provides several ways to print output, each with a different use:

// System.out.println — prints with a newline at the end
System.out.println("Hello");          // "Hello\n"
System.out.println(42);              // automatic conversion to String
System.out.println(3.14);
System.out.println(true);

// System.out.print — prints without a newline
System.out.print("Name: ");
System.out.print("Budi");
System.out.println();                // manual newline

// System.out.printf — formatted like C printf
System.out.printf("Name: %s, Age: %d%n", "Budi", 25);
System.out.printf("Price: $%.2f%n", 99999.5);

// String.format — format into a String without printing directly
String message = String.format("ID: %05d, Status: %s", 42, "active");
System.out.println(message); // "ID: 00042, Status: active"

// Common format specifiers:
// %s  → String
// %d  → integer
// %f  → float/double
// %.2f → 2 decimals
// %n  → newline (platform-independent, better than "\n")
// %05d → integer with zero padding, width 5

Summary #

  • Two-stage executionjavac compiles .java into .class bytecode, then the JVM runs the bytecode; this is what makes Java write once, run anywhere.
  • File name = class name — the .java file name must be identical to the public class inside it; violating this causes an immediate compile error.
  • Packages mirror directories — declaring package com.example.service requires the file to live in com/example/service/; the reversed domain naming convention prevents conflicts between libraries.
  • Keep access modifiers as narrow as possible — use private for fields and open access only through validating methods; this is the foundation of OOP encapsulation.
  • == vs .equals()== compares memory addresses for reference types; always use .equals() to compare the contents of objects like String.
  • Primitive vs reference types — primitives (int, double, boolean, etc.) are copied on assignment; references store an address so two variables can point to the same object.
  • Naming conventions — PascalCase for classes/interfaces, camelCase for methods/variables, UPPER_SNAKE_CASE for constants; the whole Java ecosystem follows these.

← Previous: Installation   Next: Comments →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact