Java Packages and API ā Syntax, Structure, Examples & Best Practices
Everything you need to know about Java Packages and the Java API ā package declaration, import statements, built-in Java API packages, user-defined packages, access modifiers with packages, static imports, the most important API classes, anti-patterns, and real-world production code examples.
Last Updated
March 2026
Read Time
20 min
Level
Beginner to Intermediate
Chapter
10 of 35
What is a Package in Java?
A package in Java is a namespace mechanism that groups related classes, interfaces, enumerations, and annotations into a single logical unit. Think of it as a folder system for your Java code ā just as you organize files into folders on your computer, you organize Java types into packages. Packages solve two fundamental problems that arise in large projects: name collision (two classes with the same name in different packages coexist peacefully) and controlled access (package-private members are only visible within the same package).
Packages in Java are directly mapped to the directory structure on disk. A class declared in package com.techsustainify.service must physically reside in a directory path com/techsustainify/service/ relative to the source root. This one-to-one mapping between packages and directories is enforced by the Java compiler ā if the directory structure does not match the package declaration, the code will not compile.
The Java API (Application Programming Interface) is the massive collection of pre-built packages and classes that ship with every Java installation ā the Java Standard Library. It provides thousands of ready-to-use classes for collections, I/O, networking, math, concurrency, date/time, and much more. Learning which packages exist in the Java API and what they contain is one of the most practical skills a Java developer can develop.
Package Declaration ā Syntax & Rules
The package statement declares which package a Java source file belongs to. It must be the absolute first statement in the file ā before any import statements, before any class declarations, and before any comments (though comments above the package statement are technically allowed). Only one package statement is permitted per source file.
Syntax: package packageName; Example: package com.techsustainify.service; Rules: (1) Must be the FIRST statement in the source file. (2) Only ONE package declaration allowed per file. (3) Package name must be a valid Java identifier ā lowercase letters, digits, underscores. (4) Multi-level packages use dots: com.company.module.submodule. (5) Package name maps directly to directory path: com/company/module/submodule/. (6) If omitted, the class belongs to the unnamed default package ā acceptable for experiments, but never for production code. (7) By convention, package names use reverse domain notation to guarantee global uniqueness: com.google.gson, org.springframework.core, io.netty.channel.
Java enforces a strict one-to-one mapping between package names and directory structure. Package: com.techsustainify.model ā Directory: src/com/techsustainify/model/. The Java compiler (javac) and classloader use this mapping to locate class files. If EmployeeService.java declares package com.techsustainify.service; but is saved in the wrong directory, the compiler produces: error: class EmployeeService is public, should be declared in a file named EmployeeService.java. Modern IDEs (IntelliJ IDEA, Eclipse, VS Code) enforce this automatically ā creating a class in a package auto-creates the matching directory structure.
The standard Java convention for package names is reverse Internet domain notation: com.companyname.projectname.module. Examples: com.google.gson (Google's Gson library), org.springframework.boot (Spring Boot), io.micrometer.core (Micrometer metrics), com.techsustainify.tutorial. Why reverse domain? It guarantees global uniqueness ā two companies with different domains cannot produce a package name collision. Rules: all lowercase, no hyphens (use underscores if necessary), avoid Java keywords as package name segments. Segments beginning with digits are technically invalid Java identifiers ā a leading underscore is the workaround: com.example._123module.
// ā
File: src/com/techsustainify/model/Employee.java
package com.techsustainify.model; // ā FIRST statement ā mandatory
// Import statements come AFTER package declaration
import java.time.LocalDate;
import java.util.Objects;
// Class declaration comes last
public class Employee {
private final String name;
private final String department;
private final LocalDate joiningDate;
public Employee(String name, String department, LocalDate joiningDate) {
this.name = Objects.requireNonNull(name, "name must not be null");
this.department = Objects.requireNonNull(department, "department must not be null");
this.joiningDate = Objects.requireNonNull(joiningDate, "joiningDate must not be null");
}
public String getName() { return name; }
public String getDepartment() { return department; }
public LocalDate getJoiningDate() { return joiningDate; }
@Override
public String toString() {
return "Employee{name='" + name + "', dept='" + department
+ "', joined=" + joiningDate + "}";
}
}
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
// ā
File: src/com/techsustainify/service/EmployeeService.java
// package com.techsustainify.service;
//
// import com.techsustainify.model.Employee; // ā import from another package
// import java.time.LocalDate;
// import java.util.ArrayList;
// import java.util.List;
//
// public class EmployeeService {
// private List<Employee> employees = new ArrayList<>();
//
// public void hire(String name, String dept) {
// employees.add(new Employee(name, dept, LocalDate.now()));
// System.out.println("Hired: " + name + " in " + dept);
// }
//
// public List<Employee> getAll() { return List.copyOf(employees); }
// }
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
// ā Default (unnamed) package ā NO package declaration
// Acceptable for quick tests, never for production
// public class QuickTest {
// public static void main(String[] args) {
// System.out.println("This class is in the unnamed default package");
// }
// }Import Statement ā Types, Usage & Rules
The import statement makes types from other packages available in the current file by their simple name instead of requiring the fully qualified name every time. Without import, you must write java.util.ArrayList every time you use it. With import java.util.ArrayList;, you can simply write ArrayList. Import is purely a compile-time convenience ā it does not affect performance, does not load extra code, and does not change the compiled bytecode.
Imports exactly one class or interface by its full name: import java.util.ArrayList; import java.util.HashMap; import java.time.LocalDate; This is the preferred style in professional Java code ā it is explicit, unambiguous, and makes it immediately clear which specific classes the file depends on. Modern IDEs (IntelliJ, Eclipse) auto-manage specific imports ā they add the exact import when you use a class and remove unused ones automatically. Google Java Style Guide mandates specific imports over wildcard imports.
Imports all public types from an entire package at once: import java.util.*; import java.io.*; Convenient when many types from one package are used. Does NOT import sub-packages ā import java.util.* does NOT import java.util.concurrent.* or java.util.function.*. Does NOT affect compile or runtime performance ā the compiler resolves exactly which classes are needed. Potential downside: name ambiguity ā if two imported packages both contain a class named Date (java.util.Date and java.sql.Date), the compiler reports an ambiguous symbol error and you must use the fully qualified name for at least one.
Imports static members (fields and methods) of a class so they can be used without the class name prefix: import static java.lang.Math.PI; import static java.lang.Math.sqrt; import static java.lang.Math.pow; // Then use: double r = sqrt(pow(x, 2) + pow(y, 2)); // Instead of: double r = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)); Also useful for test code: import static org.junit.jupiter.api.Assertions.*; ā allows assertEquals(), assertTrue() directly. Use judiciously ā overuse makes code harder to understand (readers cannot tell which class a static method belongs to).
package com.techsustainify.demo;
// ā
Single-type imports ā preferred in professional code
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Collections;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
// ā
Static import ā use static members without class name
import static java.lang.Math.PI;
import static java.lang.Math.sqrt;
import static java.lang.Math.pow;
public class ImportStatements {
public static void main(String[] args) {
// ā
Using imported classes by simple name
List<String> cities = new ArrayList<>();
cities.add("Delhi");
cities.add("Mumbai");
cities.add("Bangalore");
Collections.sort(cities);
System.out.println("Sorted cities: " + cities);
// ā
Using Map ā imported from java.util
Map<String, Integer> population = new HashMap<>();
population.put("Delhi", 32941000);
population.put("Mumbai", 20667656);
population.put("Bangalore", 13193000);
population.forEach((city, pop) ->
System.out.printf("%-12s: %,d%n", city, pop));
// ā
Using LocalDate ā imported from java.time
LocalDate today = LocalDate.now();
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd MMMM yyyy");
System.out.println("Today: " + today.format(fmt));
// ā
Using static import ā PI, sqrt, pow without Math. prefix
double radius = 7.5;
double area = PI * pow(radius, 2);
double circumference = 2 * PI * radius;
double diagonal = sqrt(pow(radius, 2) + pow(radius, 2));
System.out.printf("Circle r=%.1f: area=%.2f, circ=%.2f, diag=%.2f%n",
radius, area, circumference, diagonal);
// ā
Fully qualified name ā no import needed
java.util.Scanner sc = new java.util.Scanner(System.in);
// Verbose but valid ā used when import is omitted or ambiguous
sc.close();
// ā
Name collision ā both java.util.Date and java.sql.Date exist
// If both are imported, use fully qualified names to resolve ambiguity:
java.util.Date utilDate = new java.util.Date();
java.sql.Date sqlDate = new java.sql.Date(System.currentTimeMillis());
System.out.println("util.Date : " + utilDate);
System.out.println("sql.Date : " + sqlDate);
}
}Built-in Java API Packages ā The Standard Library
The Java Standard Library ships with hundreds of packages containing thousands of classes. Knowing which packages exist and what they contain is a practical superpower ā it prevents you from reinventing solutions that Java already provides. The table below covers the most important packages every Java developer must know.
java.lang ā The Automatically Imported Package
java.lang is the only Java package that is automatically imported into every Java source file. It contains the absolute bedrock of the Java language ā classes so fundamental that Java programs cannot function without them. You use java.lang classes constantly without realizing it: every time you declare a String, call System.out.println(), use Math.abs(), catch a NullPointerException, or create a Thread.
// No import needed ā java.lang is auto-imported
public class JavaLangPackage {
public static void main(String[] args) {
// ā
String ā most used class in java.lang
String name = "TechSustainify";
System.out.println("Length : " + name.length()); // 14
System.out.println("Upper : " + name.toUpperCase()); // TECHSUSTAINIFY
System.out.println("Contains : " + name.contains("Sustain")); // true
System.out.println("Replace : " + name.replace("Tech", "Pro")); // ProSustainify
System.out.println("Substring : " + name.substring(4, 12)); // Sustaini
System.out.println("Starts : " + name.startsWith("Tech")); // true
System.out.println("Index : " + name.indexOf("Sustain")); // 4
System.out.println("CharAt : " + name.charAt(0)); // T
// ā
StringBuilder ā mutable string (also java.lang)
StringBuilder sb = new StringBuilder();
sb.append("Java");
sb.append(" ").append("Packages");
sb.insert(0, "Learning ");
sb.reverse();
System.out.println("SB : " + sb); // segatkcaP avaJ gninraeL
sb.reverse(); // Restore
System.out.println("Restored : " + sb); // Learning Java Packages
// ā
Math ā mathematical operations (java.lang)
System.out.println("abs(-7) : " + Math.abs(-7)); // 7
System.out.println("max(9,3) : " + Math.max(9, 3)); // 9
System.out.println("min(9,3) : " + Math.min(9, 3)); // 3
System.out.println("pow(2,10) : " + (int) Math.pow(2, 10)); // 1024
System.out.println("sqrt(144) : " + Math.sqrt(144)); // 12.0
System.out.println("floor(3.9): " + Math.floor(3.9)); // 3.0
System.out.println("ceil(3.1) : " + Math.ceil(3.1)); // 4.0
System.out.println("round(3.5): " + Math.round(3.5)); // 4
System.out.println("PI : " + Math.PI); // 3.141592...
System.out.println("random() : " + Math.random()); // [0.0, 1.0)
System.out.println("log(E) : " + Math.log(Math.E)); // 1.0
// ā
Integer, Long, Double ā Wrapper classes (java.lang)
int parsed = Integer.parseInt("42");
System.out.println("parseInt : " + parsed); // 42
System.out.println("MAX_VALUE : " + Integer.MAX_VALUE); // 2147483647
System.out.println("MIN_VALUE : " + Integer.MIN_VALUE); // -2147483648
System.out.println("toBinary : " + Integer.toBinaryString(255)); // 11111111
System.out.println("toHex : " + Integer.toHexString(255)); // ff
System.out.println("valueOf : " + Integer.valueOf("100")); // 100
double d = Double.parseDouble("3.14");
System.out.println("parseDouble: " + d); // 3.14
System.out.println("isNaN : " + Double.isNaN(0.0 / 0.0)); // true
// ā
System ā system-level utilities (java.lang)
long startTime = System.currentTimeMillis();
long startNano = System.nanoTime();
// ... do some work ...
for (int i = 0; i < 1_000_000; i++) { }
System.out.printf("Elapsed : %d ms%n",
System.currentTimeMillis() - startTime);
System.out.println("JVM arch : " + System.getProperty("os.arch"));
System.out.println("Java ver : " + System.getProperty("java.version"));
// ā
Object ā root of all Java classes (java.lang)
Object obj = new Object();
System.out.println("hashCode : " + obj.hashCode());
System.out.println("class : " + obj.getClass().getName()); // java.lang.Object
// ā
Handling java.lang exceptions ā no import needed
try {
int[] arr = new int[3];
arr[5] = 10; // Throws ArrayIndexOutOfBoundsException (java.lang)
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Caught : " + e.getClass().getSimpleName());
}
try {
String s = null;
s.length(); // Throws NullPointerException (java.lang)
} catch (NullPointerException e) {
System.out.println("Caught : " + e.getClass().getSimpleName());
}
}
}java.util ā Collections, Data Structures & Utilities
java.util is the second most important package in the Java API. It contains the entire Java Collections Framework (List, Set, Map, Queue and their implementations), utility classes for sorting, searching, and randomness, the Scanner for input, Optional for null-safety, and UUID for unique identifiers. If java.lang is Java's heartbeat, java.util is its muscle.
import java.util.*;
import java.util.stream.Collectors;
public class JavaUtilPackage {
public static void main(String[] args) {
// ā
ArrayList ā dynamic, ordered, allows duplicates
List<String> languages = new ArrayList<>(List.of("Java", "Python", "Go", "Rust"));
languages.add("Kotlin");
languages.remove("Go");
System.out.println("ArrayList : " + languages);
System.out.println("Contains Java : " + languages.contains("Java")); // true
System.out.println("Size : " + languages.size()); // 4
// ā
HashMap ā key-value pairs, O(1) average lookup
Map<String, Integer> scores = new HashMap<>();
scores.put("Alice", 95);
scores.put("Bob", 82);
scores.put("Charlie", 91);
scores.put("Alice", 98); // Overwrites existing key
System.out.println("\nHashMap : " + scores);
System.out.println("Alice's score : " + scores.get("Alice")); // 98
System.out.println("Bob exists : " + scores.containsKey("Bob")); // true
scores.getOrDefault("Diana", 0); // Returns 0 ā Diana not present
scores.forEach((name, score) ->
System.out.printf(" %-10s: %d%n", name, score));
// ā
HashSet ā unique elements, O(1) contains
Set<String> uniqueTags = new HashSet<>(List.of("java", "oop", "java", "api"));
System.out.println("\nHashSet (unique): " + uniqueTags); // no duplicates
// ā
LinkedList ā doubly-linked, O(1) add/remove at ends
Queue<String> taskQueue = new LinkedList<>();
taskQueue.offer("Task A");
taskQueue.offer("Task B");
taskQueue.offer("Task C");
System.out.println("\nQueue peek : " + taskQueue.peek()); // Task A
System.out.println("Queue poll : " + taskQueue.poll()); // Task A (removed)
System.out.println("Queue size : " + taskQueue.size()); // 2
// ā
TreeMap ā sorted by key, O(log n) operations
Map<String, Integer> sorted = new TreeMap<>(scores);
System.out.println("\nTreeMap (sorted): " + sorted);
// ā
Collections utility class
List<Integer> nums = new ArrayList<>(List.of(5, 2, 8, 1, 9, 3));
Collections.sort(nums);
System.out.println("\nSorted : " + nums);
System.out.println("Max : " + Collections.max(nums)); // 9
System.out.println("Min : " + Collections.min(nums)); // 1
System.out.println("Frequency 5 : " + Collections.frequency(nums, 5)); // 1
Collections.reverse(nums);
System.out.println("Reversed : " + nums);
Collections.shuffle(nums, new Random(42)); // Reproducible shuffle
System.out.println("Shuffled : " + nums);
// ā
Arrays utility class
int[] arr = {5, 2, 8, 1, 9, 3};
Arrays.sort(arr);
System.out.println("\nArrays.sort : " + Arrays.toString(arr));
System.out.println("binarySearch 8: " + Arrays.binarySearch(arr, 8)); // index 4
int[] copy = Arrays.copyOf(arr, arr.length);
System.out.println("Arrays.copyOf : " + Arrays.toString(copy));
int[] filled = new int[5];
Arrays.fill(filled, 7);
System.out.println("Arrays.fill 7 : " + Arrays.toString(filled));
// ā
Optional ā null-safe container (java.util, Java 8+)
Optional<String> present = Optional.of("Hello");
Optional<String> empty = Optional.empty();
System.out.println("\nOptional : " + present.get()); // Hello
System.out.println("OrElse : " + empty.orElse("Default")); // Default
System.out.println("IsPresent : " + present.isPresent()); // true
present.ifPresent(v -> System.out.println("Value : " + v)); // Hello
// ā
UUID ā universally unique identifier
UUID id = UUID.randomUUID();
System.out.println("\nUUID : " + id); // e.g. 550e8400-e29b-41d4-a716-446655440000
// ā
Scanner ā reading input (java.util)
// Scanner sc = new Scanner(System.in);
// System.out.print("Enter name: ");
// String input = sc.nextLine();
// sc.close();
}
}java.io & java.nio ā Input/Output Operations
java.io is the original Java I/O package ā it provides stream-based I/O for reading and writing bytes and characters, working with files, and Java object serialization. java.nio (New I/O, introduced in Java 1.4) and its sub-package java.nio.file (Java 7) provide a modern, more powerful file API through Path, Files, and Paths. For new code, always prefer java.nio.file over java.io.File.
import java.io.*;
import java.nio.file.*;
import java.nio.charset.StandardCharsets;
import java.util.List;
public class JavaIOPackage {
public static void main(String[] args) throws IOException {
// ā
java.nio.file.Path & Files ā modern API (preferred)
Path filePath = Paths.get("demo_output.txt");
// Write lines to file
List<String> lines = List.of(
"Java Packages and API",
"Learning java.io and java.nio",
"TechSustainify Tutorial 2026"
);
Files.write(filePath, lines, StandardCharsets.UTF_8);
System.out.println("ā
File written: " + filePath.toAbsolutePath());
// Read all lines from file
List<String> readBack = Files.readAllLines(filePath, StandardCharsets.UTF_8);
System.out.println("Lines read: " + readBack.size());
readBack.forEach(line -> System.out.println(" > " + line));
// Check file properties using Files
System.out.println("Exists : " + Files.exists(filePath));
System.out.println("Size : " + Files.size(filePath) + " bytes");
System.out.println("Readable: " + Files.isReadable(filePath));
// Append to file
Files.writeString(filePath, "\nAppended line",
StandardOpenOption.APPEND);
System.out.println("ā
Appended to file");
// Read entire file as string
String content = Files.readString(filePath, StandardCharsets.UTF_8);
System.out.println("Full content:\n" + content);
// Copy, move, delete
Path copyPath = Paths.get("demo_copy.txt");
Files.copy(filePath, copyPath, StandardCopyOption.REPLACE_EXISTING);
System.out.println("ā
File copied to: " + copyPath);
Files.delete(copyPath);
System.out.println("ā
Copy deleted");
Files.delete(filePath);
System.out.println("ā
Original deleted");
// ā
java.io.BufferedReader ā character-based streaming read
String csvData = "Alice,30,HR\nBob,25,IT\nCarol,28,Finance";
try (BufferedReader br = new BufferedReader(
new StringReader(csvData))) { // StringReader for in-memory demo
String line;
System.out.println("\nBufferedReader:");
while ((line = br.readLine()) != null) {
String[] parts = line.split(",");
System.out.printf(" Name: %-8s Age: %s Dept: %s%n",
parts[0], parts[1], parts[2]);
}
} // Auto-closes BufferedReader ā try-with-resources
// ā
java.io.PrintWriter ā formatted text output
StringWriter sw = new StringWriter();
try (PrintWriter pw = new PrintWriter(sw)) {
pw.printf("%-10s %5s %10s%n", "Name", "Age", "Department");
pw.printf("%-10s %5d %10s%n", "Alice", 30, "HR");
pw.printf("%-10s %5d %10s%n", "Bob", 25, "IT");
}
System.out.println("\nPrintWriter output:\n" + sw);
// ā
Path operations
Path base = Paths.get("/home/user/projects/java");
System.out.println("\nPath operations:");
System.out.println(" FileName : " + base.getFileName()); // java
System.out.println(" Parent : " + base.getParent()); // /home/user/projects
System.out.println(" NameCount : " + base.getNameCount()); // 4
System.out.println(" Name(0) : " + base.getName(0)); // home
}
}java.math ā Arbitrary-Precision Arithmetic
java.math provides two critical classes for situations where primitive double and long are simply not precise enough: BigDecimal for exact decimal arithmetic (financial calculations, tax, currency) and BigInteger for arbitrarily large integers (cryptography, factorial of large numbers, number theory). These classes are slower than primitives but completely accurate ā never use double for money calculations.
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.math.RoundingMode;
public class JavaMathPackage {
public static void main(String[] args) {
// ā Why double fails for money ā classic precision bug
double d1 = 0.1 + 0.2;
System.out.println("double 0.1+0.2 = " + d1); // 0.30000000000000004 ā
System.out.println("double 1.03-0.42 = " + (1.03-0.42)); // 0.6100000000000001 ā
// ā
BigDecimal ā exact decimal arithmetic
BigDecimal b1 = new BigDecimal("0.1"); // ALWAYS use String constructor
BigDecimal b2 = new BigDecimal("0.2");
System.out.println("BigDecimal 0.1+0.2 = " + b1.add(b2)); // 0.3 ā
// ā
Financial calculation ā GST example
BigDecimal price = new BigDecimal("1499.00");
BigDecimal gstRate = new BigDecimal("0.18"); // 18% GST
BigDecimal gst = price.multiply(gstRate).setScale(2, RoundingMode.HALF_UP);
BigDecimal total = price.add(gst);
System.out.println("\n--- GST Calculation ---");
System.out.println("Base Price : ā¹" + price); // 1499.00
System.out.println("GST (18%) : ā¹" + gst); // 269.82
System.out.println("Total : ā¹" + total); // 1768.82
// ā
BigDecimal arithmetic operations
BigDecimal a = new BigDecimal("100.50");
BigDecimal b = new BigDecimal("33.25");
System.out.println("\nBigDecimal ops:");
System.out.println(" add : " + a.add(b)); // 133.75
System.out.println(" subtract : " + a.subtract(b)); // 67.25
System.out.println(" multiply : " + a.multiply(b)); // 3341.6250
System.out.println(" divide : " + a.divide(b, 4, RoundingMode.HALF_UP)); // 3.0226
System.out.println(" compareTo: " + a.compareTo(b)); // 1 (a > b)
System.out.println(" abs : " + new BigDecimal("-50.5").abs()); // 50.5
System.out.println(" scale : " + a.scale()); // 2
System.out.println(" precision: " + a.precision()); // 5
// ā
BigDecimal ā always use HALF_UP for rounding money
BigDecimal val = new BigDecimal("2.345");
System.out.println(" HALF_UP : " + val.setScale(2, RoundingMode.HALF_UP)); // 2.35
System.out.println(" HALF_DOWN: " + val.setScale(2, RoundingMode.HALF_DOWN)); // 2.34
System.out.println(" FLOOR : " + val.setScale(2, RoundingMode.FLOOR)); // 2.34
System.out.println(" CEILING : " + val.setScale(2, RoundingMode.CEILING)); // 2.35
// ā
BigInteger ā arbitrarily large integers
BigInteger big1 = new BigInteger("99999999999999999999999999999");
BigInteger big2 = new BigInteger("11111111111111111111111111111");
System.out.println("\n--- BigInteger ---");
System.out.println(" add : " + big1.add(big2));
System.out.println(" multiply : " + big1.multiply(big2));
System.out.println(" isPrime : " + big1.isProbablePrime(50));
// ā
Factorial of 50 ā impossible with long, trivial with BigInteger
BigInteger factorial = BigInteger.ONE;
for (int i = 2; i <= 50; i++) {
factorial = factorial.multiply(BigInteger.valueOf(i));
}
System.out.println(" 50! : " + factorial);
// 30414093201713378043612608166979581188299763898377856200000000000
// ā
MathContext ā precision control for BigDecimal division
BigDecimal x = new BigDecimal("10");
BigDecimal y = new BigDecimal("3");
BigDecimal result = x.divide(y, new MathContext(10));
System.out.println(" 10/3 (10 sig digits): " + result); // 3.333333333
}
}java.time ā The Modern Date & Time API
The java.time package (introduced in Java 8, JSR-310) is the definitive replacement for the legacy java.util.Date and Calendar classes, which were notoriously confusing, mutable, and poorly designed. java.time provides an immutable, thread-safe, and richly expressive API for dates, times, durations, periods, and time zones. In all new Java code, always use java.time ā never java.util.Date or java.util.Calendar.
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
public class JavaTimePackage {
public static void main(String[] args) {
// ā
LocalDate ā date without time or timezone
LocalDate today = LocalDate.now();
LocalDate birthday = LocalDate.of(1995, Month.JULY, 15);
LocalDate nextMonth = today.plusMonths(1);
LocalDate lastYear = today.minusYears(1);
System.out.println("Today : " + today);
System.out.println("Birthday : " + birthday);
System.out.println("Next month : " + nextMonth);
System.out.println("Last year : " + lastYear);
System.out.println("Day of week : " + today.getDayOfWeek()); // e.g. MONDAY
System.out.println("Day of year : " + today.getDayOfYear()); // 1-366
System.out.println("Is leap year : " + today.isLeapYear());
System.out.println("Before birthday: " + today.isBefore(birthday));
// ā
LocalTime ā time without date or timezone
LocalTime now = LocalTime.now();
LocalTime meetingTime = LocalTime.of(14, 30, 0); // 2:30 PM
LocalTime afterTwoHrs = meetingTime.plusHours(2);
System.out.println("\nCurrent time : " + now);
System.out.println("Meeting : " + meetingTime); // 14:30
System.out.println("+2 hours : " + afterTwoHrs); // 16:30
System.out.println("Hour : " + now.getHour());
System.out.println("Minute : " + now.getMinute());
// ā
LocalDateTime ā date + time, no timezone
LocalDateTime dateTime = LocalDateTime.now();
LocalDateTime deadline = LocalDateTime.of(2026, 12, 31, 23, 59, 59);
System.out.println("\nDateTime : " + dateTime);
System.out.println("Deadline : " + deadline);
System.out.println("Before deadline: " + dateTime.isBefore(deadline));
// ā
ZonedDateTime ā with timezone
ZonedDateTime istNow = ZonedDateTime.now(ZoneId.of("Asia/Kolkata"));
ZonedDateTime utcNow = ZonedDateTime.now(ZoneId.of("UTC"));
ZonedDateTime nyNow = ZonedDateTime.now(ZoneId.of("America/New_York"));
System.out.println("\nIST : " + istNow);
System.out.println("UTC : " + utcNow);
System.out.println("New York : " + nyNow);
// ā
Duration ā time-based amount (hours, minutes, seconds)
LocalTime start = LocalTime.of(9, 0);
LocalTime end = LocalTime.of(17, 30);
Duration workDay = Duration.between(start, end);
System.out.println("\nWork duration : " + workDay); // PT8H30M
System.out.println("Hours : " + workDay.toHours()); // 8
System.out.println("Minutes total : " + workDay.toMinutes()); // 510
// ā
Period ā date-based amount (years, months, days)
LocalDate dob = LocalDate.of(1995, 7, 15);
LocalDate refDay = LocalDate.of(2026, 3, 20);
Period age = Period.between(dob, refDay);
System.out.println("\nAge : " + age.getYears() + " years, "
+ age.getMonths() + " months, "
+ age.getDays() + " days");
long daysOld = ChronoUnit.DAYS.between(dob, refDay);
System.out.println("Days old : " + daysOld);
// ā
DateTimeFormatter ā formatting and parsing
DateTimeFormatter fmt1 = DateTimeFormatter.ofPattern("dd/MM/yyyy");
DateTimeFormatter fmt2 = DateTimeFormatter.ofPattern("dd MMMM yyyy, hh:mm a");
DateTimeFormatter iso = DateTimeFormatter.ISO_LOCAL_DATE;
System.out.println("\nFormatted : " + today.format(fmt1)); // 20/03/2026
System.out.println("Formatted : " + dateTime.format(fmt2));
System.out.println("ISO format : " + today.format(iso)); // 2026-03-20
// ā
Parsing a date string
LocalDate parsed = LocalDate.parse("25/12/2025",
DateTimeFormatter.ofPattern("dd/MM/yyyy"));
System.out.println("Parsed date : " + parsed); // 2025-12-25
}
}User-Defined Packages ā Structuring Your Own Code
Beyond using the Java API, you will create your own packages to organize your application's source code. A well-designed package structure reflects your application's architecture ā it groups classes by their responsibility and layer, makes navigation intuitive, and controls what is visible across package boundaries.
Layer-based (traditional): com.company.app.model, com.company.app.service, com.company.app.repository, com.company.app.controller, com.company.app.util, com.company.app.exception. Feature-based (modern, preferred for large systems): com.company.app.employee, com.company.app.payroll, com.company.app.reporting ā each feature package contains its own model, service, repository, and controller. The feature-based structure is favored in microservices and domain-driven design because each feature's code is co-located and can be independently extracted into a separate service.
Java packages are NOT hierarchical in terms of access ā a sub-package does NOT have special access to its parent package. com.example.app and com.example.app.service are completely separate, independent packages. Package-private members in com.example.app are NOT visible in com.example.app.service ā only public and protected members (with inheritance) are accessible. This surprises many beginners who assume sub-packages can access parent package internals. Each package is a completely isolated namespace ā only the naming convention (dots) creates the visual hierarchy; there is no actual inheritance of access rights.
Compiling: javac -d out src/com/techsustainify/model/Employee.java ā the -d flag specifies the output directory root; javac creates the matching subdirectory structure automatically. Running: java -cp out com.techsustainify.Main ā the fully qualified class name (package + class) is required. With multiple source files: javac -d out src/**/*.java or use a build tool (Maven, Gradle) which handles compilation, packaging, and dependency management automatically. In practice, you will almost always use a build tool ā manual javac commands are for learning purposes only.
// āā Project structure āāāāāāāāāāāāāāāāāāāāāāāāāā
// src/
// com/techsustainify/
// model/
// Product.java
// service/
// ProductService.java
// repository/
// ProductRepository.java
// exception/
// ProductNotFoundException.java
// Main.java
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
// ā
FILE: com/techsustainify/model/Product.java
package com.techsustainify.model;
import java.math.BigDecimal;
import java.util.Objects;
public class Product {
private final String id;
private final String name;
private final BigDecimal price;
private int stock;
public Product(String id, String name, BigDecimal price, int stock) {
this.id = Objects.requireNonNull(id, "id must not be null");
this.name = Objects.requireNonNull(name, "name must not be null");
this.price = Objects.requireNonNull(price, "price must not be null");
this.stock = stock;
}
public String getId() { return id; }
public String getName() { return name; }
public BigDecimal getPrice() { return price; }
public int getStock() { return stock; }
public void setStock(int s) { this.stock = s; }
@Override public String toString() {
return String.format("Product{id='%s', name='%s', price=%s, stock=%d}",
id, name, price, stock);
}
}
// ā
FILE: com/techsustainify/exception/ProductNotFoundException.java
package com.techsustainify.exception;
public class ProductNotFoundException extends RuntimeException {
public ProductNotFoundException(String productId) {
super("Product not found: " + productId);
}
}
// ā
FILE: com/techsustainify/repository/ProductRepository.java
package com.techsustainify.repository;
import com.techsustainify.model.Product;
import com.techsustainify.exception.ProductNotFoundException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
public class ProductRepository {
private final Map<String, Product> store = new HashMap<>();
public void save(Product p) { store.put(p.getId(), p); }
public Product findById(String id) {
Product p = store.get(id);
if (p == null) throw new ProductNotFoundException(id);
return p;
}
public Collection<Product> findAll() { return store.values(); }
public boolean exists(String id) { return store.containsKey(id); }
public void delete(String id) { store.remove(id); }
}
// ā
FILE: com/techsustainify/service/ProductService.java
package com.techsustainify.service;
import com.techsustainify.model.Product;
import com.techsustainify.repository.ProductRepository;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.UUID;
public class ProductService {
private final ProductRepository repo = new ProductRepository();
public Product addProduct(String name, BigDecimal price, int stock) {
String id = UUID.randomUUID().toString().substring(0, 8).toUpperCase();
Product p = new Product(id, name, price, stock);
repo.save(p);
System.out.println(" Added : " + p);
return p;
}
public Product getProduct(String id) { return repo.findById(id); }
public Collection<Product> getAllProducts() { return repo.findAll(); }
public void updateStock(String id, int delta) {
Product p = repo.findById(id);
p.setStock(p.getStock() + delta);
System.out.println(" Updated: " + p);
}
}
// ā
FILE: com/techsustainify/Main.java
package com.techsustainify;
import com.techsustainify.service.ProductService;
import com.techsustainify.model.Product;
import com.techsustainify.exception.ProductNotFoundException;
import java.math.BigDecimal;
public class Main {
public static void main(String[] args) {
ProductService svc = new ProductService();
Product laptop = svc.addProduct("Laptop Pro", new BigDecimal("89999.00"), 10);
Product phone = svc.addProduct("SmartPhone X", new BigDecimal("29999.00"), 25);
svc.addProduct("Wireless Mouse", new BigDecimal("999.00"), 50);
System.out.println("\nAll Products:");
svc.getAllProducts().forEach(p -> System.out.println(" " + p));
System.out.println("\nUpdate stock:");
svc.updateStock(laptop.getId(), -2); // Sold 2 laptops
System.out.println("\nGet specific product:");
Product found = svc.getProduct(phone.getId());
System.out.println(" Found: " + found.getName());
System.out.println("\nTry non-existent product:");
try {
svc.getProduct("INVALID_ID");
} catch (ProductNotFoundException e) {
System.out.println(" Error: " + e.getMessage());
}
}
}Access Modifiers & Package Visibility
Java's four access modifiers work in direct conjunction with packages. Understanding their interaction is essential for designing clean APIs and enforcing encapsulation. The package-private level (no modifier) is one of Java's most underused and most powerful encapsulation tools.
// ā
FILE: com/techsustainify/engine/QueryEngine.java
package com.techsustainify.engine;
public class QueryEngine {
// public ā visible to ALL packages
public String executeQuery(String sql) {
validate(sql);
String plan = buildPlan(sql);
return runPlan(plan);
}
// package-private ā visible ONLY inside com.techsustainify.engine
// Other packages cannot call this ā it is an internal implementation detail
String buildPlan(String sql) {
return "PLAN[" + sql.toUpperCase() + "]";
}
// package-private ā internal utility
boolean validate(String sql) {
if (sql == null || sql.isBlank()) {
throw new IllegalArgumentException("SQL cannot be blank");
}
return true;
}
// private ā visible only inside THIS class
private String runPlan(String plan) {
return "RESULT of " + plan;
}
}
// ā
FILE: com/techsustainify/engine/QueryOptimizer.java
// This is in the SAME package ā can access package-private members
package com.techsustainify.engine;
public class QueryOptimizer {
private final QueryEngine engine = new QueryEngine();
public String optimizeAndExecute(String sql) {
// ā
Can access buildPlan() ā same package
String plan = engine.buildPlan(sql);
System.out.println("Optimizing plan: " + plan);
return engine.executeQuery(sql);
}
}
// ā
FILE: com/techsustainify/api/QueryController.java
// DIFFERENT package ā can only access public members
package com.techsustainify.api;
import com.techsustainify.engine.QueryEngine;
public class QueryController {
private final QueryEngine engine = new QueryEngine();
public String handleQuery(String sql) {
// ā
CAN call executeQuery() ā it is public
return engine.executeQuery(sql);
// ā CANNOT call engine.buildPlan() ā package-private, different package
// ā CANNOT call engine.validate() ā package-private, different package
// Compile error: buildPlan() is not public in QueryEngine;
// cannot be accessed from outside package
}
}Static Import ā Using Static Members Without Class Prefix
Static import (introduced in Java 5) allows you to import static methods and fields directly, so they can be used without qualifying them with the class name. It is most useful for heavily used constants like Math.PI, test assertions like Assertions.assertEquals, and factory methods. Use it judiciously ā overuse makes code confusing because readers cannot tell which class a static member belongs to.
package com.techsustainify.demo;
// ā
Static import ā import specific static members
import static java.lang.Math.PI;
import static java.lang.Math.sqrt;
import static java.lang.Math.pow;
import static java.lang.Math.abs;
import static java.lang.Math.max;
import static java.lang.Math.min;
// ā
Static import of constants from a custom class
import static com.techsustainify.demo.AppConstants.MAX_RETRIES;
import static com.techsustainify.demo.AppConstants.DEFAULT_TIMEOUT_MS;
import static com.techsustainify.demo.AppConstants.APP_NAME;
// ā
Wildcard static import ā imports ALL static members
// import static java.lang.Math.*; // Use with care ā reduces clarity
public class StaticImport {
public static void main(String[] args) {
// ā
Using Math constants/methods WITHOUT Math. prefix
double radius = 5.0;
double area = PI * pow(radius, 2); // No Math.PI, Math.pow
double circum = 2 * PI * radius;
double hyp = sqrt(pow(3, 2) + pow(4, 2)); // 5.0
System.out.printf("Circle r=%.1f: area=%.2f, circumference=%.2f%n",
radius, area, circum);
System.out.println("Hypotenuse: " + hyp);
System.out.println("abs(-42) : " + abs(-42)); // 42
System.out.println("max(9,4) : " + max(9, 4)); // 9
System.out.println("min(9,4) : " + min(9, 4)); // 4
// ā
Using custom constants directly
System.out.println("\nApp: " + APP_NAME);
System.out.println("Max retries : " + MAX_RETRIES);
System.out.println("Timeout(ms) : " + DEFAULT_TIMEOUT_MS);
// ā
When static import helps vs hurts readability
// GOOD: abs(x) is universally understood
// GOOD: PI is universally understood
// BAD: processOrder(id) ā which class does processOrder come from?
// Rule: if the static member's name is self-documenting and globally
// recognizable, static import is fine. Otherwise, keep the class name.
}
}
// ā
AppConstants.java ā same package
// package com.techsustainify.demo;
// public class AppConstants {
// public static final int MAX_RETRIES = 3;
// public static final long DEFAULT_TIMEOUT_MS = 5000L;
// public static final String APP_NAME = "TechSustainify App";
// private AppConstants() { /* prevent instantiation */ }
// }Common Mistakes & Pitfalls
These are the most common package and import mistakes in Java ā from directory-package mismatches and forgotten imports, to the classic double precision bug that makes java.math.BigDecimal essential, to the fall-through of sub-package access rules.
// ā MISTAKE 1: Package declaration does not match directory
// File saved at: src/com/example/Employee.java
// But declares: package com.example.model;
// Compiler error: class Employee is public, should be in file Employee.java
// inside directory com/example/model/
// ā
Fix: either move file to com/example/model/ or change package to com.example
// ā MISTAKE 2: Placing package declaration after import
// import java.util.List;
// package com.example; // ā COMPILE ERROR ā package must be FIRST
// public class MyClass { }
// ā
Fix: package always comes before imports and class
// ā MISTAKE 3: Assuming wildcard import covers sub-packages
import java.util.*;
// java.util.* does NOT import java.util.concurrent.* or java.util.function.*
// ā ExecutorService executors = ...; // COMPILE ERROR ā not in java.util.*
// ā
Fix: explicitly import java.util.concurrent.ExecutorService
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
// ā MISTAKE 4: Assuming sub-package inherits parent package access
// com.example.service.UserService CANNOT access package-private members
// of com.example ā they are SEPARATE packages despite the naming
// ā
Fix: make the member public, or move the class into the same package
// ā MISTAKE 5: Using double for money ā precision bug
double price1 = 19.99;
double tax1 = 0.18;
double total1 = price1 * (1 + tax1);
System.out.println("double total: " + total1); // 23.588200000000003 ā WRONG
// ā
Fix: use BigDecimal for ALL financial arithmetic
import java.math.BigDecimal;
import java.math.RoundingMode;
BigDecimal price2 = new BigDecimal("19.99");
BigDecimal tax2 = new BigDecimal("0.18");
BigDecimal total2 = price2.multiply(BigDecimal.ONE.add(tax2))
.setScale(2, RoundingMode.HALF_UP);
System.out.println("BigDecimal total: " + total2); // 23.59 ā CORRECT
// ā MISTAKE 6: new BigDecimal(0.1) ā still uses double imprecision!
BigDecimal wrong = new BigDecimal(0.1); // ā Same floating-point imprecision
System.out.println("BD from double: " + wrong);
// 0.1000000000000000055511151231257827021181583404541015625 ā WRONG
BigDecimal right = new BigDecimal("0.1"); // ā
Always use String constructor
System.out.println("BD from String: " + right); // 0.1 ā CORRECT
// ā MISTAKE 7: Using java.util.Date for new code
// java.util.Date is mutable, poorly designed, and deprecated in spirit.
// ā
Fix: always use java.time.LocalDate / LocalDateTime / ZonedDateTime
import java.time.LocalDate;
LocalDate today = LocalDate.now(); // ā
Immutable, thread-safe, expressive
// ā MISTAKE 8: Import ambiguity ā both java.util.Date and java.sql.Date
// import java.util.*;
// import java.sql.*;
// Date d = new Date(); // ā COMPILE ERROR: reference to Date is ambiguous
// ā
Fix: use fully qualified names for ambiguous types
java.util.Date utilDate = new java.util.Date();
java.sql.Date sqlDate = new java.sql.Date(System.currentTimeMillis());Bad Practices & Anti-Patterns ā What Senior Developers Reject
These anti-patterns represent common misuses of packages and the Java API in professional code. Each one either causes maintenance problems, correctness bugs, or signals poor understanding of Java's design.
Placing all classes in the unnamed default package (no package declaration) is fine for tiny experiments but unacceptable in any real project. It makes classes impossible to import from other packages, prevents proper access control, causes IDE navigation chaos, and signals no understanding of Java project structure. Every production Java class must be in a named package following the reverse domain convention. Build tools like Maven and Gradle will also not package default-package classes correctly in JARs.
import java.util.*; is convenient during development but is rejected in most professional codebases and style guides (Google Java Style: specific imports only). Wildcard imports hide which classes are actually used, can cause silent ambiguity when two packages share a class name, and make code navigation harder ā you cannot Ctrl+Click an import to jump to the class. Enable 'Optimize Imports' in your IDE to auto-manage specific imports. The one exception: test code with import static org.junit.jupiter.api.Assertions.* is widely accepted because all assertions clearly belong to JUnit.
Using primitive double or float for any monetary amount ā price, tax, discount, interest rate, total ā is a critical correctness bug, not just a code smell. IEEE 754 floating-point arithmetic cannot represent most decimal fractions exactly: 0.1 + 0.2 == 0.3 is FALSE in Java. For all financial and currency arithmetic, use java.math.BigDecimal with String constructor and explicit RoundingMode. ALWAYS: new BigDecimal("19.99") ā NEVER: new BigDecimal(19.99). Passing a double to BigDecimal's double constructor defeats the purpose entirely.
java.util.Date and java.util.Calendar are deprecated in spirit ā they are mutable, poorly designed, non-thread-safe, and confusing (months in Calendar are 0-based). Since Java 8, the java.time package provides everything you need ā immutably, thread-safely, and expressively. New code should use LocalDate, LocalDateTime, ZonedDateTime, Duration, Period, and DateTimeFormatter exclusively. If you encounter a legacy API requiring java.util.Date, use the conversion methods: Date.from(instant) and date.toInstant(). Code reviews should reject any new java.util.Date usage.
Putting all classes in a single package (e.g., com.company.app with 100+ classes) eliminates the organizational and access-control benefits of packages entirely. It is the package equivalent of a 10,000-line God class. Split by layer (model, service, repository, controller) or by feature (user, product, order, payment). A good rule of thumb: if a package contains more than 15-20 classes, consider splitting it. Package structure should reflect your architecture ā if your architecture is well-designed, your package structure will be naturally modular.
Static import of non-obvious static members makes code cryptic. When a reader sees processOrder(id) in your code, they have no idea which class it belongs to ā they must search the imports or hover with an IDE. This slows comprehension and makes bugs harder to spot. Reserve static import for: universally known constants (PI, E, MAX_VALUE), test assertion methods (assertEquals, assertTrue in JUnit files), and factory/builder methods where the class name is obvious from context. Never static-import business logic methods or domain-specific utility methods.
Real-World Production Code Examples ā Packages & API in Context
The following examples show idiomatic, production-grade Java package and API usage ā patterns seen in real Spring Boot services, financial applications, and enterprise Java codebases.
package com.techsustainify.billing;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
public class InvoiceService {
private static final BigDecimal GST_RATE = new BigDecimal("0.18");
private static final DateTimeFormatter FMT =
DateTimeFormatter.ofPattern("dd MMM yyyy");
public record LineItem(String description, int qty, BigDecimal unitPrice) {
public BigDecimal subtotal() {
return unitPrice.multiply(BigDecimal.valueOf(qty))
.setScale(2, RoundingMode.HALF_UP);
}
}
public record Invoice(String invoiceNo, String customer,
LocalDate issueDate, LocalDate dueDate,
List<LineItem> items, BigDecimal subtotal,
BigDecimal gst, BigDecimal total) {}
public Invoice generateInvoice(String customer, List<LineItem> items) {
String invoiceNo = "INV-" + UUID.randomUUID().toString()
.substring(0, 8).toUpperCase();
LocalDate issueDate = LocalDate.now();
LocalDate dueDate = issueDate.plusDays(30);
// BigDecimal accumulation ā never double
BigDecimal subtotal = items.stream()
.map(LineItem::subtotal)
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal gst = subtotal.multiply(GST_RATE)
.setScale(2, RoundingMode.HALF_UP);
BigDecimal total = subtotal.add(gst);
return new Invoice(invoiceNo, customer, issueDate, dueDate,
Collections.unmodifiableList(items),
subtotal, gst, total);
}
public void printInvoice(Invoice inv) {
System.out.println("āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā");
System.out.printf( "ā INVOICE: %-35sā%n", inv.invoiceNo());
System.out.printf( "ā Customer: %-34sā%n", inv.customer());
System.out.printf( "ā Issued : %-34sā%n", inv.issueDate().format(FMT));
System.out.printf( "ā Due : %-34sā%n", inv.dueDate().format(FMT));
System.out.println("ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā£");
System.out.printf( "ā %-25s %5s %12sā%n", "Description", "Qty", "Amount");
System.out.println("āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā");
for (LineItem item : inv.items()) {
System.out.printf("ā %-25s %5d %11s ā%n",
item.description(), item.qty(), "ā¹" + item.subtotal());
}
System.out.println("ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā£");
System.out.printf( "ā %-33s ā¹%9sā%n", "Subtotal", inv.subtotal());
System.out.printf( "ā %-33s ā¹%9sā%n", "GST (18%)", inv.gst());
System.out.printf( "ā %-33s ā¹%9sā%n", "TOTAL", inv.total());
System.out.println("āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā");
}
public static void main(String[] args) {
InvoiceService svc = new InvoiceService();
List<LineItem> items = List.of(
new LineItem("Java Course (Annual)", 1, new BigDecimal("4999.00")),
new LineItem("Tech eBooks Bundle", 2, new BigDecimal("799.00")),
new LineItem("Premium Support (3 mo)", 1, new BigDecimal("1499.00"))
);
Invoice inv = svc.generateInvoice("Rahul Sharma", items);
svc.printInvoice(inv);
}
}package com.techsustainify.audit;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
public class UserActivityLogger {
private static final DateTimeFormatter LOG_FMT =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public record ActivityEvent(String userId, String action,
ZonedDateTime timestamp, Map<String, String> metadata) {
@Override public String toString() {
return String.format("[%s] USER=%s ACTION=%s META=%s",
timestamp.withZoneSameInstant(ZoneId.of("Asia/Kolkata"))
.format(LOG_FMT),
userId, action, metadata);
}
}
// ConcurrentHashMap from java.util.concurrent ā thread-safe
private final Map<String, List<ActivityEvent>> eventStore =
new ConcurrentHashMap<>();
public void log(String userId, String action, Map<String, String> meta) {
ActivityEvent event = new ActivityEvent(
userId, action,
ZonedDateTime.now(ZoneId.of("Asia/Kolkata")),
Collections.unmodifiableMap(meta)
);
eventStore.computeIfAbsent(userId, k -> Collections.synchronizedList(
new ArrayList<>())).add(event);
System.out.println(" Logged: " + event);
}
public List<ActivityEvent> getRecentActivity(String userId, int lastNMinutes) {
ZonedDateTime cutoff = ZonedDateTime.now(ZoneId.of("Asia/Kolkata"))
.minus(lastNMinutes, ChronoUnit.MINUTES);
return eventStore.getOrDefault(userId, Collections.emptyList())
.stream()
.filter(e -> e.timestamp().isAfter(cutoff))
.collect(Collectors.toList());
}
public Map<String, Long> getActionSummary(String userId) {
return eventStore.getOrDefault(userId, Collections.emptyList())
.stream()
.collect(Collectors.groupingBy(
ActivityEvent::action,
Collectors.counting()));
}
public static void main(String[] args) {
UserActivityLogger logger = new UserActivityLogger();
logger.log("user_101", "LOGIN", Map.of("ip", "192.168.1.10", "device", "Chrome"));
logger.log("user_101", "VIEW_PAGE", Map.of("page", "/dashboard"));
logger.log("user_101", "PURCHASE", Map.of("item", "Java Course", "amount", "4999"));
logger.log("user_101", "VIEW_PAGE", Map.of("page", "/profile"));
logger.log("user_102", "LOGIN", Map.of("ip", "10.0.0.5", "device", "Firefox"));
logger.log("user_101", "LOGOUT", Map.of("session_duration", "45min"));
System.out.println("\nAction summary for user_101:");
logger.getActionSummary("user_101").forEach((action, count) ->
System.out.printf(" %-12s: %d time(s)%n", action, count));
System.out.println("\nRecent activity (last 60 min):");
logger.getRecentActivity("user_101", 60).forEach(
e -> System.out.println(" " + e.action() + " @ " +
e.timestamp().format(DateTimeFormatter.ofPattern("HH:mm:ss"))));
}
}Package Structure Flow Diagram
This diagram shows how a Java source file's package declaration, import statements, and class definition relate to each other, and how the Java compiler resolves class references using packages and the classpath.
Code Execution Flow ā from source to output
Java Packages & API Interview Questions ā Beginner to Advanced
Package and API questions test your understanding of Java's namespace system, visibility rules, the standard library, and practical use of key API classes. These appear consistently in Java interviews at all experience levels.
Practice Questions ā Test Your Packages & API Knowledge
Challenge yourself with these practice questions. Attempt each independently before reading the answer ā writing and tracing code by hand significantly improves retention and interview readiness.
1. A Java file is saved at src/com/example/util/StringHelper.java. What must the first executable statement in this file be?
Easy2. What is the output of this code and why? System.out.println(0.1 + 0.2 == 0.3); System.out.println(new BigDecimal("0.1").add(new BigDecimal("0.2")).compareTo(new BigDecimal("0.3")));
Medium3. Write a program using java.time to calculate how many days until the next New Year (January 1st of next year) from today's date.
Easy4. Explain the output: package com.example.a; public class A { String secret = "hidden"; // package-private public String visible = "public"; } package com.example.b; import com.example.a.A; public class B { public static void main(String[] args) { A a = new A(); System.out.println(a.visible); // Line A System.out.println(a.secret); // Line B } }
Medium5. Using java.util, write a program that: creates a list of 5 student names, sorts them alphabetically, removes all names shorter than 4 characters, and prints the final list.
Easy6. What is wrong with this code? How many errors can you find? // File: src/MyApp.java import java.util.ArrayList; package com.example; public class MyApp { public static void main(String[] args) { BigDecimal price = new BigDecimal(9.99); } }
Medium7. Write a program using java.math.BigDecimal to calculate compound interest: P = 10000, rate = 8.5% per annum, time = 3 years. Formula: A = P * (1 + r)^n. Round to 2 decimal places.
Medium8. How would you properly organize a simple e-commerce application into packages? Describe the package structure and what each package contains.
HardConclusion ā Packages & API: Java's Organizational Backbone
Packages are not just a code organization tool ā they are Java's fundamental namespace, visibility control, and architectural expression mechanism. Every professional Java codebase is a carefully designed package hierarchy that reflects the application's architecture, enforces encapsulation through access control, and prevents naming conflicts at scale. How you organize your packages reveals how clearly you understand your system's design.
The Java API ā hundreds of packages and thousands of battle-tested classes ā is one of Java's greatest strengths. Before writing any utility code, ask: does the Java API already have this? The answer is almost always yes. java.time for dates, java.math.BigDecimal for money, java.util for collections, java.nio.file for files ā these are not optional libraries but core skills every Java developer must master. Knowing the API deeply is what separates a Java programmer from a Java developer.
Your next step: Java Arrays ā the foundational data structure where everything you have learned about loops, break/continue, and the Java API comes together for traversal, searching, sorting, and manipulation. ā