Packages and Imports
Explore how Java packages help organize classes by grouping related types and preventing naming conflicts. Learn to use import statements and static imports to access classes and members from different packages. Understand benefits and best practices to write maintainable, scalable Java applications.
As our application grows, keeping all files in a single folder becomes difficult to manage. Imagine a library where every book is added to a single pile. Finding “The Great Gatsby” would be impractical once the collection grows large.
In Java, we solve this problem using packages, which group related code together, much like shelves in a library that group related books. By organizing classes into packages, we can keep large applications manageable and avoid name conflicts, e.g., having two different classes both named User coming from different parts of an application.
Organizing code with packages
A package is a
To understand how to make package in java, we must look at the file system. The package name strictly dictates the folder structure. If we declare a package com.educative.geometry, the Java source file must reside in a directory path com/educative/geometry. If the folder structure does not match the package declaration, the compiler will reject our code.
This organization is especially useful for utility classes, which contain static helper methods (such as Math or Collections) that don’t require instantiation. By placing these utilities in a dedicated package (e.g., com.educative.util), we keep our helper logic separate from the core business logic.
Line 2: We declare that
GeometryUtilsclass belongs to thecom.educative.geometrypackage.Line 5: A private constructor prevents anyone from creating an instance of this utility class.
Line 8: The
calculateCircleArea()method isstatic, meaning it is designed to be used without creating an object ofGeometryUtilsclass.
Importing classes
By default, Java classes can only see other classes that are in the same package. If we want to use GeometryUtils inside a Main class located in a different package (e.g., com.educative.app), we must explicitly import it.
The import statement tells the Java compiler where to find a specific class. These statements must appear after the package declaration but before the class definition.
// File location: src/com/educative/geometry/GeometryUtils.java
package com.educative.geometry;
public class GeometryUtils {
// A static utility class typically has a private constructor
private GeometryUtils() {}
// A static method that belongs to the class, not an instance
public static double calculateCircleArea(double radius) {
return Math.PI * radius * radius;
}
}Line 2: This
Main.javafile belongs tocom.educative.app.Line 4: We import
GeometryUtilsclass so we can refer to it by its short name.Line 9: We access the static method
calculateCircleArea()directly through the class name.
Importing static members (static imports)
When using utility classes heavily, repeating the class name can become verbose. For example, writing Math.abs(), Math.max(), and Math.sqrt() repeatedly clutters the code.
Java provides static imports (import static) to import static members (fields or methods) directly. This allows us to use them as if they were defined in our own class, without the class prefix.
package com.educative.math;
// Import specific static members
import static java.lang.Math.PI;
import static java.lang.Math.sqrt;
import static java.lang.Math.pow;
public class HypotenuseCalculator {
public static void main(String[] args) {
double a = 3.0;
double b = 4.0;
// No need to write Math.sqrt, Math.pow, or Math.PI
double c = sqrt(pow(a, 2) + pow(b, 2));
System.out.println("Hypotenuse: " + c);
System.out.println("Value of PI: " + PI);
}
}Lines 4–6: We import only the specific static members we need (
PI,sqrt,pow).Line 13: The code reads almost like a mathematical formula, without the visual noise of
Math.prefixes.
Implicit imports: The java.lang package
You might wonder why we have used classes like String, System, and Math in previous lessons without writing any import statements.
Java automatically imports the java.lang package into every Java source file. This package contains the core classes essential to the language’s functionality. However, for any other class in the Java Class Library, such as java.util.Scanner or java.util.Random, we must import it manually.
package com.educative.app;
import java.util.Random; // Explicit import required
public class Main {
public static void main(String[] args) {
// String is in java.lang, so no import is needed
String message = "Hello, Java!";
// Random is also in java.util, but we imported it above
Random random = new Random();
System.out.println(message + " " + random.nextInt(100));
}
}Line 3: We explicitly import
Randombecause it lives injava.util.Line 8:
Stringworks automatically becausejava.langis always available.
Wildcard imports and specificity
An application may require access to multiple classes from the same package. If you are exploring how to import a package in Java to access all its contents at once, you can use the wildcard character (*). This imports all classes from a specific package, rather than listing them individually.
package com.educative.app;
import java.util.*; // Imports List, ArrayList, Map, Scanner, etc.
public class Main {
public static void main(String[] args) {
// ArrayList is available because of java.util.*
List<String> names = new ArrayList<>();
names.add("Alice");
System.out.println(names);
}
}Line 3: The
*imports every public class insidejava.util.Line 8:
ListandArrayListare both resolved successfully fromjava.util.
While convenient, excessive use of wildcard imports may reduce code readability, as the origin of class is no longer immediately apparent. It can also lead to subtle naming conflicts, as packages can have classes with same name. For these reasons, it is generally considered best practice to import only the classes required.
Handling name conflicts
As mentioned above, a common issue occurs when importing multiple packages that contain classes with the same name. For example, both java.util and java.sql define a class named Date. If both packages are imported, the compiler cannot determine which Date class is being referred to.
To solve this, we must use the fully qualified name for at least one of the classes.
package com.educative.app;
import java.util.Date;
// We cannot import java.sql.Date here as well; it would collide.
public class Main {
public static void main(String[] args) {
// Uses the imported java.util.Date
Date utilDate = new Date();
// Uses the fully qualified name for java.sql.Date
java.sql.Date sqlDate = new java.sql.Date(System.currentTimeMillis());
System.out.println("Util Date: " + utilDate);
System.out.println("SQL Date: " + sqlDate);
}
}Line 3: We explicitly import
java.util.Date. This tells the compiler that whenever we writeDate, we mean the utility version, not the SQL version.Line 9: Because of the import on line 3, the simple name
Dateresolves tojava.util.Date.Line 12: We explicitly type
java.sql.Dateto create a SQL date object, bypassing the import ambiguity.
Packages are fundamental to writing professional Java code. They allow us to organize not just our objects, but also our shared utility libraries, keeping our codebase clean and structured. By mastering standard imports and static imports, you can write code that is both readable and conflict-free, ensuring your project remains scalable as it grows.