Java Access Modifiers Explained: public, private, protected, default

Java Access Modifiers Explained: public, private, protected, default


Access control is one of the first skills Java learners should master. It determines which code can see your classes, fields, constructors, and methods. In this beginner-friendly guide, you’ll learn the four Java access levels—public, private, protected, and default (package‑private)—with simple explanations, code examples, and tips you can apply in school or college projects. If you’re just starting out, bookmark this page and also explore more Java basics on the CodDesire Java hub.

What are access modifiers in Java?

Java access modifiers public private protected default visual across packages, subclasses, and other classes
How public, private, protected, and default scope flows across classes, packages, and subclasses

Access modifiers in Java are keywords (or the lack of one) that control the visibility of types and members. They help you hide internal details and expose only what other code needs. This supports encapsulation, makes APIs cleaner, reduces bugs, and simplifies maintenance.

  • public: visible from everywhere (subject to module boundaries in modern Java).
  • private: visible only within the same top-level class (including its nested classes).
  • protected: visible within the same package and to subclasses (with rules for cross‑package use).
  • default (package‑private): visible only within the same package; this is when you use no access keyword.

Focus keyphrase: Java access modifiers public private protected default

Java access levels at a glance

Java access modifiers public private protected default shown with Student class encapsulation, subclass access, package scope
Encapsulation in action: private fields, public API, protected subclass reach, default package-only
Modifier Inside the same top-level class Other classes in the same package Subclass in a different package (from subclass code) Other packages (non-subclass)
public Yes Yes Yes Yes
protected Yes Yes Yes (from subclass code; see note) No
default (package‑private) Yes Yes No No
private Yes No No No

Note on protected: Across packages, a protected member is accessible only from code in the subclass and typically through the subclass instance (not via an arbitrary base instance). See examples below.

Visibility reach of Java access modifiers
Qualitative comparison of how far each modifier can be accessed.
public
Everywhere

protected
Package + subclasses

default (package‑private)
Same package only

private
Declaring class only

Syntax and simple examples (beginner-friendly)

1) public — expose for everyone

Use public when other packages or applications should be able to use the class or member directly.

Code
class="cd-keyword cd-access">public class Greeter {
    class="cd-keyword cd-access">public String message = "Hello";

    class="cd-keyword cd-access">public Greeter() { }

    class="cd-keyword cd-access">public void greet() {
        System.out.println(message + ", Java learners!");
    }
}

class Demo {
    class="cd-keyword cd-access">public static void main(String[] args) {
        Greeter g = new Greeter();
        g.greet(); // Accessible from anywhere
    }
}
Output
Hello, Java learners!

2) private — hide the internals

Use private to keep fields and helper methods hidden inside the same top-level class. This is the core of encapsulation.

Code
class="cd-keyword cd-access">public class Counter {
    class="cd-keyword cd-access">private int value = 0;               // Not visible outside this class

    class="cd-keyword cd-access">private void increment() {           // Helper is private
        value++;
    }

    class="cd-keyword cd-access">public void addOne() {               // Public method controls access
        increment();
    }

    class="cd-keyword cd-access">public int getValue() {
        return value;
    }

    class="cd-keyword cd-access">public static void main(String[] args) {
        Counter c = new Counter();
        c.addOne();
        c.addOne();
        System.out.println(c.getValue());

        // c.value = 100;     // Compile error: value has private access
        // c.increment();     // Compile error: increment() has private access
    }
}
Output
2

3) default (package‑private) — share inside the package

There is no keyword for the default access level. If you omit public, private, and protected, the member or top-level class is package‑private: visible only to classes in the same package.

Example with a package showing a public API that uses a package‑private helper:

Code
class="cd-package">package com.tools;

// Public API class (visible to other packages)
class="cd-keyword cd-access">public class ToolApi {
    class="cd-keyword cd-access">public void use() {
        InternalTool helper = new InternalTool(); // OK: same package
        helper.doIt();
    }
}

// No modifier => package-private (default)
class InternalTool {
    void doIt() {
        System.out.println("Working inside com.tools only");
    }
}

Code in another package can use ToolApi but cannot access InternalTool directly:

Code
class="cd-package">package app;

class="cd-package">import com.tools.ToolApi;

class="cd-keyword cd-access">public class App {
    class="cd-keyword cd-access">public static void main(String[] args) {
        ToolApi api = new ToolApi();
        api.use(); // OK

        // com.tools.InternalTool it = new com.tools.InternalTool();
        // ^ Compile error: InternalTool is not public in com.tools; cannot be accessed from outside package
    }
}

4) protected — package access plus subclass access

protected members are visible within the same package and also to subclasses. When accessing from a subclass in a different package, you typically call it on this (or the subclass instance), not on an unrelated base instance.

Code
class="cd-package">package com.lib;

class="cd-keyword cd-access">public class Base {
    class="cd-keyword cd-access">protected void hook() {
        System.out.println("Base hook");
    }
}
Code
class="cd-package">package com.app;

class="cd-package">import com.lib.Base;

class="cd-keyword cd-access">public class Sub extends Base {
    class="cd-keyword cd-access">public void test() {
        hook();       // OK: subclass can access protected across packages
        this.hook();  // OK

        Base b = new Base();
        // b.hook();  // Compile error: not accessible via a Base reference in another package
    }

    class="cd-keyword cd-access">public static void main(String[] args) {
        new Sub().test();
    }
}
Output
Base hook

Access modifiers for classes, fields, methods, and constructors

  • Top-level types: A top-level class, interface, enum, or record can be only public or package‑private (default). They cannot be private or protected.
  • Nested/inner types: Inside a class, nested classes and interfaces may use all four modifiers.
  • Fields and methods: Can use any of the four modifiers.
  • Constructors: Can use any of the four modifiers; a private constructor is common for factory patterns or singletons.

Constructor access example

Code
class="cd-keyword cd-access">public class Person {
    class="cd-keyword cd-access">private final String name;

    // Private constructor: restrict direct instantiation
    class="cd-keyword cd-access">private Person(String name) {
        this.name = name;
    }

    // Public factory method
    class="cd-keyword cd-access">public static Person of(String name) {
        return new Person(name);
    }

    class="cd-keyword cd-access">public String getName() {
        return name;
    }
}

class UsePerson {
    class="cd-keyword cd-access">public static void main(String[] args) {
        Person p = Person.of("Sam");  // OK
        System.out.println(p.getName());

        // new Person(___CDPHSTR1___); // Compile error: Person(String) has private access
    }
}
Output
Sam

public vs private vs protected in Java — when to use which?

  • Start with the most restrictive modifier that still lets your code work. Widen only if needed.
  • private: Default choice for fields and helper methods. Encapsulate state and behavior.
  • package‑private (default): Share implementation details among classes in the same package but hide them from other packages. Great for internal utilities and testing collaborators.
  • protected: Use sparingly as an inheritance hook. It makes sense if you expect legitimate subclasses (possibly in other packages) to extend behavior.
  • public: Only for your intended API surface—types and members you commit to support. Keep it small and stable.
How to choose an access modifier (quick flow)
Start with private for fields and helper methods to protect state.
Need sharing only within the same package? Use default (package‑private).
Expect real subclasses to extend behavior? Use protected for intended hooks.
Need access from other packages/apps? Make it public and treat it as part of your API.

Tip: Widen visibility only when a concrete, valid use case requires it.

Beginner guide to Java access control with code

This small example combines multiple modifiers to show a clean design:

Code
class="cd-keyword cd-access">public class BankAccount {
    class="cd-keyword cd-access">private int balance;                 // hidden state

    class="cd-keyword cd-access">public BankAccount(int opening) {    // public API constructor
        if (opening < 0) {
            throw new IllegalArgumentException("Opening balance must be non-negative");
        }
        this.balance = opening;
    }

    class="cd-keyword cd-access">protected void audit(String msg) {   // inheritance hook for subclasses
        System.out.println("[AUDIT] " + msg);
    }

    void depositInternal(int amount) {   // package-private for same-package helpers/tests
        balance += amount;
    }

    class="cd-keyword cd-access">public void deposit(int amount) {    // public API
        if (amount <= 0) throw new IllegalArgumentException("Amount must be positive");
        depositInternal(amount);
        audit("Deposited " + amount);
    }

    class="cd-keyword cd-access">public int getBalance() {
        return balance;
    }
}

class AccountDemo {
    class="cd-keyword cd-access">public static void main(String[] args) {
        BankAccount a = new BankAccount(100);
        a.deposit(50);
        System.out.println(a.getBalance());

        // a.depositInternal(10); // Compile error: not visible if called from another package
    }
}
Output
[AUDIT] Deposited 50
150

Common mistakes (and how to avoid them)

  • Thinking there is a default keyword. There isn’t. Package‑private access is the absence of an access modifier.
  • Marking a top-level class as private or protected. This is not allowed. Use public or no modifier (package‑private) only.
  • Assuming protected works like “public to subclasses everywhere.” Across packages, access must happen from subclass code and usually via the subclass instance, not an arbitrary base instance.
  • Confusing package‑private with “visible to subpackages.” Subpackages are different packages; package‑private does not flow into subpackages.
  • Exposing too much with public. Start private, then open visibility only when necessary for valid use cases.

Java modules: a quick note for modern Java

Since Java 9, the module system adds an extra boundary above packages. A public class in a non-exported package isn’t visible to other modules. For simple school projects you may not use modules, but it’s useful to know:

Code
// module-info.java in module com.example.api
class="cd-package">module com.example.api {
    class="cd-package">exports com.tools;  // Only packages you export are accessible to other modules
}

In strongly encapsulated JDKs (e.g., JDK 17+), deep reflection can’t automatically bypass access checks; frameworks often require explicit opens/exports options at runtime.

Java access levels with examples: quick comparison

Here’s a compact, practical reminder focused on Java class and method access modifiers:

  • public class or method: visible everywhere (subject to modules).
  • private field or method: usable only within the same top-level class.
  • protected method: visible within the package and to subclasses (careful with cross‑package access rules).
  • default access modifier in Java: no keyword; visibility restricted to the same package.

Practice for students: try this small exercise

  1. Create a package school with a public class Student, a package‑private class GradeBook, and make Student use GradeBook internally.
  2. In another package app, try to instantiate GradeBook directly (observe the compile error) and still use Student publicly.
  3. Add a protected method in Student and create a subclass in app to call it properly.

FAQ: quick answers for beginners

What are the four access modifiers in Java?

The four access levels are public, private, protected, and default (package‑private). The last one has no keyword—you get it by omitting any access modifier.

What is the difference between public, private, protected, and default in Java?

public is visible everywhere; private is only inside the same top-level class; protected is visible to the package and subclasses (with cross‑package rules); default is visible only within the same package.

When should I use public vs private in Java?

Prefer private for fields and helpers to enforce encapsulation. Make members public only if they form your intended API. For new code, start private and widen only as needed.

What does default (package‑private) mean in Java?

It means the member or type is visible to any class in the same package and hidden from other packages. There is no default keyword; it’s simply the absence of an access modifier.

Can variables and constructors use access modifiers in Java?

Yes. Fields (variables) and constructors can be public, private, protected, or package‑private. For example, a private constructor is often used with factory methods.

Key takeaways

  • Use private by default for safety and clarity.
  • Use package‑private to share within a package but hide from others.
  • Use protected sparingly for intended subclass extension points.
  • Use public to expose your stable, minimal API surface.
  • Remember: top-level types can be only public or package‑private.

Sources / Further reading

Want more beginner-friendly lessons? Visit the CodDesire Java section for step-by-step tutorials and simple code examples.

Subscribe
Notify of
guest
0 Comments
Oldest
Newest Most Voted