Professional Java Practices

Organizing Java Applications with Packages

ReadingPreview

You are viewing a free preview lesson.

Lesson Overview

ছোট Java program অনেক সময় এক বা দুইটি class দিয়েই শুরু হয়।

Example:

Main.java
Course.java

কিন্তু application বড় হলে dozens বা hundreds of classes তৈরি হতে পারে।

তখন প্রশ্ন আসে:

কোন class কোথায় থাকবে?
কোন class কোন feature-এর অংশ?
কোন class অন্য class access করতে পারবে?
একই নামের class থাকলে কী হবে?
Project structure কীভাবে maintainable রাখা যায়?

Java এই organization-এর জন্য provide করে:

package

Packages শুধু folder structure নয়।

এগুলো help করে:

  • Related classes group করতে
  • Naming conflicts avoid করতে
  • Access boundaries তৈরি করতে
  • Codebase navigate করতে
  • Application architecture communicate করতে

এই lesson-এ আমরা শিখব:

  • Package কী
  • Package declaration
  • Package naming conventions
  • Directory structure
  • import
  • Fully qualified class names
  • Same-package access
  • public
  • Package-private access
  • protected সম্পর্কে প্রয়োজনীয় context
  • Feature-based package organization
  • Layer-based organization
  • Avoiding giant packages
  • Avoiding unnecessary package fragmentation
  • Designing clear package boundaries

Learning Objectives

এই lesson শেষে আপনি পারবেন:

  • Java package-এর purpose explain করতে
  • Correct package declaration লিখতে
  • Package এবং directory structure align করতে
  • import ব্যবহার করতে
  • Fully qualified class name বুঝতে
  • Package-private access explain করতে
  • Related classes logically group করতে
  • Reasonable Java project package structure design করতে
  • Over-organization এবং under-organization দুইটাই avoid করতে

What Is a Package?

A package is a namespace used to organize Java types.

A type হতে পারে:

class
interface
enum
record
annotation

Example:

package io.liveklass.course;

এর অর্থ:

এই type io.liveklass.course package-এর অংশ

Basic Example

package io.liveklass.course;

public class Course {

}

The fully qualified name of this class is:

io.liveklass.course.Course

Why Packages Exist

Suppose two libraries both have:

User

Without namespaces:

Which User?

Packages make them distinct:

io.liveklass.user.User
com.example.security.User

Both classes can exist in the same application.


Package as a Namespace

Consider:

io.liveklass.course.Course

and:

io.liveklass.catalog.Course

Java considers them different types because their fully qualified names are different.

Package is part of type identity.


Package Declaration

The package declaration normally appears at the top of the file:

package io.liveklass.course;

public class Course {

}

Imports come after package declaration:

package io.liveklass.course;

import java.util.List;

public class Course {

}

Typical order:

package
imports
type declaration

Package and Directory Structure

For:

package io.liveklass.course;

the source file is normally stored under:

src/main/java/io/liveklass/course/

Example:

src/
└── main/
    └── java/
        └── io/
            └── liveklass/
                └── course/
                    └── Course.java

This convention is followed by standard Java build tools and IDEs.


Package Naming Convention

Java package names are normally lowercase.

Good:

io.liveklass.course
io.liveklass.enrollment
io.liveklass.shared

Avoid:

io.LiveKlass.Course
IO.LIVEKLASS.COURSE

Reverse Domain Naming

A common convention starts with a reversed domain name.

For:

liveklass.io

package root can be:

io.liveklass

Then features:

io.liveklass.course
io.liveklass.lesson
io.liveklass.enrollment

This reduces naming collisions between organizations.


Do Not Overthink the Domain Prefix

The important part is consistency.

For a learning project:

io.liveklass

is enough.

You do not need unnecessarily deep roots such as:

io.liveklass.application.backend.platform.course.management.internal.core

Long package names do not automatically create better architecture.


Package Names Should Communicate Meaning

Weak:

io.liveklass.misc
io.liveklass.stuff
io.liveklass.common2

Better:

io.liveklass.course
io.liveklass.lesson
io.liveklass.enrollment

A developer should be able to guess what kinds of classes exist in a package.


The Default Package

Java technically allows a class with no package declaration:

public class Main {

}

This is called the:

default package

Avoid it for real applications.

Why?

  • Poor organization
  • Difficult reuse across packages
  • Build/tooling limitations
  • No meaningful namespace

