🔒 Static Keyword in Java

📚 Introduction to Static Keyword in Java

In Java programming, the static is fundamental concepts that significantly influence how we design and implement our code. These keywords might seem simple at first glance, but they have profound implications for memory management, performance, security, and overall program design.

The static keyword relates to the class itself rather than instances of the class. It creates elements that belong to the class as a whole, rather than to specific objects. This means that all instances of the class share the same static elements.

Let's dive in and master these essential Java concepts!


🔍 The Static Keyword

What Does Static Mean in Java?

The static keyword in Java indicates that a particular member belongs to the class itself, rather than to any specific instance of the class. This means:

  • A static member is shared across all instances of the class
  • It can be accessed without creating an instance of the class
  • It's loaded into memory when the class is loaded by the JVM

Types of Static Members

Java allows four types of static members:

  1. Static variables (class variables)
  2. Static methods (class methods)
  3. Static blocks
  4. Static nested classes

Let's explore each of these in detail.

📊 Static Variables

Static variables, also known as class variables, belong to the class rather than to any instance of the class. This means all instances of the class share the same static variable.

Basic Example:

public class Counter {
    // Static variable - shared by all instances
    public static int count = 0;
    
    // Instance variable - each instance has its own copy
    public int instanceCount = 0;
    
    public Counter() {
        count++;         // Increment the static counter
        instanceCount++; // Increment the instance counter
    }
}

public class StaticVariableDemo {
    public static void main(String[] args) {
        Counter c1 = new Counter();
        System.out.println("c1: static count = " + Counter.count);
        System.out.println("c1: instance count = " + c1.instanceCount);
        
        Counter c2 = new Counter();
        System.out.println("c2: static count = " + Counter.count);
        System.out.println("c2: instance count = " + c2.instanceCount);
        
        Counter c3 = new Counter();
        System.out.println("c3: static count = " + Counter.count);
        System.out.println("c3: instance count = " + c3.instanceCount);
        
        // We can also access the static variable directly through the class
        System.out.println("Total count: " + Counter.count);
    }
}

Output:

c1: static count = 1
c1: instance count = 1
c2: static count = 2
c2: instance count = 1
c3: static count = 3
c3: instance count = 1
Total count: 3

In this example, the count variable is static and shared among all instances of the Counter class. Each time a new Counter object is created, the same count variable is incremented. In contrast, each instance has its own copy of instanceCount, which is always 1 for each new instance.

Common Use Cases for Static Variables:

  1. Counters and Statistics: Tracking the number of instances created or operations performed
  2. Constants: Values that remain the same across all instances (often combined with final)
  3. Configuration Settings: Application-wide settings that all objects need to access
  4. Caching: Storing data that all instances can use to avoid redundant calculations

Example with Constants:

public class MathConstants {
    // Static constants (note the use of final, which we'll discuss later)
    public static final double PI = 3.14159265359;
    public static final double E = 2.71828182846;
    public static final double GOLDEN_RATIO = 1.61803398875;
    
    // Private constructor to prevent instantiation
    private MathConstants() {
        // This class should not be instantiated
    }
}

public class CircleCalculator {
    public static double calculateCircumference(double radius) {
        return 2 * MathConstants.PI * radius;
    }
    
    public static double calculateArea(double radius) {
        return MathConstants.PI * radius * radius;
    }
}

In this example, MathConstants provides static constants that can be accessed without creating an instance of the class. The CircleCalculator class uses these constants in its calculations.

🔄 Static Methods

Static methods, like static variables, belong to the class rather than to any instance. They can be called without creating an instance of the class.

Key Characteristics of Static Methods:

  1. They can access only static variables and other static methods directly
  2. They cannot access instance variables or instance methods directly
  3. They cannot use the this keyword (since there is no instance)
  4. They can be called using the class name, without creating an object

Basic Example:

public class MathUtils {
    // Static method
    public static int add(int a, int b) {
        return a + b;
    }
    
    // Static method
    public static int multiply(int a, int b) {
        return a * b;
    }
    
    // Instance method
    public int subtract(int a, int b) {
        return a - b;
    }
}

public class StaticMethodDemo {
    public static void main(String[] args) {
        // Calling static methods without creating an instance
        int sum = MathUtils.add(5, 3);
        int product = MathUtils.multiply(5, 3);
        
        System.out.println("Sum: " + sum);
        System.out.println("Product: " + product);
        
        // To call an instance method, we need to create an instance
        MathUtils utils = new MathUtils();
        int difference = utils.subtract(5, 3);
        System.out.println("Difference: " + difference);
    }
}

