HackToTech

Hack To Technology

LocalVariableTableParameterNameDiscovererが削除されてAutowireに失敗するようになった話

github.com

SpringBootを3.2.0に上げた際に、
同じ型のBeanを定義していて、名前による(Qualifierではない)Autowireが失敗するようになってしまったので、それの備忘録
とりあえず複数のBeanがマッチするところまでの話は以下の記事に書いてるので端折る

www.hacktotech.net

DependencyDescriptor#getDependencyName が呼ばれる
https://github.com/spring-projects/spring-framework/blob/v6.1.2/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java#L1728

MethodParameter#getParameterName が呼ばれる
https://github.com/spring-projects/spring-framework/blob/v6.1.2/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java#L356

ここでパラメータ名を解決するわけだが、
参照している parameterNameDiscoverer
KotlinReflectionParameterNameDiscoverer
StandardReflectionParameterNameDiscoverer
LocalVariableTableParameterNameDiscoverer
の順番でパラメータ名の解決を行うようになっていたのが、削除されたことによって失敗するようになっていた
(元のバージョンでも上2つのdiscovererは最終的にnullを返していたので、そのままでは解決できなくなった)
https://github.com/spring-projects/spring-framework/blob/v6.1.2/spring-core/src/main/java/org/springframework/core/MethodParameter.java#L714

で、困ったことに自分たちのコードはKotlinで書いているが、
外部ライブラリのstarterがJavaで書かれており、wikiの対応がまだされていなかったので、自分たちのSpringBootのバージョンをあげたら壊れてしまったという話だった
(自分たちのコードで、Kotlin内で同じ型のBeanが複数構成されていた場合の名前による解決はうまく動いたので、少なくともこのパターンだけは対応しないとうまく動かないとかな気がする)

↓はPoC

github.com

jmodule は外部ライブラリをイメージして書いていて、
src 配下は自分たちのコードをイメージして書いている

org.springframework.boot3.1.7 であれば
jmodule 側の build.gradle に↓を書かなくても問題なく動くが、

tasks.withType(JavaCompile).configureEach {
    options.compilerArgs.add("-parameters")
}

3.2.1 など spring-core6.1 系になると失敗するようになる
最初出会したときは、自分たちの build.gradle.kts だけ対応すればいけるのかと思っていたが、
そんなことはなかったのでバージョンを上げる際は注意したほうが良さそう

一応コンパイルオプションつけたときの ResolverAutoConfiguration.java のclassファイルの違いもみてみた

無効

public class org.example.resolver.ResolverAutoConfiguration
  minor version: 0
  major version: 61
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #12                         // org/example/resolver/ResolverAutoConfiguration
  super_class: #2                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 2, attributes: 2
Constant pool:
   #1 = Methodref          #2.#3          // java/lang/Object."<init>":()V
   #2 = Class              #4             // java/lang/Object
   #3 = NameAndType        #5:#6          // "<init>":()V
   #4 = Utf8               java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Class              #8             // org/example/resolver/Resolver
   #8 = Utf8               org/example/resolver/Resolver
   #9 = Methodref          #7.#10         // org/example/resolver/Resolver."<init>":(Lorg/example/resolver/ParameterNameResolver;)V
  #10 = NameAndType        #5:#11         // "<init>":(Lorg/example/resolver/ParameterNameResolver;)V
  #11 = Utf8               (Lorg/example/resolver/ParameterNameResolver;)V
  #12 = Class              #13            // org/example/resolver/ResolverAutoConfiguration
  #13 = Utf8               org/example/resolver/ResolverAutoConfiguration
  #14 = Utf8               Code
  #15 = Utf8               LineNumberTable
  #16 = Utf8               LocalVariableTable
  #17 = Utf8               this
  #18 = Utf8               Lorg/example/resolver/ResolverAutoConfiguration;
  #19 = Utf8               resolver
  #20 = Utf8               (Lorg/example/resolver/ParameterNameResolver;)Lorg/example/resolver/Resolver;
  #21 = Utf8               parameterNameResolver
  #22 = Utf8               Lorg/example/resolver/ParameterNameResolver;
  #23 = Utf8               RuntimeVisibleAnnotations
  #24 = Utf8               Lorg/springframework/context/annotation/Bean;
  #25 = Utf8               SourceFile
  #26 = Utf8               ResolverAutoConfiguration.java
  #27 = Utf8               Lorg/springframework/context/annotation/Configuration;
{
  public org.example.resolver.ResolverAutoConfiguration();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 7: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lorg/example/resolver/ResolverAutoConfiguration;

  public org.example.resolver.Resolver resolver(org.example.resolver.ParameterNameResolver);
    descriptor: (Lorg/example/resolver/ParameterNameResolver;)Lorg/example/resolver/Resolver;
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=3, locals=2, args_size=2
         0: new           #7                  // class org/example/resolver/Resolver
         3: dup
         4: aload_1
         5: invokespecial #9                  // Method org/example/resolver/Resolver."<init>":(Lorg/example/resolver/ParameterNameResolver;)V
         8: areturn
      LineNumberTable:
        line 10: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  this   Lorg/example/resolver/ResolverAutoConfiguration;
            0       9     1 parameterNameResolver   Lorg/example/resolver/ParameterNameResolver;
    RuntimeVisibleAnnotations:
      0: #24()
        org.springframework.context.annotation.Bean
}
SourceFile: "ResolverAutoConfiguration.java"
RuntimeVisibleAnnotations:
  0: #27()
    org.springframework.context.annotation.Configuration

有効

public class org.example.resolver.ResolverAutoConfiguration
  minor version: 0
  major version: 61
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #12                         // org/example/resolver/ResolverAutoConfiguration
  super_class: #2                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 2, attributes: 2
Constant pool:
   #1 = Methodref          #2.#3          // java/lang/Object."<init>":()V
   #2 = Class              #4             // java/lang/Object
   #3 = NameAndType        #5:#6          // "<init>":()V
   #4 = Utf8               java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Class              #8             // org/example/resolver/Resolver
   #8 = Utf8               org/example/resolver/Resolver
   #9 = Methodref          #7.#10         // org/example/resolver/Resolver."<init>":(Lorg/example/resolver/ParameterNameResolver;)V
  #10 = NameAndType        #5:#11         // "<init>":(Lorg/example/resolver/ParameterNameResolver;)V
  #11 = Utf8               (Lorg/example/resolver/ParameterNameResolver;)V
  #12 = Class              #13            // org/example/resolver/ResolverAutoConfiguration
  #13 = Utf8               org/example/resolver/ResolverAutoConfiguration
  #14 = Utf8               Code
  #15 = Utf8               LineNumberTable
  #16 = Utf8               LocalVariableTable
  #17 = Utf8               this
  #18 = Utf8               Lorg/example/resolver/ResolverAutoConfiguration;
  #19 = Utf8               resolver
  #20 = Utf8               (Lorg/example/resolver/ParameterNameResolver;)Lorg/example/resolver/Resolver;
  #21 = Utf8               parameterNameResolver
  #22 = Utf8               Lorg/example/resolver/ParameterNameResolver;
  #23 = Utf8               MethodParameters
  #24 = Utf8               RuntimeVisibleAnnotations
  #25 = Utf8               Lorg/springframework/context/annotation/Bean;
  #26 = Utf8               SourceFile
  #27 = Utf8               ResolverAutoConfiguration.java
  #28 = Utf8               Lorg/springframework/context/annotation/Configuration;
{
  public org.example.resolver.ResolverAutoConfiguration();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 7: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lorg/example/resolver/ResolverAutoConfiguration;

  public org.example.resolver.Resolver resolver(org.example.resolver.ParameterNameResolver);
    descriptor: (Lorg/example/resolver/ParameterNameResolver;)Lorg/example/resolver/Resolver;
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=3, locals=2, args_size=2
         0: new           #7                  // class org/example/resolver/Resolver
         3: dup
         4: aload_1
         5: invokespecial #9                  // Method org/example/resolver/Resolver."<init>":(Lorg/example/resolver/ParameterNameResolver;)V
         8: areturn
      LineNumberTable:
        line 10: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  this   Lorg/example/resolver/ResolverAutoConfiguration;
            0       9     1 parameterNameResolver   Lorg/example/resolver/ParameterNameResolver;
    MethodParameters:
      Name                           Flags
      parameterNameResolver
    RuntimeVisibleAnnotations:
      0: #25()
        org.springframework.context.annotation.Bean
}
SourceFile: "ResolverAutoConfiguration.java"
RuntimeVisibleAnnotations:
  0: #28()
    org.springframework.context.annotation.Configuration

