<< Home

Chapter 2: Foundations of the Java Language

Every programming language has its own internal logic; its way of expressing ideas through structure and rules. Java’s logic is grounded in clarity, order, and predictability. It was designed to help programmers think in terms of classes, objects, and strict type boundaries, while still feeling familiar to anyone who has worked with other C-like languages.

At its heart, Java encourages you to write code that is both explicit and self-explanatory. Its syntax may appear formal at first, but that discipline pays off in code that scales gracefully and remains understandable even after years of maintenance. Each program has a clear entry point, each variable a defined type, and each class a distinct purpose within a hierarchy of behavior and inheritance.

Understanding the foundations of Java means understanding how it thinks about structure. The language expects everything to live inside a class, and every piece of code to follow predictable rules about scope, access, and typing. This may feel more constrained than dynamically typed languages, but those constraints provide the consistency that makes large-scale Java systems reliable and maintainable.

This chapter introduces the building blocks that define how Java code works and how programs are interpreted by the Java Virtual Machine. You will learn how statements and expressions form the core of Java’s logic, how identifiers and variables are defined and scoped, and how primitive types, constants, and naming conventions shape the rhythm of the language. Together, these ideas create the framework upon which every Java application is built.

💡 If you have used a dynamically typed language before, such as Python or JavaScript, you may find Java’s structure stricter at first. That structure is intentional because it helps the compiler catch errors early, long before your program runs.

By mastering these fundamentals, you will develop an instinct for how Java organizes data and behavior, and why its design has remained steady for decades. Once these core principles feel natural, the rest of the language (its objects, generics, exceptions, and concurrency) will make far more sense, because they all follow the same consistent foundation introduced here.

Statements, expressions, and blocks

Every Java program is built from statements and expressions, the smallest units of meaning in the language. Understanding how these pieces interact is essential to reading and writing Java fluently. A statement performs an action. An expression produces a value. Together, they form the logical rhythm that drives all program flow.

Expressions

An expression is any piece of code that evaluates to a value. It can be as simple as a literal number or as complex as a nested method call. Expressions appear inside statements, parameters, and control structures. They do not perform actions on their own. Rather they simply compute and return something that can be used elsewhere.

int total = 3 + 4;          // An expression
boolean ready = total > 5;  // An expression
System.out.println(total);  // An expression used as an argument

Java evaluates expressions in a defined order, respecting operator precedence and parentheses. Each expression produces a value whose type is known at compile time. This predictability is part of Java’s static type system, ensuring that incompatible operations (like adding a number to a string) are caught before the program runs.

Statements

A statement is a complete instruction that tells the Java Virtual Machine to do something. Statements usually end with a semicolon (;) and can include expressions as part of their structure. Assignment, method calls, and control flow instructions like if and while are all statements.

int count = 0;              // declaration statement
count++;                    // expression statement
System.out.println(count);  // method call statement

In Java, each statement executes in sequence unless altered by control flow. Statements can appear alone or be grouped together into larger structures called blocks.

Blocks

A block is a group of one or more statements enclosed in braces ({…}). Blocks define scope, or regions of code where variables exist and can be accessed. They are used in method bodies, control structures, and class definitions. For example:

if (count > 0) {
  System.out.println("Positive count");
  count--;
}

Here, the two statements inside the braces form a block that executes only if the condition is true. Variables declared within a block exist only inside that block and are discarded once it ends.

💡 In Java, every executable statement appears inside a method or block. You cannot place code directly at the top level of a class file as in some other languages. This requirement reinforces Java’s structured, object-oriented nature.

Declarations vs. executable statements

Java distinguishes between declarations (which define variables, methods, or classes) and executable statements (which perform actions when the program runs). This separation makes Java’s flow easy to follow and its structure consistent. Declarations tell the compiler what exists; executable statements tell the runtime what to do.

⚠️ Forgetting a semicolon at the end of a statement is one of the most common beginner errors. In Java, a missing semicolon almost always leads to a compile-time syntax error rather than a runtime problem, making it easy to detect and fix.

Comments and documentation

Java code is meant to be read by people as well as compiled by the machine. Comments and documentation are how you explain intent, clarify design choices, and help future readers (including your future self) understand why the code was written the way it was. They never affect program execution but play an essential role in maintainability and teamwork.

Single-line and multi-line comments

Single-line comments begin with // and extend to the end of the line. They are ideal for short notes, explanations, or temporary reminders. Multi-line comments begin with /* and end with */, allowing you to span several lines or temporarily disable blocks of code during testing.

// This is a single-line comment
int count = 5; // You can also place it at the end of a statement

/*
  This is a multi-line comment.
  It can span several lines and is often used
  for larger explanations or code sections.
*/

Good comments explain why something is done, not what the code does. The code itself should make the “what” clear through naming and structure. Over-commenting every line makes code harder to read rather than easier.

