Chapter 15: Visibility Modifiers & Packages
Visibility Modifiers & Packages — this is the chapter where your Kotlin code starts looking professional, well-organized, and secure — exactly like real-world projects in Mumbai tech companies in 2026.
We’re going to go very slowly and clearly, like I’m sitting next to you in a quiet Bandra café with cutting chai ☕ — I’ll explain every single detail with real-life analogies, complete runnable examples, folder structures, step-by-step breakdowns, tables, common mistakes, and practical tips you can use tomorrow in your own projects.
Let’s dive in!
1. Why Visibility Modifiers Matter in Kotlin
In Java → everything is public by default unless you say otherwise. In Kotlin → everything is private by default unless you explicitly open it up.
Real-life analogy: Imagine your house in Mumbai.
- By default, the doors are locked (private) — only you (inside the class) can use things.
- You decide who gets keys: family (protected), neighbors in the same building (internal), or the whole city (public).
This default-private philosophy makes Kotlin code safer and cleaner — you only expose what you really want to expose.
2. The Four Visibility Modifiers in Kotlin
| Modifier | Who can see it? | Real-life analogy (Mumbai building) | Most common use case |
|---|---|---|---|
| public | Everyone (any class, any module, any package) | Public street — anyone can walk in | Public API, functions users should call |
| private | Only inside the same class/file | Your bedroom — only you | Internal helper functions, private fields |
| protected | Same class + subclasses (even in other packages) | Family flat — parents & children only | Methods/fields subclasses should use |
| internal | Same module (same Gradle module / IntelliJ module) | Same building / same society | Internal library code, not exposed outside |
Important:
- internal is Kotlin’s unique modifier — very useful in multi-module Android / backend projects
- There is no package-private like Java’s default — Kotlin uses internal for module visibility
3. Visibility Examples – All Four in Action
Create this folder structure (recommended real-world style):
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
src/ └── main/ └── kotlin/ └── com/ └── webliance/ ├── core/ │ └── BaseClass.kt └── ui/ └── UserScreen.kt |
BaseClass.kt (in com.webliance.core)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
package com.webliance.core open class BaseClass { // Everyone can see & use public val publicField = "Public data" // Only inside BaseClass private val privateField = "Secret data" // Subclasses can see (even in other packages) protected val protectedField = "Family data" // Only inside same module (com.webliance.*) internal val internalField = "Module-only data" fun showAll() { println("public: $publicField") println("private: $privateField") println("protected: $protectedField") println("internal: $internalField") } } |
UserScreen.kt (in com.webliance.ui)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
package com.webliance.ui import com.webliance.core.BaseClass class UserScreen : BaseClass() { fun display() { println(publicField) // OK // println(privateField) // ERROR! println(protectedField) // OK – subclass println(internalField) // OK – same module } } fun main() { val screen = UserScreen() screen.display() screen.showAll() } |
Output (inside same module):
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
Public data Family data Module-only data public: Public data private: Secret data protected: Family data internal: Module-only data |
If you move UserScreen to another Gradle module → internalField becomes invisible — compile error.
4. Top-Level Private – Private at File Level
In Kotlin, you can make top-level functions, properties, classes private — visible only inside the same file.
Very useful for helper functions you don’t want to expose.
Example – Top-level private
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// File: Utils.kt package com.webliance.utils private fun secretHelper() { println("This is secret – only inside Utils.kt") } fun publicUtility() { println("Public function") secretHelper() // OK – same file } // In another file → secretHelper() is invisible |
Common pattern:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// validation.kt private fun isValidEmail(email: String): Boolean = email.contains("@") fun registerUser(email: String) { if (!isValidEmail(email)) { throw IllegalArgumentException("Invalid email") } println("User registered: $email") } |
5. Packages & Imports – Quick Refresher + Best Practices
Package declaration (same as Java):
|
0 1 2 3 4 5 6 |
package com.webliance.features.auth |
Import styles (all valid):
|
0 1 2 3 4 5 6 7 8 |
import com.webliance.core.BaseClass // specific class import com.webliance.core.* // wildcard (use sparingly) import com.webliance.core.BaseClass as Base // alias |
Best practice in 2026:
- Use specific imports (import …BaseClass)
- Avoid wildcard * unless you really need many classes
- Use alias (as) when names clash
Multi-module tip:
- internal classes/functions are only visible inside the same Gradle module
- Perfect for hiding implementation details between :core, :ui, :data modules in Android
Quick Recap Table (Your Cheat Sheet)
| Modifier | Visible to | Default? | Real-world use |
|---|---|---|---|
| public | Everywhere | Yes (top-level) | Public API, entry points |
| private | Same class / same file (top-level) | Yes (inside class) | Helpers, internal state |
| protected | Same class + subclasses | — | Template methods, shared state |
| internal | Same module (same Gradle module) | — | Module-internal logic, library internals |
Common Newbie Mistakes & Fixes
| Mistake | Problem | Fix |
|---|---|---|
| Forgetting open on parent class | Cannot inherit | Add open to class you want to extend |
| Using protected expecting package visibility | Visible to subclasses only | Use internal for module visibility |
| Making top-level function private and trying to use it outside file | Compile error | Move to same file or make internal/public |
| Not using internal in multi-module project | Leaks implementation details | Mark module-internal code as internal |
| Overusing public | Exposes too much → fragile API | Default to private/internal, expose only what’s needed |
Homework for You (Very Practical!)
- Basic Create open class Animal with open fun makeSound(). Create Dog that overrides it.
- Medium Create package com.webliance.core with internal class DatabaseHelper. Try accessing it from another package → see the error.
- Advanced Create open class Shape with open val area: Double. Create Circle and Rectangle → override area.
- Fun Create top-level private fun calculateTax(amount: Double) in a file → use it only inside a public fun bill(amount: Double).
- Challenge In a multi-module mindset: imagine :core module → make a class internal → try accessing from :ui module → understand the visibility.
You’ve just mastered Kotlin’s visibility system — now your code can be clean, safe, modular, and enterprise-ready!