MethodParameters を見れば分かる通り、オプションによって有無の違いが見て取れる

docs.oracle.com

SpringBootでAutowireする際に複数のBeanがマッチした際の挙動をみたのでメモ

雰囲気で挙動を理解していたのをちゃんと見たのでメモ

spring.pleiades.io

@Nullable
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
        @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
        ...
        // Step 3b: direct bean matches, possibly direct beans of type Collection / Map
        Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor); // ここで型に対して一致するbeanの一覧を取得する
        ...

        // Step 4: determine single candidate
        if (matchingBeans.size() > 1) {
            autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor); // 複数のbeanが当てはまる場合はどれを使うかを決定する
            if (autowiredBeanName == null) {
                if (isRequired(descriptor) || !indicatesArrayCollectionOrMap(type)) {
                    // Raise exception if no clear match found for required injection point
                    return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans);
                }
                else {
                    // In case of an optional Collection/Map, silently ignore a non-unique case:
                    // possibly it was meant to be an empty collection of multiple regular beans
                    // (before 4.3 in particular when we didn't even look for collection beans).
                    return null;
                }
            }
            instanceCandidate = matchingBeans.get(autowiredBeanName);
        }
        ...
}
protected String determineAutowireCandidate(Map<String, Object> candidates, DependencyDescriptor descriptor) {
    Class<?> requiredType = descriptor.getDependencyType();
    String primaryCandidate = determinePrimaryCandidate(candidates, requiredType); // @Primaryがないかを優先して探す
    if (primaryCandidate != null) {
        return primaryCandidate; // 見つかったら返す
    }
    String priorityCandidate = determineHighestPriorityCandidate(candidates, requiredType); // @Priorityで指定した中で最も優先度の高い(値の低い)ものを探す
    if (priorityCandidate != null) {
        return priorityCandidate; // 見つかったら返す
    }
    // Fallback: pick directly registered dependency or qualified bean name match
    for (Map.Entry<String, Object> entry : candidates.entrySet()) {
        String candidateName = entry.getKey();
        Object beanInstance = entry.getValue();
        if ((beanInstance != null && this.resolvableDependencies.containsValue(beanInstance)) ||
                matchesBeanName(candidateName, descriptor.getDependencyName())) {
            return candidateName; // Autowireする際の変数名が一致するものが見つかったら返す
        }
    }
    return null;
}

@Priority でInjectするbeanを操作しようとした際に、↓は一見 @Priority が評価されそうに見えるが、
実際は評価されず exampleAexampleB どちらを使えば良いかわからずに終了する

import jakarta.annotation.Priority
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.Ordered


interface MarkerExample
data class ExampleImpl1(val value: String): MarkerExample
data class ExampleImpl2(val value: String): MarkerExample

@Configuration
class ExampleConfig {
    @Bean
    @Priority(Ordered.LOWEST_PRECEDENCE)
    fun exampleA(): MarkerExample {
        return ExampleImpl1(
            value = "ExampleA",
        )
    }

    @Bean
    @Priority(Ordered.HIGHEST_PRECEDENCE)
    fun exampleB(): MarkerExample {
        return ExampleImpl2(
            value = "ExampleB",
        )
    }

    @Bean
    fun exampleC(example: MarkerExample): String {
        return "ExampleC"
    }
}

逆に↓は動く(Bで解決される)

import jakarta.annotation.Priority
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.Ordered


interface MarkerExample
@Priority(Ordered.LOWEST_PRECEDENCE)
data class ExampleImpl1(val value: String): MarkerExample
@Priority(Ordered.HIGHEST_PRECEDENCE)
data class ExampleImpl2(val value: String): MarkerExample

@Configuration
class ExampleConfig {
    @Bean
    fun exampleA(): MarkerExample {
        return ExampleImpl1(
            value = "ExampleA",
        )
    }

    @Bean
    fun exampleB(): MarkerExample {
        return ExampleImpl2(
            value = "ExampleB",
        )
    }

    @Bean
    fun exampleC(example: MarkerExample): String {
        return "ExampleC"
    }
}

こういったパターンはほぼ @Primary があれば足りるし使うことはないが、
思っていた挙動と違ったので意外だった

@Configurationで@Beanを宣言した際に誰がどう登録しているのかを見てたのでメモ

詳しく書いてあるところが見当たらなかったので単にコードを追った
完全に個人的なメモ