Use explicit packages.


What Is import?

Suppose we have:

java.util.List

Without an import:

java.util.List<String> names;

With:

import java.util.List;

you can write:

List<String> names;

Basic Import Example

package io.liveklass.course;

import java.util.ArrayList;
import java.util.List;

public class Course {

    private final List<String> lessons =
            new ArrayList<>();
}

Import Does Not Copy Code

Important misconception:

import java.util.List;

does not:

Load List into your class
Copy List source code
Create a dependency at runtime by itself

It mainly allows you to reference a type using its short name:

List

instead of:

java.util.List

java.lang Is Automatically Available

Types in:

java.lang

do not need explicit imports.

Examples:

String
Object
Integer
Long
RuntimeException
System

You normally do not write:

import java.lang.String;

Same-Package Types Do Not Need Import

Suppose:

package io.liveklass.course;

public class Course {

}

and:

package io.liveklass.course;

public class CourseService {

    private Course course;
}

No:

import io.liveklass.course.Course;

is required because both classes belong to the same package.


Import from Another Package

Suppose:

package io.liveklass.repository;

public interface CourseRepository {

}

Then:

package io.liveklass.course;

import io.liveklass.repository.CourseRepository;

public class CourseService {

    private final CourseRepository repository;

    public CourseService(
            CourseRepository repository
    ) {
        this.repository =
                repository;
    }
}

Now the import is needed because the interface is in another package.


Fully Qualified Class Name

A fully qualified class name includes the complete package:

java.util.List
io.liveklass.course.Course

You can use it directly:

java.util.List<String> names =
        new java.util.ArrayList<>();

This is valid but verbose.


When Fully Qualified Names Are Useful

Mostly when two imported types have the same short name.

Suppose:

java.util.Date
java.sql.Date

Both are named:

Date

You cannot import both and use only Date unambiguously.

You may write:

java.util.Date createdAt =
        new java.util.Date();

java.sql.Date databaseDate =
        ...;

Wildcard Imports

Java allows:

import java.util.*;

This means types under:

java.util

can be referenced without individual imports.

But wildcard import does not recursively import subpackages.

For example:

java.util.*

does not import:

java.util.concurrent.*

Individual Imports vs Wildcards

Many teams prefer:

import java.util.List;
import java.util.Map;
import java.util.Set;

because dependencies are explicit.

Modern IDEs can manage imports automatically.

Follow project conventions.


Static Imports

Java also supports:

import static java.lang.Math.max;

Then:

int result =
        max(
                10,
                20
        );

instead of:

Math.max(
        10,
        20
);

Use Static Imports Carefully

They can improve readability in some contexts:

Tests
Constants
Mathematical utilities

But excessive static imports can hide where methods come from.

Weak:

save();
load();
create();
update();

if nobody can tell which class provides them.

Clarity matters more than saving a few characters.


Packages Also Affect Access

Packages are not only organizational namespaces.

They also participate in Java access control.

We already know:

public
private
protected

Java also has:

package-private

which has no explicit keyword.


Package-Private Access

Example:

package io.liveklass.course;

final class CourseValidator {

}

Notice there is no:

public

before class.

This class is package-private.

Only code in:

io.liveklass.course

can directly access it.


Package-Private Method

public final class Course {

    void markInternalReviewComplete() {

    }
}

Because the method has no access modifier, it is package-private.

Classes in the same package can call it.

Classes in another package cannot.


Why Package-Private Is Useful

Suppose:

CourseValidator
CourseMapper
CourseFileName

are implementation details used only inside the course package.

Making all of them:

public

exposes unnecessary API surface.

Better:

final class CourseValidator {

}

Package-private access communicates:

Internal to this package

Public API Surface

A package may contain many classes but expose only a few public ones.

Example:

io.liveklass.course
├── Course.java                 public
├── CourseService.java          public
├── CourseValidator.java        package-private
├── CourseTitleNormalizer.java  package-private
└── CourseRules.java            package-private

External packages depend on:

Course
CourseService

Implementation details remain internal.


Why Smaller Public Surface Is Good

Every public type creates a potential dependency.

If many unrelated classes use:

CourseTitleNormalizer

directly, later refactoring becomes harder.

Keeping internal implementation package-private reduces accidental coupling.


private

private is narrower than package-private.

Example:

public final class Course {