Output:

Sum: 8
Product: 15
Difference: 2

In this example, the add and multiply methods are static and can be called directly using the class name MathUtils. The subtract method is an instance method and requires an instance of MathUtils to be called.

Common Use Cases for Static Methods:

  1. Utility Functions: Methods that perform operations without needing instance data
  2. Factory Methods: Methods that create and return instances of the class
  3. Main Method: The entry point of a Java application is always static
  4. Helper Methods: Methods that provide functionality to other methods

Example with Factory Methods:

public class Employee {
    private String name;
    private String department;
    private double salary;
    
    // Private constructor - instances can only be created through factory methods
    private Employee(String name, String department, double salary) {
        this.name = name;
        this.department = department;
        this.salary = salary;
    }
    
    // Static factory method for creating a developer
    public static Employee createDeveloper(String name, double salary) {
        return new Employee(name, "Development", salary);
    }
    
    // Static factory method for creating a manager
    public static Employee createManager(String name, double salary) {
        return new Employee(name, "Management", salary + 10000);
    }
    
    // Static factory method for creating a tester
    public static Employee createTester(String name, double salary) {
        return new Employee(name, "Quality Assurance", salary);
    }
    
    // Instance method to display employee details
    public void displayDetails() {
        System.out.println("Name: " + name);
        System.out.println("Department: " + department);
        System.out.println("Salary: $" + salary);
    }
}

public class FactoryMethodDemo {
    public static void main(String[] args) {
        // Creating employees using factory methods
        Employee dev = Employee.createDeveloper("John Doe", 75000);
        Employee manager = Employee.createManager("Jane Smith", 85000);
        Employee tester = Employee.createTester("Bob Johnson", 70000);
        
        System.out.println("Developer Details:");
        dev.displayDetails();
        
        System.out.println("\nManager Details:");
        manager.displayDetails();
        
        System.out.println("\nTester Details:");
        tester.displayDetails();
    }
}

Output:

Developer Details:
Name: John Doe
Department: Development
Salary: $75000.0

Manager Details:
Name: Jane Smith
Department: Management
Salary: $95000.0

Tester Details:
Name: Bob Johnson
Department: Quality Assurance
Salary: $70000.0

In this example, the Employee class provides static factory methods for creating different types of employees. These methods encapsulate the creation logic and provide a clear, descriptive way to create instances with specific configurations.

🔄 Static Blocks

Static blocks, also known as static initialization blocks, are used to initialize static variables. They are executed when the class is loaded into memory, before any instance of the class is created and before the main method is called.

Key Characteristics of Static Blocks:

  1. They are executed only once, when the class is loaded
  2. They can access only static variables and methods
  3. They cannot access instance variables or methods
  4. Multiple static blocks in a class are executed in the order they appear

Basic Example:

public class DatabaseConnection {
    // Static variables
    private static String url;
    private static String username;
    private static String password;
    private static boolean isInitialized;
    
    // Static block to initialize the connection parameters
    static {
        System.out.println("Static block executed - Loading database configuration");
        // In a real application, these might be loaded from a configuration file
        url = "jdbc:mysql://localhost:3306/mydb";
        username = "admin";
        password = "securepassword";
        isInitialized = true;
    }
    
    // Another static block
    static {
        System.out.println("Second static block executed - Performing additional setup");
        // Additional initialization if needed
    }
    
    // Static method to check if the connection is initialized
    public static boolean isConnectionInitialized() {
        return isInitialized;
    }
    
    // Static method to get connection details
    public static String getConnectionDetails() {
        if (isInitialized) {
            return "URL: " + url + ", Username: " + username;
        } else {
            return "Connection not initialized";
        }
    }
}

public class StaticBlockDemo {
    public static void main(String[] args) {
        System.out.println("Main method started");
        
        // The static blocks will be executed when the class is first referenced
        System.out.println("Is connection initialized? " + DatabaseConnection.isConnectionInitialized());
        System.out.println("Connection details: " + DatabaseConnection.getConnectionDetails());
    }
}

Output:

Main method started
Static block executed - Loading database configuration
Second static block executed - Performing additional setup
Is connection initialized? true
Connection details: URL: jdbc:mysql://localhost:3306/mydb, Username: admin

