CodeToConcept
About

java

java Tutorials

Introduction
Java Variables
Java Data Types
Java Type Casting
Java Operators
Java Input/Output
Java Class & Object
java Method
Java Constructor
Java this keyword
Java super keyword
Java Method Overloading and Overriding
Java Interface
Java Abstract Class
Java Memory Management
Design Patterns
JDBC (Database Connectivity)
Java IDEs

java Tutorials

Introduction
Java Variables
Java Data Types
Java Type Casting
Java Operators
Java Input/Output
Java Class & Object
java Method
Java Constructor
Java this keyword
Java super keyword
Java Method Overloading and Overriding
Java Interface
Java Abstract Class
Java Memory Management
Design Patterns
JDBC (Database Connectivity)
Java IDEs

Sample Question

Think it through, then check the tutorial.

CodeToConcept

Quickly learn through simple, structured, and practical coding tutorials. Build your confidence from step zero to real-world experience.

© 2026 CodeToConcept. All rights reserved.

Quick Links

AboutTutorials

Tutorials

JavaHTMLCSS

Support

Privacy PolicyTerms of ServiceContact
GitHubTwitterYouTube

Java Non-Access Modifiers

Non-access modifiers are Java keywords that specify how a class, variable, or method works. For example, they can make a member belong to the class, prevent a variable from being reassigned, or require a child class to provide a method body.

Sometimes we want all objects to share a value. Sometimes we want to stop a value from being changed or let a child class write a method. Non-access modifiers help us add these rules to Java code. Each modifier has a different purpose.

An access modifier decides which classes can use a class or member. A non-access modifier adds another rule, such as whether a value is shared or can be assigned again. We can use both kinds together. For example, in public static final, public controls access, static makes the field belong to the class, and final stops reassignment.

Common Non-Access Modifiers

ModifierUsed onMain purpose
staticClass fields, methods, member classesMake the member belong to the class
finalVariables, methods, classesPrevent reassignment, overriding, or inheritance
abstractClasses and methodsPrevent direct creation of an abstract class; leave a method body for a child
synchronizedMethodsUse a lock while the method runs
volatileFieldsLet threads see updates to the field
transientInstance fieldsSkip a field when normally saving an object
nativeMethodsUse method code provided outside Java source
strictfpClasses, interfaces, concrete methodsUse older floating-point rules; not needed since Java 17

static also introduces static initialization blocks. Not every modifier fits every declaration.

Save each complete example separately as HelloWorld.java. Most examples use Java 8-compatible syntax; version-specific features are identified below.

1. final

final means a variable cannot get a new value after it has been assigned. A child class cannot override a final instance method or hide a final static method. No class can extend a final class. If a final variable refers to an object, the object can still change; only the variable cannot be made to refer to another object.

A final Variable Cannot Be Reassigned

class LessonGoal {
    static final int WEEKLY_LESSONS = 4;
}
 
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println(LessonGoal.WEEKLY_LESSONS);
        // LessonGoal.WEEKLY_LESSONS = 6; // Does not compile: the field is final.
    }
}

Output:

4

After a final variable gets its value, we cannot assign another value to it. We must give it a value before reading it. A final instance field that has no value at its declaration can get its value in a constructor. final does not always mean the value is known when the code is compiled; some final values are set while the program runs.

A final Reference Does Not Freeze Its Object

class LessonTopic {
    String topic = "Packages";
}
 
public class HelloWorld {
    public static void main(String[] args) {
        final LessonTopic list = new LessonTopic();
        list.topic = "Inheritance";
        System.out.println(list.topic);
        // list = new LessonTopic(); // Does not compile: list cannot be reassigned.
    }
}

Output:

Inheritance

Here list must keep referring to the same LessonTopic object. We can still change that object's topic field because the field is not final and is accessible here. Therefore, final on an object variable does not make every value inside the object unchangeable. The commented line fails because it tries to make list refer to a new object.

final Methods and Classes

class LessonInfo {
    final void showId() {
        System.out.println("Record: L-24");
    }
}
 
final class SavedLesson extends LessonInfo {
    // Cannot override showId().
}
 
public class HelloWorld {
    public static void main(String[] args) {
        SavedLesson record = new SavedLesson();
        record.showId();
    }
}

Output:

Record: L-24