    private String normalizeTitle(
            String title
    ) {
        return title.strip();
    }
}

Only the containing class can call this method.


public

public means the type or member can generally be accessed from other packages, assuming the enclosing type is also accessible.

Example:

public final class CourseService {

    public Course findCourse(...) {

    }
}

protected

protected access involves:

  • Same package
  • Subclasses under specific inheritance rules

It is most relevant when designing inheritance.

Do not use protected simply as:

Almost public

If a class should expose behavior publicly, use public.

If it is internal to a package, package-private may be more appropriate.


Access Comparison

Simplified:

ModifierSame ClassSame PackageSubclass Outside PackageOther Package
privateYesNoNoNo
package-privateYesYesNoNo
protectedYesYesYes*No
publicYesYesYesYes

protected has additional inheritance rules, so this table is intentionally simplified.


Packages as Design Boundaries

A package can communicate:

These types belong together

Example:

io.liveklass.course

may contain:

Course
CourseCode
CourseStatus
CourseService

This forms a meaningful feature boundary.


Organizing by Technical Layer

A common beginner project structure:

io.liveklass
├── model
├── service
├── repository
├── exception
└── util

Example:

model/
    Course.java
    Lesson.java
    Enrollment.java

service/
    CourseService.java
    LessonService.java
    EnrollmentService.java

repository/
    CourseRepository.java
    LessonRepository.java

This is called:

layer-based organization

Problem with Large Layer-Based Packages

As application grows:

model/

may contain:

Course
Lesson
Enrollment
Payment
Organization
Subscription
User
Address
...

Everything gets grouped by technical category rather than business meaning.

Finding all course-related code now requires jumping across multiple packages.


Feature-Based Organization

Another approach:

io.liveklass
├── course
├── lesson
├── enrollment
└── payment

Each feature owns related types.

Example:

course/
    Course.java
    CourseCode.java
    CourseStatus.java
    CourseRepository.java
    CourseService.java

This groups code by domain capability.


Feature-Based Organization Is Often Easier to Navigate

If you are working on:

Course

most related code is near:

io.liveklass.course

Instead of jumping through:

model
service
repository
exception
validator

But Do Not Turn Every Class into Its Own Package

Over-fragmented:

io.liveklass.course.model
io.liveklass.course.model.value
io.liveklass.course.service
io.liveklass.course.service.internal
io.liveklass.course.repository
io.liveklass.course.repository.file
io.liveklass.course.validation
io.liveklass.course.validation.internal

for only six classes is unnecessary.

Start simple.

Split when the package becomes meaningfully crowded.


A Reasonable Small Application Structure

io.liveklass
├── Main.java
├── course/
│   ├── Course.java
│   ├── CourseCode.java
│   ├── CourseStatus.java
│   ├── CourseRepository.java
│   └── CourseService.java
└── enrollment/
    ├── Enrollment.java
    ├── EnrollmentRepository.java
    └── EnrollmentService.java

This is enough for many small applications.


Splitting a Growing Feature

If course grows to dozens of classes, you might create:

io.liveklass.course
├── Course.java
├── CourseCode.java
├── CourseStatus.java
├── CourseService.java
├── repository/
└── storage/

Only split when it makes navigation clearer.


Package by Responsibility Inside a Feature

A larger feature may eventually look like:

io.liveklass.course
├── Course.java
├── CourseCode.java
├── CourseStatus.java
├── CourseService.java
├── CourseRepository.java
└── storage/
    └── FileCourseRepository.java

This communicates:

CourseRepository = feature contract
FileCourseRepository = storage implementation

Package Dependency Direction

Package organization should also help clarify dependency direction.

Example:

course
↓
course.storage

But be careful.

If:

CourseService

directly depends on:

FileCourseRepository

then business logic becomes coupled to a particular storage implementation.

Better:

CourseService
        ↓
CourseRepository
        ↑
FileCourseRepository

Example Repository Contract in Feature Package

package io.liveklass.course;

public interface CourseRepository {

    Course findByCode(
            CourseCode courseCode
    );

    void save(
            Course course
    );
}

Implementation:

package io.liveklass.course.storage;

import io.liveklass.course.Course;
import io.liveklass.course.CourseCode;
import io.liveklass.course.CourseRepository;

public final class FileCourseRepository
        implements CourseRepository {

}

This can make the dependency direction clearer.


Avoid util as a Dumping Ground

