Java Default Access Modifier

In the last lesson you learned about Java access modifiers. Now let’s zoom in on the one that confuses people most: the level you get when you write nothing at all. That is the default access modifier, and it does not mean β€œopen to everyone” like many people assume. Let’s clear that up.

πŸ€” What happens when you write no modifier?

Here is the trap. You write a class, you forget to add public, and the code still compiles. So you think it must be wide open. It is not.

class Box {
int size; // ❌ no modifier β€” this is NOT public
}
  • size has no access keyword in front of it.
  • Many beginners read that as β€œpublic by default.” It is not public.
  • It got a quieter, in-between level called default access, also known as package-private.
  • The pain shows up later. Split your code into separate folders and half of it stops compiling.

So let’s define it properly, then prove it with real code.

🧩 What is default (package-private) access?

When you write a field, a method, or a class with no access modifier, it gets default access:

  • The member is visible to every class in the same package.
  • It is invisible to everything outside that package.
  • A package is just a folder of related classes, declared with package shop; at the top of a file.
  • People also call this package-private. Inside the package it acts almost public. Outside, almost private.
  • There is no keyword for it. You get it by writing nothing.

Now let’s see both halves in action.

βœ… Same package: it works

Let’s put two classes in the same shop package and have one use the other’s default members. First, the class with default members:

package shop;
class Product {
String name; // default field
double price; // default field
void show() { // default method
System.out.println(name + " costs " + price);
}
}

Now a second class, in the same shop package, that reaches into Product directly:

package shop;
public class Store {
public static void main(String[] args) {
Product p = new Product();
p.name = "Pen"; // βœ… allowed, same package
p.price = 12.5; // βœ… allowed, same package
p.show(); // βœ… allowed, same package
}
}

Store and Product share the shop package, so Store touches Product’s default fields and calls its default method as if they were public.

Output

Pen costs 12.5

Inside one package, default access feels open. Now let’s cross the package line and watch it change.

❌ Different package: it fails to compile

Keep the same Product class in shop, but write the second class in a different package called web, importing it as usual:

package web;
import shop.Product; // import a class from another package
public class WebApp {
public static void main(String[] args) {
Product p = new Product(); // ❌ Product itself is not public
p.name = "Pen"; // ❌ name has default access
p.price = 12.5; // ❌ price has default access
p.show(); // ❌ show() has default access
}
}

This does not even compile. Product and all its members are default, so they are invisible from the web package. The compiler stops you before the program ever starts.

Compile error

WebApp.java: error: Product is not public in shop; cannot be accessed from outside package
Product p = new Product();
^
WebApp.java: error: name has default access in Product
p.name = "Pen";
^
WebApp.java: error: price has default access in Product
p.price = 12.5;
^
WebApp.java: error: show() has default access in Product
p.show();
^

Read that first line carefully. β€œProduct is not public in shop”. The class itself is hidden too, not just its fields. The key lesson: default access stops at the package boundary. Same package, yes. Other package, no.

🚫 Not even subclasses in another package

Here is the part that surprises people who already know protected. With protected, a subclass in another package can reach the inherited member. With default access, it cannot. A subclass gets no special pass across the package line. Let’s prove it with a subclass in the web package.

package web;
import shop.Product;
public class WebProduct extends Product { // ❌ cannot even extend it
void demo() {
System.out.println(name); // ❌ name is not visible here
}
}

Even though WebProduct is a Product, it lives in a different package, so the default members stay hidden. Inheritance does not change the rule. The package boundary still wins.

Compile error