SavedLesson inherits showId() and can call it. It cannot replace showId() with its own version because the method is final. Another class cannot extend SavedLesson because that class is final too. We can still declare a method with the same name and different parameters; that is overloading, not overriding.

2. static

static makes a field or method belong to the class instead of each object. Objects of that class share the same static field. A static method has no current object called this, and we can call it using the class name. A static nested class can be used without first creating an object of the outer class.

Share a Field Across Objects

class LessonVisit {
    private static int visits;
 
    LessonVisit() {
        visits++;
    }
 
    static int getVisits() {
        return visits;
    }
}
 
public class HelloWorld {
    public static void main(String[] args) {
        LessonVisit first = new LessonVisit();
        LessonVisit second = new LessonVisit();
        System.out.println(LessonVisit.getVisits());
    }
}

Output:

2

visits belongs to LessonVisit, so the two objects share the same counter. Each constructor call increases it by one. After creating two objects, the shared value is 2. This program uses one thread; static alone does not protect the counter if several threads update it together.

Call a static Method Using the Class Name

class ReadingTime {
    static int getMinutes(int pages) {
        return pages * 3;
    }
}
 
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println(ReadingTime.getMinutes(5));
    }
}

Output:

15

We call getMinutes() using the class name, so we do not need to create a ReadingTime object. Inside a static method, there is no current object called this. To use an instance field, the method needs a reference to an object. Static methods can have overloads with different parameters. If a child declares a static method with the same signature, it hides the parent method instead of overriding it.

static Initialization and a Nested Class

class LessonTools {
    static final String LABEL;
 
    static {
        LABEL = "Lesson preview";
    }
 
    static class Label {
        String getText() {
            return LABEL;
        }
    }
}
 
public class HelloWorld {
    public static void main(String[] args) {
        LessonTools.Label badge = new LessonTools.Label();
        System.out.println(badge.getText());
    }
}

Output:

Lesson preview

Here LABEL stores the message. The static block assigns that message, and getText() returns it. main creates a Label object and prints the returned message.

The static block gives LABEL its value when Java initializes LessonTools. Label is a class inside LessonTools, and it is marked static. We can therefore create a Label without first creating a LessonTools object. A class declared outside all other classes is a top-level class, and it cannot be static.

3. abstract

abstract on a class means we cannot create an object of that class directly. abstract on a method means its declaration has no method body. Before we can create an object of a child class, that child must have working versions of all its inherited abstract methods. These versions can come from the child itself or another parent class. An abstract class can also have methods with bodies, even if it has no abstract methods.

Suppose every report needs a summary, but different reports show different text. The parent class LessonReport declares summary() without a body. It uses abstract to say that a child must provide this method before we can create an object of that child. WeeklyReport provides the method body. The parent also has a normal print() method that the child can reuse. An abstract class can have fields and constructors as well as methods.

abstract class LessonReport {
    abstract String summary();
 
    void print() {
        System.out.println(summary());
    }
}
 
class WeeklyReport extends LessonReport {
    @Override
    String summary() {
        return "Weekly report: 4 lessons completed";
    }
}
 
public class HelloWorld {
    public static void main(String[] args) {
        LessonReport report = new WeeklyReport();
        report.print();
        // new LessonReport(); // Does not compile: LessonReport is abstract.
    }
}

Output:

Weekly report: 4 lessons completed

The variable report has type LessonReport, but the object is a WeeklyReport. This is allowed. We cannot create a LessonReport object directly because the class is abstract. A class can also be abstract even if all its methods already have bodies.

4. synchronized

synchronized makes a method take a lock before running its code. An instance method uses the lock on the object used to call it. A static synchronized method uses the lock on the class's Class object. Another thread that needs the same lock must wait. Java releases the lock when the method finishes, even if it ends by throwing an exception.

A thread is a separate flow of work in a program. Two threads may try to increase the same counter at the same time. synchronized uses a lock so that only one thread at a time runs a synchronized method on the same counter object. This object lock is also called a monitor. Each counter object has its own lock. A static synchronized method uses the lock on the class's Class object instead.

class CompletionCounter {
    private int count;
 
    synchronized void increment() {
        count++;
    }
 
    synchronized int getCount() {
        return count;
    }
}
 
