I have a java project with gradle dependency from org.javamoney:moneta:1.3
.
Also I have two Kubernetes clusters. I deploy my java application using docker-container.
When I deploy my app in the first Kubernetes cluster everything is fine. But when I deploy my app (the same docker-container) in the second Kubernetes cluster following error appears:
javax.money.MonetaryException: No MonetaryAmountsSingletonSpi loaded.
at javax.money.Monetary.lambda$getDefaultAmountFactory$13(Monetary.java:291)
at java.base/java.util.Optional.orElseThrow(Optional.java:408)
at javax.money.Monetary.getDefaultAmountFactory(Monetary.java:291)
It appears in the following code:
MonetaryAmount amount = javax.money.Monetary.getDefaultAmountFactory()
.setCurrency("USD")
.setNumber(1L)
.create();
1.3
.6.0.1
.openjdk:11.0.7-jdk-slim
.2.2.7.RELEASE
.Server Version: version.Info{Major:"1", Minor:"15", GitVersion:"v1.15.3", GitCommit:"2d3c76f9091b6bec110a5e63777c332469e0cba2", GitTreeState:"clean", BuildDate:"2019-08-19T11:05:50Z", GoVersion:"go1.12.9", Compiler:"gc", Platform:"linux/amd64"}
.java -version
openjdk version "11.0.7" 2020-04-14
OpenJDK Runtime Environment 18.9 (build 11.0.7+10)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.7+10, mixed mode)
.I found this question and it gave me an idea try to declare gradle-dependency in some different way. I have tried:
implementation 'org.javamoney:moneta:1.3'
compile group: 'org.javamoney', name: 'moneta', version: '1.3', ext: 'pom'
compile 'org.javamoney:moneta:1.3'
runtimeOnly 'org.javamoney:moneta:1.3'
Unfortunately, it did not give any positive results.
As mentioned in this comment I've tried to copy service loader configuration from Moneta to following project directory: src/main/resources/META-INF/services
.
Unfortunately, it didn't help.
I've tried to do it just in the Main-class, but it didn't solve the problem.
The problem was in concurrent moneta SPI initialization within Java 11.
The problem can be solved by extracting MonetaryAmountFactory
to spring-bean and injecting it where needed:
@Bean
public MonetaryAmountFactory<?> money() {
return Monetary.getDefaultAmountFactory();
}
@Component
@RequiredArgsConstructor
public static class Runner implements CommandLineRunner {
private final MonetaryAmountFactory<?> amountFactory;
@Override
public void run(String... args) {
var monetaryAmount = this.amountFactory
.setCurrency("EUR")
.setNumber(1)
.create();
System.out.println("monetaryAmount = " + monetaryAmount);
}
}
instead of using this factory directly:
public static class Runner implements CommandLineRunner {
@Override
public void run(String... args) {
var monetaryAmount = Monetary.getDefaultAmountFactory()
.setCurrency("EUR")
.setNumber(1)
.create();
System.out.println("monetaryAmount = " + monetaryAmount);
}
}
I discovered that there were diferrent resource limit configuration on above-mentioned Kubernetes-clusters.
Cluster with exception:
Limits:
cpu: 6
memory: 20G
Requests:
cpu: 3
memory: 20G
Cluster without exception:
Limits:
cpu: 2
memory: 2G
Requests:
cpu: 2
memory: 128Mi
Seems that cluster with more resources gives more opportunity to concurrent moneta initialization happened.
The minimal reproducible example can be found in this github-repository.
It is worth mentioned that the bug is not reproduced on Java 8.