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 Modifier | Same Class | Same Package | Subclass | Other 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
- Be intentional about using default access - don't just forget to specify public
- Use for true package-private implementation details
- Document why something is package-private if it's not obvious
- Use package structure to logically group related functionality
- Consider making utility class constructors private to prevent instantiation
- 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!