A package named:

util

often becomes:

Everything we did not know where to put

Example:

DateUtil
StringUtil
CourseUtil
FileUtil
ValidationUtil
PriceUtil

This can become a design smell.


Prefer Meaningful Ownership

Instead of:

util/CourseFileUtil.java

consider:

course/storage/CourseContentStorage.java

Instead of:

util/PriceUtil.java

maybe behavior belongs in:

Money

or:

CoursePrice

Not every reusable method needs a util package.


Avoid common Becoming Another Dumping Ground

Packages such as:

common
shared
core

can be legitimate.

But they should not mean:

Anything used by more than one feature

Shared code should have a clear reason for being shared.


A Better Question

Before moving a class to:

shared

ask:

Does this concept genuinely belong to multiple features?

not:

Can multiple features technically call it?

Example: Validation Helper

Suppose:

CourseTitleValidator

is only meaningful for courses.

It belongs in:

course

not:

shared.validation

just because it performs validation.


Keep Related Changes Together

Good package organization helps developers make changes locally.

Suppose a requirement says:

Course code validation changes

Ideally relevant types are near each other:

CourseCode
Course
CourseRepository

This lowers cognitive load.


Package Name Should Not Describe Implementation Forever

Example:

course.file

may be fine if it specifically contains file implementation.

But:

course.temporary
course.new
course.v2

usually indicates unstable naming.

Names should describe durable concepts.


Avoid Version Packages Unless Necessary

Weak:

io.liveklass.course.v1
io.liveklass.course.v2

for internal implementation changes.

Versioning belongs in package names only when versions are truly independent public APIs or long-lived contracts.


Circular Package Dependencies

Imagine:

course
depends on enrollment

enrollment
depends on course

Some domain relationships naturally reference each other, but excessive circular package dependencies often indicate unclear boundaries.

Example:

CourseService

calls:

EnrollmentService

and:

EnrollmentService

calls:

CourseService

This can become difficult to reason about.


Reduce Circular Dependencies with Smaller Contracts

Instead of:

EnrollmentService

depending on the entire:

CourseService

it might depend on a smaller contract:

CourseLookup

Example:

public interface CourseLookup {

    boolean exists(
            CourseCode courseCode
    );
}

Only introduce this when there is a genuine dependency problem.

Do not create interfaces preemptively for every class.


Package Structure Should Follow the Application

There is no universal perfect package structure.

A good package structure should optimize for:

  • Discoverability
  • Cohesion
  • Clear ownership
  • Controlled dependencies
  • Reasonable visibility

Not:

Maximum number of folders

Cohesion

Cohesion means related responsibilities stay together.

High cohesion:

course/
    Course
    CourseCode
    CourseStatus
    CourseService

Low cohesion:

misc/
    CourseCode
    CsvExporter
    DateParser
    PaymentValidator

Coupling

Coupling means how strongly one part depends on another.

Packages should avoid unnecessary coupling.

Example:

course

should not depend on:

file storage
HTTP controller
console printing

unless those concerns are intentionally part of the package.


Package Design and Testing

Good packages make tests easier to organize.

Typical structure:

src/
├── main/
│   └── java/
│       └── io/liveklass/course/
│           └── Course.java
└── test/
    └── java/
        └── io/liveklass/course/
            └── CourseTest.java

The test package often mirrors production package structure.


Package-Private and Tests

If a test is declared in the same package:

package io.liveklass.course;

it can access package-private members.

This can occasionally be useful.

But do not expose internals purely for testing if behavior can be tested through the public API.


Example: Poorly Organized Application

io.liveklass
├── Main.java
├── Course.java
├── CourseService.java
├── FileHelper.java
├── Lesson.java
├── Enrollment.java
├── EnrollmentService.java
├── StringHelper.java
├── CourseRepository.java
└── FileCourseRepository.java

As the project grows, the root package becomes crowded.


Improved Structure

io.liveklass
├── Main.java
├── course/
│   ├── Course.java
│   ├── CourseCode.java
│   ├── CourseRepository.java
│   ├── CourseService.java
│   └── storage/
│       └── FileCourseRepository.java
├── lesson/
│   └── Lesson.java
└── enrollment/
    ├── Enrollment.java
    └── EnrollmentService.java

Now feature ownership is easier to see.


Example Package Declaration

Course.java

package io.liveklass.course;