In this example, the static blocks in DatabaseConnection are executed when the class is first referenced in the main method. They initialize the static variables before any methods are called.

Common Use Cases for Static Blocks:

  1. Complex Initialization: When static variables require complex initialization logic
  2. Loading Resources: Loading configuration files, properties, or resources
  3. Registering Drivers: Registering JDBC drivers or other components
  4. Performing One-time Setup: Operations that should happen only once when the application starts

Example with Exception Handling:

public class ResourceLoader {
    private static Map<String, String> resources = new HashMap<>();
    private static boolean loadSuccessful;
    
    // Static block with exception handling
    static {
        try {
            System.out.println("Loading resources...");
            // Simulate loading resources from a file
            resources.put("config.timeout", "30000");
            resources.put("config.maxConnections", "100");
            resources.put("config.serverUrl", "https://api.example.com");
            
            // Simulate a potential error
            if (Math.random() < 0.1) { // 10% chance of failure
                throw new IOException("Failed to load resources");
            }
            
            loadSuccessful = true;
            System.out.println("Resources loaded successfully");
        } catch (Exception e) {
            System.err.println("Error loading resources: " + e.getMessage());
            loadSuccessful = false;
        }
    }
    
    // Static method to get a resource
    public static String getResource(String key) {
        if (!loadSuccessful) {
            return "Resource not available - loading failed";
        }
        return resources.getOrDefault(key, "Resource not found");
    }
    
    // Static method to check if resources were loaded successfully
    public static boolean isLoadSuccessful() {
        return loadSuccessful;
    }
}

public class ResourceLoaderDemo {
    public static void main(String[] args) {
        System.out.println("Main method started");
        
        // Check if resources were loaded successfully
        System.out.println("Resources loaded successfully? " + ResourceLoader.isLoadSuccessful());
        
        // Get some resources
        System.out.println("Timeout: " + ResourceLoader.getResource("config.timeout"));
        System.out.println("Max Connections: " + ResourceLoader.getResource("config.maxConnections"));
        System.out.println("Server URL: " + ResourceLoader.getResource("config.serverUrl"));
        System.out.println("Unknown Resource: " + ResourceLoader.getResource("config.unknown"));
    }
}

Output (successful loading):

Main method started
Loading resources...
Resources loaded successfully
Resources loaded successfully? true
Timeout: 30000
Max Connections: 100
Server URL: https://api.example.com
Unknown Resource: Resource not found

Output (failed loading - less likely due to the random factor):

Main method started
Loading resources...
Error loading resources: Failed to load resources
Resources loaded successfully? false
Timeout: Resource not available - loading failed
Max Connections: Resource not available - loading failed
Server URL: Resource not available - loading failed
Unknown Resource: Resource not available - loading failed

In this example, the static block in ResourceLoader attempts to load resources and handles any exceptions that might occur. The loadSuccessful flag indicates whether the loading was successful, and the getResource method returns appropriate messages based on this flag.


📦 Static Nested Classes

A static nested class is a static member of its enclosing class. Unlike non-static (inner) classes, a static nested class does not have access to the instance variables and methods of its enclosing class.

Key Characteristics of Static Nested Classes:

  1. They can access only static members of the outer class
  2. They can be instantiated without an instance of the outer class
  3. They can have both static and non-static members
  4. They are accessed using the outer class name

Basic Example:

public class OuterClass {
    private static String staticOuterField = "Static outer field";
    private String instanceOuterField = "Instance outer field";
    
    // Static nested class
    public static class StaticNestedClass {
        private String nestedField = "Nested field";
        
        public void display() {
            // Can access static members of the outer class
            System.out.println("From nested class - Static outer field: " + staticOuterField);
            
            // Cannot access instance members of the outer class
            // System.out.println(instanceOuterField); // This would cause a compilation error
            
            System.out.println("From nested class - Nested field: " + nestedField);
        }
    }
    
    // Non-static (inner) class for comparison
    public class InnerClass {
        private String innerField = "Inner field";
        
        public void display() {
            // Can access both static and instance members of the outer class
            System.out.println("From inner class - Static outer field: " + staticOuterField);
            System.out.println("From inner class - Instance outer field: " + instanceOuterField);
            System.out.println("From inner class - Inner field: " + innerField);
        }
    }
    
    // Method to demonstrate creating instances of both classes
    public void createNestedClassInstances() {
        // Creating an instance of the static nested class
        StaticNestedClass staticNested = new StaticNestedClass();
        staticNested.display();
        
        // Creating an instance of the inner class
        InnerClass inner = new InnerClass();
        inner.display();
    }
}

