Question

Java static final Equivalent in Kotlin: Constants with const val and Companion Objects

kotlinjava

Question

In Java, a constant is often declared like this:

class Hello {
    public static final int MAX_LEN = 20;
}

What is the Kotlin equivalent of this pattern?

More specifically:

  • How do you declare a constant in Kotlin?
  • When should you use const val versus val?
  • Where should the constant be placed: top-level, inside an object, or inside a companion object?

Short Answer

By the end of this page, you will understand how Kotlin represents constants that are similar to Java static final fields. You will learn when to use const val, when a plain val is enough, and where constants are typically declared in Kotlin code such as top-level files, object declarations, and companion objects.

Concept

In Java, static final is commonly used for constants:

public static final int MAX_LEN = 20;

This combines two ideas:

  • static: the value belongs to the class, not to an instance
  • final: the value cannot be reassigned

Kotlin does not have the static keyword in the same way Java does. Instead, Kotlin offers several ways to define values that behave like class-level constants.

The most direct equivalent for a compile-time constant is:

const val MAX_LEN = 20

A const val means:

  • it is read-only
  • its value is known at compile time
  • it can be used in places that require a compile-time constant

Examples of allowed const val values include:

  • strings
  • numbers
  • booleans
  • chars

If a value is read-only but not known at compile time, use val instead:

val appName = getAppNameFromConfig()

That is not a compile-time constant, so it cannot be const.

In real Kotlin code, constants are commonly placed in one of these locations:

  • Top-level: ideal for general file-level constants
  • Inside an object: useful for grouping related constants
  • Inside a companion object: useful when the constant conceptually belongs to a class

So the Kotlin equivalent depends on the exact use case, but the core idea is usually const val.

Mental Model

Think of Java static final as a value pinned to the wall of a room:

  • static means everyone in the room shares the same pinned note
  • final means nobody can replace the note

In Kotlin, you still pin the note in one shared place, but the language gives you different walls to pin it on:

  • a file wall: top-level const val
  • a category wall: inside an object
  • a class-related wall: inside a companion object

If the note must be written before the program starts and never change, use const val. If it is still read-only but created later, use val.

Take Quiz

Syntax and Examples

The most common Kotlin forms are:

1. Top-level constant

const val MAX_LEN = 20

Use this when the constant does not need to belong to a specific class.

2. Inside an object

object Limits {
    const val MAX_LEN = 20
}

Usage:

println(Limits.MAX_LEN)

This is useful for grouping related constants.

3. Inside a companion object

class Hello {
    companion object {
        const val MAX_LEN = 20
    }
}

Usage:

println(Hello.MAX_LEN)

This is the closest style to a Java class constant.

4. Read-only but not compile-time constant

class Hello {
    companion object {
        val maxLen = listOf(10, 20).max()
    }
}

This must be val, not const val, because the value is computed.

Beginner-friendly example

class UserValidator {
    companion object {
        const val MIN_USERNAME_LENGTH = 3
    }

    fun isValid(username: String): Boolean {
        return username.length >= MIN_USERNAME_LENGTH
    }
}

fun main() {
    val validator = UserValidator()
    println(validator.isValid("Al"))
    println(validator.isValid("Alice"))
    println(UserValidator.MIN_USERNAME_LENGTH)
}

Explanation:

  • MIN_USERNAME_LENGTH belongs to the class, not each validator instance
  • it never changes
  • its value is known at compile time
  • companion object lets you access it as UserValidator.MIN_USERNAME_LENGTH

Step by Step Execution

Consider this example:

class Hello {
    companion object {
        const val MAX_LEN = 20
    }
}

fun main() {
    println(Hello.MAX_LEN)
}

Step by step:

  1. class Hello defines a class.
  2. Inside it, companion object creates a shared object associated with the class.
  3. const val MAX_LEN = 20 defines a constant inside that companion object.
  4. Because it is in the companion object, Kotlin lets you access it using the class name: Hello.MAX_LEN.
  5. main() calls println(Hello.MAX_LEN).
  6. The output is:
20

Now compare with a top-level constant:

const val MAX_LEN = 20

fun main() {
    println(MAX_LEN)
}

What happens here:

  1. MAX_LEN is declared outside any class.
  2. It becomes a shared constant for the file/package.
  3. main() can use it directly.
  4. The output is still 20.

The main difference is organization: top-level constants are not attached to a class.

Real World Use Cases

Constants like Java static final fields appear everywhere in Kotlin applications.

Common examples

  • API paths
  • error codes
  • configuration keys
  • default limits
  • screen route names
  • database column names
  • intent/action names in Android

Example: API configuration

object ApiConfig {
    const val BASE_URL = "https://api.example.com"
    const val TIMEOUT_SECONDS = 30
}

Example: validation rules

class PasswordValidator {
    companion object {
        const val MIN_LENGTH = 8
    }
}

Example: keys used across a project

object PreferencesKeys {
    const val USER_TOKEN = "user_token"
    const val THEME = "theme"
}

These constants make code easier to maintain because the value is defined once and reused everywhere.

Take Quiz

Real Codebase Usage

In real Kotlin projects, developers usually choose the location of a constant based on meaning and scope.

Common patterns

Top-level constants

Used when the constant is shared and does not belong to one class.

const val DEFAULT_PAGE_SIZE = 20

This is common in utility files or package-level configuration.

Grouping with object

Used to organize related constants.

object HttpHeaders {
    const val AUTHORIZATION = "Authorization"
    const val CONTENT_TYPE = "Content-Type"
}

This avoids scattering unrelated constants across files.

Companion object for class-related values

Used when the constant logically belongs to a class.

class CacheManager {
    companion object {
        const val DEFAULT_CACHE_SIZE = 100
    }
}

Validation and guard clauses

Constants are often used in checks.

const val MIN_AGE = 18

fun register(age: Int) {
    require(age >= MIN_AGE) { "Age must be at least $MIN_AGE" }
}

Error handling and message reuse

object ErrorMessages {
    const val NETWORK_ERROR = "Network request failed"
}

Using constants avoids duplicated string literals and reduces mistakes.

Common Mistakes

Here are some common beginner mistakes when translating Java static final to Kotlin.

1. Using const with a non-constant expression

Broken code:

const val MAX_LEN = listOf(10, 20).max()

Why it fails:

  • const val requires a compile-time constant
  • this value is computed at runtime

Correct version:

val maxLen = listOf(10, 20).max()

2. Expecting val to be the same as const val

val name = "Kotlin"

This is read-only, but not necessarily a compile-time constant in all contexts.

Use const val when the value is truly constant and allowed by Kotlin:

const val NAME = "Kotlin"

3. Looking for the static keyword

Broken expectation:

class Hello {
    static final val MAX_LEN = 20
}

Why it fails:

  • Kotlin does not use static like Java

Correct version:

class Hello {
    companion object {
        const val MAX_LEN = 20
    }
}

4. Putting every constant inside a class unnecessarily

This works, but may be poor organization if the constant is not class-specific.

Better alternative:

const val DEFAULT_TIMEOUT = 30

Use top-level constants when no class ownership is needed.

5. Using mutable variables for fixed values

Broken code:

var MAX_LEN = 20

Why it is a problem:

  • var can be reassigned
  • constants should not change

Correct version:

const val MAX_LEN = 20

6. Assuming const val works with any type

Broken code:

data class Config(val size: Int)
const val DEFAULT_CONFIG = Config(10)

Why it fails:

  • custom objects are not compile-time constants

Correct version:

data class Config(val size: Int)
val DEFAULT_CONFIG = Config(10)

Comparisons

Here is a quick comparison of Kotlin options.

Kotlin formMutable?Compile-time constant?Best use case
const valNoYesTrue constants like fixed strings and numbers
valNoNo, not necessarilyRead-only values computed at runtime
varYesNoValues that must change

Placement comparison

LocationExampleUse when
Top-levelconst val MAX_LEN = 20The constant is general and not tied to a class
objectobject Limits { const val MAX_LEN = 20 }You want to group related constants
companion objectclass Hello { companion object { const val MAX_LEN = 20 } }The constant logically belongs to a class

Java vs Kotlin

JavaKotlin
public static final int MAX_LEN = 20;const val MAX_LEN = 20 or companion object { const val MAX_LEN = 20 }
Uses static for class-level membersUses top-level declarations, object, or companion object
Uses final for read-only fieldsUses val for read-only, const val for compile-time constants
Take Quiz

Cheat Sheet

// Top-level compile-time constant
const val MAX_LEN = 20

// Grouped constants
object Limits {
    const val MAX_LEN = 20
}

// Class-related constant
class Hello {
    companion object {
        const val MAX_LEN = 20
    }
}

// Read-only runtime value
val maxLen = listOf(10, 20).max()

Rules

  • Use const val for compile-time constants only.
  • Use val for read-only values computed at runtime.
  • Use var only when reassignment is needed.
  • Kotlin does not use Java-style static.
  • Put constants at top level if they do not belong to a specific class.
  • Use object to group related constants.
  • Use companion object if the constant belongs to a class.

Common valid const val types

  • String
  • Int
  • Long
  • Double
  • Float
  • Boolean
  • Char

Not allowed with const val

  • values returned by functions
  • lists, maps, and custom objects
  • runtime calculations that are not compile-time constants

FAQ

What is the Kotlin equivalent of Java static final?

Usually const val for true constants, often placed at top level or inside a companion object.

Should I use const val or val in Kotlin?

Use const val if the value is known at compile time. Use val if it is read-only but computed at runtime.

Does Kotlin have static fields?

Not in the Java syntax. Kotlin uses top-level declarations, object, and companion object instead.

Can I declare a constant inside a class in Kotlin?

Yes. Put it inside a companion object if you want class-style access like MyClass.MY_CONSTANT.

Can const val hold a custom object?

No. const val only works for compile-time constant values such as strings, numbers, booleans, and chars.

Is top-level const val better than companion object?

Not always. Use top-level for general constants and companion object when the constant logically belongs to a class.

Why not just use var for constants?

Because var allows reassignment. Constants should be read-only, so use const val or val.

Take Quiz

Mini Project

Description

Build a small Kotlin validation utility for user registration. The project demonstrates how to declare constants in idiomatic Kotlin using top-level constants, grouped constants in an object, and class-related constants in a companion object.

Goal

Create a simple program that validates username and password input using Kotlin constants declared in the most appropriate places.

Requirements

  • Create at least one top-level constant for a general application rule.
  • Create an object that groups related text keys or messages.
  • Create a class with a companion object constant used during validation.
  • Write a function that checks whether a username and password are valid.
  • Print clear results for at least two test users.
Take Quiz

Keep learning

Related questions

View more