public final class Course {

}

CourseRepository.java

package io.liveklass.course;

public interface CourseRepository {

}

FileCourseRepository.java

package io.liveklass.course.storage;

import io.liveklass.course.CourseRepository;

public final class FileCourseRepository
        implements CourseRepository {

}

Package-Private Helper Example

package io.liveklass.course;

final class CourseTitleNormalizer {

    String normalize(
            String title
    ) {
        if (title == null) {
            throw new IllegalArgumentException(
                    "Title is required."
            );
        }

        return title.strip();
    }
}

External code cannot directly depend on it.


Public Service Using an Internal Helper

package io.liveklass.course;

public final class CourseService {

    private final CourseTitleNormalizer normalizer;

    public CourseService() {
        this.normalizer =
                new CourseTitleNormalizer();
    }

    public String normalizeTitle(
            String title
    ) {
        return normalizer.normalize(
                title
        );
    }
}

Only:

CourseService

needs to be part of the public package API.


Don't Make Everything Public

Beginner code often uses:

public class ...
public method ...
public field ...

for everything.

That creates a wide API surface.

Ask:

Does another package actually need this?

If not, package-private or private may be better.


But Don't Hide Things Arbitrarily

Making everything package-private can also hurt design.

Public application capabilities should remain public.

Example:

public interface CourseRepository

may need to be implemented by another package.

So visibility should reflect intended usage.


Naming Classes Inside Packages

If package already provides context:

io.liveklass.course

you usually do not need:

CourseCourseService
CourseCourseValidator

Use:

CourseService
CourseValidator

Avoid repetitive naming.


Avoid Generic Names Without Context

Within:

io.liveklass.course

a class named:

Manager

is still vague.

Prefer:

CourseService
CoursePublisher
CourseCatalog

Names should explain responsibility.


When Should You Create a New Package?

Useful signals:

  • Package has many unrelated responsibilities
  • A clear sub-capability has emerged
  • Several classes are always changed together
  • Internal implementation should be hidden from public feature API
  • Navigation is becoming difficult

Not a good reason:

There are three classes

alone.


When Should You Merge Packages?

Consider merging if:

  • Each package contains one trivial class
  • Names differ but responsibilities are tightly coupled
  • Developers constantly jump between packages
  • Package boundaries provide no meaningful access or dependency benefit

Package Structure Evolves

A small project may start:

io.liveklass.course

Later:

io.liveklass.course.storage
io.liveklass.course.importing

This is normal.

You do not need to predict the final architecture on day one.


A Practical Starting Rule

For a small application:

Organize primarily by feature.
Keep related domain types together.
Split implementation details only when complexity justifies it.

This provides a strong default without overengineering.


Complete Example

Project:

src/main/java/
└── io/liveklass/
    ├── Main.java
    └── course/
        ├── Course.java
        ├── CourseCode.java
        ├── CourseRepository.java
        ├── CourseService.java
        └── storage/
            └── InMemoryCourseRepository.java

CourseCode.java

package io.liveklass.course;

import java.util.Objects;

public final class CourseCode {

    private final String value;

    public CourseCode(
            String value
    ) {
        if (
                value == null
                || value.isBlank()
        ) {
            throw new IllegalArgumentException(
                    "Course code is required."
            );
        }

        this.value =
                value.strip()
                        .toUpperCase();
    }

    public String getValue() {
        return value;
    }

    @Override
    public boolean equals(
            Object other
    ) {
        if (this == other) {
            return true;
        }

        if (
                !(other
                instanceof CourseCode courseCode)
        ) {
            return false;
        }

        return value.equals(
                courseCode.value
        );
    }

    @Override
    public int hashCode() {
        return Objects.hash(
                value
        );
    }
}

Course.java

package io.liveklass.course;

public final class Course {

    private final CourseCode code;
    private final String title;

    public Course(
            CourseCode code,
            String title
    ) {
        if (code == null) {
            throw new IllegalArgumentException(
                    "Course code is required."
            );
        }

        if (
                title == null
                || title.isBlank()
        ) {
            throw new IllegalArgumentException(
                    "Course title is required."
            );
        }

        this.code = code;
        this.title = title.strip();
    }

    public CourseCode getCode() {
        return code;
    }

    public String getTitle() {
        return title;
    }
}

CourseRepository.java

package io.liveklass.course;

import java.util.List;