WebProduct.java: error: cannot inherit from a non-public class Product in a different package
public class WebProduct extends Product {
^
WebProduct.java: error: name has default access in Product
System.out.println(name);
^

Default vs protected

This is the cleanest way to remember the difference. protected means same package plus subclasses anywhere. Default means same package only, with no exception for subclasses in other packages. So protected is strictly more open than default.

πŸ“¦ Default works on classes too

The no-modifier rule applies to classes, not just fields and methods. A class written without public is a default (package-private) class, usable only inside its own package. Here is a default class and a public class side by side.

package shop;
class Helper { // ❌ default class β€” only the shop package can use it
void run() {
System.out.println("Helping inside the shop package.");
}
}
public class Catalog { // βœ… public class β€” any package can use it
void load() {
Helper h = new Helper(); // βœ… fine, same package
h.run();
}
}
  • Inside shop, Catalog creates and uses Helper with no trouble.
  • Code in any other package cannot even name Helper.
  • This lets you write helper classes that support one package without leaking to the whole program.

One public class per file

A Java source file can hold at most one public class, and its name must match the file name. But you can put several default classes in the same file alongside it. That is a common way to keep small package-internal helpers next to the class that uses them.

πŸ’‘ When should you use default access?

Sometimes a class, field, or method is meant to be shared within a feature but hidden from the rest of the program. Default access is the tool for that. Here a payments package has a public PaymentService and a default TaxCalculator that only the payment code should touch.

package payments;
class TaxCalculator { // default β€” internal to payments
double addTax(double amount) {
return amount * 1.1; // add 10% tax
}
}
public class PaymentService { // public β€” the outside entry point
public double charge(double amount) {
TaxCalculator tax = new TaxCalculator(); // βœ… same package
double total = tax.addTax(amount);
System.out.println("Charging " + total);
return total;
}
}

The rest of the program calls PaymentService.charge(...) and never sees TaxCalculator. You could rewrite, rename, or delete it, and no outside code would break, because none was ever allowed to depend on it. Reach for default access when:

  • A helper class is only useful inside one package and should not be part of the public surface.
  • A field or method is shared between a few classes in the same package but is not meant for the outside world.
  • You want to keep your public surface small, so callers only see what they actually need.

⚠️ Common Mistakes

Default access trips people up in a few predictable ways.

Thinking no modifier means public. This is the big one. The code compiles, so it feels open, but it is only open inside the package.

// ❌ wrong assumption: "no keyword means everyone can use it"
class Order {
int id; // this is package-private, NOT public
}
// βœ… if you truly want it usable everywhere, say so
public class Order {
public int id;
}

Expecting cross-package access to work. You move a class into a new package, import it, and expect it to behave like before. If it had no modifier, the import does not help.

// ❌ wrong: importing does not unlock default members
import shop.Product;
Product p = new Product(); // fails if Product is default
// βœ… right: make the class and the members you need public
// (in shop) public class Product { public String name; ... }

Assuming a subclass can reach inherited default members from another package. It cannot. Only protected or public cross that line for subclasses.

// ❌ wrong: a subclass in another package still cannot see default fields
package web;
class WebProduct extends shop.Product {
void demo() { System.out.println(name); } // name is hidden
}
// βœ… right: use protected in the parent if subclasses elsewhere need it
// (in shop) protected String name;

βœ… Best Practices

Here are the habits that keep default access working for you, not against you.

  • Be deliberate, not accidental. If you leave off a modifier, mean it. Do not let default access happen just because you forgot to type public.
  • Use it to hide package internals. Keep helper classes and shared-but-internal methods package-private so your public surface stays small.
  • Make a member public the moment another package needs it. If you find yourself wanting to import a default member, that is your signal to promote it.
  • Prefer protected over default when subclasses elsewhere need access. Default blocks outside subclasses, so pick protected if inheritance across packages is the goal.
  • Keep related classes in the same package. Default access only helps when the classes that need each other actually share a package.

🧩 What You’ve Learned

Nice work. Let’s recap the default access modifier.

  • βœ… Writing no modifier gives a member default (package-private) access.
  • βœ… Default members are visible to every class in the same package.
  • βœ… They are invisible from other packages, even after an import.
  • βœ… A subclass in another package still cannot reach inherited default members.
  • βœ… Classes can be default too, which hides them outside their package.
  • βœ… Use default access for package-internal helpers you do not want to expose.

Check Your Knowledge

Test what you learned. Pick an answer for each question, then click Check.

  1. 1

    What access level does a member with no modifier get?

    Why: Writing no access modifier gives the member default access, also called package-private.

  2. 2

    Who can use a default member?

    Why: Default members are visible to every class in the same package and nothing outside it.

  3. 3

    Can a subclass in a different package access an inherited default field?

    Why: Default access stops at the package boundary, with no exception for subclasses in other packages. Use protected for that.

  4. 4

    What does a class with no modifier mean?

    Why: A class without public is package-private, so only classes in the same package can use it.

πŸš€ What’s Next?

Default access keeps things inside a package. The opposite end of the scale opens a member to the entire program, every class in every package. That is the public modifier, and it is what you use for the methods and classes you want everyone to call. Let’s learn it.

Java Public Access Modifier

Share & Connect