Packages in Java
-
Last Updated: April 3, 2026
-
By: javahandson
-
Series
Learn Java in a easy way
Packages in Java help organize classes, avoid naming conflicts, and structure applications efficiently. In this guide, we cover package types, imports, access modifiers, directory structure, real-world usage, best practices, and interview questions.
When we start learning Java, we quickly realize that classes are not written randomly. Instead, they are grouped in a structured way. This grouping is done using something called packages.
A package in Java is simply a way to organize related classes and interfaces. We can think of it like a folder on our computer. Just as we create folders to organize files, Java uses packages to organize classes.
For example, imagine we are building a large application. We might have:
Instead of keeping all classes in one place, we group them into different packages. This makes the project easier for us to understand and maintain.
package com.javahandson.user;
public class UserService {
}
In this example, the UserService class belongs to the package com.javahandson.user. This indicates that the class is part of user-related functionality.
Packages also help us avoid confusion when multiple classes have the same name. For instance, two different libraries may have a class called Date, but since they are in different packages, Java can clearly identify which one we are referring to.
At a high level, packages help us bring:
As our project grows, packages become very important. Without them, managing a large number of classes would become difficult and messy.
Since packages organize classes, you should first understand Classes and Objects in Java.
When we work on small programs, keeping all classes in one place may seem manageable. But as our application grows, this approach quickly becomes confusing and difficult to maintain. This is where packages become very important.
The first major reason we need packages is to avoid class name conflicts. In real-world projects, it is very common for different teams or libraries to use the same class name. For example, we might have two different Date classes coming from different libraries. Without packages, Java would not be able to distinguish between them.
java.util.Date java.sql.Date
Here, both classes have the same name Date, but they belong to different packages. As a result, Java can clearly determine which class we want to use.
Another important reason is better code organization. When we group related classes into packages, our project becomes easier to navigate. For example, we can keep all user-related classes in one package, all payment-related classes in another, and so on. This structured approach helps us quickly find and understand code.
Packages also support code reusability. When our classes are well-organized, it becomes easier for us (or other developers) to reuse them across different parts of the application or even in different projects. Well-structured packages make our code more modular.
A very important benefit of packages is access control. In Java, access modifiers like public, protected, and default behave differently depending on packages. This allows us to control which classes or members can be accessed from outside the package and which should remain restricted. This becomes critical when building secure and maintainable applications.
At a high level, packages help us:
As our applications scale, packages are no longer optional—they become essential for writing clean, maintainable, and professional Java code.
In Java, we mainly work with two types of packages. Understanding these helps us see how Java organizes its own code and how we can organize ours.
Java provides many ready-made packages that we use in our daily programming. These are called built-in packages. They are part of the Java standard library and include commonly used classes and interfaces.
For example, when we write programs, we often use classes like String, ArrayList, or Scanner. These classes are already defined inside Java’s built-in packages.
import java.util.ArrayList; import java.util.Scanner;
Some commonly used built-in packages are:
java.lang → contains core classes like String, Math, Systemjava.util → contains utility classes like collections (ArrayList, HashMap)java.io → used for input and output operationsOne important point to remember is that the java.lang package is automatically available. We do not need to import it explicitly.
These built-in packages save us time because we do not need to write common functionality from scratch.
In addition to built-in packages, we can also create our own packages. These are called user-defined packages.
When we develop applications, we usually organize our own classes into meaningful packages based on functionality. This helps us keep our code clean and easy to maintain.
We create a package using the package keyword at the top of the Java file.
package com.javahandson.payment;
public class PaymentService {
}
Here, we are creating our own package com.javahandson.payment and placing the PaymentService class inside it.
In real-world projects, we use user-defined packages extensively to separate different parts of the application, such as:
This makes the project more structured and easier to scale.
Now that we understand the types of packages, let us see how we can create our own package in Java. The package statement is part of the basic Structure of a Java Program.
In Java, we create a package using the package keyword. This statement must be written at the very top of the Java file, before any class or import statements.
package com.javahandson.util;
public class Calculator {
}
In this example, we are creating a package named com.javahandson.util, and the Calculator class belongs to this package.
When we define a package, we are telling Java that this class should be logically grouped under that package name. But for Java to properly organize it, we also need to compile the code in a specific way.
To create the correct folder structure, we use the -d option while compiling.
javac -d . Calculator.java
When we run this command:
.class file is placed inside that folder structureSo the directory structure will look like this:
com/
└── javahandson/
└── util/
└── Calculator.class
Once compiled, we need to run the class using its fully qualified name (package + class name):
java com.javahandson.util.Calculator
package statement must always be the first line in the file-d option to let Java create directories automaticallyWhen we create a package in Java, it is not just a logical concept. Java also represents packages as folders in the file system. This is an important concept because understanding it helps us avoid many common mistakes.
In simple terms, the package name directly maps to a directory structure.
For example, if we declare a package like this:
package com.javahandson.util;
Java expects the class to be placed in a folder structure like this:
com/
└── javahandson/
└── util/
└── ClassName.class
Each part of the package name becomes a folder:
com → top-level folderjavahandson → subfolder inside comutil → subfolder inside javahandsonWhen we compile using:
javac -d . Calculator.java
Java automatically creates the required folders and places the .class file in the correct location. This ensures that the package and directory structures remain consistent.
This mapping between packages and directories is very important because:
For example, when we run:
java com.javahandson.util.Calculator
Java looks inside the com/javahandson/util directory to find the Calculator.class file.
So far, we have seen how to create packages and how they map to directory structures. Now, let us understand how to use classes from one package in another package.
In real-world applications, this is very common. We often create classes in different packages and need to use them across the application.
There are two main ways to access a class from another package.
The first way is to use the fully qualified name, which means writing the complete package name along with the class name.
public class Main {
public static void main(String[] args) {
com.javahandson.util.Calculator calc = new com.javahandson.util.Calculator();
}
}
Here, instead of importing the class, we use its full path directly. This works, but it makes the code longer and less readable, especially if we use the class multiple times.
A more common and cleaner way is to use the import keyword. This allows us to use the class directly without having to write the full package name every time.
import com.javahandson.util.Calculator;
public class Main {
public static void main(String[] args) {
Calculator calc = new Calculator();
}
}
This approach makes the code easier to read and maintain.
When we use classes from another package, we are essentially telling Java where to find that class. We can either:
In most cases, we prefer using import because it keeps our code clean and simple.
In the previous section, we saw that we can use classes from another package either by writing the full package name or by using the import keyword. Now, let us understand the import keyword in more detail.
The import statement allows us to bring a class or a group of classes from another package into our current file. This helps us avoid writing the fully qualified name every time we use that class.
We can import a single class by specifying its full package path.
import java.util.ArrayList;
After this, we can directly use ArrayList in our code without writing java.util.ArrayList again and again.
We can also import all classes from a package using *.
import java.util.*;
This allows us to use any class from the java.util package without importing each one separately.
However, there is an important point to understand here. The * only imports classes from that package, not from its sub-packages.
For example:
import java.util.*;
This will import classes like ArrayList and HashMap, but it will not import classes from java.util.concurrent.
Java automatically imports the java.lang package. That is why we can use classes like String, System, and Math without writing any import statements.
So far, we have used the import keyword to access classes from other packages. Java also provides a special feature called static import, which allows us to directly use static members of a class without referring to the class name.
Normally, when we use static methods or variables, we access them using the class name.
System.out.println(Math.sqrt(16));
Here sqrt() is a static method of the Math class, so we use it as Math.sqrt().
With static import, we can remove the class name and use the method directly.
import static java.lang.Math.*;
public class Test {
public static void main(String[] args) {
System.out.println(sqrt(16));
}
}
In this example, we imported all static members of the Math class, so we can call them directly sqrt() without writing Math.sqrt().
Instead of importing everything, we can also import only specific static members.
import static java.lang.Math.sqrt;
Now we can use only sqrt() directly, while other methods from Math still need the class name.
Static import can make code shorter and cleaner, especially when we use static methods frequently, such as in:
However, overusing static import can make the code confusing because it may not be clear where a method is coming from.
In Java, we often see package names written in multiple levels, such as java.util.concurrent. These are commonly referred to as sub-packages.
At first glance, it may look like sub-packages are automatically connected to their parent packages. However, an important point to understand is that Java does not treat sub-packages as part of the parent package. Each package is considered completely independent.
For example:
java.utiljava.util.concurrentEven though the names look related, Java treats them as two separate packages.
Because packages are independent, classes in one package do not automatically get access to classes in another package, even if it appears to be a sub-package.
For example, if we write:
import java.util.*;
This will import classes like ArrayList and HashMap, but it will not import anything from java.util.concurrent.
To use classes from java.util.concurrent, we must import them explicitly:
import java.util.concurrent.*;
This design keeps things clear and controlled. If sub-packages were automatically included, it could lead to confusion and unintended access to classes.
By treating each package separately, Java ensures:
Packages are not just used for organizing code—they also play a very important role in controlling access. In Java, access modifiers behave differently depending on whether classes are in the same package or in different packages.
This is one of the most important concepts to understand, especially from an interview perspective.
Java provides four access modifiers:
publicprotectedprivateEach of these controls how and where a class or its members can be accessed.
Let us understand how these modifiers behave in relation to packages.
When we declare something as public, It can be accessed from anywhere—within the same package or from a different package.
When to use: When we want the class or member to be available globally.
package com.javahandson.user;
public class User {
public String name = "Shweta";
}
Access from another package:
package com.javahandson.app;
import com.javahandson.user.User;
public class Test {
public static void main(String[] args) {
User user = new User();
System.out.println(user.name); // ✅ accessible
}
}
protected members are accessible:
When to use: When we want to allow access to subclasses but not to everyone.
package com.javahandson.user;
public class User {
protected int age = 30;
}
Access to different packages via inheritance:
package com.javahandson.app;
import com.javahandson.user.User;
public class ChildUser extends User {
public void test() {
System.out.println(age); // ✅ accessible via inheritance
}
}
However, if we create an object directly (without inheritance), it will not work.
When we do not specify any modifier, it is called default access. It is accessible only within the same package.
When to use: When we want to restrict access to the package only.
package com.javahandson.user;
public class User {
String city = "Hyderabad"; // default
}
Access within the same package:
package com.javahandson.user;
public class Test {
public static void main(String[] args) {
User user = new User();
System.out.println(user.city); // ✅ accessible
}
}
Access from a different package: Not accessible outside the package.
private members are accessible only within the same class.
When to use: When we want to hide implementation details completely.
package com.javahandson.user;
public class User {
private String password = "secret";
public String getPassword() {
return password;
}
}
Access:
User user = new User(); // System.out.println(user.password); // ❌ not accessible System.out.println(user.getPassword()); // ✅ via method
We can summarize it like this:
public → accessible everywhereprotected → same package + different package via inheritanceprivate → only within the same classPackages and access modifiers together help us:
| Modifier | Same Package | Different Package |
|---|---|---|
| public | Yes | Yes |
| protected | Yes | Yes (via inheritance) |
| default | Yes | No |
| private | No | No |
When we create packages in Java, following proper naming conventions is very important. It helps us maintain consistency, avoid conflicts, and make our code look professional.
Unlike classes, package names follow a specific style. In most real-world projects, we follow standard conventions so that other developers can easily understand our code structure.
a. Use lowercase letters – Package names should always be written in lowercase. This avoids confusion and keeps naming consistent across different systems.
package com.javahandson.util; // ✅ correct package com.JavaHandsOn.Util; // ❌ not recommended
b. Use reverse domain naming – The most common convention is to use reverse domain names. This helps ensure that package names are unique worldwide.
For example, if our website is javahandson.com, we write the package name as:
package com.javahandson.project;
This approach is widely used in real-world applications and frameworks.
For example:
com.javahandson.user com.javahandson.payment com.javahandson.report
Each package represents a specific part of the application.
d. Avoid very long or very deep package structures – While packages can be nested, we should avoid nesting them too deeply or making them overly complex. Very long package names can make code harder to read and manage.
When we start working with packages, everything may look simple at first. But in practice, many beginners (and even experienced developers) make small mistakes that can lead to confusion or errors.
Let us go through some of the most common mistakes so that we can avoid them early.
a. Forgetting to compile with the -d option – One of the most common issues is compiling the class without using the -d option.
javac Calculator.java // ❌
This may compile the class, but it will not create the correct directory structure based on the package.
The correct way is:
javac -d . Calculator.java // ✅
This ensures that Java creates folders according to the package name.
b. Mismatch between package name and folder structure – If the package name and directory structure do not match, Java will not be able to locate the class properly.
For example, if we declare:
package com.javahandson.util;
Then the class must be inside:
com/javahandson/util/
If this structure is incorrect, we may get runtime errors.
c. Thinking * imports sub-packages – Many developers assume that:
import java.util.*;
will also import sub-packages like java.util.concurrent. This is not true.
We must import sub-packages separately:
import java.util.concurrent.*;
d. Placing package statement incorrectly – The package statement must always be the first line in the Java file (except comments). Placing it after imports or code will cause compilation errors.
e. Overusing wildcard imports – Using * imports everywhere can make code less clear, especially in large files. It becomes difficult to know which classes are being used.
In most cases, it is better to import only the required classes.
f. Not understanding access issues across packages – Sometimes we try to access members of another package and encounter errors, without realizing it is due to access modifiers.
For example:
default members are not accessible outside the packageprotected members need inheritance for cross-package accessUnderstanding this avoids confusion during development.
Most mistakes with packages are not complex—they usually come from small misunderstandings about structure and rules.
If we remember:
Then working with packages becomes smooth and predictable.
So far, we have understood packages from a conceptual and technical point of view. Now, let us see how packages are actually used in real-world applications.
In real projects, we do not create packages randomly. Instead, we organize them by the application’s structure and responsibilities. This helps teams work efficiently and keeps the codebase clean as it grows.
a. Organizing code by functionality – In most applications, we divide packages based on features or responsibilities. For example, in a typical application, we might see:
com.javahandson.user com.javahandson.payment com.javahandson.order com.javahandson.report
Each package contains classes related to a specific feature. This makes it easy for us to locate code and quickly understand the system.
b. Layered architecture (common in Spring applications) – In frameworks like Spring, packages are often organized by application layers.
For example:
com.javahandson.controller com.javahandson.service com.javahandson.repository com.javahandson.model
controller → handles incoming requestsservice → contains business logicrepository → interacts with the databasemodel → represents dataThis structure is very common and helps maintain separation of concerns.
c. Large team collaboration – In enterprise projects, multiple developers work on the same codebase. Packages help divide the work clearly.
For example:
payment packageuser packageThis reduces conflicts and makes collaboration smoother.
d. Integration with build tools – When we use tools like Maven or Gradle, packages play a key role in organizing source code. For example, in a Maven project:
src/main/java/com/javahandson/...
This structure follows the same package hierarchy, making it easy for build tools to compile and manage the project.
In real-world applications, packages help us:
Packages are not just a Java concept—they are a foundation for designing clean and maintainable applications.
Now that we have seen how packages are used in real-world applications, let us look at some best practices for working with them. These practices help us keep our code clean, scalable, and easy to maintain.
a. Keep packages focused and meaningful – Each package should have a clear purpose. We should group related classes by functionality rather than putting everything into a single package.
For example, instead of mixing everything, it is better to separate concerns:
com.javahandson.user com.javahandson.payment
This makes the code easier to understand and maintain.
b. Follow consistent naming conventions – We should always use lowercase names and follow the reverse domain naming pattern. Consistency is very important, especially when working in teams.
c. Avoid very deep package structures – While Java allows nested packages, creating very deep structures can make code harder to read and navigate.
For example, something like this becomes difficult to manage:
com.company.project.module.submodule.feature.impl.util
We should keep the structure simple and meaningful.
d. Do not create too many small packages unnecessarily – Over-separating classes into too many packages can also make the project confusing. We should strike a balance between organization and simplicity.
e. Import only what is required – Instead of importing entire packages using *, it is better to import only the required classes in most cases. This improves readability and avoids confusion.
f. Align packages with project architecture – Our package structure should reflect the application’s design. For example, if we are following a layered architecture, our packages should match those layers.
In this article, we explored Java packages, from the basics to real-world usage. We started by understanding what packages are and why they are needed, especially as applications grow in size and complexity.
We then looked at different types of packages, how to create them, and how Java maps packages to directory structures. Along the way, we also learned how to access classes from different packages, how the import keyword works, and how static import can simplify code in certain situations.
One of the most important parts was understanding how access modifiers behave with packages, as this plays a key role in building secure and well-structured applications. We also covered naming conventions, common mistakes, and best practices to help us write clean, maintainable code.
In real-world projects, packages are not just about grouping classes—they form the foundation of how we design and organize our applications. When used properly, they make our code easier to understand, scale, and collaborate on.
A package in Java is a namespace that groups related classes and interfaces together. It helps organize code and avoid naming conflicts.
We use packages to avoid class-name conflicts, organize code, control access with access modifiers, and manage large applications.
import and a fully qualified name?Using a fully qualified name means writing the complete package path every time we use a class. Using import allows us to write the class name directly, which makes the code cleaner and easier to read.
import java.util.* import sub-packages?No, it only imports classes from the java.util package. Sub-packages like java.util.concurrent these must be imported separately.
If we do not declare any package, the class belongs to the default package. However, using the default package is not recommended in real-world applications.
Packages can have hierarchical names like com.example.util, but Java treats each package as independent. Sub-packages are not automatically included or accessible.
A static import allows us to use static methods or variables directly without referring to the class name. It helps shorten the code, but it should be used carefully to maintain readability.
Yes, we can have two classes with the same name as long as they are in different packages. Java uses the package name to uniquely identify them.
To strengthen your understanding of packages in Java, you can also explore these resources: