JavaRush/Courses/Kotlin SELF/package and folders...

package and folders: how code gets an “address” in a project

Open

1. Introduction

When a program is small, the temptation to keep everything in a single .kt file is huge. It’s like “one box for everything”: socks, chargers, documents — while the box is small, it even feels convenient. But once the code grows, the box turns into a black hole: you clearly remember there was a function readIntOrNull() somewhere, but where exactly — not anymore.

Packages (package) in Kotlin are a way to give code a clear “address” so that you (and the IDE) can quickly find the functions you need, and identical names don’t fight each other for the right to live in the same space.

It’s important to catch the key idea right away:

package is a “namespace”, a logical grouping of code.
Folders in a project are the “physical organization” of files on disk. They are usually aligned, but they are different things.

2. What is package: a namespace and the “address” of declarations

If you look at a Kotlin file like a letter, package is the “deliver to” line. At the beginning of the file you write:

package app

And that’s it — everything declared in this file (functions, constants, etc.) starts “living” at the address app.

Minimal example: a file in the app package

Let’s imagine we’re building a console game “Guess the Number” (from the mini-project). While everything is simple, we can have a single file Main.kt:

package app

fun main() {
    println("The game has started!") // The game has started!
}

Now main formally has a fully qualified name: app.main. Usually you don’t write it that way, but it’s useful to think in these terms.

One file — one package

In Kotlin, package is defined for the entire file. You can’t declare half the functions in app and half in app.util within the same .kt file. If you need a different namespace, create another file.

Top-level declarations “belong” to the package

You’ve already seen that in Kotlin you can declare variables and functions at the top level of a file (outside classes). Such declarations also live in the file’s package. The package is the “home” for declarations, while the file is more like a “container/sheet of paper” where you wrote those declarations down.

3. Fully qualified name: how Kotlin distinguishes identical names

As a project grows, you will almost inevitably write two functions with the same “short” name. For example, format() is a great name, and you’ll want to use it in different places.

From Kotlin’s point of view, name uniqueness is ensured not only by the “function name”, but by the combination:

package + name

(and also the signature, but that’s a separate story).

Two identical functions in different packages is fine

File 1: src/main/kotlin/app/util/format.kt

package app.util

fun formatScore(score: Int): String {
    return "Score: $score"
}

File 2: src/main/kotlin/app/cli/format.kt

package app.cli

fun formatPrompt(text: String): String {
    return ">> $text"
}

Both functions are named differently (formatScore and formatPrompt), but even if they were both simply called format, Kotlin could still distinguish them by package. The problem won’t start with “existence”, but with “usage” — when you try to call format() from a third place. We’ll handle that carefully via import in the next lecture, but today it’s important to understand the addressing principle itself.

An important nuance: a file is not a scope for name resolution

This point sounds like a line from a compiler document, but it’s genuinely practical. Kotlin (and the IDE) try to make it so that moving a function to another file within the same package doesn’t break code just because “it’s in a different file now”. The name resolution specification explicitly discusses the idea that “there is no separate file scope” — what matters is the package, not the specific .kt.

Translated into plain language: the file is not the “owner” of the function. The owner is the package. So you can think of a package as a “folder in a phone book”: it doesn’t matter which page the number is written on, what matters is which category it’s in.

4. Folders and package: the convention that saves your nerves

Now comes an important practical part: how it’s customary to organize a Kotlin/JVM project.

In a Gradle project (and for most of you it’s exactly that), code is usually located here:

src/main/kotlin/

And then the interesting part begins: inside src/main/kotlin you typically create folders that mirror the package.

Mapping “dots ↔ folders”

There is a simple rule of association:

In package On disk
app
app/
app.util
app/util/
app.cli
app/cli/
com.example.myapp
com/example/myapp/

Meaning:

  • dots in package turn into “folders”;
  • the folder order mirrors the “address”.

For example, if you write:

package app.util

then the file is usually located at:

src/main/kotlin/app/util/FileName.kt

Why this is a “convention” rather than a strict law

The Kotlin compiler can technically build a project where a file is in the “wrong” folder (especially if you manually configured source directories). But the IDE and people will suffer.

IntelliJ IDEA can highlight the issue in a style like Package directive doesn't match file location. And this is that rare case where the IDE isn’t nitpicking — it’s genuinely trying to save you from future chaos.

Analogy: address and warehouse

package is the “address on the envelope” (how to find you logically). Folders are the “placement of boxes in a warehouse” (how things are stored physically).

If the address and the warehouse match, you’re happy. If they don’t, you can still find the box, but you’ll do it like a quest hero: “bring the artifact from the basement, but your map is from a different game”.

5. Project-level practice: splitting the “Guess the Number” game into packages

Now we won’t add new language constructs. We’ll take already familiar functions and simply lay them out by meaningful “addresses”.

Let’s imagine we have logic for:

  • number input,
  • secret generation,
  • the main game loop,
  • printing messages to the user.

If you keep everything in one file Main.kt, it will quickly bloat. So we’ll split it into packages:

  • app — entry point and scenario assembly (that is, main)
  • app.game — game logic
  • app.io — input and validation (reading utilities)
  • app.util — small formatting helpers

Let’s draw it as an “address map”:

flowchart TD
    A["package app
Main.kt"] --> B["package app.game
Game.kt"] A --> C["package app.io
Input.kt"] A --> D["package app.util
Text.kt"]

app: entry point

File: src/main/kotlin/app/Main.kt

package app

fun main() {
    println("Welcome to 'Guess the Number'!") // Welcome to 'Guess the Number'!
}

So far there isn’t much here. But the idea is: main is the “conductor”. It calls functions from other packages and stitches the scenario into one application.

app.util: text normalization

File: src/main/kotlin/app/util/Text.kt

package app.util

fun normalizeCommand(raw: String): String {
    return raw.trim().lowercase()
}

This function is small, but very “package-appropriate”: it’s not about the game, not about numeric input, but about string processing in general. So it makes sense for it to live in util.

app.io: reading an integer “the human way”

File: src/main/kotlin/app/io/Input.kt

package app.io

fun readIntOrNull(prompt: String): Int? {
    print(prompt)
    val raw = readln().trim()
    return raw.toIntOrNull()
}

Note: there’s no magic here. It’s the same logic you wrote earlier; it’s just now in a separate file with a clear address app.io.readIntOrNull.

app.game: secret generation

File: src/main/kotlin/app/game/Secret.kt

package app.game

import kotlin.random.Random

fun generateSecret(from: Int, until: Int): Int {
    return Random.nextInt(from, until)
}

Here we use the already familiar Random topic again (you covered it earlier). What matters is that game-related logic starts living in app.game, which makes the project more readable: you can tell what a file is “about” directly from its path.

6. Package discipline: default package and naming

The default package and why it’s almost always a bad idea

Sometimes you see a file without a package line at the top. That means the file is in the default package.

Technically it works. Practically it’s like “living without an address”: the courier can still call you and ask “so you’re somewhere near the entrance, right?”, but normal life doesn’t work like that.

The main problems of the default package usually don’t show up immediately. At first everything is fine: one file, second file, third file… And then suddenly you want to:

  • split code into subpackages,
  • reuse functions in another project,
  • understand who is responsible for what,
  • avoid naming conflicts.

And that’s where the lack of packages starts to bite back.

A good rule for learning and small projects: as soon as you have more than one file, create a proper package, at least package app.
And even for a single file it’s often useful: you build discipline and suffer less as the project grows.

Package naming nuances

In the industry there are common conventions:

Package names are written in lowercase, usually separated by dots, for example app.util or com.example.myapp. This form is convenient and readable, and it almost never conflicts with class/function names.

If you really want to name a package MyCoolStuff, pause for a second and imagine living with that six months from now. Most likely you’ll end up with mycoolstuff or my.cool.stuff anyway. Kotlin code is read many times and written once — so it’s better to choose a version that doesn’t irritate the eye from the start.

One more point: a package is a logical grouping. Don’t try to make “a package for every function”. It’s like a supermarket: if you have a separate shelf for every loaf of bread, you didn’t organize the store — you built a bread museum.

7. Common mistakes when working with package and folders

Mistake #1: the package doesn’t match the file location under src.
This is the most common story: a file is located in src/main/kotlin/app/util, but at the top it says package app.utils (an extra s) or even package app. Formally you can live like this for a while, but the IDE will get confused, navigation will become weird, and the project will start to resemble a drawer where T-shirts are stored in the “spoons” compartment.

Mistake #2: everything is put into one package “because it’s easier”.
The app package quickly turns into a “junk-drawer package” where input, game logic, and formatting all live together. It feels convenient at first, and then you get afraid to open autocomplete because there are 30 functions with similar names. It’s better to add subpackages by meaning: app.io, app.game, app.util.

Mistake #3: expecting “one file = one package”.
It’s the other way around: one package almost always consists of many files. A package is a logical area, while a file is just the place where you wrote down part of the declarations. It’s important to get used to thinking in packages rather than files; otherwise you’ll be “searching for a function by file name” — not the most pleasant kind of archaeology.

Mistake #4: leaving files in the default package “while the project is small”.
It’s a trap. The project is small exactly until you add a second file and want just a bit of order. Then migration begins: moving files, fixing references, relocating directories. It’s much cheaper to set up a proper package right away, even if it’s just one.

Mistake #5: weird package names (spaces, uppercase, “random”).
Technically some things might even build (until the first serious conflict), but readability and project culture drop. Lowercase and clear words are boring, but they work — like most good engineering solutions.

Комментарии
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны sign in
This page has no comments yet