Encapsulation & Packages

Java · Lesson 16 of 32 7 min read Updated

In Classes, Objects & static, anyone could write account.balance = 1000000; and the program would happily accept it. Real code must not allow that. Encapsulation means hiding an object's data and letting the outside world change it only through methods that check the rules.

The second half of this lesson is about packages, Java's way of organising hundreds of classes into folders, and the import statements that let classes in different packages use each other. Together, access modifiers and packages decide who can see what in a Java project.

Private fields, getters and setters

Mark fields private so only code inside the same class can touch them. Then provide public methods for what outsiders are allowed to do. A method that reads a field is a getter (by convention getOwner(), or isFrozen() for a boolean); a method that changes a field is a setter (setOwner(...)).

Java
public class Bank {
    public static void main(String[] args) {
        SavingsAccount acc = new SavingsAccount("Ananya Rao", 2000);
        acc.deposit(1500);
        System.out.println(acc.getOwner() + " has Rs " + acc.getBalance());

        boolean ok = acc.withdraw(5000);
        System.out.println("Withdraw 5000 ok? " + ok);

        acc.setOwner("  ");                    // rejected by the setter
        System.out.println("Owner is still: " + acc.getOwner());
        System.out.println("Frozen? " + acc.isFrozen());
    }
}

class SavingsAccount {
    private final String accountNumber;
    private String owner;
    private double balance;
    private boolean frozen;

    private static int nextNumber = 1001;

    SavingsAccount(String owner, double openingBalance) {
        this.accountNumber = "SB" + nextNumber++;
        setOwner(owner);
        this.balance = openingBalance;
    }

    public String getOwner() { return owner; }
    public double getBalance() { return balance; }
    public boolean isFrozen() { return frozen; }
    public String getAccountNumber() { return accountNumber; }

    public void setOwner(String owner) {
        if (owner == null || owner.isBlank()) {
            System.out.println("Ignored: owner name cannot be blank");
            return;
        }
        this.owner = owner.strip();
    }

    public void deposit(double amount) {
        if (amount <= 0 || frozen) {
            return;
        }
        balance += amount;
    }

    public boolean withdraw(double amount) {
        if (amount <= 0 || amount > balance || frozen) {
            return false;
        }
        balance -= amount;
        return true;
    }
}
Output
Ananya Rao has Rs 3500.0
Withdraw 5000 ok? false
Ignored: owner name cannot be blank
Owner is still: Ananya Rao
Frozen? false

Look at what is missing: there is no setBalance. The balance can change only through deposit and withdraw, which enforce the rules. The account number is final with only a getter, so it can never change after creation. Outside code that tries acc.balance = 1000000; now fails to compile with error: balance has private access in SavingsAccount.

Encapsulation is not about writing a getter and setter for every field automatically. Ask for each field: should outsiders read it? Should they change it, and under what rules? Expose only what is needed. Objects whose fields can never change after construction are called immutable; they are simpler and safer, and Java's records make them easy, as you will see in Records & Enums.

The four access levels

Java has four access levels for classes, fields, methods and constructors:

ModifierSame classSame packageSubclass in another packageEverywhere
privateyesnonono
(none), called package-privateyesyesnono
protectedyesyesyesno
publicyesyesyesyes

A good default: fields private, methods public only if other classes need them, helpers private. Leaving out the modifier gives package-private access, which is useful for classes that work closely together inside one package. protected matters with inheritance, covered in the next lesson.

Packages: organising classes

A package is a named group of related classes, stored in a matching folder. Package names are lowercase and usually start with a reversed domain name, so they never clash with other people's code: in.college.library.model. The first line of each file says which package it belongs to.

Here is a small library project with two packages:

Text
library/
  src/
    in/college/library/model/Book.java
    in/college/library/app/LibraryApp.java
Java
package in.college.library.model;

public class Book {
    private final String title;
    private final String author;
    private boolean issued;

    public Book(String title, String author) {
        this.title = title;
        this.author = author;
    }

    public String getTitle() { return title; }
    public boolean isIssued() { return issued; }

    public void issue() { issued = true; }

    String internalCode() {            // no modifier: package-private
        return title.substring(0, 3).toUpperCase();
    }

    @Override
    public String toString() {
        return title + " by " + author + (issued ? " (issued)" : "");
    }
}

The class and its constructor are public so that code in other packages can use them. Now the main class, in a different package, imports Book:

Java
package in.college.library.app;

import in.college.library.model.Book;
import java.time.LocalDate;
import static java.lang.Math.max;

