Default Access Modifier in Java

Introduction

Imagine you're in a family where some things are shared freely among family members (same package), but aren't automatically available to neighbors or strangers (other packages). That's exactly how the default access modifier works in Java—it's like having "family-only" access for your classes, methods, and variables!

The default access modifier (also called package-private) is what you get when you don't specify any access modifier. It's Java's way of saying "this is for internal use within this package only."


What is the Default Access Modifier?

The default access modifier is the access level you get when you don't explicitly declare any access modifier (public, private, or protected). It provides package-level access, meaning the member is accessible only within its own package.

Key Characteristics:

  • Package-private: Accessible only within the same package
  • No keyword: No explicit modifier needed
  • Default behavior: What you get when you don't specify access
  • Encapsulation: Helps with package-level encapsulation
  • Simple: Easy to use for internal package components

Access Modifier Comparison

Access ModifierSame ClassSame PackageSubclassOther Packages
private✅ Yes❌ No❌ No❌ No
default✅ Yes✅ Yes❌ No❌ No
protected✅ Yes✅ Yes✅ Yes❌ No
public✅ Yes✅ Yes✅ Yes✅ Yes

Code Explanation with Examples

Example 1: Basic Default Access Modifier

// File 1: DefaultAccessDemo.java
package com.mypackage;
public class DefaultAccessDemo {
public static void main(String[] args) {
System.out.println("=== DEFAULT ACCESS MODIFIER DEMO ===");
// Create instances of classes in the same package
Person person = new Person("John", 25);
Employee employee = new Employee("Alice", "IT");
// ✅ Can access default methods and variables in same package
person.displayInfo();       // Default method - accessible
employee.showDetails();     // Default method - accessible
// ✅ Can access default variables in same package
System.out.println("Person name: " + person.name);     // Default variable
System.out.println("Employee dept: " + employee.department); // Default variable
// ✅ Can call default methods
person.setAge(26);          // Default method
System.out.println("Updated age: " + person.getAge()); // Public method
}
}
// Default access class (no public modifier)
class Person {
// Default access instance variables
String name;    // Default access
int age;        // Default access
// Default access constructor
Person(String name, int age) {
this.name = name;
this.age = age;
}
// Default access method
void displayInfo() {
System.out.println("Person: " + name + ", Age: " + age);
}
// Default access method
void setAge(int age) {
this.age = age;
}
// Public method - accessible from anywhere
public int getAge() {
return age;
}
}
// Another class in same package
class Employee {
String name;        // Default access
String department;  // Default access
// Default access constructor
Employee(String name, String department) {
this.name = name;
this.department = department;
}
// Default access method
void showDetails() {
System.out.println("Employee: " + name + ", Department: " + department);
}
}

Output:

=== DEFAULT ACCESS MODIFIER DEMO ===
Person: John, Age: 25
Employee: Alice, Department: IT
Person name: John
Employee dept: IT
Updated age: 26

Example 2: Package Structure Demonstration

Let's create a package structure to demonstrate default access:

Directory Structure:

src/
├── com/
│   ├── mypackage/
│   │   ├── PackageA.java
│   │   ├── ClassA.java
│   │   └── ClassB.java
│   └── otherpackage/
│       ├── PackageB.java
│       └── ClassC.java
// File 1: com/mypackage/PackageA.java
package com.mypackage;
public class PackageA {
public static void main(String[] args) {
System.out.println("=== SAME PACKAGE ACCESS ===");
// ✅ All these are accessible in same package
ClassA objA = new ClassA();
ClassB objB = new ClassB();
// Access default members from ClassA
objA.defaultMethod();           // Default method
System.out.println("ClassA default variable: " + objA.defaultVariable);
// Access default members from ClassB
objB.displayInfo();             // Default method
System.out.println("ClassB data: " + objB.data);
// Access protected members (also accessible in same package)
objA.protectedMethod();
System.out.println("ClassA protected variable: " + objA.protectedVariable);
}
}
// File 2: com/mypackage/ClassA.java
package com.mypackage;
class ClassA {  // Default access class
// Different access modifiers for demonstration
int defaultVariable = 10;           // Default access
protected int protectedVariable = 20; // Protected access
public int publicVariable = 30;     // Public access
private int privateVariable = 40;   // Private access
// Default access constructor
ClassA() {
System.out.println("ClassA default constructor called");
}
// Default access method
void defaultMethod() {
System.out.println("ClassA default method - package access only");
System.out.println("Can access private variable: " + privateVariable);
}
// Protected method
protected void protectedMethod() {
System.out.println("ClassA protected method");
}
// Public method
public void publicMethod() {
System.out.println("ClassA public method");
}
// Private method
private void privateMethod() {
System.out.println("ClassA private method");
}
// Public getter for private variable
public int getPrivateVariable() {
return privateVariable;
}
}
// File 3: com/mypackage/ClassB.java
package com.mypackage;
class ClassB {  // Default access class
String data = "Default access data";
// Default access method
void displayInfo() {
System.out.println("ClassB: " + data);
// Can access other default class in same package
ClassA classA = new ClassA();
classA.defaultMethod();     // ✅ Accessible - same package
classA.protectedMethod();   // ✅ Accessible - same package
classA.publicMethod();      // ✅ Accessible - always
}
}
// File 4: com/otherpackage/PackageB.java
package com.otherpackage;
import com.mypackage.ClassA;  // Can import public class
// import com.mypackage.ClassB;  // ❌ Cannot import - ClassB has default access
public class PackageB {
public static void main(String[] args) {
System.out.println("=== DIFFERENT PACKAGE ACCESS ===");
// ✅ Can access public class from different package
ClassA objA = new ClassA();
// ✅ Public members are accessible
objA.publicMethod();
System.out.println("Public variable: " + objA.publicVariable);
// ❌ DEFAULT members are NOT accessible from different package
// objA.defaultMethod();        // Compilation error
// System.out.println(objA.defaultVariable); // Compilation error
// ❌ PROTECTED members are NOT accessible from different package (unless subclass)
// objA.protectedMethod();      // Compilation error
// System.out.println(objA.protectedVariable); // Compilation error
// ❌ PRIVATE members are never accessible
// objA.privateMethod();        // Compilation error
// System.out.println(objA.privateVariable); // Compilation error
// ✅ But can use public getter for private variable
System.out.println("Private variable via getter: " + objA.getPrivateVariable());
// ❌ Cannot access default class from different package
// ClassB objB = new ClassB();  // Compilation error - ClassB not visible
}
}
// File 5: com/otherpackage/ClassC.java
package com.otherpackage;
// Cannot extend ClassB because it has default access
// class ClassC extends ClassB { }  // ❌ Compilation error
// Can extend ClassA because it's public
class ClassC extends com.mypackage.ClassA {
public void testAccess() {
System.out.println("=== SUBCLASS IN DIFFERENT PACKAGE ===");
// ✅ Public members accessible
publicMethod();
System.out.println("Public variable: " + publicVariable);
// ✅ Protected members accessible to subclasses
protectedMethod();
System.out.println("Protected variable: " + protectedVariable);
// ❌ DEFAULT members NOT accessible from different package
// defaultMethod();             // Compilation error
// System.out.println(defaultVariable); // Compilation error
// ❌ PRIVATE members never accessible
// privateMethod();             // Compilation error
// System.out.println(privateVariable); // Compilation error
}
}

Output for PackageA:

=== SAME PACKAGE ACCESS ===
ClassA default constructor called
ClassA default constructor called
ClassA default method - package access only
Can access private variable: 40
ClassA default variable: 10
ClassB: Default access data
ClassA default constructor called
ClassA default method - package access only
Can access private variable: 40
ClassA protected method
ClassA protected variable: 20

Output for PackageB:

=== DIFFERENT PACKAGE ACCESS ===
ClassA public method
Public variable: 30
Private variable via getter: 40

Example 3: Real-World Package Organization