💡 Use comments to describe intent, reasoning, or side effects. If a comment simply repeats what the code already says, it can usually be removed.

Javadoc comments

Java introduced a formal documentation system called Javadoc, which allows you to create rich, searchable API documentation directly from the source code. A Javadoc comment begins with /** and ends with */. It is placed immediately before a class, method, or field declaration.

/**
 * Calculates the area of a rectangle.
 * @param width  The width of the rectangle
 * @param height The height of the rectangle
 * @return The area as an integer
 */
public int area(int width, int height) {
  return width * height;
}

Each tag (such as @param and @return) provides structured information that can be turned into formatted documentation by running the javadoc tool, which generates HTML pages for your classes and methods. This makes Javadoc comments part of the official interface for any library or project.

Best practices for documentation

Many IDEs can generate Javadoc templates automatically as you type, prompting you to fill in parameter and return descriptions. For public APIs and reusable code, consistent Javadoc use is a sign of professional quality.

⚠️ Avoid commenting out large sections of old or unused code. Version control systems such as Git already preserve history. Leaving old code commented out can confuse others about what is still in use.

Identifiers, keywords, and naming rules

In Java, every variable, class, method, and constant is identified by a name called an identifier. Identifiers give structure and meaning to code, helping both the compiler and the reader understand what each element represents. Alongside these identifiers, Java reserves certain keywords that have predefined meanings and cannot be used as names. Together, they form the basic vocabulary of the language.

Identifiers

An identifier is a name you choose for something you define, such as a class, variable, or method. Identifiers must follow a few simple rules:

Valid examples include:

int totalCount;
String customerName;
double _balance;
boolean $debugMode;

Although the dollar sign is technically allowed, it is rarely used except by tools that generate code automatically. For readability, prefer simple, descriptive names made of letters and digits.

💡 Choose names that express purpose, not type. For example, temperatureCelsius is clearer than tempC, and maxItems conveys more meaning than x. Good names reduce the need for comments.

Keywords

Java reserves a set of words for the language itself. These keywords define syntax and structure, and cannot be redefined or used as identifiers. Examples include class, public, static, void, if, else, return, for, switch, and many others.

A full list of keywords (as of Java 25) includes:

abstract  assert  boolean  break  byte  case  catch  char  class
const  continue  default  do  double  else  enum  extends  final
finally  float  for  if  implements  import  instanceof  int
interface  long  native  new  package  private  protected  public
return  short  static  strictfp  super  switch  synchronized  this
throw  throws  transient  try  void  volatile  while

Java also reserves true, false, and null as literals that cannot be used as identifiers. In addition, newer versions may introduce contextual keywords, which are words that behave like keywords only in specific situations (for example, var or yield).

⚠️ Attempting to use a keyword as a variable or method name will always cause a compilation error. The compiler reserves these terms for its own syntax and meaning.

Naming conventions

Beyond the basic rules, Java developers follow consistent conventions to make code readable and predictable. These conventions are not enforced by the compiler but are considered standard practice throughout the Java ecosystem:

package com.example.store;

public class Product {
  private double price;
  public static final double TAX_RATE = 0.2;

  public double calculateTotal() {
    return price + (price * TAX_RATE);
  }
}

These conventions help others immediately recognize the role of each name in your program. When everyone follows the same patterns, codebases become easier to navigate and maintain.

💡 Most IDEs can automatically enforce or suggest standard naming conventions. Use these tools early and they will help you write idiomatic Java that feels natural to experienced developers.

Primitive types and literals

All data in Java belongs to one of two broad categories: primitive types or reference types. Primitive types store simple values directly; numbers, characters, and truth values. Reference types store references to objects in memory. This section focuses on the eight primitive types, which form the foundation of all data handling in Java.

The eight primitive types

Java defines exactly eight primitive data types, each with a fixed size and specific range of values. This consistency ensures that Java behaves the same way across all platforms.

TypeSizeExampleDescription
byte8-bitbyte b = 10;Small integer, often used for data streams or compact storage.
short16-bitshort s = 2000;Medium-range integer type.
int32-bitint n = 123456;Standard integer type used most often.
long64-bitlong l = 12345678900L;Large integer values, requires L suffix.
float32-bitfloat f = 3.14F;Single-precision floating-point numbers, requires F suffix.
double64-bitdouble d = 3.14159;Double-precision floating-point, default for decimal values.
char16-bitchar c = 'A';Represents a single Unicode character.
boolean1-bit (logical)boolean ready = true;Represents truth values: true or false.

Because Java defines exact sizes for each type, a program that runs on one system behaves identically on another. This predictability is part of what makes Java portable across platforms and architectures.

💡 In most cases, you can use int for whole numbers and double for decimals. The other types exist for precision control, memory efficiency, or compatibility with specific data formats.