public class LibraryApp {
    public static void main(String[] args) {
        Book book = new Book("Malgudi Days", "R. K. Narayan");
        book.issue();
        System.out.println(book);
        System.out.println("Due back: " + LocalDate.of(2026, 11, 14));
        System.out.println("Fine for 3 late days: Rs " + max(0, 3 * 2));
    }
}

Compile both files into an out folder with -d, then run the main class by its fully qualified name (package plus class name), telling Java where the compiled classes are with -cp (class path):

Terminal
javac -d out src/in/college/library/model/Book.java src/in/college/library/app/LibraryApp.java
java -cp out in.college.library.app.LibraryApp
Output
Malgudi Days by R. K. Narayan (issued)
Due back: 2026-11-14
Fine for 3 late days: Rs 6

javac -d out creates the folders out/in/college/library/... automatically. In real projects, build tools such as Maven and Gradle run these commands for you; see Maven & Gradle.

If LibraryApp tries to call book.internalCode(), compilation fails with error: internalCode() is not public in Book; cannot be accessed from outside package. The package boundary is enforced by the compiler.

Kinds of import

  • Single-type import: import java.time.LocalDate; imports one class. This is the most common and clearest style.
  • On-demand import: import java.util.*; imports every class in one package (not its sub-packages).
  • Static import: import static java.lang.Math.max; lets you call a static method without the class name. Use it sparingly; Math.max is usually clearer.
  • Classes in java.lang (String, Math, System, Integer) and in your own package never need an import.

When two packages contain classes with the same simple name, such as java.util.Date and java.sql.Date, import one and write the other's full name: java.sql.Date. Java 25 adds a third style, import module java.base;, which imports a whole module at once; see Modules & Module Imports.

Common mistakes

  • Public fields. They let any code break your rules. Make fields private and expose methods.
  • Setters for everything. A setter that accepts any value is no better than a public field. Validate, or leave the setter out.
  • Package line does not match the folder. A file in src/in/college/library/model must start with package in.college.library.model;.
  • Running a packaged class by its simple name. Use the full name: java -cp out in.college.library.app.LibraryApp.
  • Forgetting public on a class used from another package. A class without public is visible only inside its own package.

Key points

  • Encapsulation hides fields with private and exposes behaviour through methods that enforce rules.
  • Getters are named getX() (or isX() for booleans); provide setters only where changes are allowed.
  • Access levels from narrowest to widest: private, package-private, protected, public.
  • A package groups related classes; its name matches its folder path, and package is the first line of the file.
  • import brings in classes from other packages; java.lang is always imported.
  • Compile packaged code with javac -d out ... and run it with java -cp out full.class.Name.

Exercise

Write a class Thermostat for a smart AC with a private int field temperature that must stay between 16 and 30. Provide a getter, a setter that ignores out-of-range values and returns false when it does, and methods warmer() and cooler() that change the temperature by one degree but never leave the range. Start at 24, then call cooler() 10 times and setTemperature(35), printing the temperature after each step that matters.

Show solution
Java
public class SmartAc {
    public static void main(String[] args) {
        Thermostat ac = new Thermostat();
        System.out.println("Start: " + ac.getTemperature());
        for (int i = 0; i < 10; i++) {
            ac.cooler();
        }
        System.out.println("After 10 x cooler: " + ac.getTemperature());
        boolean changed = ac.setTemperature(35);
        System.out.println("Set to 35 accepted? " + changed + ", now " + ac.getTemperature());
        ac.warmer();
        System.out.println("After warmer: " + ac.getTemperature());
    }
}

class Thermostat {
    private static final int MIN = 16;
    private static final int MAX = 30;
    private int temperature = 24;

    public int getTemperature() {
        return temperature;
    }

    public boolean setTemperature(int value) {
        if (value < MIN || value > MAX) {
            return false;
        }
        temperature = value;
        return true;
    }

    public void warmer() {
        setTemperature(temperature + 1);
    }

    public void cooler() {
        setTemperature(temperature - 1);
    }
}
Output
Start: 24
After 10 x cooler: 16
Set to 35 accepted? false, now 16
After warmer: 17

warmer and cooler reuse the setter, so the range rule is written only once. After eight steps the temperature reaches 16, and the last two calls to cooler() are ignored.

Quick Quiz

1. Which access level lets only code in the same class use a field?

2. What is the main purpose of encapsulation?

3. Which classes can you use without any import?

4. How do you run the class LibraryApp in package in.college.library.app, compiled into out?

Learn Java with a mentor

Free lessons get you started. Our Java, C & C++ Programming training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call