foreach-ui logo
codeLanguages
account_treeDSA

Quick Actions

quizlock Random Quiz
trending_uplock Progress
  • 1
  • 2
  • 3
  • 4
  • quiz
Java
  • Learn when to use different access modifiers
  • Understand encapsulation patterns and anti-patterns
  • Master best practices for class API design

Access Modifiers Best Practices in Java

Access modifiers give you the tools for encapsulation. Using them well means knowing when to be restrictive and when to open up access.

Choosing the Right Access Modifier

Start with Private

Always default to private for fields and methods unless you have a good reason to be more permissive.

public class BankAccount {
    private String accountNumber;  // Private by default
    private double balance;
    
    // Only expose what's necessary
    public double getBalance() { return balance; }
    public void deposit(double amount) { /* implementation */ }
}

When to Use Protected

Use protected sparingly, mainly for inheritance hierarchies where subclasses need access to parent implementation details.

public class Shape {
    protected double area;  // Subclasses need access for calculations
    
    protected void calculateArea() {
        // Common area calculation logic
    }
}

public class Circle extends Shape {
    private double radius;
    
    @Override
    protected void calculateArea() {
        this.area = Math.PI * radius * radius;
    }
}

When to Use Public

Make methods and constructors public when they are part of the class's public API.

public class Calculator {
    // Public API methods
    public int add(int a, int b) { return a + b; }
    public int subtract(int a, int b) { return a - b; }
    
    // Constructor should be public for instantiation
    public Calculator() { }
}

Encapsulation Patterns

Data Transfer Objects (DTOs)

For simple data containers, consider public final fields (immutable DTOs):

public class UserDTO {
    public final String username;
    public final String email;
    
    public UserDTO(String username, String email) {
        this.username = username;
        this.email = email;
    }
}

Builder Pattern

For objects with many optional parameters:

public class Computer {
    private final String cpu;
    private final int ram;
    private final int storage;
    
    private Computer(Builder builder) {
        this.cpu = builder.cpu;
        this.ram = builder.ram;
        this.storage = builder.storage;
    }
    
    public static class Builder {
        // All fields have default access within the same package
        String cpu = "Intel i5";
        int ram = 8;
        int storage = 256;
        
        public Builder cpu(String cpu) { this.cpu = cpu; return this; }
        public Builder ram(int ram) { this.ram = ram; return this; }
        public Builder storage(int storage) { this.storage = storage; return this; }
        
        public Computer build() { return new Computer(this); }
    }
}

Common Anti-Patterns to Avoid

1. Making Everything Public

// Anti-pattern: Breaks encapsulation
public class BadExample {
    public String internalData;
    public void internalLogic() { }
}

2. Getters and Setters for Everything

// Sometimes unnecessary if no additional logic is needed
public class SimpleDTO {
    private String name;
    
    public String getName() { return name; }  // Unnecessary if just storing data
    public void setName(String name) { this.name = name; }
}

3. Inconsistent Access Levels

public class Inconsistent {
    public String publicField;    // Should be private
    private void privateMethod() {
        // Implementation
    }
    public void publicMethod() {
        privateMethod();  // Inconsistent - public method calls private
    }
}

Package Organization

Package-Private Classes

Use package-private classes for internal implementation:

// In com.example.util
class HelperUtils {  // Package-private
    static boolean isValidEmail(String email) {
        return email.contains("@");
    }
}

// In com.example.service
public class UserService {
    public void createUser(String email) {
        if (HelperUtils.isValidEmail(email)) {
            // Create user
        }
    }
}

Inheritance Considerations

Protected vs Package-Private

Choose based on whether subclasses in other packages need access:

public class Parent {
    // Use protected if external subclasses need access
    protected void customizableMethod() { }
    
    // Use package-private if only same-package subclasses need access
    void packageMethod() { }
}

Testing and Access Modifiers

Package-Private for Testing

Make methods package-private when they need to be tested but shouldn't be public:

public class Calculator {
    // Package-private for unit testing
    double calculateTax(double amount) {
        return amount * 0.08;
    }
    
    public double getTotalWithTax(double amount) {
        return amount + calculateTax(amount);
    }
}

Refactoring Access Modifiers

Start Restrictive, Loosen as Needed

  1. Begin with private
  2. Change to package-private if same-package classes need access
  3. Change to protected if subclasses need access
  4. Change to public only for true public API

IDE Support

Modern IDEs help identify when access modifiers can be more restrictive:

  • IntelliJ IDEA: "Make method/package-private" inspections
  • Eclipse: Access modifier warnings

Good access modifier use means balancing encapsulation with practicality. Start with private and only loosen when necessary. Think about the class's responsibilities, inheritance needs, and how you'll test it. Well-chosen access modifiers make your code more maintainable and secure.

© 2026 forEach. All rights reserved.

Privacy Policy•Terms of Service