Affects: v6.0.11 Language: kotlin Spring Boot: v3.1.3


When an application is packaged as a native binary, not all resource bundle files are available (only the default resource file). I have the following resource files in src/main/resources:

\messages\
..+messages.properties
..+messages_de.properties

application.yaml

apring:
  messages:
    basename: messages/messages
    encoding: UTF-8

RuntimeHints have been registered for the bundle:

class ResourceBundleRuntimeHints : RuntimeHintsRegistrar {

    override fun registerHints(hints: RuntimeHints, classLoader: ClassLoader?) {
        hints.resources()
            // The application fails to start in native mode when this is not registered
            .registerResourceBundle("sun.util.resources.LocaleNames")
            // Application resource bundle
            .registerResourceBundle("messages/messages")
            // Hibernate validator and GraphQL extended validation
            .registerResourceBundle("ValidationMessages")
    }
}

This is registered in src/main/resources/META-INF/spring/aot.factories:

org.springframework.aot.hint.RuntimeHintsRegistrar=\
  com.example.graph.nativex.ResourceBundleRuntimeHints

Reproducer: juliuskrah/graphql-demo.

Tests are passing in jvm mode gradle clean check.

The three tests that fail in native mode rely on the resource bundle file:

$ gradle nativeTest

(log output here)

Failures (3):
  JUnit Jupiter:NodeControllerTest:should fetch node by ID(HttpGraphQlTester)
    MethodSource [className = 'com.example.graph.NodeControllerTest', methodName = 'should fetch node by ID', methodParameterTypes = 'org.springframework.graphql.test.tester.HttpGraphQlTester']
    => java.lang.AssertionError: 
Expecting
  Product(id=Z2lkOi8vZGVtby9Qcm9kdWN0LzU3MWEyMzc2LTI0YjYtNGIwNS04YzkwLTdkMzhjZDA0MWJmZA==, title=Headphones, description=null)
to have a property or a field named "title" with value
  "Kopfhörer"
but value was:
  "Headphones"
(static and synthetic fields are ignored)
Request: document='query productNodeDetails($id: ID!) {
    node(id: $id) {
        id
        __typename
        ... on Product {
            title
            description
        }
    }
}
', variables={id=Z2lkOi8vZGVtby9Qcm9kdWN0LzU3MWEyMzc2LTI0YjYtNGIwNS04YzkwLTdkMzhjZDA0MWJmZA}
       org.springframework.graphql.test.tester.DefaultGraphQlTester$DefaultRequest.lambda$assertDecorator$3(DefaultGraphQlTester.java:183)
       org.springframework.graphql.test.tester.DefaultGraphQlTester$ResponseDelegate.doAssert(DefaultGraphQlTester.java:241)
       org.springframework.graphql.test.tester.DefaultGraphQlTester$DefaultPath$DefaultEntity.satisfies(DefaultGraphQlTester.java:536)
       com.example.graph.NodeControllerTest.should fetch node by ID(NodeControllerTest.kt:42)
       java.base@17.0.7/java.lang.reflect.Method.invoke(Method.java:568)
       [...]
     Caused by: java.lang.AssertionError: 
Expecting
  Product(id=Z2lkOi8vZGVtby9Qcm9kdWN0LzU3MWEyMzc2LTI0YjYtNGIwNS04YzkwLTdkMzhjZDA0MWJmZA==, title=Headphones, description=null)
to have a property or a field named "title" with value
  "Kopfhörer"
but value was:
  "Headphones"
(static and synthetic fields are ignored)
       com.example.graph.NodeControllerTest.should_fetch_node_by_ID$lambda$3(NodeControllerTest.kt:46)
       org.springframework.graphql.test.tester.DefaultGraphQlTester$DefaultPath$DefaultEntity.lambda$satisfies$5(DefaultGraphQlTester.java:536)
       org.springframework.graphql.test.tester.DefaultGraphQlTester$DefaultRequest.lambda$assertDecorator$3(DefaultGraphQlTester.java:180)
       [...]
  JUnit Jupiter:NodeControllerTest:should fetch product by ID(HttpGraphQlTester)
    MethodSource [className = 'com.example.graph.NodeControllerTest', methodName = 'should fetch product by ID', methodParameterTypes = 'org.springframework.graphql.test.tester.HttpGraphQlTester']
    => java.lang.AssertionError: 
expected: "Smartphone-Hülle"
 but was: "Phone Case"
