Back to Blog
Java

Understanding the java public Keyword

Learn what the java public keyword does, how it controls visibility, and when to use it for clean, maintainable code.

access modifiersencapsulationJava syntaxvisibilityAPI design
Illustration of the java public keyword showing a door open to external code while private members remain locked inside

The public keyword in Java is an access modifier that controls where a class, method, or field can be accessed from. It is the least restrictive access level in the language: a public member is visible to any code that can see its containing class. This makes it the default choice for the entry points of an API, but using it everywhere without thought can weaken encapsulation and make future changes harder.

What the java public Keyword Actually Controls

When you declare a class, method, or field as public, you are explicitly stating that it is part of the class's external contract. Any other class in the same module, in a different package, or even in a different application that has the class on its classpath can access it. This is different from the default (package-private) access, which restricts visibility to the same package, and from protected, which adds subclass access.

The Java compiler enforces these rules at compile time. If you try to access a non-public member from outside its allowed scope, the code will not compile. This is a deliberate design choice: the access modifier system helps you define boundaries that prevent accidental coupling between unrelated parts of your codebase.

Declaring a Public Class, Method, or Field

A public class is the most common top-level declaration. A file can have at most one public class, and its name must match the file name. This is a language rule, not just a convention.

// File: Calculator.java public class Calculator { public int add(int a, int b) { return a + b; } }

Here, Calculator is public, so it can be instantiated from any other class that can see the package. The add method is also public, meaning any code with a reference to a Calculator instance can call it.

Fields can also be public, but doing so is generally discouraged because it exposes internal state directly. A public field can be read and modified from anywhere, bypassing any validation or logic that a setter method might provide.

public class User { public String name; // public field - usually a poor choice }

A better approach is to make the field private and expose it through public getter and setter methods, which gives you control over how the value is set and read.

The Access Modifier Hierarchy in Java

Java provides four access levels, from most restrictive to least restrictive:

ModifierSame classSame packageSubclass (different package)Any class
privateYesNoNoNo
(default)YesYesNoNo
protectedYesYesYesNo
publicYesYesYesYes

This table shows the visibility rules. The default access (no modifier) is often called package-private. It is more restrictive than protected because it does not allow subclass access from a different package. public is the only modifier that allows access from any class in any package.

Understanding this hierarchy is essential for designing a clean API. If you expose too much as public, you lock yourself into maintaining those signatures. If you expose too little, you may prevent useful extension or integration.

Public API Design and Encapsulation

The primary reason to use public is to define what other developers can rely on. A well-designed public API should be minimal and stable. Every public member is a promise: you are saying that this class, method, or field will continue to exist and behave in a compatible way, at least within the current major version.

Consider a BankAccount class. The balance should not be a public field, because that would allow any code to set it to an arbitrary value. Instead, you expose a public method to deposit or withdraw, and a public method to check the balance. The internal state stays private.

public class BankAccount { private double balance; public void deposit(double amount) { if (amount > 0) { balance += amount; } } public double getBalance() { return balance; } }

Here, the public methods are the API. They validate input and protect the invariant that the balance cannot go negative (if that is a requirement). The private field is an implementation detail. If you later change the internal representation from a double to a BigDecimal, you can do so without breaking any code that uses the class, because the public interface remains the same.

Common Mistakes and Misunderstandings

One frequent mistake is marking every method public because it is easier than thinking about access levels. This leads to a bloated API where internal helper methods become part of the public contract. Once other code starts calling those helpers, you cannot easily rename or remove them without breaking dependent code.

Another misunderstanding is that public is required for testability. It is true that test code often needs to access methods that are not part of the public API. However, tests can be placed in the same package as the class under test, allowing them to use package-private access. This keeps internal methods hidden from external consumers while still making them testable.

A third issue is confusing public with static. These are orthogonal concepts. public controls visibility, while static controls whether a member belongs to the class or to an instance. A public static method is accessible without an instance, but it is still subject to the same visibility rules as any other public member.

Choosing Between public, protected, and Package-Private

The decision of which access modifier to use depends on the intended scope of the member. Here are concrete criteria:

  • Use public for the entry points of your API: classes that other packages will instantiate, methods that other code will call, and constants that are part of the contract.
  • Use protected when you want to allow subclasses to override or use a method, but you do not want arbitrary code in other packages to call it. This is common in template method patterns.
  • Use package-private (no modifier) for internal implementation details that need to be shared among classes in the same package but not exposed beyond it. This is useful for helper classes and internal utility methods.
  • Use private for anything that is only used within the same class. This is the default for fields and helper methods.

A common pattern is to make the constructor public for a class that is meant to be instantiated directly, but to make the constructor private for a singleton or a class with a static factory method. The factory method is public, while the constructor is hidden.

public class DatabaseConnection { private DatabaseConnection() { // private constructor prevents direct instantiation } public static DatabaseConnection connect(String url) { // perform connection setup return new DatabaseConnection(); } }

Here, the public static factory method is the only way to obtain an instance. The private constructor ensures that no other code can bypass the connection setup logic.

Impact on Maintainability and Production Behavior

From a maintainability perspective, every public member increases the surface area of your code. It is a commitment to backward compatibility. When you change a public method's signature or behavior, you risk breaking downstream code that you may not even know about. This is especially important in libraries and frameworks, where the public API is consumed by many external projects.

In a production environment, the visibility of members also affects how the JIT compiler optimizes code. While modern JVMs can often inline and optimize across package boundaries, a smaller public surface allows for more aggressive optimizations because the compiler can assume that a private method is not overridden. This is a subtle performance consideration, not a primary reason to restrict access, but it reinforces the value of keeping implementation details hidden.

Another operational concern is serialization and reflection. Many frameworks, such as ORMs and JSON serializers, rely on reflection to access fields and methods. If you expose fields as public, they are trivially accessible, but this often bypasses validation. Using private fields with public getters and setters gives you a chance to validate or transform data during serialization and deserialization, which is a common production requirement.

Finally, consider the evolution of your code. A public API is hard to change. If you start with a public field and later want to add validation, you must either keep the field and add a setter (which does not enforce validation) or break the API by making the field private. Starting with private fields and public methods gives you the flexibility to change the internal representation without breaking callers. This is why encapsulation is a core principle of object-oriented design, and the public keyword is the tool that defines the boundary between what is exposed and what is hidden.

java public Keyword Explained | RYUSLOG DEV