Literals

A literal is a fixed value written directly into your code. Java supports several literal forms for each primitive type.

int count = 10;           // integer literal
long distance = 45_000L;  // underscores improve readability
double price = 19.99;     // decimal literal
float ratio = 0.75F;      // float literal requires F
char letter = 'J';        // single character literal
boolean success = true;   // boolean literal

Numeric literals can also be written in different bases:

int hex = 0xFF;    // hexadecimal (255)
int bin = 0b1010;  // binary (10)
int oct = 012;     // octal (10)

Underscores within numeric literals make large numbers easier to read, but they have no effect on value:

long population = 7_950_000_000L;
⚠️ Always match the literal’s type to the variable’s type. Assigning a double literal to a float variable without the F suffix, for example, will cause a compilation error because Java does not perform implicit narrowing conversions.

Type ranges and overflow

Each primitive type has a defined minimum and maximum value. When a value goes beyond that range, it overflows or underflows, wrapping around instead of producing an error.

byte b = 127;
b++;  // wraps around to -128

This behavior can lead to subtle bugs if not expected. Java does not raise an exception for overflow, so it is up to the programmer to ensure calculations stay within range or to use larger data types when necessary.

Default values

When primitive fields are declared inside a class but not initialized, Java assigns them default values automatically. Local variables inside methods, however, must be explicitly initialized before use.

TypeDefault value
byte, short, int, long0
float, double0.0
char'\u0000' (null character)
booleanfalse

This behavior simplifies class construction and ensures all fields start with predictable values.

💡 It is good practice to initialize variables explicitly even when defaults exist. Doing so makes your intent clear and improves readability for anyone reviewing the code.

Variables, constants, and scope

Variables are the named storage locations that hold your program’s data. Each variable in Java has a specific type, a name, and a lifetime. Understanding how variables are declared, how long they exist, and where they can be accessed (known as their scope) is essential to writing reliable and maintainable programs.

Declaring and initializing variables

Every variable in Java must be declared with a type before it can be used. The declaration tells the compiler what kind of data the variable will hold, allowing it to check that only valid operations are performed on it. You can declare and initialize a variable in one statement or separately:

int count;   // declaration
count = 10;  // initialization

String name = "Java";  // declaration and initialization together

Java also allows multiple variables of the same type to be declared on one line, but this is best reserved for simple cases:

int x = 1, y = 2, z = 3;
💡 Give variables meaningful names that describe their purpose. Descriptive names like userCount or filePath make code easier to understand and maintain.

Variable types and mutability

Variables can represent different kinds of data. Primitive variables store values directly (like numbers or characters), while reference variables store a reference to an object in memory. Both kinds of variables can be reassigned unless declared as constants using the final keyword.

int score = 100;
score = 120;  // allowed

final int MAX_SCORE = 200;
MAX_SCORE = 250;
// error: cannot assign a value to final variable

Declaring a variable as final makes it immutable, such that it cannot be reassigned once a value is set. Constants are typically written in uppercase letters with underscores separating words.

Variable scope

The scope of a variable defines where it can be accessed within a program. In Java, scope is determined by the block structure created with braces ({…}). A variable declared inside a block exists only within that block and is destroyed when the block ends.

public class ScopeExample {
  public static void main(String[] args) {
    int outer = 10;
    {
      int inner = 5;
      System.out.println(outer + inner);
      // both visible here
    }
    // System.out.println(inner);
    // error: inner no longer exists
  }
}

This block-based scoping helps prevent naming conflicts and keeps variables local to where they are needed. Variables declared at the class level (outside any method) are known as fields, while those declared inside methods or blocks are local variables.

⚠️ Java does not allow two variables with the same name to exist in the same scope. If you declare a variable inside a nested block with the same name as one in an outer block, the inner one temporarily shadows the outer variable.

Local, instance, and class variables

Java defines three main categories of variables, based on where they are declared:

public class Counter {
  static int totalCount = 0;  // class variable
  int value;                  // instance variable

  public Counter(int start) {
    value = start;
    totalCount++;
  }

  public void increment() {
    int step = 1;             // local variable
    value += step;
  }
}

Here, totalCount belongs to the class itself, value belongs to each individual object, and step exists only while the increment() method runs.

Variable lifetime

Each variable’s lifetime is tied to its scope. Local variables exist only while their method or block executes. Instance variables persist as long as the object they belong to remains in memory. Class variables persist for as long as the program or class loader keeps the class loaded.

Java’s garbage collector automatically reclaims memory when objects are no longer referenced. You never need to manually free variables, but you should always limit their scope and lifetime to what is necessary. Doing so keeps your programs efficient and reduces the chance of errors.

💡 A good rule of thumb is to declare variables in the smallest possible scope where they are used. This makes code safer, cleaner, and easier to reason about.