// File 1: com/company/bank/Account.java
package com.company.bank;
// Public class - accessible from outside package
public class Account {
private String accountNumber;
private double balance;
String accountType;  // Default access - internal package use only
protected String currency;  // Protected - for subclasses
// Default access constructor - for internal package use
Account(String accountNumber, String accountType) {
this.accountNumber = accountNumber;
this.accountType = accountType;
this.balance = 0.0;
this.currency = "USD";
}
// Public methods - external interface
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
System.out.println("Deposited: $" + amount);
}
}
public void withdraw(double amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
System.out.println("Withdrawn: $" + amount);
}
}
public double getBalance() {
return balance;
}
public String getAccountNumber() {
return accountNumber;
}
// Default access method - internal package use
void internalAccountUpdate(String newType) {
this.accountType = newType;
System.out.println("Account type updated to: " + newType);
}
// Protected method - for subclasses
protected void setCurrency(String currency) {
this.currency = currency;
}
}
// File 2: com/company/bank/BankManager.java
package com.company.bank;
// Default access class - internal to bank package
class BankManager {
private String managerId;
// Default access constructor
BankManager(String managerId) {
this.managerId = managerId;
}
// Default access method - can access all Account's default members
void reviewAccount(Account account) {
System.out.println("Manager " + managerId + " reviewing account:");
System.out.println("Account #: " + account.getAccountNumber());
System.out.println("Account Type: " + account.accountType);  // ✅ Default access
System.out.println("Balance: $" + account.getBalance());
// Can call default method
account.internalAccountUpdate("PREMIUM");  // ✅ Default access
}
// Internal package utility method
void generateInternalReport(Account[] accounts) {
System.out.println("\n=== INTERNAL BANK REPORT ===");
for (Account acc : accounts) {
System.out.println(acc.getAccountNumber() + " - " + 
acc.accountType + " - $" + acc.getBalance());
}
}
}
// File 3: com/company/bank/Bank.java
package com.company.bank;
import java.util.ArrayList;
import java.util.List;
// Public class - main bank interface
public class Bank {
private List<Account> accounts;
private BankManager manager;  // Default access class - usable in same package
public Bank() {
this.accounts = new ArrayList<>();
this.manager = new BankManager("MGR001");  // ✅ Can create - same package
}
public void openAccount(String accountNumber, String accountType) {
// Using default constructor of Account - ✅ accessible in same package
Account newAccount = new Account(accountNumber, accountType);
accounts.add(newAccount);
System.out.println("New account opened: " + accountNumber);
}
public void managerReview(String accountNumber) {
Account account = findAccount(accountNumber);
if (account != null) {
manager.reviewAccount(account);  // ✅ Default method accessible
}
}
public void generateReport() {
Account[] accountArray = accounts.toArray(new Account[0]);
manager.generateInternalReport(accountArray);  // ✅ Default method accessible
}
private Account findAccount(String accountNumber) {
return accounts.stream()
.filter(acc -> acc.getAccountNumber().equals(accountNumber))
.findFirst()
.orElse(null);
}
}
// File 4: com/company/bank/SavingsAccount.java
package com.company.bank;
// Public subclass in same package
public class SavingsAccount extends Account {
private double interestRate;
public SavingsAccount(String accountNumber, double interestRate) {
super(accountNumber, "SAVINGS");  // ✅ Default constructor accessible
this.interestRate = interestRate;
this.currency = "USD";  // ✅ Protected variable accessible
}
public void applyInterest() {
double interest = getBalance() * interestRate / 100;
deposit(interest);
System.out.println("Interest applied: $" + interest);
}
// Can access protected method from parent
public void changeCurrency(String newCurrency) {
setCurrency(newCurrency);  // ✅ Protected method accessible
System.out.println("Currency changed to: " + newCurrency);
}
}
// File 5: com/company/client/ClientApp.java
package com.company.client;
import com.company.bank.Bank;
import com.company.bank.Account;
import com.company.bank.SavingsAccount;
// import com.company.bank.BankManager;  // ❌ Cannot import - default access
public class ClientApp {
public static void main(String[] args) {
System.out.println("=== CLIENT APPLICATION ===");
Bank bank = new Bank();
// Open accounts
bank.openAccount("ACC001", "CHECKING");
bank.openAccount("ACC002", "SAVINGS");
// Client can only use public interface
Account account = new SavingsAccount("ACC003", 2.5);
account.deposit(1000);     // ✅ Public method
account.withdraw(200);     // ✅ Public method
System.out.println("Balance: $" + account.getBalance());
// ❌ Client CANNOT access default members
// account.internalAccountUpdate("PREMIUM");  // Compilation error
// System.out.println(account.accountType);   // Compilation error
// ❌ Client CANNOT access protected members directly
// account.setCurrency("EUR");               // Compilation error
// System.out.println(account.currency);     // Compilation error
// ❌ Client CANNOT access default classes
// BankManager manager = new BankManager("MGR001");  // Compilation error
System.out.println("Client can only use public methods - proper encapsulation!");
}
}

Example 4: Utility Package with Default Access

