Syntax
The syntax of Java is defined by the Java Language Specification (JLS), maintained by the JCP and Oracle. The grammar resembles C++ at the surface and shares lineage with C and C++; the principal departures are the absence of pointer arithmetic, the requirement that every callable belong to a class, and the substantial set of access modifiers and contextual keywords. Java’s syntax has been intentionally conservative: extensions across each major revision have favoured readability and backward compatibility over expressive density. The current revision (Java 23 at the time of writing, with Java 25 designated as the next long-term-support release) admits substantial modern features — records, sealed classes, pattern matching, text blocks, switch expressions — while remaining recognisable as the Java that was first released in 1995.
This page covers the surface a working programmer encounters routinely. The dedicated pages cover the major sub-grammars (classes, interfaces, generics, the control-flow forms).
A complete program
The classical form, with a class containing the entry-point main method:
public class Greeter {
public static void main(String[] args) {
if (args.length == 0) {
System.out.println("Hello, world.");
} else {
for (String name : args) {
System.out.printf("Hello, %s.%n", name);
}
}
}
}
Compilation and execution:
javac Greeter.java # produces Greeter.class
java Greeter alice bob # executes the main method
Java 21 introduced unnamed classes and instance main methods as a preview, admitting a substantially shorter form for introductory programs:
void main() {
System.out.println("Hello, world.");
}
The compiler synthesises an enclosing class and the conventional main(String[]) method. The form is intended for scripts and learning exercises; production code retains the explicit class declaration.
Source character set
Java source is interpreted as Unicode (typically UTF-8 with optional BOM). The basic source set includes the ASCII letters, digits, and conventional punctuation; identifiers may use any Unicode character whose category the JLS admits, though in practice most code restricts identifiers to ASCII. Unicode escapes (\uXXXX) are processed before lexical analysis, which produces the occasional surprise — a in the middle of a line becomes a newline that the rest of the lexer must accommodate.
Identifiers and keywords
An identifier consists of a Unicode letter or underscore followed by any number of letter, digit, or underscore characters. The keywords are reserved and may not be used as identifiers; the contextual keywords (such as var, record, yield, sealed, permits) are reserved only in specific syntactic positions.
The reserved keywords (Java 23):
abstract continue for new switch
assert default goto* package synchronized
boolean do if private this
break double implements protected throw
byte else import public throws
case enum instanceof return transient
catch extends int short try
char final interface static void
class finally long strictfp volatile
const* float native super while
The starred keywords (const, goto) are reserved but unused; the language reserves them to permit future use without breaking existing programs.
Java has no @-prefixed escape for using keywords as identifiers (as C# admits with @class); a Java program cannot use a reserved word as an identifier under any circumstances.
Comments
Three comment forms:
// a line comment
/* a block comment, possibly spanning multiple lines */
/**
* A Javadoc comment. Tools extract these to produce documentation.
*
* @param input the input string
* @return the processed result
*/
Javadoc comments — /** … */ immediately above a declaration — are the conventional form for API documentation. They are extracted by the javadoc tool and consumed by IDE tooling. Javadoc admits a small set of structured tags (@param, @return, @throws, @see, @since, @deprecated) and embedded HTML.
Declarations
Java declarations attach a name to a type, with optional modifiers. The principal forms:
// Field declaration
private final int counter = 0;
// Method declaration
public static int max(int a, int b) {
return a > b ? a : b;
}
// Class declaration
public class Counter {
private int value;
public Counter(int initial) {
this.value = initial;
}
}
// Interface declaration
public interface Comparable<T> {
int compareTo(T other);
}
// Enum declaration
public enum Direction {
NORTH, EAST, SOUTH, WEST
}
// Record declaration (Java 14)
public record Point(double x, double y) { }
// Annotation declaration
public @interface Marker { }
Every declaration occurs at some enclosing scope: a class, an interface, an enum, a method, or a block. There are no top-level variables or top-level functions in Java (apart from the Java 21 preview unnamed classes, which the compiler implicitly wraps).
Statements
The statement grammar resembles C and C++ with several Java-specific additions:
expression; // expression statement
{ /* statements */ } // block
if (cond) statement // selection
if (cond) statement else statement
switch (expr) { /* cases */ }
switch (expr) { /* arrows */ }; // switch expression as a statement (Java 14+)
while (cond) statement // iteration
do statement while (cond);
for (init; cond; step) statement
for (Type x : iterable) statement // enhanced for (since Java 5)
break; // unconditional transfer
break label;
continue;
continue label;
return;
return expr;
throw expr; // exception
try { … } catch (…) { … } finally { … }
try (Resource r = …) { … } // try-with-resources (since Java 7)
assert expr; // assertion
assert expr : message;
yield expr; // switch-expression value (since Java 14)
Each statement is terminated by a semicolon or, for compound statements, by a closing brace.
Type qualifiers and modifiers
Several modifiers refine declarations:
| Modifier | Effect |
|---|---|
public | Visible from any code |
protected | Visible to subclasses and same-package code |
private | Visible only within the declaring class |
| (none — package-private) | Visible within the same package |
static | Member belongs to the type, not to instances |
final | (Field) cannot be reassigned; (method) cannot be overridden; (class) cannot be subclassed |
abstract | (Class) cannot be instantiated; (method) has no body |
volatile | (Field) reads and writes are not optimised across thread boundaries |
transient | (Field) excluded from default serialisation |
synchronized | (Method) acquires the instance/class monitor for the duration of the call |
native | (Method) implemented in non-Java code (typically through JNI) |
strictfp | Floating-point semantics are platform-independent (mostly redundant since Java 17) |
default | (Interface method) provides an implementation in the interface |
sealed / non-sealed | (Class, interface) restricts which types may extend / implement (Java 17) |
Several modifiers are contextual — they have keyword status only in specific positions. record, sealed, permits, non-sealed, var, yield are contextual.
Storage and lifetime
Java does not have C/C++ storage-class specifiers. The lifetime of a value is governed by reachability:
- Local variables are valid for the duration of the method (more precisely, for as long as they are reachable); the GC reclaims them when no references remain.
- Instance fields live as long as the enclosing object.
- Static fields live as long as the type — typically the entire program.
- Allocated objects (instances of classes, arrays) live as long as references to them exist; they are reclaimed by the GC.
The combination of stack-allocated locals, GC-managed heap allocations, and the absence of explicit delete is the substance of the Java memory model; the full treatment is in Memory and the JVM.
Annotations
Annotations are declarative metadata attached to types, members, parameters, and packages. They are the Java mechanism for serialisation hints, validation rules, ORM mappings, dependency injection, and similar declarative configuration:
@Deprecated(since = "21", forRemoval = true)
public class OldApi { /* ... */ }
public class Document {
@Override
public String toString() { return title; }
@SuppressWarnings("unchecked")
public List<Object> get() { /* ... */ }
@Nullable
private String body;
}
Annotations are applied with @Name immediately before the declaration. Each annotation is a type derived from java.lang.annotation.Annotation; the @interface declaration introduces a new annotation type.
The Java standard library defines several attributes that the compiler recognises:
@Override— verifies the method overrides a superclass or interface method.@Deprecated— marks the entity as deprecated; usage produces a diagnostic.@SuppressWarnings— silences specified warnings.@FunctionalInterface— verifies the interface has exactly one abstract method.@SafeVarargs— suppresses unchecked-warning for varargs of generic types.
Frameworks define their own (@Autowired, @Entity, @Test, etc.); the runtime exposes them through reflection.
A note on undefined behaviour
Java has substantially less undefined behaviour than C or C++. Most operations are defined: integer overflow wraps (well-defined), array access throws ArrayIndexOutOfBoundsException, null dereference throws NullPointerException, type cast failure throws ClassCastException. The principal exceptions are:
- JNI (Java Native Interface) admits calling native code, which has its own undefined-behaviour categories.
sun.misc.Unsafeand the related internal APIs admit raw memory access; these are formally not part of the language.- Concurrent unsynchronised access to non-
volatilefields is governed by the Java memory model and may produce surprising visibility effects, though never the C-style undefined behaviour.
The discipline of writing correct Java is therefore largely the discipline of using the standard library correctly, handling exceptions appropriately, and respecting the concurrency rules. The compiler and runtime catch many errors that would compile silently in C; the cases that remain are typically logic errors and concurrency issues rather than memory-safety violations.