tl;dr

最終的に DefaultSingletonBeanRegistry で持ってそう spring.pleiades.io

バージョン

  • Spring Boot 3.1.5
  • Spring Framework 6.0.13

コード

data class Example(val value: String)

@Configuration
class ExampleConfig {
    @Bean
    fun exampleA(): Example {
        return Example(
            value = "ExampleA",
        )
    }
}

メモ

PGAuditのログを減らしたくてバージョンごとの挙動の違いを見てたのでメモ

github.com

  • PGAuditのissueとcommitを眺めていて、 個人的に悩みだった下記の問題が解決してそうだったのでバージョンごとの違いを確認する
    • INSERT時の外部キーの監査ログがWRITEとして出てしまう問題
      • 正直あまり意味のあるログではないので、出来る限り取りたくない
    • SELECT FOR UPDATEがWRITEのログとして出てしまう問題
      • WRITEだけログに出したいのに、キューとして使用しているテーブルのポーリングログ(SELECT FOR UPDATE)が出て邪魔になったりしている

検証結果

  • PostgreSQLの13以降を使用していて、且つ pgaudit.logall とか read じゃなければ、
    オブジェクト監査ログで SELECT を取得しているテーブル以外は、これらのログを取得しなくても済むようになりそう

以下は、それぞれ検証したPostgreSQLのバージョン(PGAuditのバージョン)

12.17(1.4.3)

  • INSERTの外部キー監査ログ
2023-11-28 16:38:34.149 UTC [75] LOG:  AUDIT: SESSION,2,1,WRITE,INSERT,TABLE,public.ref,"INSERT INTO ref values(1, 'insert');",<none>
2023-11-28 16:38:34.150 UTC [75] LOG:  AUDIT: SESSION,2,2,WRITE,UPDATE,TABLE,public.main,"SELECT 1 FROM ONLY ""public"".""main"" x WHERE ""id"" OPERATOR(pg_catalog.=) $1 FOR KEY SHARE OF x",1
  • SELECT FOR UPDATEのログ
2023-11-28 16:42:18.902 UTC [75] LOG:  AUDIT: SESSION,5,1,MISC,BEGIN,,,BEGIN;,<none>
2023-11-28 16:42:22.407 UTC [75] LOG:  AUDIT: SESSION,6,1,WRITE,UPDATE,TABLE,public.ref,SELECT * FROM ref WHERE main_id = 1 FOR UPDATE;,<none>
2023-11-28 16:42:35.654 UTC [75] LOG:  AUDIT: SESSION,7,1,WRITE,UPDATE,TABLE,public.ref,UPDATE ref SET memo = 'update' WHERE main_id = 1;,<none>
2023-11-28 16:42:39.230 UTC [75] LOG:  AUDIT: SESSION,8,1,MISC,COMMIT,,,COMMIT;,<none>

13.13(1.5.2)

  • INSERTの外部キー監査ログ
2023-11-28 17:01:04.890 UTC [42] LOG:  AUDIT: SESSION,4,1,WRITE,INSERT,TABLE,public.ref,"INSERT INTO ref values(1, 'insert');",<none>
2023-11-28 17:01:04.890 UTC [42] LOG:  AUDIT: SESSION,4,2,READ,SELECT,TABLE,public.main,"SELECT 1 FROM ONLY ""public"".""main"" x WHERE ""id"" OPERATOR(pg_catalog.=) $1 FOR KEY SHARE OF x",1
  • SELECT FOR UPDATEのログ
2023-11-28 17:02:49.548 UTC [42] LOG:  AUDIT: SESSION,5,1,MISC,BEGIN,,,BEGIN;,<none>
2023-11-28 17:02:49.548 UTC [42] LOG:  AUDIT: SESSION,6,1,READ,SELECT,TABLE,public.ref,SELECT * FROM ref WHERE main_id = 1 FOR UPDATE;,<none>
2023-11-28 17:02:49.549 UTC [42] LOG:  AUDIT: SESSION,7,1,WRITE,UPDATE,TABLE,public.ref,UPDATE ref SET memo = 'update' WHERE main_id = 1;,<none>
2023-11-28 17:02:49.549 UTC [42] LOG:  AUDIT: SESSION,8,1,MISC,COMMIT,,,COMMIT;,<none>

14.10(1.6.2)

  • INSERTの外部キー監査ログ
2023-11-28 17:13:31.837 UTC [41] LOG:  AUDIT: SESSION,4,1,WRITE,INSERT,TABLE,public.ref,"INSERT INTO ref values(1, 'insert');",<none>
2023-11-28 17:13:31.838 UTC [41] LOG:  AUDIT: SESSION,4,2,READ,SELECT,TABLE,public.main,"SELECT 1 FROM ONLY ""public"".""main"" x WHERE ""id"" OPERATOR(pg_catalog.=) $1 FOR KEY SHARE OF x",1
  • SELECT FOR UPDATEのログ
2023-11-28 17:13:57.347 UTC [41] LOG:  AUDIT: SESSION,5,1,MISC,BEGIN,,,BEGIN;,<none>
2023-11-28 17:13:57.347 UTC [41] LOG:  AUDIT: SESSION,6,1,READ,SELECT,TABLE,public.ref,SELECT * FROM ref WHERE main_id = 1 FOR UPDATE;,<none>
2023-11-28 17:13:57.347 UTC [41] LOG:  AUDIT: SESSION,7,1,WRITE,UPDATE,TABLE,public.ref,UPDATE ref SET memo = 'update' WHERE main_id = 1;,<none>
2023-11-28 17:13:57.347 UTC [41] LOG:  AUDIT: SESSION,8,1,MISC,COMMIT,,,COMMIT;,<none>

検証環境構築

バージョン違うだけでほぼ同じなので12のパターンだけ

docker run --name pg12.17 -e POSTGRES_PASSWORD=password -d postgres:12.17
docker exec -it pg12.17 /bin/bash

# PGAuditのbuildに必要なものとかを入れる
apt update &&\
apt install -y build-essential curl libssl-dev libkrb5-dev postgresql-server-dev-12

cd tmp &&\
curl -L https://github.com/pgaudit/pgaudit/archive/refs/tags/1.4.3.tar.gz | tar xz --strip 1 &&\
make install USE_PGXS=1
  • shared_preload_libraries を設定する
psql -Upostgres
ALTER SYSTEM SET shared_preload_libraries TO 'pgaudit';
  • 読み込ませる為にコンテナの再起動をする
docker restart pg12.17
  • 再起動後に、PGAuditの設定をする
CREATE EXTENSION pgaudit;
CREATE ROLE auditor;
ALTER SYSTEM SET pgaudit.role = 'auditor';
ALTER SYSTEM SET pgaudit.log = 'all';
ALTER SYSTEM SET pgaudit.log_catalog = off;
ALTER SYSTEM SET pgaudit.log_level = 'log';
ALTER SYSTEM SET pgaudit.log_parameter = on;
ALTER SYSTEM SET pgaudit.log_relation = on;
ALTER SYSTEM SET pgaudit.log_statement_once = on;
SELECT pg_reload_conf();
  • SQLを流してログを確かめる
CREATE TABLE main (
   id SERIAL PRIMARY KEY
);

CREATE TABLE ref (
   main_id SERIAL REFERENCES main,
   memo    TEXT
);

INSERT INTO main values(1);
INSERT INTO ref values(1, 'insert');

BEGIN;
SELECT * FROM ref WHERE main_id = 1 FOR UPDATE;
UPDATE ref SET memo = 'update' WHERE main_id = 1;
COMMIT;

PGAuditのPR

SpringBoot + KotlinでListの要素にConstraintsをかけたかった話

tl;dr コンパイラオプションに -Xemit-jvm-type-annotationsを指定する必要がある
あとの文章は個人的なメモ

リストの要素にConstraintsをかけたかったが、
なんか一見正しく見えるのに正しく動かなかった

package com.example.exampleboot3

import org.hibernate.validator.constraints.Length

data class Request(val values: List<@Length(min = 1) String>)
package com.example.exampleboot3

import jakarta.validation.Valid
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RestController

@RestController("/")
class SampleController {
    @PostMapping
    fun post(@RequestBody @Valid r: Request): String {
        return "ok"
    }
}

そのまま投げると、通ってしまう

curl -XPOST localhost:8080 -H "Content-Type: application/json" -d '{"values": ["test", ""] }'
ok 

でまあ、公式を見て -Xemit-jvm-type-annotations をコンパイルオプションに付与すればいいという話だった

kotlinlang.org