public class NestedClassDemo {
    public static void main(String[] args) {
        // Creating an instance of the static nested class without an outer class instance
        OuterClass.StaticNestedClass staticNested = new OuterClass.StaticNestedClass();
        staticNested.display();
        
        // Creating an instance of the inner class requires an outer class instance
        OuterClass outer = new OuterClass();
        OuterClass.InnerClass inner = outer.new InnerClass();
        inner.display();
        
        // Alternatively, use the convenience method
        System.out.println("\nUsing convenience method:");
        outer.createNestedClassInstances();
    }
}

Output:

From nested class - Static outer field: Static outer field
From nested class - Nested field: Nested field

From inner class - Static outer field: Static outer field
From inner class - Instance outer field: Instance outer field
From inner class - Inner field: Inner field

Using convenience method:
From nested class - Static outer field: Static outer field
From nested class - Nested field: Nested field
From inner class - Static outer field: Static outer field
From inner class - Instance outer field: Instance outer field
From inner class - Inner field: Inner field

In this example, the StaticNestedClass is a static nested class that can access only static members of the OuterClass. It can be instantiated without an instance of the OuterClass. In contrast, the InnerClass is a non-static inner class that can access both static and instance members of the OuterClass, but requires an instance of the OuterClass to be instantiated.

Common Use Cases for Static Nested Classes:

  1. Helper Classes: Classes that are closely related to the outer class but don't need access to its instance members
  2. Grouping Related Classes: Keeping related classes together for better organization
  3. Encapsulation: Hiding implementation details within the outer class
  4. Builder Pattern: Implementing the Builder pattern for the outer class

Example with Builder Pattern:

public class Person {
    // Private fields
    private final String firstName;
    private final String lastName;
    private final int age;
    private final String address;
    private final String phoneNumber;
    private final String email;
    
    // Private constructor - instances can only be created through the Builder
    private Person(Builder builder) {
        this.firstName = builder.firstName;
        this.lastName = builder.lastName;
        this.age = builder.age;
        this.address = builder.address;
        this.phoneNumber = builder.phoneNumber;
        this.email = builder.email;
    }
    
    // Getters
    public String getFirstName() { return firstName; }
    public String getLastName() { return lastName; }
    public int getAge() { return age; }
    public String getAddress() { return address; }
    public String getPhoneNumber() { return phoneNumber; }
    public String getEmail() { return email; }
    
    // Display method
    public void displayInfo() {
        System.out.println("Name: " + firstName + " " + lastName);
        System.out.println("Age: " + age);
        System.out.println("Address: " + (address != null ? address : "N/A"));
        System.out.println("Phone: " + (phoneNumber != null ? phoneNumber : "N/A"));
        System.out.println("Email: " + (email != null ? email : "N/A"));
    }
    
    // Static Builder class
    public static class Builder {
        // Required parameters
        private final String firstName;
        private final String lastName;
        
        // Optional parameters - initialized to default values
        private int age = 0;
        private String address = null;
        private String phoneNumber = null;
        private String email = null;
        
        // Constructor with required parameters
        public Builder(String firstName, String lastName) {
            this.firstName = firstName;
            this.lastName = lastName;
        }
        
        // Methods to set optional parameters and return the Builder
        public Builder age(int age) {
            this.age = age;
            return this;
        }
        
        public Builder address(String address) {
            this.address = address;
            return this;
        }
        
        public Builder phoneNumber(String phoneNumber) {
            this.phoneNumber = phoneNumber;
            return this;
        }
        
        public Builder email(String email) {
            this.email = email;
            return this;
        }
        
        // Build method to create a Person instance
        public Person build() {
            return new Person(this);
        }
    }
}

public class BuilderPatternDemo {
    public static void main(String[] args) {
        // Creating a Person using the Builder pattern
        Person person1 = new Person.Builder("John", "Doe")
                            .age(30)
                            .address("123 Main St, Anytown, USA")
                            .phoneNumber("555-123-4567")
                            .email("john.doe@example.com")
                            .build();
        
        System.out.println("Person 1 Details:");
        person1.displayInfo();
        
        // Creating another Person with only required fields
        Person person2 = new Person.Builder("Jane", "Smith")
                            .build();
        
        System.out.println("\nPerson 2 Details:");
        person2.displayInfo();
        
        // Creating a Person with some optional fields
        Person person3 = new Person.Builder("Bob", "Johnson")
                            .age(45)
                            .email("bob.johnson@example.com")
                            .build();
        
        System.out.println("\nPerson 3 Details:");
        person3.displayInfo();
    }
}

