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!