Packages in Java
-
Last Updated: April 3, 2026
-
By: javahandson
-
Series
Packages in Java group related classes into folders, keep class names unique, and decide what the rest of your code may touch. This guide walks through built-in and user-defined packages, the import keyword, static imports, sub-packages, access modifiers, naming rules, common mistakes, a hands-on walkthrough, and the interview questions people actually ask.
Write your first Java program and you drop one file on the desktop. Write your fiftieth and that desktop turns into a swamp. Java solves this with packages.
A package is a named group of related classes and interfaces. Think of a phone that sorts photos into albums. The photos do not change, but finding one takes seconds instead of minutes.
Packages do more than tidy up, though. They also give every class a globally unique name and they act as a fence that access modifiers can lean on. We will unpack all of that below.
Here is the shortest possible example. One line at the top of a file puts that class in a package.
package com.javahandson.user;
public class UserService {
}The class is no longer just UserService. Its real name is now com.javahandson.user.UserService. That long form is the fully qualified name, and no other class on the planet can claim it.
Since packages hold classes, it helps to be comfortable with Classes and Objects in Java before you go further.
Ten classes in one folder feels fine. Three hundred classes in one folder feels like a punishment. But size is only half the story.
Two libraries can each ship a class called Date. Without packages, the compiler would have no way to tell them apart.
java.util.Date java.sql.Date
Same simple name, different packages, zero confusion. The JDK itself relies on this trick constantly.
A new teammate opens your project. Which folder do they click first? With good packages, the answer is obvious.
userpaymentreportA tidy package travels well. Zip up com.javahandson.util, drop it into another project, and it still works because nothing outside it had to change.
Scattered classes never travel that cleanly. You end up copying five files and forgetting the sixth.
This one surprises beginners. Access modifiers in Java do not just say “open” or “closed” – several of them answer the question “same package or not?”
A helper class with no modifier stays invisible outside its package. That lets you expose a small, clean surface and hide the messy plumbing behind it. Section 10 covers the exact rules.
Java splits packages into two families, plus one special case nobody should rely on.
The JDK ships thousands of ready-made classes, and every one of them sits in a package. You have already used several without thinking about it.
import java.util.ArrayList; import java.util.Scanner;
A few you will meet early:
java.lang holds the core types such as String, Math, and SystemArrayList and HashMap live in java.utiljava.iojava.netOne handy quirk: the compiler imports java.lang for you automatically. That is why String works with no import line at the top.
The second family is the one you create. Add a package line and the class belongs there.
package com.javahandson.payment;
public class PaymentService {
}Real projects lean on these heavily. Most teams end up with a dozen or more, each holding one slice of the application.
Skip the package line entirely and your class lands in the unnamed package, usually called the default package.
It works for a quick throwaway file. It falls apart everywhere else, because a class in a named package cannot import a class from the unnamed one. There is no name to import.
Treat the default package as scratch paper. Anything you plan to keep deserves a proper name.
Creating a package takes one keyword and one compiler flag. Let us walk the whole loop, from source file to running program.
The package keyword must come first in the file. Comments may sit above it, but no code may.
package com.javahandson.util;
public class Calculator {
public int add(int a, int b) {
return a + b;
}
}One file may declare only one package. That single line applies to every type in the file.
The statement itself is part of the standard Structure of a Java Program, right above the imports.
Now compile it. The -d flag tells javac where to drop the output, and the compiler builds the folder tree for you.
javac -d . Calculator.java
Two things happen when you run that command:
.class file lands at the bottom of that treeSo you end up with this:
com/
javahandson/
util/
Calculator.classHere is where most beginners trip. You cannot run java Calculator any more. The class needs its full name.
java com.javahandson.util.Calculator
Run that from the folder that contains com, not from inside util. The JVM walks down the tree itself.
package on the first line of code, above every import-d so the compiler does the folder workOne extra tip: a file named package-info.java can hold documentation and annotations for the package itself. Frameworks read it, and it costs you nothing.
A package is not only an idea in the compiler’s head. It has a physical shape on disk, and that shape matters.
Read the package name left to right and you have read the path.
package com.javahandson.util; -> com/javahandson/util/
com becomes the top folderjavahandsonutil nests one level deeperTwo trees exist side by side in most projects. One holds .java files, the other holds compiled .class files.
src/com/javahandson/util/Calculator.java out/com/javahandson/util/Calculator.class
Notice the package part repeats in both. Only the root differs, which is exactly what -d out controls.
How does the JVM find com/javahandson/util/Calculator.class? It searches the classpath, which is simply a list of root folders and jar files.
java -cp out com.javahandson.util.Calculator
Read that as: start at out, then follow the package name down. If the folders do not match the package, the JVM gives up with ClassNotFoundException or NoClassDefFoundError.
Your Main class sits in one package. The Calculator it needs sits in another. Java offers exactly two ways to bridge that gap.
Spell out the whole path every time you mention the class.
public class Main {
public static void main(String[] args) {
com.javahandson.util.Calculator calc = new com.javahandson.util.Calculator();
System.out.println(calc.add(2, 3)); // Output: 5
}
}It compiles, and it needs no import line. It also reads terribly once you use the class more than once.
Declare the long name once at the top, then use the short name below.
import com.javahandson.util.Calculator;
public class Main {
public static void main(String[] args) {
Calculator calc = new Calculator();
System.out.println(calc.add(2, 3)); // Output: 5
}
}Worth knowing: import costs nothing at runtime. It does not load a class or copy any code. The compiler simply learns which long name your short name refers to.
Use import almost always. Reach for the fully qualified name only when two classes share a simple name and you need to point at one of them without ambiguity.
The keyword looks simple, and mostly it is. A few details still catch people out in interviews.
import java.util.ArrayList;
This form names exactly one class. Most teams prefer it because a reader can see every dependency at a glance.
import java.util.*;
The star covers every class sitting directly in java.util. It does not walk any deeper, so java.util.concurrent stays out of reach.
Does the wildcard slow your program down? No. The compiler resolves it at build time and the bytecode looks identical either way.
Every source file behaves as though it starts with an invisible import java.lang.*;. That covers String, System, Math, Integer, Thread, and the rest of the core set.
Classes in your own package come free too. Two classes in com.javahandson.user can see each other with no import line at all.
Try to import two classes with the same simple name and the compiler stops you right there.
import java.util.Date; import java.sql.Date; // compile error: Date is already defined
Two wildcards behave differently. They compile happily until you actually write Date, and then the compiler calls the reference ambiguous.
The fix is the same in both cases. Import the one you use often and spell out the other in full.
import java.util.Date;
public class Report {
Date created = new Date();
java.sql.Date dueDate;
}Normal imports bring in a class. A static import brings in the static members of a class, so you can drop the class name from the call.
System.out.println(Math.sqrt(16)); // Output: 4.0
With a static import, the same call gets shorter.
import static java.lang.Math.*;
public class Test {
public static void main(String[] args) {
System.out.println(sqrt(16)); // Output: 4.0
System.out.println(max(3, 9)); // Output: 9
}
}import static java.lang.Math.sqrt;
Now only sqrt() loses its prefix. Everything else on Math still needs the class name, which keeps the code honest.
The same syntax works for constants, and that is where it shines. Test code full of assertEquals and assertTrue reads far better without a class prefix on every line. If static members feel fuzzy, the static keyword in Java guide is worth a detour.
sqrt, abs, or pow repeatedlyUse it sparingly, though. A bare sqrt(16) with no visible owner leaves the next reader hunting for where that method came from.
Names like java.util.concurrent look nested, and on disk they truly are. The language, however, sees something flatter.
Java has no concept of a parent-child relationship between packages. java.util and java.util.concurrent are two unrelated packages that happen to share a prefix.
That has one very practical consequence.
import java.util.*; // ArrayList, HashMap, Scanner... import java.util.concurrent.*; // still needed for ExecutorService
The first line does nothing for the second package. You must import each one you actually use.
Imagine the alternative. A single import com.acme.*; would drag in hundreds of classes from dozens of nested folders, and name clashes would explode.
That last point matters more than it sounds. A class in com.javahandson.billing.model gets no special privileges inside com.javahandson.billing, even though the name suggests family ties.
Now for the part interviewers love. Java has four access levels, and two of them change behaviour depending on the package.
A public member opens the door to everyone, inside the package or outside it.
package com.javahandson.user;
public class User {
public String name = "Shweta";
}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); // Output: Shweta
}
}Reach for it when the class or member is genuinely part of your public API. Everything else deserves something tighter.
A protected member is open to the whole package, plus subclasses anywhere else.
package com.javahandson.user;
public class User {
protected int age = 30;
}package com.javahandson.app;
import com.javahandson.user.User;
public class ChildUser extends User {
public void show() {
System.out.println(age); // Output: 30
}
}There is a catch that trips up even senior developers. From a different package, a subclass may touch the inherited member only through itself or its own subtype. Create a plain User object there and the field turns invisible again.
public class ChildUser extends User {
public void show() {
System.out.println(this.age); // fine
User other = new User();
// System.out.println(other.age); // compile error
}
}Write no modifier at all and you get default access, better known as package-private. Only classes in the very same package may look.
package com.javahandson.user;
public class User {
String city = "Hyderabad"; // package-private
}A class in com.javahandson.user reads city without trouble. A class in com.javahandson.app cannot see it, and neither can a subclass sitting outside the package.
This level deserves more love than it gets. It is the natural home for helper classes that support a feature but should never leak out of it. The idea sits at the heart of encapsulation in Java.
A private member never leaves its own class. Packages do not soften this at all.
package com.javahandson.user;
public class User {
private String password = "secret";
public String getPassword() {
return password;
}
}User user = new User(); // System.out.println(user.password); // compile error System.out.println(user.getPassword()); // Output: secret
| Modifier | Same class | Same package | Subclass, other package | Anywhere else |
|---|---|---|---|---|
| public | Yes | Yes | Yes | Yes |
| protected | Yes | Yes | Yes, through the subclass type | No |
| default | Yes | Yes | No | No |
| private | Yes | No | No | No |
Read the table top to bottom and you see one idea: each row narrows the circle a little further.
The compiler accepts almost any legal name. Your teammates will not. These four conventions cover the whole industry.
package com.javahandson.util; // good package com.JavaHandsOn.Util; // avoid
Lowercase avoids a nasty trap. Windows treats Util and util as the same folder, while Linux does not, so mixed case breaks builds that cross machines.
Java keywords are also off limits. A package called com.acme.new or com.acme.int simply will not compile, which is one reason keywords and identifiers in Java are worth knowing cold.
Own javahandson.com? Then flip it and start every package with com.javahandson.
package com.javahandson.project;
Domains are unique, so this convention hands you a namespace nobody else can accidentally collide with. Every serious library on Maven Central follows it.
com.javahandson.user com.javahandson.payment com.javahandson.report
Someone hunting a refund bug knows exactly where to click. Vague names such as misc, common, or stuff turn into dumping grounds within a month.
com.company.project.module.submodule.feature.impl.util // too deep
Nobody enjoys scrolling through seven folders to reach one file. Three or four levels below the domain prefix handles almost every project.
Java 9 added modules, and beginners often wonder whether packages still matter. They do. A module is a layer above packages, not a replacement for them.
A module is a named bundle of packages with a small descriptor file that says which packages the outside world may use.
module com.javahandson.billing {
exports com.javahandson.billing.api;
requires java.sql;
}Notice what that buys you. A public class inside a package that the module never exports stays unreachable, even though it carries the public keyword. Packages alone could never enforce that.
| Point | Package | Module |
|---|---|---|
| Arrived in | Java 1.0 | Java 9 |
| Groups | Classes and interfaces | Packages |
| Declared in | The package line of each file | module-info.java |
| Hides code | Through default access | By not exporting a package |
| Needed? | Always | Only for modular projects |
Most day-to-day Java still runs on the classpath with no module descriptor in sight. Learn packages properly first, then read up on module import declarations in Java 25 when you meet them.
Almost every package problem comes from one of six small misunderstandings. Recognise them once and you will never lose an afternoon to them again.
javac Calculator.java // class file lands right next to the source javac -d . Calculator.java // folders appear, class file goes inside
Both commands compile. Only the second one puts the .class file where the JVM will look for it later.
Declare package com.javahandson.util; and the class file must end up under com/javahandson/util/. Rename a folder by hand and the program dies at startup, not at compile time, which makes the error feel mysterious.
We covered this in section 9, and it still bites people every week. import java.util.*; gives you nothing from java.util.concurrent. Add a second import line.
import java.util.List; package com.javahandson.util; // compile error: wrong order
Order is fixed: package, then imports, then types. Comments may go anywhere.
A file with no package line cannot be imported by anything that does have one. Beginners hit this the moment they split their first project into two folders, and the compiler error rarely explains why.
Sometimes the import line is perfect and the code still refuses to compile. Check the modifier before you check the folders.
protected member needs inheritance once you leave the packagepublic, and its name must match the fileTextbook examples stop at com.example.demo. Production codebases go further, and they tend to settle into a few familiar shapes.
com.javahandson.user com.javahandson.payment com.javahandson.order com.javahandson.report
One package equals one business capability. Delete the feature, delete the folder, and almost nothing else moves.
com.javahandson.controller com.javahandson.service com.javahandson.repository com.javahandson.model
controllerservicerepositorymodelSpring projects use this shape constantly. Larger systems often combine both styles: feature first, then layers inside each feature.
Clear package boundaries reduce merge conflicts. One team owns payment, another owns user, and the two rarely touch the same file on the same day.
Package-private classes help here too. A team can refactor its own internals freely, because nothing outside the folder could depend on them in the first place.
src/main/java/com/javahandson/... src/test/java/com/javahandson/...
Maven and Gradle both assume that layout. Follow it and the tools need almost no configuration; fight it and every build file grows a paragraph of overrides.
Everything above boils down to a short checklist you can apply to your next project.
public only on purposeOne more rule of thumb: if you cannot describe a package in a single short sentence, it probably holds two packages that have not been separated yet.
Theory sticks better once you build something. Let us wire up a tiny billing program that spans three packages.
src/com/javahandson/billing/model/Invoice.java src/com/javahandson/billing/service/InvoiceService.java src/com/javahandson/billing/app/Main.java
Three packages, three responsibilities. Data lives in model, the calculation lives in service, and app starts everything.
package com.javahandson.billing.model;
public class Invoice {
private final String customer;
private final double amount;
public Invoice(String customer, double amount) {
this.customer = customer;
this.amount = amount;
}
public String getCustomer() {
return customer;
}
public double getAmount() {
return amount;
}
}Both fields carry private, so nothing outside this class can reach them directly. The getters are public because two other packages need them.
package com.javahandson.billing.service;
import com.javahandson.billing.model.Invoice;
public class InvoiceService {
private static final double TAX_RATE = 0.18;
public double totalWithTax(Invoice invoice) {
return invoice.getAmount() * (1 + TAX_RATE);
}
}Look at that import line. Invoice sits in a different package, so the service must name it explicitly before using the short form.
package com.javahandson.billing.app;
import com.javahandson.billing.model.Invoice;
import com.javahandson.billing.service.InvoiceService;
public class Main {
public static void main(String[] args) {
Invoice invoice = new Invoice("Shweta", 1000.0);
InvoiceService service = new InvoiceService();
System.out.println(invoice.getCustomer()); // Output: Shweta
System.out.println(service.totalWithTax(invoice)); // Output: 1180.0
}
}Two imports, because this file touches two foreign packages. Its own package needs no import at all.
javac -d out src/com/javahandson/billing/model/Invoice.java src/com/javahandson/billing/service/InvoiceService.java src/com/javahandson/billing/app/Main.java java -cp out com.javahandson.billing.app.Main
The first command fills out with a matching folder tree. The second points the JVM at that root and asks for one fully qualified class.
Shweta 1180.0
private fields survived untouched, while the getters carried the data acrossTry one experiment. Drop public from getAmount() and recompile. The build fails immediately, because package-private cannot reach across from model into service.
A: A package is a named group of related classes and interfaces. It gives every class a unique full name, maps onto a folder on disk, and forms the boundary that default and protected access rely on.
A: Packages prevent class-name clashes, group code by feature so a project stays navigable, make a slice of code easy to reuse elsewhere, and let you hide helper classes behind package-private access.
A: A fully qualified name spells out the package path at every single use. An import declares that path once at the top of the file so you can write the short class name below. Neither costs anything at runtime.
A: No. The wildcard covers only the classes sitting directly in java.util. To use ExecutorService you still need a separate import for java.util.concurrent, because Java treats every package as independent.
A: Any class without a package statement belongs to the unnamed, or default, package. It works for quick experiments, but a class in a named package can never import from it, so real code should always declare a package.
A: Folders nest, packages do not. Names such as com.example.util and com.example.util.text look like parent and child, yet the compiler treats them as two unrelated packages with no shared access rights.
A: A static import pulls in the static members of a class, letting you write sqrt(16) instead of Math.sqrt(16). It suits maths code and unit tests, but heavy use hides where each method actually comes from.
A: Yes, as long as they sit in different packages. java.util.Date and java.sql.Date prove the point. If one file needs both, import one and write the other with its full package path.
A: Inside the package, protected behaves like default access. Outside it, only a subclass may touch the member, and only through a reference of its own type. A plain superclass object gives no access at all.
A: Packages group classes and have existed since Java 1.0. Modules arrived in Java 9 and group packages instead, declaring in module-info.java which ones the outside world may use. Any public class inside a package that the module never exports stays hidden.
Let us wrap up what we covered. A package groups related classes, gives each one a globally unique name, and turns into a real folder tree the moment you compile.
Two families exist: the built-in packages that ship with the JDK, and the ones you write yourself. Skipping the package line drops a class into the unnamed package, which named code can never import.
Crossing a package boundary takes either a fully qualified name or an import. Wildcards stay shallow, static imports shorten calls to static members, and sub-packages get no special treatment from the compiler.
Access modifiers give packages their real power. Default access keeps a class inside its folder, protected extends the reach to subclasses, and public throws the door wide open.
Keep names lowercase, start from a reverse domain, name each package after its job, and stay shallow. Do that and your folder tree will explain the design before anyone opens a single file.