public class HelloWorld {
    public static void main(String[] args) throws Exception {
        CompletionCounter counter = new CompletionCounter();
        // This is the work that each thread will run.
        Runnable work = () -> {
            for (int i = 0; i < 3; i++) {
                counter.increment();
            }
        };
        // Give the same work to two threads.
        Thread first = new Thread(work);
        Thread second = new Thread(work);
        first.start();
        second.start();
        // Wait for both threads before reading the result.
        first.join();
        second.join();
        System.out.println(counter.getCount());
    }
}

Output:

6

Runnable describes the work a thread should perform. The arrow in () -> introduces that work. new Thread(work) creates a thread, and start() asks it to begin running.

Both threads use the same counter object. Each thread increases the count 3 times, so the final count is 6. join() makes main wait until a thread finishes. All reads and updates that need this protection should use the same lock. Adding synchronized to one method does not protect direct field access elsewhere.

5. volatile

volatile helps threads see changes to a shared field. When one thread changes a volatile field and another thread later reads it, Java also makes the earlier changes from the writing thread visible to the reader. volatile does not lock the field. An update such as count++ still has several steps, so volatile alone does not stop two threads from interfering with that update.

One thread may change a flag while another thread keeps checking it. volatile makes changes to this field visible to other threads that read it. It also provides rules about the order in which related changes become visible. It does not lock the object or protect a group of steps as one operation. The example uses a flag named ready.

class PreviewSignal {
    private volatile boolean ready;
 
    void markReady() {
        ready = true;
    }
 
    boolean isReady() {
        return ready;
    }
}
 
public class HelloWorld {
    public static void main(String[] args) throws Exception {
        PreviewSignal signal = new PreviewSignal();
        // The worker changes ready to true.
        Thread worker = new Thread(() -> signal.markReady());
        worker.start();
        // Keep checking until the flag becomes true.
        while (!signal.isReady()) {
            Thread.yield();
        }
        worker.join();
        System.out.println("Preview is ready");
    }
}

Output:

Preview is ready

The arrow in () -> signal.markReady() means that the worker thread will call markReady(). The worker sets ready to true. main checks the flag until it sees true, then prints the message. Thread.yield() gives the scheduler a hint that another thread may run; it is not what makes the flag visible. The volatile field provides that visibility. For real applications, a tool such as CountDownLatch can let a thread wait without repeatedly checking a flag. volatile does not make count++ a single protected operation. If a volatile field refers to an object, it also does not make all fields inside that object volatile.

6. transient

transient tells Java to skip an instance field when saving an object's fields through default serialization. The field still exists and can be used while the program runs. Custom serialization code can still save its value if the programmer chooses to do so.

Serialization means converting an object's data into bytes so it can be saved or sent. Deserialization means reading those bytes to rebuild the object. A class implements Serializable to use Java's built-in object serialization. A transient field is skipped when Java saves the object's fields in the normal way. When the object is read back, that field gets its default value unless custom code sets it. For a String field, the default value is null.

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
 
class LessonData implements Serializable {
    private static final long serialVersionUID = 1L;
    String title = "Reading compiler errors";
    transient String temporaryNote = "preview-24";
}
 
public class HelloWorld {
    public static void main(String[] args) throws Exception {
        LessonData draft = new LessonData();
        // Step 1: Create a place to hold the saved bytes.
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        // Step 2: Save the object into those bytes.
        try (ObjectOutputStream output = new ObjectOutputStream(buffer)) {
            output.writeObject(draft);
        }
        // Step 3: Read the bytes to create the object again.
        try (ObjectInputStream input = new ObjectInputStream(
                new ByteArrayInputStream(buffer.toByteArray()))) {
            LessonData restored = (LessonData) input.readObject();
            System.out.println(restored.title);
            System.out.println(restored.temporaryNote);
        }
    }
}

Output:

Reading compiler errors
null

ByteArrayOutputStream holds the bytes in memory. ObjectOutputStream writes the object into those bytes. ObjectInputStream reads them back, and readObject() returns the restored object. The cast (LessonData) tells Java to treat the returned object as LessonData. The try blocks close the streams when their work finishes.

The first output line shows that title was saved and read back. The second line is null because temporaryNote was skipped. A programmer can still write custom serialization code that saves a transient field, so transient does not guarantee that data stays secret. Static fields belong to the class and are not saved as normal object fields either. This example reads back only the bytes it created itself.

7. native

native marks a method whose body is provided outside Java source, often in a language such as C or C++. The Java method declaration ends with a semicolon instead of a body. Before the method can run, Java must find the matching native code.

Sometimes a Java program needs a method written in another language, such as C or C++. We can declare that method with native. Java lists the method name, parameters, and return type, but the working code is provided by a native library. A library is a separate collection of code that the program can load. The native method ends with a semicolon because its body is not written in this Java class.

class ScreenInfo {
    native int getScreenWidth();
}
 
public class HelloWorld {
    public static void main(String[] args) {
        ScreenInfo bridge = new ScreenInfo();
        System.out.println("Bridge created");
        // bridge.getScreenWidth(); // Requires a linked native implementation.
    }
}

Output:

Bridge created

The program creates a ScreenInfo object but does not call getScreenWidth(). It therefore prints the message without needing a native library. If we uncomment the call without providing the native implementation, Java normally throws UnsatisfiedLinkError. To make the call work, we must provide and link the matching native code. This example only shows how to declare the method.

8. strictfp

strictfp was used to apply strict calculation rules to float and double expressions. Java 17 and later use those rules automatically. We can still write strictfp, but it no longer changes how these calculations are evaluated.

Floating-point values are numbers stored using types such as float and double. Before Java 17, strictfp could request consistent floating-point evaluation rules for a class or method. Java 17 and later always use those strict rules. We therefore do not need strictfp for this purpose in new Java code. A modern compiler may show a warning when we use it.

strictfp class PageMath {
    double getHalf(double pages) {
        return pages / 2.0;
    }
}
 
public class HelloWorld {
    public static void main(String[] args) {
        PageMath fraction = new PageMath();
        System.out.println(fraction.getHalf(7.0));
    }
}

Output:

3.5

This example shows the older strictfp syntax. On Java 17 or later, we can remove strictfp and keep the same evaluation rules. float and double still store numbers in binary. Some decimal fractions cannot be stored exactly, even when strict evaluation is used.

Modern Class Modifiers: sealed and non-sealed

sealed lets a class choose which classes may directly extend it. A sealed interface can also choose which types may directly extend or implement it. An allowed direct child can use non-sealed to let other types extend or implement that child normally. Both modifiers became standard Java features in Java 17.

Java 17 also provides sealed classes. A sealed class chooses which classes may directly extend it. Each allowed direct child must be final, sealed, or non-sealed. final stops further inheritance, sealed limits it again, and non-sealed allows other classes to extend that child normally. These modifiers give more control over inheritance. They are additional options beyond the common modifiers discussed above.

Combining Modifiers

In public static final int LESSON_LIMIT = 4, public allows access where the class is accessible. static makes the field belong to the class. final stops another value from being assigned to it. Java allows this combination, but some modifier combinations are not allowed.

CombinationValid?Reason
public static final fieldYesPublic class-level value that cannot be reassigned
public abstract classYesAccessible abstract base class
abstract final classNoabstract and final cannot be used on the same class
abstract final methodNoAn abstract method needs implementation; final prevents overriding
private abstract class methodNoA private method is not inherited, so it cannot be abstract
static abstract class methodNoA static method cannot be overridden to provide an abstract method body
final volatile fieldNoJava forbids this field-modifier combination
native abstract methodNoA method cannot be both native and abstract

The abstract-method rules in this table apply to methods declared in classes. Interfaces have some different rules. For example, not writing an access keyword on an ordinary interface method does not make it package-private. Learn interface rules separately instead of applying every class rule to an interface.

Access vs Non-Access Modifiers

Access modifiers control where a declaration can be used. Non-access modifiers define rules such as sharing state, preventing reassignment, or requiring a method implementation.

Easy way to remember

  • Use static for class-level members, not as a shortcut for thread safety.
  • Use final to prevent reassignment, overriding, or inheritance. It does not freeze an object's contents.
  • Use abstract for a parent class that should not be created directly or for a method that needs a body in a child.
  • Use synchronized when threads need the same lock. Use volatile when threads need to see changes to a field. They serve different purposes.
  • Use transient for an instance field that should be skipped by default Java object serialization.
  • Check version-specific behavior before using older keywords such as strictfp.

See you in next page....🙂

← Java Access ModifiersJava Interface →
← Java Access ModifiersJava Interface →