Skip to content
Dev Dump

Static Keyword

  • Allocated once during class loading, not per object
  • Shared across all instances of the class
  • Accessible without creating an object
TypePurposeExample
Static VariablesShared state across instancespublic static int counter = 0;
Static MethodsUtility behaviorpublic static int add(int a, int b)
Static BlocksConfiguration at class load timestatic { /* initialization */ }
Static Nested ClassesLogically grouped helperspublic static class Builder
Static ImportsImport static membersimport static java.lang.Math.PI;
AspectstaticInstance
Belongs toClassObject
MemoryMethod Area (Metaspace)Heap (per object)
AccessClassName.memberobject.member
LifetimeClass load → ClassLoader GCObject creation → Object GC
this referenceNot availableAvailable
OverridingNo (method hiding only)Yes (polymorphism)
Thread safetyShared — must synchronizePer-object — safer by default

ConceptBehavior
Memory LocationStored in Method Area (part of Metaspace in modern JVMs)
GC EligibilityLives as long as the class remains loaded in JVM
RiskHolding large static references may lead to memory leaks
InitializationWhen class is first loaded by ClassLoader
CleanupWhen ClassLoader is garbage collected

public class Counter {
private static int count = 0;
private int instanceId;
public Counter() {
instanceId = ++count;
}
public static int getTotalCount() {
return count;
}
}
Counter c1 = new Counter(); // count = 1
Counter c2 = new Counter(); // count = 2
System.out.println(Counter.getTotalCount()); // 2
public class AppConstants {
public static final double PI = 3.14159;
public static final int MAX_RETRY = 3;
public static final String DEFAULT_ENCODING = "UTF-8";
private AppConstants() {} // prevent instantiation
}
public class Configuration {
private static final Properties config = new Properties();
static {
try (InputStream input = Configuration.class
.getClassLoader()
.getResourceAsStream("config.properties")) {
if (input != null) {
config.load(input);
}
} catch (IOException e) {
throw new ExceptionInInitializerError(e);
}
}
public static String getProperty(String key) {
return config.getProperty(key);
}
}

public class StringUtils {
private StringUtils() {}
public static boolean isEmpty(String str) {
return str == null || str.trim().isEmpty();
}
public static String reverse(String str) {
if (str == null) return null;
return new StringBuilder(str).reverse().toString();
}
}

Static factory methods offer advantages over constructors: descriptive names, caching, and returning subtypes.

public class Connection {
private final String url;
private final boolean readOnly;
private Connection(String url, boolean readOnly) {
this.url = url;
this.readOnly = readOnly;
}
public static Connection readWrite(String url) {
return new Connection(url, false);
}
public static Connection readOnly(String url) {
return new Connection(url, true);
}
}
// Readable, self-documenting API
Connection rw = Connection.readWrite("jdbc:mysql://localhost/db");
Connection ro = Connection.readOnly("jdbc:mysql://localhost/db");

Static blocks run once when the class is first loaded, before any constructor or static method call.

public class DatabaseConnection {
private static Connection connection;
static {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
connection = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/mydb", "user", "pass"
);
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
public static Connection getConnection() {
return connection;
}
}

Multiple Static Blocks & Initialization Order

Section titled “Multiple Static Blocks & Initialization Order”

Static blocks and static field initializers execute top-to-bottom in source order:

public class InitOrder {
static { System.out.println("1. Static block A"); }
private static String field = initField();
static { System.out.println("3. Static block B"); }
private static String initField() {
System.out.println("2. Field initializer");
return "value";
}
}
// Output: 1 → 2 → 3

Static Nested ClassInner Class (non-static)
Outer instance needed?NoYes (implicit reference)
Can access outer instance members?No — static onlyYes — all members
MemoryNo hidden referenceHolds reference → can cause leaks
Typical useBuilder, helper, EntryIterator, event handler
public class Outer {
private static String staticField = "static";
private String instanceField = "instance";
public static class Nested {
public void display() {
System.out.println(staticField); // OK
// System.out.println(instanceField); // compile error
}
}
}
Outer.Nested nested = new Outer.Nested(); // no Outer instance needed
public class Person {
private final String name;
private final int age;
private final String email;
private Person(Builder b) {
this.name = b.name;
this.age = b.age;
this.email = b.email;
}
public static class Builder {
private String name;
private int age;
private String email;
public Builder name(String name) { this.name = name; return this; }
public Builder age(int age) { this.age = age; return this; }
public Builder email(String email) { this.email = email; return this; }
public Person build() {
Objects.requireNonNull(name, "name is required");
return new Person(this);
}
}
}
Person p = new Person.Builder().name("John").age(30).email("j@x.com").build();

class Parent {
static void greet() { System.out.println("Parent"); }
void instanceGreet() { System.out.println("Parent"); }
}
class Child extends Parent {
static void greet() { System.out.println("Child"); } // hides
@Override
void instanceGreet() { System.out.println("Child"); } // overrides
}
Parent ref = new Child();
ref.greet(); // "Parent" — static, resolved by reference type
ref.instanceGreet(); // "Child" — instance, resolved by object type
Static (hiding)Instance (overriding)
BindingCompile-time (early)Runtime (late)
Resolved byReference typeActual object type
@OverrideNot applicableRequired
PolymorphismNoYes

Static imports let you use static members without class-qualifying them.

import static java.lang.Math.PI;
import static java.lang.Math.sqrt;
import static java.util.Collections.unmodifiableList;
double circumference = 2 * PI * radius; // instead of Math.PI
double hypotenuse = sqrt(a * a + b * b); // instead of Math.sqrt(...)

Static fields are shared across all threads. Without synchronization, concurrent access causes race conditions.

public class UnsafeCounter {
private static int count = 0;
public static void increment() {
count++; // NOT atomic: read-modify-write
}
}
// Option 1: AtomicInteger (preferred for simple counters)
private static final AtomicInteger count = new AtomicInteger(0);
public static void increment() { count.incrementAndGet(); }
// Option 2: synchronized method
private static int count = 0;
public static synchronized void increment() { count++; }
// Option 3: volatile (only for flags, not compound actions)
private static volatile boolean running = true;
TechniqueUse CaseOverhead
AtomicInteger / AtomicReferenceSingle-variable CAS operationsLow
synchronizedMulti-step critical sectionsMedium
volatileVisibility-only (flags, published refs)Low
ThreadLocalPer-thread isolated stateLow

When a class hierarchy is loaded, the JVM follows a strict order:

1. Parent static fields & static blocks (top-to-bottom)
2. Child static fields & static blocks (top-to-bottom)
3. Parent instance fields & instance blocks
4. Parent constructor
5. Child instance fields & instance blocks
6. Child constructor
class Parent {
static { System.out.println("1. Parent static block"); }
{ System.out.println("3. Parent instance block"); }
Parent() { System.out.println("4. Parent constructor"); }
}
class Child extends Parent {
static { System.out.println("2. Child static block"); }
{ System.out.println("5. Child instance block"); }
Child() { System.out.println("6. Child constructor"); }
}
new Child();
// Output: 1 → 2 → 3 → 4 → 5 → 6