Course contents

The static keyword in Java

Static fields, methods, blocks, nested classes and imports: where static state lives in HotSpot, exactly when a class is initialised, method hiding, thread safety, memory leaks, and static versus singleton.

FreeMust know for interviewsLesson16 min readCoreReviewed Oct 2026 · Java 21
On this page
  1. Picture it first: the notice board and the desks
  2. Under the hood: static fields and static methods
  3. Static blocks and when a class is initialised
  4. Real world: static state in production
  5. Advanced: edge cases interviewers probe
  6. How to say it in the interview

📌 What is the static keyword?

static marks a member that belongs to the class itself, not to any one object. A static field exists once per class, no matter whether you create zero orders or a million. For example, private static final AtomicLong NEXT_ID in Order is one shared counter that hands out IDs 1001, 1002, 1003 to every new order, while each order keeps its own id.

Every Java developer has typed public static void main, so interviewers skip the definition and go straight to the questions that need a mental model: where do static fields actually live in memory? When exactly does a static block run? Why can a static method be called on a null reference without a NullPointerException? Is a static counter thread-safe? Why are static methods "hidden" and not overridden?

What you’ll learn

  • Explain where static fields live (the java.lang.Class mirror on the heap) versus class metadata in Metaspace
  • Say why static methods have no this, compile to invokestatic, and are hidden rather than overridden
  • List the triggers for class initialisation on first active use, and why reading a compile-time constant isn't one
  • Predict static initialisation order across a hierarchy, and debug ExceptionInInitializerError followed by NoClassDefFoundError
  • Choose between static nested and inner classes, and between a static utility and a singleton (holder idiom)
  • Keep static mutable state thread-safe and bounded to avoid races and memory leaks

For this lesson, we'll focus on static fields and where the JVM stores them, static methods and why they have no this, static blocks and the exact rules for when a class is initialised, static nested classes and static imports, and the production side: memory leaks, thread safety and static versus singleton.

Below the line (out of scope)

The 30-second answer

A static member belongs to the class, not to instances. Static fields exist once per class per class loader; in HotSpot they live inside the class's java.lang.Class object on the heap. Static methods have no this, can't touch instance members directly, are bound at compile time with invokestatic, and are hidden, not overridden, by subclasses. Static initialisers run once, in textual order, when the class is first actively used: new, a static method call, or a read or write of a non-constant static field. Static nested classes have no hidden reference to an outer instance. Static mutable state is global state: it needs thread safety and it lives as long as its class loader, so unbounded static caches are a classic memory leak.

Interview radar

Asked very often: Where do static fields live in memory, and exactly when does a static block run? Senior rounds probe: How does method hiding differ from overriding, and why does calling a static method on a null reference not throw? Production angle: An unbounded static cache that is a GC root for the life of the class loader, plus the races on a shared static counter under load.

01Picture it first: the notice board and the desks

Picture an office. Each employee has a desk: their own stapler, their own notes. That's instance state. On the wall there's one notice board that everyone reads and writes: the next ticket number, today's on-call engineer. That's static state. Hiring a new employee adds a desk; it doesn't add a notice board. And if two people scribble on the board at the same moment, you get a mess, which is exactly the thread-safety problem static fields have.

The diagram below shows where each piece lives in a HotSpot JVM.

Fig. 1

Stack variables point to two Order objects on the heap, each with its own id field and a header pointing to the Order class metadata in Metaspace; the class metadata is linked to the java.lang.Class mirror on the heap, which holds the single copy of the static fields NEXT_ID and created

Stack variables point to two Order objects on the heap, each with its own id field and a header pointing to the Order class metadata in Metaspace; the class metadata is linked to the java.lang.Class mirror on the heap, which holds the single copy of the static fields NEXT_ID and created

Notice three things:

  • Each Order object holds only instance fields. Its header has a pointer (the klass pointer) to the class's metadata.
  • Class metadata (method bytecode, the vtable, the constant pool) lives in Metaspace, native memory outside the Java heap since Java 8 removed PermGen.
  • Static fields live in the java.lang.Class mirror, an ordinary heap object. HotSpot moved them there in JDK 7. So static fields are garbage-collected memory like everything else, and they stay alive as long as the class does.

02Under the hood: static fields and static methods

Try it yourself first

Before reading on: after creating three orders, what do Order.created() and the third order's id() return?