public interface CourseRepository {

    void save(
            Course course
    );

    Course findByCode(
            CourseCode courseCode
    );

    List<Course> findAll();
}

CourseService.java

package io.liveklass.course;

import java.util.List;

public final class CourseService {

    private final CourseRepository repository;

    public CourseService(
            CourseRepository repository
    ) {
        if (repository == null) {
            throw new IllegalArgumentException(
                    "Course repository is required."
            );
        }

        this.repository =
                repository;
    }

    public void createCourse(
            CourseCode code,
            String title
    ) {
        Course existing =
                repository.findByCode(
                        code
                );

        if (existing != null) {
            throw new IllegalStateException(
                    "Course already exists."
            );
        }

        repository.save(
                new Course(
                        code,
                        title
                )
        );
    }

    public List<Course> findAll() {
        return repository.findAll();
    }
}

InMemoryCourseRepository.java

package io.liveklass.course.storage;

import io.liveklass.course.Course;
import io.liveklass.course.CourseCode;
import io.liveklass.course.CourseRepository;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public final class InMemoryCourseRepository
        implements CourseRepository {

    private final Map<CourseCode, Course> courses =
            new LinkedHashMap<>();

    @Override
    public void save(
            Course course
    ) {
        courses.put(
                course.getCode(),
                course
        );
    }

    @Override
    public Course findByCode(
            CourseCode courseCode
    ) {
        return courses.get(
                courseCode
        );
    }

    @Override
    public List<Course> findAll() {
        return new ArrayList<>(
                courses.values()
        );
    }
}

Main.java

package io.liveklass;

import io.liveklass.course.CourseCode;
import io.liveklass.course.CourseRepository;
import io.liveklass.course.CourseService;
import io.liveklass.course.storage.InMemoryCourseRepository;

public class Main {

    public static void main(
            String[] args
    ) {
        CourseRepository repository =
                new InMemoryCourseRepository();

        CourseService service =
                new CourseService(
                        repository
                );

        service.createCourse(
                new CourseCode(
                        "JAVA-OOP"
                ),
                "Java and OOP Foundation"
        );

        service.findAll()
                .forEach(
                        course ->
                                System.out.println(
                                        course.getTitle()
                                )
                );
    }
}

What This Structure Communicates

io.liveklass.course

owns:

Course domain concepts
Repository contract
Course application behavior
io.liveklass.course.storage

contains a persistence implementation.

io.liveklass

contains application entry point.

This is already enough structure for a small application.


Common Mistakes

Putting Everything in the Root Package

Becomes difficult to navigate as the codebase grows.


Creating Too Many Packages Too Early

Adds navigation overhead without improving design.


Making Every Class public

Creates unnecessary dependencies between packages.


Using util as a Dumping Ground

Often hides unclear ownership.


Grouping Only by Technical Layer Forever

Can scatter feature-related code across the codebase.


Creating Deep Package Hierarchies for Prestige

More folders do not mean better architecture.


Hardcoding Package Names Based on Temporary Implementation

Package names should represent durable concepts.


Ignoring Dependency Direction

A clean folder tree does not help if business code directly depends on infrastructure details.


Using the Default Package

Fine for tiny experiments, poor for structured applications.


Practice Exercises

Exercise 1: Create Package Structure

Organize these classes:

Course
CourseCode
CourseService
CourseRepository
FileCourseRepository
Enrollment
EnrollmentService
Main

Create a reasonable package structure.


Exercise 2: Identify Public Types

For package:

io.liveklass.course

decide whether these should be public or package-private:

Course
CourseService
CourseTitleNormalizer
CourseRepository

Explain your reasoning.


Exercise 3: Fix a Dumping-Ground Package

Given:

io.liveklass.util
├── CourseValidator
├── PaymentCalculator
├── EnrollmentFormatter
└── FileCourseRepository

Move each class to a more meaningful package.


Exercise 4: Import Practice

Given:

io.liveklass.course.Course
io.liveklass.course.CourseService
io.liveklass.repository.CourseRepository

Write the imports needed inside a class in:

io.liveklass.course

that uses all three types.


Exercise 5: Same Class Name

Your application needs:

java.util.Date
java.sql.Date

Explain how fully qualified class names can resolve the naming conflict.


Exercise 6: Package-Private Helper

Create:

CourseTitleNormalizer

that should only be used inside:

io.liveklass.course

