1. Overview
When working with constant values using const val in Kotlin, we might encounter the compile-time error: “*Const ‘val’ is only allowed on top level, in named objects, or in companion objects.*” This error can be confusing, especially if we’re new to Kotlin or transitioning from another programming language.
In this tutorial, we’ll discuss why this error occurs and explore how we can resolve it effectively.
2. Introduction to the Problem
As usual, let’s understand a problem through examples. Let’s say we have a Kotlin data class:
data class UserEvent(val name: String) {
const val MSG_PREFIX: String = "App User Event:"
fun login(): String {
return "$MSG_PREFIX User [$name] login successful"
}
}
In UserEvent, we defined a login() function. When a user successfully logs in to our application, we call login(), and the function returns a String message telling which user has logged in to the application. Here, we use the Kotlin String template to produce the message.
As we would like to prepend a fixed prefix to each message login() returns, we declared a const val variable MSG_PREFIX in the data class.
Then, if we instantiate a UserEvent object and call login(), for example, UserEvent(“Kai”).login(), we expect to get the correct message: “App User Event: User [Kai] login successful“.
However, when we compile the UserEvent class, the compiler complains:
Kotlin: Const 'val' is only allowed on top level, in named objects, or in companion objects.
So next, let’s explore why the above compiler error occurs and how to resolve it.
3. Understanding the Error
To understand the error, let’s first take a closer look at Kotlin’s const keyword.
In Kotlin, the const keyword is used to declare compile-time constants. Furthermore, constants declared by const must be of a primitive type (like Int, Float, etc.) or a String.
It’s critical to remember that const is designed for values that are known at compile-time and don’t change during the program’s execution.
The error “Const ‘val’ is only allowed on top level, in named objects, or in companion objects.” occurs when we try to declare a const val constant in a scope that doesn’t meet Kotlin’s requirements for const.
As we’ve mentioned, const values are inlined at compile time, meaning the Kotlin compiler replaces them with their literal values wherever they are used. For this to work, the constants must be accessible at compile time.
In our example, we declared MSG_PREFIX as a class property. A class’s properties are tied to the lifecycle of its instances. In other words**, we cannot set a class instance’s property at compile time**, as we don’t have any instance of that class.
Now that we understand the cause of this compiler error, let’s fix it!
4. How to Resolve the Error
To resolve the error, we must place our const val declarations in the correct scope. In fact, the error message reminds us how to fix the problem: “Const ‘val’ is only allowed on top level, in named objects, or in companion objects.”
Next, let’s walk through the suggestions in the error message and resolve this error by placing the const val in an appropriate location.
We’ll leverage unit test assertions to verify whether we get the expected results.
4.1. Moving const val Declarations to Top Level
The most straightforward fix might be moving the const val declaration to the top level of our Kotlin file. A top-level declaration here means the const val is not inside any class, function, or block of code, for example:
const val MSG_PREFIX_TOP_LVL: String = "App User Event (top level):"
data class UserEvent(val name: String) {
fun loginTopLevelPrefix(): String {
return "$MSG_PREFIX_TOP_LVL User [$name] login successful"
}
}
In this example, we declare const val MSG_PREFIX_TOP_LVL at the top level of the .kt file. Therefore, we can access it anywhere in the file, including within classes and functions. For example, we used the constant in loginTopLevelPrefix().
Now, let’s instantiate a UserEvent object and call the function:
val result = UserEvent("Kai").loginTopLevelPrefix()
assertEquals("App User Event (top level): User [Kai] login successful", result)
The test shows we get the expected message. Therefore, we fixed the error.
4.2. Wrapping const val Declarations in a Kotlin object
In Kotlin, wrapping const val declarations in an object is a common and practical approach. An object in Kotlin is a singleton, meaning it is initialized once and only once during the application’s lifetime. This aligns with the nature of const, which represents immutable, compile-time constants.
Next, let’s declare our const val in a Kotlin object and use it in a class function:
object AppConstants {
const val MSG_PREFIX: String = "App User Event (in object):"
// other const val declarations may come here as well
}
data class UserEvent(val name: String) {
fun loginObjectPrefix(): String {
return "${AppConstants.MSG_PREFIX} User [$name] login successful"
}
}
As the code above shows, we’ve created AppConstants, which holds our const val declarations. Of course, if our application requires more constants, we can pack them in AppConstants.
Encapsulating constants in an object makes it clear that these values are intended to be accessed globally or within a specific context, improving code clarity and maintainability.
Next, let’s see if this fix does the job:
val result = UserEvent("Kai").loginObjectPrefix()
assertEquals("App User Event (in object): User [Kai] login successful", result)
If we run the test, it passes. Wrapping const val declarations in an object fixes the error.
4.3. Declaring const val in a Companion Object
Now, let’s look at the last option mentioned in the error message: “… in companion objects“.
In Kotlin, a companion object is a special type of object associated with a class that allows us to define members that belong to the class itself rather than to instances of the class. A companion object in Kotlin acts similarly to static members in Java.
If we need to associate the constant with a specific class but still need it to be accessible as a compile-time constant, we can place it inside a companion object:
data class UserEvent(val name: String) {
fun loginCompanionObjPrefix(): String {
return "$MSG_PREFIX_IN_COMPANION_OBJ User [$name] login successful"
}
companion object {
const val MSG_PREFIX_IN_COMPANION_OBJ: String = "App User Event (in Companion object):"
}
}
As we can see in the above code, we put the const val declaration inside the companion object of UserEvent. It’s worth noting that it’s still a compile-time constant, but it is now scoped to UserEvent and can be accessed like UserEvent.MSG_PREFIX_IN_COMPANION_OBJ.
Now, if we create a UserEvent instance and call the loginCompanionObjPrefix() function, we get the correct String message:
val result = UserEvent("Kai").loginCompanionObjPrefix()
assertEquals("App User Event (in Companion object): User [Kai] login successful", result)
It’s also worth mentioning if we declare the constant as private in a class’s companion object, then it’s only accessible within the class.
5. Conclusion
In this article, we’ve discussed why the compiler error “Const ‘val’ is only allowed on top level, in named objects, or in companion objects” occurs and explored three possible fixes to the problem by understanding the restrictions on const val and knowing how to structure our code properly.
Whether by moving the const val declarations to the top level, wrapping const val constant values inside a Kotlin object, or declaring const val constants in a class’s companion object, we can ensure our Kotlin programs compile without errors and behave as expected.
As always, the complete source code for the examples is available over on GitHub.