javaspring-bootdockerasynchronousjaxb

Async methods throws "Implementation of JAXB-API has not been found on module path or classpath"


I have a project based on java 17 and springboot 3. Recently I added an async method to the project. The async configuration class is the following:

@Configuration
@EnableAsync
public class AsyncConfig {
    @Bean(name = "taskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("Async-");
        executor.initialize();
        return executor;
    }
}

And the async method looks like this:

@Async("taskExecutor")
public CompletableFuture<Response<Exception>> createOrUpdateEntryAsync(
        .....,
        @Nullable ProgressListener progressListener) {

    return CompletableFuture.supplyAsync(() -> createOrUpdateEntry(
        ......,
        progressListener));
}

During dev and debugging locally using IntelliJ, the method works fine. However, after I used Docker to pack it in a image (using openjdk:17-jdk-slim as base image) and deployed it on a server, an error occurs by calling this async method: "Implementation of JAXB-API has not been found on module path or classpath". I have been searching for answer, found some hints and I added the following Maven dependency, but it doesn't solve the problem:

    <dependency>
        <groupId>javax.xml.bind</groupId>
        <artifactId>jaxb-api</artifactId>
        <version>2.3.1</version>
    </dependency>
    <dependency>
        <groupId>org.glassfish.jaxb</groupId>
        <artifactId>jaxb-runtime</artifactId>
        <version>2.3.2</version>
    </dependency>
    <dependency>
        <groupId>org.glassfish.jaxb</groupId>
        <artifactId>jaxb-core</artifactId>
        <version>2.3.0</version>
    </dependency>
    <dependency>
        <groupId>com.sun.xml.bind</groupId>
        <artifactId>jaxb-impl</artifactId>
        <version>2.3.3</version>
    </dependency>

Here is part of the the exception trace below my project codes:

java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1768)
java.base/java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1760)
java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373)
java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182)
java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655)
java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622)
java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165)

Could any one please help me on that? Thank you for help in advance!


Solution

  • I found out what went wrong. JAXB api seems to be invisible in the new thread created by the async method. The context class loader needs to be set explicitly like the following:

    @Async("taskExecutor")
    public CompletableFuture<Response<Exception>> createOrUpdateEntryAsync(
            .....,
            @Nullable ProgressListener progressListener) {
    
        ClassLoader contextClassLoader = this.getClass().getClassLoader();
        log.debug("Context ClassLoader: " + contextClassLoader);
    
        return CompletableFuture.supplyAsync(() -> {
            Thread.currentThread().setContextClassLoader(contextClassLoader);
            createOrUpdateEntry(
            ......,
            progressListener));
        }
    }