Do not make the class public.


Predict the Result

Question 1

package io.liveklass.course;

class CourseValidator {

}

Can:

io.liveklass.payment.PaymentService

directly instantiate CourseValidator?

Answer

No.

CourseValidator is package-private.


Question 2

Two classes are both in:

io.liveklass.course

Does one need to import the other?

Answer

No.

Same-package types are directly available by short name.


Question 3

Does:

import java.util.*;

also import:

java.util.concurrent.ExecutorService

?

Answer

No.

Wildcard imports do not recursively import subpackages.


Question 4

What is the fully qualified name of:

package io.liveklass.course;

public class Course {

}

?

Answer

io.liveklass.course.Course

Question 5

Does making a class public improve its design automatically?

Answer

No.

Visibility should reflect intended usage.

Unnecessary public access increases coupling.


Knowledge Check

Question 1

What is a Java package?

Question 2

Why are packages useful?

Question 3

Where is the package declaration placed?

Question 4

Why are package names usually lowercase?

Question 5

What does an import do?

Question 6

Do classes in the same package require imports?

Question 7

What is a fully qualified class name?

Question 8

What is package-private access?

Question 9

Why avoid making every type public?

Question 10

What is the difference between feature-based and layer-based organization?

Question 11

Why can large util packages be problematic?

Question 12

What does package cohesion mean?

Question 13

Why should package boundaries influence dependency direction?

Question 14

Should every three classes get their own subpackage?

Question 15

What is a good starting rule for small Java applications?


Knowledge Check Answers

Answer 1

A namespace used to organize related Java types and participate in access control.

Answer 2

They organize code, prevent naming collisions, create visibility boundaries, and improve navigation.

Answer 3

At the top of the Java source file before imports.

Answer 4

It is the established Java naming convention and keeps package names consistent.

Answer 5

It allows a type to be referenced by its short name instead of its fully qualified name.

Answer 6

No.

Answer 7

The package name plus type name, such as:

io.liveklass.course.Course

Answer 8

Access available to types in the same package when no explicit access modifier is used.

Answer 9

Every public type creates a larger API surface and allows more code to depend on implementation details.

Answer 10

Layer-based organization groups by technical role; feature-based organization groups code by domain capability or feature.

Answer 11

They often become dumping grounds for unrelated responsibilities and hide ownership.

Answer 12

Related responsibilities are kept near each other.

Answer 13

Folder organization is useful only if dependencies also respect the intended boundaries.

Answer 14

No. Split packages only when doing so improves cohesion, visibility, or navigation.

Answer 15

Organize primarily by feature, keep closely related domain types together, and split further only when complexity justifies it.


Lesson Summary

এই lesson-এ আমরা শিখেছি:

  • Packages Java typesকে namespaces-এর মধ্যে organize করে
  • Package name type identity-এর অংশ
  • Reverse-domain naming common convention
  • Package structure সাধারণত directory structure-এর সঙ্গে align করে
  • import short type names ব্যবহার করতে সাহায্য করে
  • java.lang automatically available
  • Same-package types import প্রয়োজন করে না
  • Fully qualified names naming conflicts resolve করতে পারে
  • Wildcard imports subpackages recursively import করে না
  • Package-private access explicit keyword ছাড়াই তৈরি হয়
  • Package-private types internal implementation hide করতে useful
  • Everything public করা unnecessary coupling তৈরি করে
  • Packages meaningful design boundaries হিসেবেও কাজ করতে পারে
  • Layer-based organization simple হলেও large applications-এ feature code scatter করতে পারে
  • Feature-based organization related code কাছাকাছি রাখে
  • Over-fragmented package trees navigation harder করতে পারে
  • util, common, এবং shared dumping grounds হওয়া উচিত নয়
  • Package cohesion এবং dependency direction গুরুত্বপূর্ণ
  • Small applications-এর জন্য simple feature-oriented structure একটি strong default
  • Package organization evolve করতে পারে; final architecture day one-এ predict করার দরকার নেই

Next Lesson

পরবর্তী lesson:

Immutability and Defensive Programming

আমরা শিখব:

  • Mutable vs immutable objects
  • Why immutability reduces bugs
  • final fields
  • Constructor validation
  • Defensive copying
  • Protecting collections
  • Returning snapshots
  • Immutable value objects
  • Mutation leaks
  • Designing classes that protect their own invariants