// File 1: com/utils/Logger.java
package com.utils;
// Public utility class
public class Logger {
private static LogLevel currentLevel = LogLevel.INFO;
// Default access enum - internal to package
enum LogLevel {
DEBUG, INFO, WARN, ERROR
}
// Default access constructor - prevent instantiation from outside package
Logger() {
throw new AssertionError("Cannot instantiate utility class");
}
// Public static methods
public static void info(String message) {
log(LogLevel.INFO, message);
}
public static void error(String message) {
log(LogLevel.ERROR, message);
}
public static void setLevel(LogLevel level) {
currentLevel = level;
}
// Default access method - internal package use
static void debug(String message) {
if (currentLevel.ordinal() <= LogLevel.DEBUG.ordinal()) {
System.out.println("[DEBUG] " + message);
}
}
// Private method
private static void log(LogLevel level, String message) {
if (level.ordinal() >= currentLevel.ordinal()) {
System.out.println("[" + level + "] " + message);
}
}
// Default access utility method
static String formatMessage(String source, String message) {
return source + ": " + message;
}
}
// File 2: com/utils/ConfigLoader.java
package com.utils;
// Default access class - internal utility
class ConfigLoader {
private static final String CONFIG_FILE = "app.config";
// Default access constructor
ConfigLoader() {}
// Default access method
static void loadConfiguration() {
System.out.println("Loading configuration from: " + CONFIG_FILE);
Logger.debug("Configuration loading started");  // ✅ Can access default method
}
// Default access method
static String getDatabaseUrl() {
Logger.debug("Fetching database URL");  // ✅ Can access default method
return "jdbc:mysql://localhost:3306/mydb";
}
}
// File 3: com/utils/FileUtils.java
package com.utils;
// Public utility class
public class FileUtils {
// Default access constant - internal use
static final int MAX_FILE_SIZE = 1024 * 1024; // 1MB
// Private constructor
private FileUtils() {
throw new AssertionError("Cannot instantiate utility class");
}
// Public method
public static boolean isValidFile(String filename) {
return filename != null && !filename.trim().isEmpty();
}
// Default access method - internal package use
static boolean isFileTooLarge(long fileSize) {
return fileSize > MAX_FILE_SIZE;
}
// Default access method
static void logFileOperation(String operation) {
Logger.debug("File operation: " + operation);  // ✅ Can access default method
}
}
// File 4: com/utils/UtilsDemo.java
package com.utils;
public class UtilsDemo {
public static void main(String[] args) {
System.out.println("=== UTILITIES PACKAGE DEMO ===");
// ✅ Can use public methods
Logger.info("Application started");
Logger.error("Something went wrong");
// ✅ Can use default methods in same package
Logger.debug("Debug message");  // ✅ Default method - same package
ConfigLoader.loadConfiguration();  // ✅ Default class and method
// ✅ Can access default members
String dbUrl = ConfigLoader.getDatabaseUrl();
System.out.println("Database URL: " + dbUrl);
// ✅ Can use default methods from FileUtils
FileUtils.logFileOperation("read");  // ✅ Default method - same package
// Test file validation
System.out.println("Valid file: " + FileUtils.isValidFile("document.pdf"));
// ❌ Cannot access private members
// Logger.currentLevel = Logger.LogLevel.DEBUG;  // Compilation error
}
}
// File 5: com/application/MainApp.java
package com.application;
import com.utils.Logger;
import com.utils.FileUtils;
// import com.utils.ConfigLoader;  // ❌ Cannot import - default access
// import com.utils.Logger.LogLevel;  // ❌ Cannot import - default access
public class MainApp {
public static void main(String[] args) {
System.out.println("=== MAIN APPLICATION ===");
// ✅ Can use public utility methods
Logger.info("Main application started");
Logger.error("An error occurred");
// ✅ Can use public FileUtils methods
System.out.println("Valid file: " + FileUtils.isValidFile("test.txt"));
// ❌ CANNOT access default members from different package
// Logger.debug("Debug message");  // Compilation error
// Logger.setLevel(Logger.LogLevel.DEBUG);  // Compilation error
// ❌ CANNOT access default classes
// ConfigLoader.loadConfiguration();  // Compilation error
// ❌ CANNOT access default methods
// FileUtils.logFileOperation("write");  // Compilation error
// System.out.println(FileUtils.MAX_FILE_SIZE);  // Compilation error
System.out.println("External packages can only use public interface");
}
}

Example 5: Common Pitfalls and Best Practices