# build.gradle.kts
tasks.withType<KotlinCompile> {
    kotlinOptions {
        freeCompilerArgs = listOf(
            "-Xjsr305=strict",
            "-Xemit-jvm-type-annotations",
        )
        jvmTarget = "17"
    }
}

で、期待した通りに動くようにはなった

curl -XPOST localhost:8080 -H "Content-Type: application/json" -d '{"values": ["test", ""] }'
{"timestamp":"2023-10-28T17:51:52.475+00:00","status":400,"error":"Bad Request","path":"/"}

で、なんでそもそもJava8以降をターゲットとしてコンパイルする必要があるんだっけと思ったら

JSR 308 Explained: Java Type Annotations

JSR 308, Annotations on Java Types, has been incorporated as part of Java SE 8. This JSR builds upon the existing annotation framework, allowing type annotations to become part of the language. Beginning in Java SE 8, annotations can be applied to types in addition to all of their existing uses within Java declarations. This means annotations can now be applied anywhere a type is specified, including during class instance creation, type casting, the implementation of interfaces, and the specification of throws clauses. This allows developers to apply the benefits of annotations in even more places.

そもそもJSR308がJava8からの話だった(この辺よく理解してなかった)
1.8 よりも前を指定したらコンパイルコケるのか試したかったが、 1.6 指定したら Unknown Kotlin JVM target: 1.6 とか言われたので、古い環境用意するのも面倒になって諦めた

最近の環境ならデフォルトで有効でも良いんじゃないか?と思ってissueを眺めてたが、
基本的な使い方をする分には困らないけど、反変/共変での射影とかまだ色々と問題を抱えているから各自で有効にしてくれという話だと理解した https://youtrack.jetbrains.com/issue/KT-35843

There are a bunch of open questions that should be carefully discussed and full support of type annotations in JVM bytecode can't be fully implemented without discussing them.

でまあ一応中身の違いがどうなっているのかも気になったので、オプションの無効/有効で生成したclassファイルもみてみた

無効

public com.example.exampleboot3.Request(java.util.List<java.lang.String>);
descriptor: (Ljava/util/List;)V
flags: (0x0001) ACC_PUBLIC
Code:
  stack=2, locals=2, args_size=2
     0: aload_1
     1: ldc           #10                 // String values
     3: invokestatic  #16                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
     6: aload_0
     7: invokespecial #19                 // Method java/lang/Object."<init>":()V
    10: aload_0
    11: aload_1
    12: putfield      #22                 // Field values:Ljava/util/List;
    15: return
  LineNumberTable:
    line 5: 6
  LocalVariableTable:
    Start  Length  Slot  Name   Signature
        0      16     0  this   Lcom/example/exampleboot3/Request;
        0      16     1 values   Ljava/util/List;
Signature: #7                           // (Ljava/util/List<Ljava/lang/String;>;)V
RuntimeInvisibleParameterAnnotations:
  parameter 0:
    0: #9()
      org.jetbrains.annotations.NotNull
MethodParameters:
  Name                           Flags
  values

有効

public com.example.exampleboot3.Request(java.util.List<java.lang.String>);
descriptor: (Ljava/util/List;)V
flags: (0x0001) ACC_PUBLIC
Code:
  stack=2, locals=2, args_size=2
     0: aload_1
     1: ldc           #13                 // String values
     3: invokestatic  #19                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
     6: aload_0
     7: invokespecial #22                 // Method java/lang/Object."<init>":()V
    10: aload_0
    11: aload_1
    12: putfield      #25                 // Field values:Ljava/util/List;
    15: return
  LineNumberTable:
    line 5: 6
  LocalVariableTable:
    Start  Length  Slot  Name   Signature
        0      16     0  this   Lcom/example/exampleboot3/Request;
        0      16     1 values   Ljava/util/List;
Signature: #7                           // (Ljava/util/List<Ljava/lang/String;>;)V
RuntimeVisibleTypeAnnotations:
  0: #9(#10=I#11): METHOD_FORMAL_PARAMETER, param_index=0, location=[TYPE_ARGUMENT(0)]
    org.hibernate.validator.constraints.Length(
      min=1
    )
RuntimeInvisibleParameterAnnotations:
  parameter 0:
    0: #12()
      org.jetbrains.annotations.NotNull
MethodParameters:
  Name                           Flags
  values

RuntimeVisibleTypeAnnotations を見れば分かる通り、オプションによって有無の違いが見て取れる

docs.oracle.com