Java Scope

Su Suriya Ravichandran Updated 14 Nov 2026
8 min read ·Lesson 27 of 133

Scope is the region of a program where a name can be used. A variable declared in one place is visible in some parts of the code and invisible in others. Java's scope rules are strict and checked at compile time, which keeps variables from being used before they exist, after they are gone, or by code that has no business touching them.

Block Scope

In Java, scope is defined by blocks: the code between a pair of braces { }. A variable declared inside a block is visible from its declaration to the closing brace of that block, including any nested blocks.

public static void main(String[] args) {
    int a = 1;                       // a: visible until the end of main

    {
        int b = 2;                   // b: visible until the end of this block
        System.out.println(a + b);   // both visible: 3
    }

    System.out.println(a);           // 1
    System.out.println(b);           // compile error: cannot find symbol b
}

Two rules follow directly:

  1. A variable cannot be used before its declaration, even later in the same block.
  2. A variable cannot be used after its block closes.
System.out.println(total);   // compile error: total not yet declared
int total = 10;

Method Scope

Local variables and parameters belong to one method call. They are invisible to other methods, even ones in the same class:

static void first() {
    int count = 5;
}

static void second() {
    System.out.println(count);   // compile error: count belongs to first()
}

Each call also gets its own fresh copies. A local variable does not remember its value between calls:

static void visit() {
    int visits = 0;
    visits++;
    System.out.println(visits);
}

visit();   // 1
visit();   // 1, not 2

To share data between methods, pass it as a parameter, return it, or store it in a field.

Scope in Control Structures

The body of an if, else, for, while, switch, or try is a block, so variables declared there are local to it:

if (isMember) {
    double discount = 0.1;
}
System.out.println(discount);   // compile error

Declare the variable before the block when the value is needed afterward:

double discount = 0;
if (isMember) {
    discount = 0.1;
}
System.out.println(discount);   // works

Loop variables

A variable declared in a for header is scoped to the loop:

for (int i = 0; i < 3; i++) {
    System.out.println(i);
}
// i does not exist here

for (int i = 10; i < 13; i++) {   // reusing the name is fine
    System.out.println(i);
}

A variable declared inside a loop body is created fresh on every iteration:

for (int i = 0; i < 3; i++) {
    int square = i * i;           // new variable each pass
    System.out.println(square);
}

Placing an accumulator inside the loop by mistake resets it every time:

for (int price : prices) {
    int total = 0;                // reset on every iteration
    total += price;
}
// total does not even exist here

The accumulator must be declared before the loop.

catch parameters and try-with-resources

The exception variable in a catch exists only in that catch block, and resources declared in a try-with-resources header exist only in the try block:

try (Scanner sc = new Scanner(file)) {
    process(sc);
} catch (IOException e) {
    System.out.println(e.getMessage());
}
// neither sc nor e exists here

No Redeclaration in Nested Blocks

Java does not allow a local variable to be redeclared in a nested block while the outer one is still in scope:

int count = 0;
if (true) {
    int count = 5;      // compile error: variable count is already defined
}

Many languages permit this kind of local shadowing; Java forbids it because two local variables with the same name in overlapping scopes are almost always a mistake.

Sibling blocks that do not overlap may reuse names:

if (x > 0) {
    String msg = "positive";
    System.out.println(msg);
} else {
    String msg = "not positive";   // separate scope: allowed
    System.out.println(msg);
}

Class Scope: Fields

Variables declared in the class body, outside any method, are fields. They are visible throughout the class, in every method, regardless of where in the class they are declared:

public class Thermostat {
    private double target = 22.0;      // field: visible in all methods

    void raise() {
        target += 0.5;
    }

    void report() {
        System.out.println("Target: " + target);
    }
}

Fields live as long as their object (instance fields) or the loaded class (static fields), unlike locals, which disappear when the method returns. This is why a counter that must persist between calls is a field, not a local:

public class PageView {
    private int views = 0;

    void visit() {
        views++;
        System.out.println(views);
    }
}

PageView page = new PageView();
page.visit();   // 1
page.visit();   // 2

Access modifiers (private, public, protected, package-private) control whether a field is visible from other classes. That is a separate concept from scope and is covered in the Access Modifiers lesson.

Shadowing Fields

A local variable or parameter may have the same name as a field. Within the local's scope, the simple name refers to the local, hiding the field. this.name reaches the field:

public class Product {
    private String name;
    private double price;

    public Product(String name, double price) {
        this.name = name;      // field = parameter
        this.price = price;
    }

    public void rename(String name) {
        name = name.strip();   // modifies the parameter only
        this.name = name;      // updates the field
    }
}

Without this., name = name; in the constructor would assign the parameter to itself and leave the field null. The compiler accepts it, which makes this one of the more confusing beginner bugs. Using this. for fields in constructors and setters is the standard convention.

Static Context and Scope

A static method has no object, so instance fields are not available in it by simple name:

public class App {
    int counter = 0;

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

The field is in scope (it is declared in the class), but there is no instance to read it from. Either create an object (new App().counter++) or make the field static.

Lambdas and Effectively Final Variables

A lambda or anonymous class may read local variables from the enclosing method only if those variables are effectively final: never reassigned after initialization.

int threshold = 50;
List<Integer> high = scores.stream()
        .filter(s -> s > threshold)   // allowed: threshold is never changed
        .toList();

int count = 0;
scores.forEach(s -> count++);         // compile error: count is modified

The lambda may run later or on another thread, after the method's frame is gone, so Java captures a copy of the value. Allowing modification would make the copy and the original diverge. Lambdas have their own section.

Scope Summary

Declared inVisibleLifetime
A block { }From declaration to end of the blockUntil the block exits
A for headerThe loop header and bodyUntil the loop ends
A method parameterThe entire method bodyOne method call
A catch clauseThat catch blockWhile handling the exception
A class body (instance field)All instance methods and constructors of the classAs long as the object exists
A class body (static field)All methods of the classAs long as the class is loaded

Keeping Scope Narrow

Declaring each variable in the smallest scope that works, as close as possible to its first use, has concrete benefits:

  • A reader sees the declaration and the use together.
  • The variable cannot be accidentally reused or modified by unrelated code later in the method.
  • The garbage collector can reclaim objects sooner, since references in finished scopes are gone.
// Wide scope: line and parts exist for the whole method
String line;
String[] parts;
for (int i = 0; i < rows.size(); i++) {
    line = rows.get(i);
    parts = line.split(",");
    process(parts);
}

// Narrow scope: each exists only where it is used
for (String line : rows) {
    String[] parts = line.split(",");
    process(parts);
}

Practical Example: Tracking Scope in a Small Program

public class LibraryStats {
    private static int totalLoans = 0;                 // class scope: shared state

    static int countOverdue(int[] daysBorrowed, int limit) {   // parameters: method scope
        int overdue = 0;                               // method scope
        for (int days : daysBorrowed) {                // loop scope
            if (days > limit) {
                int lateBy = days - limit;             // if-block scope
                System.out.println("Late by " + lateBy + " days");
                overdue++;
            }
            totalLoans++;
        }
        return overdue;
    }

    public static void main(String[] args) {
        int[] branchA = {5, 16, 21, 9};
        int[] branchB = {30, 2};

        int overdueA = countOverdue(branchA, 14);
        int overdueB = countOverdue(branchB, 14);

        System.out.println("Overdue: " + (overdueA + overdueB));
        System.out.println("Loans processed: " + totalLoans);
    }
}

Output:

Late by 2 days
Late by 7 days
Late by 16 days
Overdue: 3
Loans processed: 6

lateBy exists only within the if that needs it. days exists only within the loop. overdue starts at 0 on each call, so each branch is counted separately and returned. totalLoans is a static field, so it survives between calls and accumulates across both branches. Each variable's position in the code decides exactly how long it lives and who can see it.

0 Comments

Reviewed before they appear

No comments yet.

Java
Java Introduction Java Features Java Syntax Java Comments Java Statements Java Output Java Variables Java Data Types Java Type Casting Java Operators Java Strings Java Numbers Java Math Java Booleans Java User Input Java Conditions Java If Else Java Switch Java While Loop Java For Loop Java Break Java Continue Java Arrays Java Methods Java Parameters Java Return Values Java Scope Java Recursion Install Java Install JDK Java IDEs Java JDK Java JRE Java JVM Java Compiler Java File Compile Java Run Java JShell Basics Java Packages Java OOP Java Classes Java Objects Class Methods Constructors Constructor Overload this Keyword static Keyword final Keyword Encapsulation Inheritance Polymorphism Abstraction Access Modifiers super Keyword Method Overload Method Override Instanceof Operator Nested Classes Inner Classes Anonymous Classes Enums Records Array Basics Array Length Loop Arrays Multidimensional Arrays Array Copy Array Sorting Array Search ArrayList LinkedList HashSet TreeSet HashMap TreeMap Queue Interface Deque Interface Stack Class Iterator List Interface Set Interface Map Interface Collections Class Comparable Comparator Generics Basics Generic Classes Generic Methods Wildcards String Basics String Length String Methods String Concatenation String Compare String Search String Replace String Split String Format StringBuilder StringBuffer Text Blocks Regular Expressions Number Types Integer Class Float Class Double Class BigInteger BigDecimal Math Class Random Numbers Number Format Date Basics LocalDate LocalTime LocalDateTime ZonedDateTime Date Formatting Date Parsing Period Class Duration Class Java Instant Class Java Exception Basics Java try...catch Java Multiple Catch Blocks Java finally Block Java throw Keyword Java throws Keyword Java Checked Exceptions Java Unchecked Exceptions Java Custom Exceptions Java try-with-resources Java Exception Chaining
Ask about this post
AI Ask about this post

Ask questions about Java Scope and get answers drawn from it.

Signed-in readers only.