package com.pitfalls;
public class CommonPitfalls {
public static void main(String[] args) {
System.out.println("=== COMMON PITFALLS ===");
// ❌ PITFALL 1: Accidentally using default when you meant public
InternalUtility utility = new InternalUtility();
utility.internalMethod();  // Works here but not from other packages
// ❌ PITFALL 2: Assuming default access is more restrictive than it is
SamePackageClass samePkg = new SamePackageClass();
samePkg.defaultMethod();  // Accessible - might be unexpected
System.out.println("\n=== BEST PRACTICES ===");
// ✅ GOOD: Using default for package-private implementation
PackagePrivateImpl impl = new PackagePrivateImpl();
impl.doSomething();
// ✅ GOOD: Public interface with default implementation
PublicAPI api = new PublicAPI();
api.publicMethod();
}
}
// ❌ PITFALL: Class meant to be public but accidentally default
class InternalUtility {  // Should probably be public if used externally
void internalMethod() {
System.out.println("This might be accidentally package-private");
}
}
// ✅ GOOD: Intentional package-private class
class PackagePrivateImpl {
// Default access - clearly intended for internal use
void doSomething() {
System.out.println("Internal implementation detail");
}
}
// ✅ GOOD: Public API with internal helpers
public class PublicAPI {
// Public method - external interface
public void publicMethod() {
internalHelper();  // Use internal implementation
System.out.println("Public method completed");
}
// Default method - internal implementation
void internalHelper() {
System.out.println("Internal helper method");
}
}
// Class to demonstrate same-package access
class SamePackageClass {
String defaultVariable = "Accessible in same package";
void defaultMethod() {
System.out.println("Default method - accessible in package");
}
}
// Another package to demonstrate access restrictions
package com.anotherpackage;
import com.pitfalls.PublicAPI;
// import com.pitfalls.InternalUtility;  // ❌ Cannot import - default access
// import com.pitfalls.PackagePrivateImpl;  // ❌ Cannot import - default access
public class ExternalClient {
public static void main(String[] args) {
System.out.println("=== EXTERNAL CLIENT VIEW ===");
// ✅ Can access public class and methods
PublicAPI api = new PublicAPI();
api.publicMethod();
// ❌ Cannot access default classes
// InternalUtility utility = new InternalUtility();  // Compilation error
// PackagePrivateImpl impl = new PackagePrivateImpl();  // Compilation error
// ❌ Cannot access default methods even from public class
// api.internalHelper();  // Compilation error
System.out.println("External clients see only the public interface");
}
}

When to Use Default Access Modifier

✅ Appropriate Uses:

  • Package-internal classes that shouldn't be used outside
  • Helper methods within a package
  • Implementation details that should stay within the package
  • Package-level utilities and helpers
  • Classes/methods that are part of package's internal architecture

❌ Avoid When:

  • The class/method is part of public API
  • You want the member to be accessible from other packages
  • The member should be accessible to subclasses in other packages

Best Practices

  1. Be intentional about using default access - don't just forget to specify public
  2. Use for true package-private implementation details
  3. Document why something is package-private if it's not obvious
  4. Use package structure to logically group related functionality
  5. Consider making utility class constructors private to prevent instantiation
  6. Use default for testing package-internal methods if needed

Common Patterns

// Pattern 1: Package-private implementation with public interface
public interface Service {
void publicMethod();
}
class ServiceImpl implements Service {  // Default access - internal
public void publicMethod() {
internalHelper();
}
void internalHelper() {  // Default access - package private
// implementation
}
}
// Pattern 2: Utility class with package-private helpers
public final class StringUtils {
private StringUtils() {}
public static boolean isBlank(String s) {
return s == null || s.trim().isEmpty();
}
static String normalizeInternal(String s) {  // Default access
return s == null ? "" : s.trim().toLowerCase();
}
}
// Pattern 3: Factory with package-private products
public class ProductFactory {
public static Product createProduct() {
return new ConcreteProduct();  // ConcreteProduct has default access
}
}
class ConcreteProduct implements Product {  // Default access
// implementation
}

Conclusion

The default access modifier is Java's package-level encapsulation mechanism:

  • Package-private: Accessible only within the same package
  • Simple: No keyword needed - the default behavior
  • Encapsulation: Hides implementation details within packages
  • Organization: Encourages logical package structure

Key Takeaways:

  • Default access = package-private access
  • Use for internal implementation within a package
  • Be intentional - don't accidentally make things package-private
  • Package structure matters for organizing related functionality
  • Combines well with public interfaces for clean architecture

The default access modifier is a powerful tool for creating well-organized, encapsulated Java packages where internal implementation details are hidden from external users, leading to more maintainable and robust code!

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper