6

I'm getting an error while trying to assign a environment variable value to a lateinit variable. The error is "'lateinit' modifier is not allowed on properties of primitive types".

My application.properties (reading the environment variable)

my.property.from.properties.file=true

MyService class:

@Component
class MyService @Autowired constructor(
    private val someService: SomeService) {

    @Value("\${my.property.from.properties.file}")
    private lateinit var myBooleanEnabled: Boolean

Assigning a value to it does not solve the problem. For example, with

private lateinit var myBooleanEnabled: Boolean = true

I get 2 errors:

  • 'lateinit' modifier is not allowed on properties of primitive types
  • 'lateinit' modifier is not allowed on properties with initializer

For what I read, I need a Delegated (https://kotlinlang.org/docs/reference/delegated-properties.html) but I could not grasp it fully. Also, I don't want to have to write another method to set the property if there is a "cleaner" solution. Any ideas?

JonyD
  • 1,237
  • 3
  • 21
  • 34
  • 1
    If you can initialize the variable, simply remove the `lateinit` modifier. – yole Dec 10 '18 at 17:12
  • if you are wondering why it is that way: [Why doesn't Kotlin allow to use lateinit with primitive types?](https://stackoverflow.com/questions/38761294/why-doesnt-kotlin-allow-to-use-lateinit-with-primitive-types/38762276)... so what should the default value be of that variable if it is never initialized? if it is `false`, use: `private var myBooleanEnabled : Boolean = false` and set it to the system property, if it is there.... problem solved? – Roland Dec 11 '18 at 06:32
  • You could vote in https://youtrack.jetbrains.com/issue/KT-15284 for this feature to be implemented – Holger Brandl Mar 08 '23 at 07:06

2 Answers2

6

The simplest thing is to define myBooleanEnabled as nullable and remove lateinit

private var myBooleanEnabled: Boolean? = null

In this case, it will not be interpreted as a primitive boolean in bytecode.

However, in your case, I'd suggest a constructor injection.

Sergei Rybalkin
  • 3,337
  • 1
  • 14
  • 27
  • Your suggestion does not work. I get "'lateinit' modifier is not allowed on properties of nullable types" – JonyD Dec 10 '18 at 17:09
  • 1
    but lateinit is desired here – JonyD Dec 10 '18 at 17:15
  • Why do you think the `lateinit` is desired? What are you trying to accomplish by using it? – yole Dec 10 '18 at 17:22
  • 1
    the value will come from a container environment variable – JonyD Dec 10 '18 at 17:23
  • 1
    So why does it have to be lateinit? You can still assign it from the container variable, while providing a default value as a normal initializer. – yole Dec 10 '18 at 17:49
  • @yole, lateinit matters when you don't have a default value at compile time and the actual value comes at runtime. The good example is a `@ConfigurationProperties` class that gets values from properties file. – AbstractVoid Feb 20 '19 at 11:32
  • @WallTearer So what exactly prevents you from initializing the value with 0 at compile time? The correct value will still get injected through `@ConfigurationProperties`. – yole Feb 26 '19 at 15:43
  • 1
    @yole initializing with invalid value is not a good idea. What if some developer forgets to set some value in properties, or a production system will not have a proper configuration? Having no value assigned at all would be better, because the system will fail fast, as opposed to having invalid default value and weird behavior down the line. – AbstractVoid Feb 26 '19 at 16:06
  • @WallTearer If you want to support setting the field via `@ConfigurationProperties`, there is no way to provide the fail-fast `lateinit` semantics that you want. The injection will set the field via reflection, so there will be no way for the Kotlin runtime to track whether it has been set, and there is also no value that can be used to mark an uninitialized value of a primitive type. – yole Feb 26 '19 at 16:33
  • 1
    @yole, yes, failing fast on compilation or startup will not be possible. But by "failing fast" in my example I meant "failing when value is accessed", so it's of course not as fast as failing on compilation or startup, but still faster and better then having invalid value and potentially causing damage to the system without seeing any error (or some very confusing error in a completely different place). – AbstractVoid Feb 26 '19 at 17:58
  • Failing when the value is accessed will also be impossible: there is no way to distinguish whether a value of a primitive type is completely unset or set via reflection. – yole Feb 26 '19 at 18:02
  • 1
    @yole, I'm repeating once again: having invalid value is worse than no value (proved by lots of prod apps) - this is the main problem that should be addressed, and I don't know why you ignore it. In any case, `lateinit` works for non-primitive types (e.g. Strings), which is already one part of the fix and is better than no fix. And handling of primitive values could be done like `var testBool: Boolean by Delegates.notNull()` - here you get a second part of the fix. – AbstractVoid Feb 27 '19 at 11:09
  • @WallTearer I'm not ignoring the problem, I'm saying it's impossible to solve. If you want injection to work, you cannot use `Delegates.notNull()` or an equivalent approach because the type of the field will be different, and frameworks will not be able to inject the value into the field (unless you update all frameworks to make them specificially aware of Kotlin's lateinit implementation). – yole Feb 27 '19 at 11:43
  • @yole please check your facts, because `Delegates.notNull()` does work at least in Spring for `@ConfigurationProperties` on primitive types. And the exception will be triggered when accessing a value if it is not set in properties. And type will be correct if value is given. See also this answer - https://stackoverflow.com/a/44386513/519035 – AbstractVoid Feb 27 '19 at 12:11
1

You can use constructor injection as shown below. If you're using Spring 4.3+ you don't need the @Autowired annotation. Spring documentation has some guidelines on this:

https://docs.spring.io/spring/docs/current/spring-framework-reference/languages.html#injecting-dependencies

@Component
class MyService(
    private val someService: SomeService,
    @Value("\${my.property.from.properties.file}")
    private val myBooleanEnabled: Boolean)
armandino
  • 17,625
  • 17
  • 69
  • 81