Output:

Person 1 Details:
Name: John Doe
Age: 30
Address: 123 Main St, Anytown, USA
Phone: 555-123-4567
Email: john.doe@example.com

Person 2 Details:
Name: Jane Smith
Age: 0
Address: N/A
Phone: N/A
Email: N/A

Person 3 Details:
Name: Bob Johnson
Age: 45
Address: N/A
Phone: N/A
Email: bob.johnson@example.com

In this example, the Builder is a static nested class within the Person class. It provides a fluent interface for creating Person instances with various combinations of optional parameters. The Person class itself has a private constructor that can only be called by the Builder, ensuring that all Person instances are created through the builder pattern.


🚫 Common Pitfalls When Using Static in Java

While the static keyword is powerful, it can lead to issues if not used carefully. Here are some common pitfalls to avoid:

1. Memory Leaks with Static Variables

Static variables remain in memory for the entire duration of the application, which can lead to memory leaks if they hold references to large objects that are no longer needed.

public class MemoryLeakExample {
    // This static variable could cause a memory leak
    private static List<byte[]> largeObjects = new ArrayList<>();
    
    public static void processData() {
        // Create a large object (1MB)
        byte[] largeObject = new byte[1024 * 1024];
        
        // Process the data...
        
        // Add to the static list - these objects will never be garbage collected
        largeObjects.add(largeObject);
    }
    
    // Better approach: clear the list when no longer needed
    public static void cleanup() {
        largeObjects.clear();
    }
}

2. Thread Safety Issues

Static variables are shared across all threads, which can lead to race conditions and other concurrency issues if not properly synchronized.

public class ThreadSafetyIssue {
    // This static variable is shared by all threads
    private static int counter = 0;
    
    // Not thread-safe - multiple threads can increment simultaneously
    public static void incrementCounter() {
        counter++;
    }
    
    // Thread-safe version using synchronization
    public static synchronized void incrementCounterSafely() {
        counter++;
    }
    
    // Thread-safe version using AtomicInteger
    private static AtomicInteger atomicCounter = new AtomicInteger(0);
    
    public static void incrementAtomicCounter() {
        atomicCounter.incrementAndGet();
    }
    
    public static int getCounter() {
        return counter;
    }
    
    public static int getAtomicCounter() {
        return atomicCounter.get();
    }
}

3. Testing Difficulties

Classes with static methods and variables can be harder to test, as they maintain state between tests and can't be easily mocked or stubbed.

// Hard to test
public class HardToTest {
    private static DatabaseConnection connection = new DatabaseConnection();
    
    public static List<User> getUsers() {
        return connection.queryUsers();
    }
}

// Easier to test
public class EasierToTest {
    private DatabaseConnection connection;
    
    // Dependency can be injected and mocked for testing
    public EasierToTest(DatabaseConnection connection) {
        this.connection = connection;
    }
    
    public List<User> getUsers() {
        return connection.queryUsers();
    }
}

4. Overuse of Static Methods

Overusing static methods can lead to procedural rather than object-oriented code, making the code less flexible and harder to maintain.

// Procedural style with static methods
public class UserUtils {
    public static User createUser(String name, String email) {
        return new User(name, email);
    }
    
    public static void validateUser(User user) {
        // Validation logic
    }
    
    public static void saveUser(User user) {
        // Save to database
    }
}

// Object-oriented style
public class UserService {
    private UserValidator validator;
    private UserRepository repository;
    
    public UserService(UserValidator validator, UserRepository repository) {
        this.validator = validator;
        this.repository = repository;
    }
    
    public User createUser(String name, String email) {
        User user = new User(name, email);
        validator.validate(user);
        repository.save(user);
        return user;
    }
}

5. Initialization Order Surprises

The order of static initialization can sometimes lead to surprising results, especially with complex class hierarchies.

public class Parent {
    static {
        System.out.println("Parent static block");
    }
    
    static {
        System.out.println("Parent second static block");
    }
    
    public static int value = initValue();
    
    private static int initValue() {
        System.out.println("Parent initializing value");
        return 100;
    }
}

public class Child extends Parent {
    static {
        System.out.println("Child static block");
    }
    
