Search code examples
javaspringgradlejava-moneyjsr354

MonetaryException: No MonetaryAmountsSingletonSpi loaded


Problem description

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();

Software versions

  • Moneta: 1.3.
  • Gradle: 6.0.1.
  • Base docker-image: openjdk:11.0.7-jdk-slim.
  • Spring boot: 2.2.7.RELEASE.
  • Kubernetes (the same version on both clusters): 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: 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).

What I have tried

Declare gradle-dependency differently

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.

Copy-paste service loader configurations for Moneta

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.

Init custom currency without spring

I've tried to do it just in the Main-class, but it didn't solve the problem.

Questions

  1. What is the root-cause of this problem?
  2. What is the proper solution to this problem?

Solution

  • TL;DR

    The problem was in concurrent moneta SPI initialization within Java 11.

    Problem solution

    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);
        }
    }
    

    Why problem occurs on Kubernetes-clusters?

    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.

    Minimal reproducible example

    The minimal reproducible example can be found in this github-repository.

    It is worth mentioned that the bug is not reproduced on Java 8.