Request: document='query productDetails($id: ID!) {
    product(id: $id) {
        id
        title
        description
    }
}', variables={id=Z2lkOi8vZGVtby9Qcm9kdWN0LzcxZTg5Nzc3LTI0ZmItNDA5MC04YTI3LTE0NzU2ZGQ2OWI3MQ}
       org.springframework.graphql.test.tester.DefaultGraphQlTester$DefaultRequest.lambda$assertDecorator$3(DefaultGraphQlTester.java:183)
       org.springframework.graphql.test.tester.DefaultGraphQlTester$ResponseDelegate.doAssert(DefaultGraphQlTester.java:241)
       org.springframework.graphql.test.tester.DefaultGraphQlTester$DefaultPath$DefaultEntity.satisfies(DefaultGraphQlTester.java:536)
       com.example.graph.NodeControllerTest.should fetch product by ID(NodeControllerTest.kt:79)
       java.base@17.0.7/java.lang.reflect.Method.invoke(Method.java:568)
       [...]
     Caused by: java.lang.AssertionError: 
expected: "Smartphone-Hülle"
 but was: "Phone Case"
       com.example.graph.NodeControllerTest.should_fetch_product_by_ID$lambda$10(NodeControllerTest.kt:83)
       org.springframework.graphql.test.tester.DefaultGraphQlTester$DefaultPath$DefaultEntity.lambda$satisfies$5(DefaultGraphQlTester.java:536)
       org.springframework.graphql.test.tester.DefaultGraphQlTester$DefaultRequest.lambda$assertDecorator$3(DefaultGraphQlTester.java:180)
       [...]
  JUnit Jupiter:ProductServiceImplTest:should fetch node()
    MethodSource [className = 'com.example.graph.ProductServiceImplTest', methodName = 'should fetch node', methodParameterTypes = '']
    => java.lang.AssertionError: 
Expecting
  Product(id=Z2lkOi8vZGVtby9Qcm9kdWN0LzcxMDA2YWZlLTFkMDctNDYwYi1hN2M3LWRhNGNkNzkwNWZlMA==, title=Calculator, description=null)
to have a property or a field named "title" with value
  "Taschenrechner"
but value was:
  "Calculator"
(static and synthetic fields are ignored)
       com.example.graph.ProductServiceImplTest$should fetch node$1.invoke(ProductServiceImplTest.kt:49)
       com.example.graph.ProductServiceImplTest$should fetch node$1.invoke(ProductServiceImplTest.kt:46)
       com.example.graph.ProductServiceImplTest.should_fetch_node$lambda$1(ProductServiceImplTest.kt:46)
       reactor.test.DefaultStepVerifierBuilder.lambda$consumeNextWith$1(DefaultStepVerifierBuilder.java:279)
       reactor.test.DefaultStepVerifierBuilder$SignalEvent.test(DefaultStepVerifierBuilder.java:2289)
       reactor.test.DefaultStepVerifierBuilder$DefaultVerifySubscriber.onSignal(DefaultStepVerifierBuilder.java:1529)
       reactor.test.DefaultStepVerifierBuilder$DefaultVerifySubscriber.onExpectation(DefaultStepVerifierBuilder.java:1477)
       reactor.test.DefaultStepVerifierBuilder$DefaultVerifySubscriber.onNext(DefaultStepVerifierBuilder.java:1146)
       reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onNext(FluxContextWrite.java:107)
       reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:74)
       [...]

Test run finished after 135 ms
[         7 containers found      ]
[         1 containers skipped    ]
[         6 containers started    ]
[         0 containers aborted    ]
[         6 containers successful ]
[         0 containers failed     ]
[        16 tests found           ]
[         2 tests skipped         ]
[        14 tests started         ]
[         0 tests aborted         ]
[        11 tests successful      ]
[         3 tests failed          ]

Comment From: sdeleuze

I think what happens here is that for PropertyResourceBundle, GraalVM currently requires registering underlying resources for properties/XML files rather that the bundle name, and RuntimeHints API just follow this behavior as it generates resource-config.json interpreted by GraalVM.

I am going to raise that point to the GraalVM team to see there feedback, and comment here to confirm my understanding is correct and check with them if that deserves a GraalVM enhancement request.

In the meantime, registerPattern("messages/*") can be used to make your sample working (after adapting ResourceBundleRuntimeHintsTest) with the current behavior.

Comment From: juliuskrah

Thank you for getting back to me @sdeleuze; registerPattern(String) was already working for me until I discovered registerResourceBundle(String) from browsing the source code and I thought that makes for excellent documentation when the project grows out of hand 😄.

Comment From: sdeleuze

The GraalVM team feedback is that it should be supported, so please create an issue on https://github.com/oracle/graal/issues and they will handle it on their side. Thanks for raising this!