    public static int childValue = initChildValue();
    
    private static int initChildValue() {
        System.out.println("Child initializing childValue");
        return value + 50; // Uses Parent.value
    }
}

public class InitializationOrderDemo {
    public static void main(String[] args) {
        System.out.println("Main method started");
        System.out.println("Child.childValue = " + Child.childValue);
    }
}

Output:

Main method started
Parent static block
Parent second static block
Parent initializing value
Child static block
Child initializing childValue
Child.childValue = 150

This example demonstrates the order of static initialization: parent class static blocks and variables are initialized before child class static blocks and variables.

🌟 Best Practices for Using Static keyword in Java

To use the static keyword effectively and avoid common pitfalls, follow these best practices:

1. Use Static for Constants

Constants (values that don't change) should be declared as static final.

public class Constants {
    // Good: Constants are static and final
    public static final double PI = 3.14159265359;
    public static final String APP_NAME = "MyApp";
    public static final int MAX_CONNECTIONS = 100;
}

2. Use Static for Utility Methods

Methods that don't depend on instance state should be static.

public class StringUtils {
    // Private constructor to prevent instantiation
    private StringUtils() {
        throw new AssertionError("Utility class should not be instantiated");
    }
    
    // Static utility methods
    public static boolean isEmpty(String str) {
        return str == null || str.trim().isEmpty();
    }
    
    public static String capitalize(String str) {
        if (isEmpty(str)) {
            return str;
        }
        return Character.toUpperCase(str.charAt(0)) + 
               (str.length() > 1 ? str.substring(1) : "");
    }
    
    public static String reverse(String str) {
        if (isEmpty(str)) {
            return str;
        }
        return new StringBuilder(str).reverse().toString();
    }
}

3. Consider Thread Safety

When using static variables, consider thread safety implications and use synchronization or thread-safe collections when necessary.

public class ThreadSafeCounter {
    // Thread-safe counter using AtomicInteger
    private static final AtomicInteger counter = new AtomicInteger(0);
    
    // Thread-safe map using ConcurrentHashMap
    private static final Map<String, Integer> counts = new ConcurrentHashMap<>();
    
    // Thread-safe method
    public static int incrementAndGet() {
        return counter.incrementAndGet();
    }
    
    // Thread-safe method
    public static void incrementCount(String key) {
        counts.compute(key, (k, v) -> (v == null) ? 1 : v + 1);
    }
    
    // Thread-safe method
    public static int getCount(String key) {
        return counts.getOrDefault(key, 0);
    }
}

4. Avoid Excessive Static State

Minimize the amount of mutable static state in your application to reduce coupling and improve testability.

// Avoid this
public class BadSingleton {
    private static Connection databaseConnection;
    
    public static Connection getDatabaseConnection() {
        if (databaseConnection == null) {
            databaseConnection = createConnection();
        }
        return databaseConnection;
    }
    
    private static Connection createConnection() {
        // Create and return a database connection
        return null; // Placeholder
    }
}

// Better approach
public class BetterSingleton {
    private static volatile BetterSingleton instance;
    private Connection databaseConnection;
    
    private BetterSingleton() {
        databaseConnection = createConnection();
    }
    
    public static BetterSingleton getInstance() {
        if (instance == null) {
            synchronized (BetterSingleton.class) {
                if (instance == null) {
                    instance = new BetterSingleton();
                }
            }
        }
        return instance;
    }
    
    public Connection getDatabaseConnection() {
        return databaseConnection;
    }
    
    private Connection createConnection() {
        // Create and return a database connection
        return null; // Placeholder
    }
}

5. Document Static Members Clearly

Clearly document the purpose and usage of static members, especially if they maintain state.

/**
 * Utility class for working with files.
 * This class cannot be instantiated.
 */
public class FileUtils {
    /**
     * The default buffer size used for file operations.
     * This value can be overridden by setting the system property "file.buffer.size".
     */
    public static final int DEFAULT_BUFFER_SIZE = 8192;
    
    /**
     * The maximum file size that can be processed, in bytes.
     * Files larger than this size will throw an exception.
     */
    public static final long MAX_FILE_SIZE = 1024 * 1024 * 100; // 100MB
    
    // Private constructor to prevent instantiation
    private FileUtils() {
        throw new AssertionError("Utility class should not be instantiated");
    }
    
    // Static utility methods...
}
Static Keyword in Java: Complete Guide with Examples and Best Practices | Stack a Byte