public class Order {
    private static final AtomicLong NEXT_ID = new AtomicLong(1000);   // one per class
    private static int created = 0;                                    // one per class

    private final long id;                                             // one per object

    public Order() {
        this.id = NEXT_ID.incrementAndGet();
        created++;
    }

    public long id()          { return id; }
    public static int created() { return created; }
}

new Order();
new Order();
Order third = new Order();
System.out.println(Order.created());   // prints: 3
System.out.println(third.id());        // prints: 1003

At the bytecode level, created++ inside the constructor is getstatic Order.created, iconst_1, iadd, putstatic Order.created. Compare that with this.id = ..., which is aload_0 (load this) followed by putfield. Static access needs no object reference at all, and that's the whole explanation for the rules that follow.

No this, no instance members

A static method is compiled to invokestatic, which pushes only the arguments, with no receiver. There's no this in local slot 0, so inside a static method:

  • you can't write this or super;
  • you can't read an instance field or call an instance method without an explicit object (order.id(), not id());
  • you can call other static members directly.

That's why main can't call a non-static helper without first creating an object, which is the first compile error most of us hit in Java.

Calling a static method through a reference (don't)

class Base    { static String who() { return "Base"; }    String me() { return "Base"; } }
class Derived extends Base {
    static String who() { return "Derived"; }              // hides Base.who()
    @Override String me() { return "Derived"; }            // overrides Base.me()
}

Base b = new Derived();
System.out.println(b.who());    // prints: Base     (static type decides)
System.out.println(b.me());     // prints: Derived  (runtime type decides)

Base nobody = null;
System.out.println(nobody.who());   // prints: Base  (no NullPointerException!)

Notice three things:

  • b.who() compiles to invokestatic Base.who, chosen from the declared type of b. The object is never consulted. This is method hiding.
  • b.me() compiles to invokevirtual, which dispatches on the runtime class. This is overriding.
  • Calling a static method through a null reference works, because the JVM never looks at the reference (JLS 15.12.4.1 evaluates it and throws the value away). Compilers and IDEs warn here; always call statics through the class name.

You can't put @Override on Derived.who() (compile error), and a static method can't hide an instance method or vice versa: both are compile errors.

03Static blocks and when a class is initialised

Try it yourself first

Before reading on: which of these lines print "Config initialised"? Config.MAX_RETRIES, Config.REGION, new Config[10], Config.class, Config.TIMEOUT.

class Config {
    static final int MAX_RETRIES = 3;               // compile-time constant
    static final String REGION = "ap-south-1";      // compile-time constant
    static final Integer TIMEOUT = 30;              // NOT a constant: Integer is not primitive or String
    static { System.out.println("Config initialised"); }
}

System.out.println(Config.MAX_RETRIES);   // prints: 3            (no init)
System.out.println(Config.REGION);        // prints: ap-south-1   (no init)
Config[] slots = new Config[10];          //                      (no init)
System.out.println(Config.class);         // prints: class Config (no init)
System.out.println(Config.TIMEOUT);       // prints: Config initialised, then 30

Only the last line initialises the class. A class is initialised on its first active use (JLS 12.4.1), which means exactly one of:

  1. creating an instance (new);
  2. calling a static method;
  3. assigning a static field, or reading one that isn't a constant variable;
  4. initialising a subclass (the parent goes first);
  5. reflection such as Class.forName("Config") (with the default initialize = true);
  6. being the class whose main method the JVM starts.

The first three are the bytecodes new, invokestatic, getstatic and putstatic (JVMS 5.5). MAX_RETRIES and REGION are compile-time constants: javac copies 3 and "ap-south-1" straight into the caller's bytecode, so there's no getstatic and nothing triggers. Config.class and array creation load the class but don't initialise it.

The diagram sums up the triggers and the order things run in.

Fig. 2

Two columns: actions that trigger class initialisation (new, static method call, non-constant static field access, subclass initialisation, Class.forName, main class) and actions that don't (reading a compile-time constant, creating an array, a class literal, accessing a parent's static field through a subclass name), plus a numbered strip showing parent statics, child statics, then instance initialisers and constructors

Two columns: actions that trigger class initialisation (new, static method call, non-constant static field access, subclass initialisation, Class.forName, main class) and actions that don't (reading a compile-time constant, creating an array, a class literal, accessing a parent's static field through a subclass name), plus a numbered strip showing parent statics, child statics, then instance initialisers and constructors

The order inside and across classes

Within one class, static field initialisers and static {} blocks run top to bottom, in textual order, combined into one hidden method called <clinit>. Across a hierarchy, the superclass is initialised first. And there's one surprise: reading a parent's static field through the child's name initialises only the parent, because the field belongs to the class that declares it.

class Parent {
    static String greeting = log("Parent.greeting");
    static { log("Parent static block"); }
    static String log(String s) { System.out.println(s); return s; }
}
class Child extends Parent {
    static String name = log("Child.name");
    static { log("Child static block"); }
}

System.out.println(Child.greeting);
// prints: Parent.greeting
//         Parent static block
//         Parent.greeting          <- Child is NOT initialised
System.out.println(Child.name);
// prints: Child.name
//         Child static block
//         Child.name

Textual order also creates the most famous static puzzle:

class Pricing {
    static final Pricing INSTANCE = new Pricing();                  // runs first
    static final BigDecimal GST = new BigDecimal("0.18");           // runs second
    final BigDecimal rate;
    Pricing() { rate = GST; }                                       // GST is still null here
}

System.out.println(Pricing.INSTANCE.rate);   // prints: null

The constructor runs during <clinit>, before GST has been assigned. If GST were static final double GST = 0.18, a constant variable, it would be inlined and the bug would vanish, which makes this even harder to spot.

Where candidates slip

Many candidates say "static blocks run when the class is loaded". That's frequently a mistake, because loading and initialisation are different steps. A class can be loaded, linked and verified long before its static block runs, or without it ever running (Config.class, new Config[10]). Say "on first active use" and list two or three triggers.

Static nested classes and static imports

A static nested class is a top-level class that happens to live inside another for packaging. It has no reference to an outer instance, so you create it with new Outer.Nested(). A non-static inner class carries a hidden this$0 field pointing at its outer object. HashMap.Node, builders like HttpRequest.Builder, and the lazy holder idiom below all use static nested classes. My rule: make nested classes static unless they need the outer instance. (Since Java 16, inner classes may also declare static members, a side effect of the records work in JEP 395.)

That hidden this$0 field is also a quiet source of leaks, as the diagram contrasts.

Fig. 3

An inner Row object holds a hidden this$0 reference to its enclosing Report object, pinning the whole Report in memory, while a static nested Row object has no such link and can be collected independently

An inner Row object holds a hidden this$0 reference to its enclosing Report object, pinning the whole Report in memory, while a static nested Row object has no such link and can be collected independently

Static imports (import static java.util.concurrent.TimeUnit.SECONDS;) let you use a static member without the class prefix. They read well for a few well-known names (assertEquals, SECONDS, Collectors.toList), and badly when a file imports * from three utility classes and nobody can tell where of comes from.

04Real world: static state in production

In the real world

A service kept a private static final Map<String, CustomerProfile> CACHE = new HashMap<>() to "save a database call". Two problems surfaced in production. First, under load, two request threads resized the HashMap at the same time and corrupted it, and lookups started returning null for keys that were present. Second, the map never evicted, so after a few days a heap dump showed 3.5 GB of profiles reachable from one static field. A static field is a GC root for as long as its class is loaded, which for application classes means forever. The fix was a bounded cache (Caffeine with maximumSize and expiry) owned by a Spring singleton bean.

That incident covers the two rules I apply to every static field:

  1. Mutable static state must be thread-safe. created++ is a read, an add and a write. Two threads each running it 100,000 times on a plain static int usually finish well short of 200,000 (one run on my laptop printed 134,435). Use AtomicLong, LongAdder, a ConcurrentHashMap, or don't share the state.
  2. Static collections need a size bound and an owner. Anything that only grows is a leak with a delay.

There's a quieter leak in app servers and plugin systems: if a class loaded by the shared parent loader holds a static reference to an object from a web app's loader, the whole web app, every class and every static field, can't be unloaded on redeploy. ThreadLocals on pooled threads cause the same thing.

Static versus singleton

A static utility class (Math, Collections, your Money.round()) is fine for stateless functions: give it a private constructor and keep it stateless. When there's state or a dependency, a singleton object is usually better: it can implement an interface, be injected and mocked in tests, be initialised lazily, and later become two instances without rewriting every caller. In Spring applications, that's what a default-scoped bean is. If you need a hand-rolled lazy singleton, the holder idiom gets thread safety for free from class initialisation:

public final class RateCard {
    private RateCard() { /* expensive: load rates from disk */ }

    private static final class Holder {               // not initialised until first use
        static final RateCard INSTANCE = new RateCard();
    }

    public static RateCard get() { return Holder.INSTANCE; }   // first call triggers Holder.<clinit>
}

It works because the JVM runs <clinit> exactly once, under a per-class initialisation lock (JLS 12.4.2). Any other thread that touches Holder waits until initialisation finishes, and then sees a fully constructed INSTANCE. An enum with one constant gives you the same guarantee plus serialization safety.

05Advanced: edge cases interviewers probe

A failed static initialiser poisons the class. If <clinit> throws, the first access gets ExceptionInInitializerError wrapping the cause. The class is then marked as erroneous, and every later access throws NoClassDefFoundError: Could not initialize class Rates, with no mention of the original cause. When you see that message in logs, scroll up to the first ExceptionInInitializerError.

Class initialisation can deadlock. If class A's static initialiser touches B, and B's touches A, and two threads start initialising them at the same time, each holds one init lock and waits for the other. Neither thread holds an ordinary monitor, so the usual "deadlock found" line in a thread dump may not appear; you see two threads stuck inside <clinit>. Keep static initialisers small and free of cross-class cycles.

One copy per class loader, not per JVM. The same class loaded by two loaders is two different classes with two sets of static fields. That's how two web apps in one Tomcat can each have their own "singleton".

Interfaces initialise lazily too. Initialising a class doesn't initialise the interfaces it implements, unless the interface declares a default method (JLS 12.4.1). Otherwise an interface is initialised only when you read one of its non-constant fields or call one of its static methods.

Performance angle

Static calls are cheap: invokestatic needs no receiver, no null check and no virtual dispatch, so the JIT can always inline small static methods. But since the JIT also inlines monomorphic instance calls, the real difference in a hot path is usually under a nanosecond. Choose static for design reasons, not speed. The expensive part of static state is contention: a single AtomicLong hammered by 32 threads can cost tens of nanoseconds or more per increment from cache-line ping-pong, while a LongAdder spreads the writes over per-thread cells and stays close to the uncontended cost of a few nanoseconds.

Static member Instance member
Copies one per class per class loader one per object
Stored in (HotSpot) the java.lang.Class mirror on the heap the object on the heap
Access bytecode getstatic / putstatic / invokestatic getfield / putfield / invokevirtual
this available no yes
Polymorphism hidden, bound to the static type overridden, bound to the runtime type
Lives until its class loader is collected the object is unreachable

06How to say it in the interview

When I ask about static, I'm listening for a correct memory picture (one copy per class, stored in the Class mirror), the precise initialisation trigger ("first active use", not "class loading"), hiding versus overriding, and production judgement: thread safety, leaks, and when a singleton or injected bean is better.

In the interview

"Static means the member belongs to the class, not to an instance. A static field exists once per class per class loader; in HotSpot it lives in the class's java.lang.Class object on the heap, while metadata is in Metaspace. Static methods compile to invokestatic, so there's no this, they can't touch instance state directly, and they're hidden rather than overridden: the declared type picks the method, which is also why calling one on a null reference doesn't throw. Static initialisers run once, in textual order, on first active use: new, a static method call, or a non-constant static field access; reading a compile-time constant doesn't trigger it because the value is inlined. In production, I treat static mutable state as global state: it must be thread-safe, and anything that grows needs a bound, because a static field keeps its objects alive as long as the class is loaded. For stateful shared services I prefer an injected singleton over static methods."

On the whiteboard

static = belongs to the class, 1 copy per class per loader
HotSpot: static fields in java.lang.Class mirror (heap, JDK 7+)
         class metadata in Metaspace (Java 8+, no PermGen)
static method: invokestatic, no this, hidden not overridden
  Base b = new Derived(); b.staticM() -> Base's; null ref OK
init on first active use (JLS 12.4.1):
  new | invokestatic | get/putstatic (non-constant) | subclass | forName
  NOT: constant read, new T[n], T.class, parent field via child
order: parent <clinit> -> child <clinit>, textual order inside
<clinit> throws -> ExceptionInInitializerError, then NoClassDefFoundError
holder idiom: lazy + thread-safe via class init lock
static mutable = global: atomic/concurrent, bounded, owned
Practise this topicstatic, final & transient: interview questions & scenarios
Further reading (7)