Encapsulation
A complete guide to encapsulation in Java: data hiding, access modifiers, getters and setters, the JavaBeans convention, packages, import statements, and an overview of the Java standard library packages.
Data Hiding Concept
Encapsulation is one of the four pillars of object-oriented programming. At its core, it means bundling the data (fields) and the code that operates on that data (methods) inside a single class, and then restricting direct access to the data from the outside. This restriction is what is commonly called "data hiding."
The motivation is straightforward. When a field is publicly accessible, any piece of code anywhere in your program can change it to any value it likes, including invalid ones. A class representing a bank account with a public balance field could have that balance set to a negative number by careless calling code. By making the field private and providing a withdraw() method, the class can enforce its own rules: the balance may never go below zero.
What encapsulation gives you
Properly encapsulated classes are easier to use, maintain, and evolve.
- Validation:Setters and methods can reject invalid input before it corrupts internal state.
- Flexibility:You can change the internal implementation of a class (how it stores data, what algorithm it uses) without breaking the code that uses it, as long as the public interface stays the same.
- Reduced coupling:Code that cannot reach inside a class can only interact through its methods. This makes the two sides of that boundary independent and easier to test separately.
- Read-only or write-only fields:You can expose a getter with no setter (making the field effectively read-only from the outside) or a setter with no getter, depending on your design needs.
Access Modifiers
Java enforces encapsulation through four access modifiers that can be applied to classes, fields, constructors, and methods. Each one defines a boundary within which the member is visible. The compiler checks these boundaries at every access point and produces a compile error if access is not permitted.
The four access levels at a glance
From most restrictive to least restrictive.
- private:Visible only within the class where it is declared. Nothing outside the class, not even a subclass, can see it. Use this as your default for fields.
- default (package-private):No keyword is written. Visible to all classes in the same package, but invisible to classes in other packages, even subclasses located in another package.
- protected:Visible within the same package AND to subclasses regardless of which package they are in. Used most often for members you intend subclasses to inherit or override.
- public:Visible everywhere: same class, same package, subclasses, and completely unrelated classes in any package. Use this for the intentional public API of your class.
Access Modifiers in One Class
JavaShowing all four levels on different members and which accesses are legal.
private
Private is the tightest access level and the right default for almost every field. By declaring fields private you force all outside code to go through your methods, giving you complete control over how and when the field is read or modified.
private Access
JavaA Temperature class where the raw Celsius value is private and only accessible through controlled methods.
protected
Protected is the appropriate level for members that are part of a class's internal API intended for subclasses. The idea is that subclasses should be able to inherit and build on those members, but unrelated external classes should not have access to them.
protected Access
JavaA base Shape class with a protected field that its subclass can use directly.
public and default (package-private)
Public members form the outer contract of your class: the things you are deliberately exposing to the world. Default access (no keyword) is useful for helper classes and members that are internal to a package but should not be part of the package's public API.
public and default Access
JavaPublic members are the class API; default members stay within the package boundary.
Getters and Setters
Once fields are private, outside code needs a way to read and modify them. The standard Java approach is to provide getter methods (for reading) and setter methods (for writing). This is not just boilerplate: it is the boundary where you can add validation, computed values, lazy loading, or logging, without any caller needing to change their code.
Naming conventions for getters and setters
Java has well-established conventions that tools and frameworks depend on.
- Getter for non-boolean field:getName(), getAge(), getPrice(), prefix the capitalised field name with get.
- Getter for boolean field:isActive(), isAvailable(), isEmpty(), prefix the capitalised field name with is (not get). Some legacy code uses get even for booleans, but is is the standard.
- Setter:setName(String name), setAge(int age), prefix the capitalised field name with set and take a single parameter of the field type.
- Return type:Getters return the field type. Setters return void, unless you are using a builder pattern where setters return this for chaining.
Getters and Setters
JavaA Student class with private fields, validated setters, and computed getters.
JavaBeans Convention
A JavaBean is a Java class that follows a specific set of conventions so that frameworks, tools, and libraries can interact with it in a predictable, automated way. The conventions are simple in themselves, but their value comes from the fact that IDEs, serialisation frameworks like Jackson, persistence frameworks like Hibernate, and dependency injection containers like Spring all rely on them to discover and manipulate your class's properties without you writing extra configuration.
The three JavaBeans requirements
A class satisfying all three qualifies as a JavaBean.
- Public no-argument constructor:The class must be instantiable without providing any initial values. Frameworks use this to create instances before populating fields.
- Private fields with public getters and setters:All properties are accessed through the get/set/is naming convention described in the previous section. The framework discovers properties by scanning for these methods.
- Implements Serializable (optional but common):Implementing java.io.Serializable allows instances to be converted to a byte stream for storage or network transfer, which is required by some older frameworks.
JavaBeans Convention
JavaA well-formed JavaBean that any compliant framework can read, write, and serialise.
Packages and Access Control
A package is a namespace that groups related classes and interfaces. Think of it as a folder for your classes, but one that the compiler and JVM are aware of and enforce. Packages serve two purposes: they organise code logically, and they are the unit of visibility for the default (package-private) access level. Any two classes in the same package can see each other's package-private members; classes in different packages cannot.
Package naming conventions
Java has a universally followed convention that prevents name collisions between third-party libraries.
- Package names are all lowercase. Mixed case is never used in package names.
- The convention is to reverse your domain name and append the project and module: com.company.project.module. For example, com.google.gson or org.springframework.core.
- The package name must match the directory structure on disk. A class in com.example.util must live in a directory path that ends with com/example/util/.
- The first statement in any Java source file (after any comments) is the package declaration: package com.example.util;
Package Declaration and Access
JavaShowing package declarations and how they affect member visibility between classes.
The import Statement
When you need to use a class from a different package, you can either write its fully qualified name every time (for example, java.util.ArrayList) or add an import statement at the top of your file so you can use the short name (just ArrayList). Import statements appear after the package declaration and before the class declaration. They have no impact on performance: they are purely a compile-time convenience that tells the compiler where to find a name.
Two forms of import
You can import specific types or everything in a package.
- Single-type import:import java.util.ArrayList;, imports only ArrayList. This is the preferred form: it is explicit about exactly which types are being used.
- Wildcard import:import java.util.*;, imports all public types from java.util at once. Convenient but it hides which specific types you are actually depending on, which can make large files harder to read.
- Static import:import static java.lang.Math.PI; or import static java.lang.Math.*;, lets you use static members without qualifying them with the class name. Useful for constants and utility methods you call frequently.
- Automatic import:java.lang (which contains String, Math, System, Object, and others) is always imported automatically. You never need to write import java.lang.String;
import Statements
JavaSingle-type imports, a static import, and using the fully qualified name as an alternative.
Wildcard Imports
A wildcard import using .* brings in all public types from a single package. It does not import sub-packages. For example, import java.util.*; does not import java.util.concurrent.* or any other sub-package. You would need a separate import for those.
When to use wildcard imports and when not to
There are reasonable arguments on both sides; most teams settle on single-type imports.
- Advantage:Shorter import sections, especially when you use many types from a single package.
- Disadvantage:When two packages each contain a class with the same name (for example, java.util.Date and java.sql.Date) and you wildcard-import both packages, any reference to the bare name Date is ambiguous and will not compile. You must then use the fully qualified name, which defeats the purpose.
- Team convention:Most professional Java teams and style guides (including Google and Oracle) recommend single-type imports and configure their IDEs to organise imports that way automatically.
Wildcard Import Ambiguity
JavaDemonstrating the naming conflict between java.util.Date and java.sql.Date when using wildcards.
Creating and Using Packages
Creating a package in Java requires two things: a package declaration in the source file and the source file placed in the corresponding directory on disk. When you compile and run from the command line, these paths must match exactly. Modern IDEs manage this automatically, but understanding the underlying structure helps you navigate and organise larger projects.
Directory structure rules
The file system structure must mirror the package hierarchy exactly.
- A class declared as package com.example.model must physically reside at com/example/model/ClassName.java relative to your source root.
- Compilation from the source root: javac com/example/model/ClassName.java
- Running from the source root: java com.example.model.ClassName (use dots, not slashes, for the class name argument).
- In a Maven or Gradle project the source root is src/main/java. Everything under that directory follows the same package-to-directory mapping.
Package Structure Example
JavaSimulating a multi-package application with classes from different layers in a single runnable demo.
Built-in Packages Overview
Java ships with a large standard library organised into packages. Knowing which package to reach for at which moment is a skill you build over time, but the packages listed below are the ones you will use in almost every project. They are worth making yourself familiar with early.
Core standard library packages
These packages address the most common programming needs. All are part of the JDK installation.
- java.lang:Automatically imported. Contains String, Math, Object, System, Thread, Integer, Double, and other fundamental classes. You use this package constantly without realising it.
- java.util:Collections: ArrayList, HashMap, HashSet, LinkedList. Utilities: Arrays, Collections, Scanner, Random, Optional, Date. This is the most-used non-lang package in Java.
- java.io:Byte and character streams for reading and writing files: InputStream, OutputStream, FileReader, FileWriter, BufferedReader, PrintWriter.
- java.nio.file:Modern file and path API added in Java 7: Path, Paths, Files. Preferred over java.io.File for new code.
- java.net:Networking: URL, HttpURLConnection, Socket, ServerSocket. Used for basic HTTP requests and TCP communication.
- java.time:Modern date and time API added in Java 8: LocalDate, LocalTime, LocalDateTime, ZonedDateTime, Duration, Period, DateTimeFormatter. Replaces the error-prone java.util.Date and java.util.Calendar.
- java.util.concurrent:Thread-safe utilities: ExecutorService, Future, ConcurrentHashMap, CountDownLatch, Semaphore. Essential for multithreaded programs.
- java.util.stream:The Stream API for functional-style operations on collections: filter, map, reduce, collect. Works alongside java.util.function.
Sampling the Standard Library
JavaUsing classes from java.util, java.time, and java.lang to illustrate how packages are used in practice.
Quiz - Test Your Knowledge
Ten questions covering data hiding, access modifiers, getters and setters, the JavaBeans convention, packages, imports, and the standard library. Read each option carefully before selecting your answer.
Knowledge Check
1. Which access modifier makes a member visible only within its own class?
2. What is the access level of a class member declared with no modifier at all?
3. Which of the following is a valid getter method following the JavaBeans convention for a boolean field named active?
4. Why are setters preferred over making fields public when mutable state is required?
5. What does the protected access modifier allow that default (package-private) does not?
6. Which import statement brings in all public types from the java.util package?
7. What is the correct directory structure for a class named Logger in the package com.example.util?
8. Which package is automatically imported in every Java file without needing an explicit import statement?
9. In the JavaBeans convention, what must a class provide to be considered a valid JavaBean?
10. What happens if two classes in different packages both have the same simple name and you import both with wildcard imports?