I am working on a gradle multi-module project, where all* of the subprojects are jigsaw-modularized java libraries. In addition to the libraries, I have a spring boot application, which pulls in some of those libraries as dependencies, and exposes services via a REST API. Certain libraries expose a service with the provides ... with ...
syntax in their module-info.java
. I also have a @Configuration
class in the spring boot app, that uses ServiceLoader
to load these services and configure them as @Bean
s. To make this work, I attempted to modularize the spring boot application as well. Here's a simplified project structure:
*(except the spring boot app, which is the reason why I'm writing this question)
root
|--libraryA (gradle project, with module-info.java in src/main/java)
|--libraryB (gradle project, with module-info.java in src/main/java)
|--springApp (gradle project, with module-info.java in src/main/java)
|--build.gradle
The module-info.java
of the springApp is as follows:
module my.springApp {
/* library dependencies */
requires my.librarya;
requires my.libraryb;
/* requirements for Spring/Boot framework */
requires spring.boot;
requires spring.boot.autoconfigure;
requires spring.web;
requires spring.context;
requires spring.core;
requires spring.beans;
requires spring.security.config;
requires spring.security.core;
requires spring.security.web;
requires spring.webmvc;
/* Gives reflection access to Spring. */
opens my.app.package;
}
In addition, I have the following top-level build.gradle
file:
/* snipped for brevity, IntelliJ default build.gradle file */
allprojects {
java {
modularity.inferModulePath = true
}
}
Running the Spring Boot app from IntelliJ starts it successfully. However, when I try to run it via gradle, with gradlew bootRun
, it crashes with an exception saying that ServiceLoader
failed to locate a service implementation. This made me suspect that the spring boot plugin does not build the fat jar as modular, because I confirmed that the plain java libraries were built as modular jars.
To try and reproduce this, I created a new Spring Boot app, from the IntelliJ SpringInitializr wizard, added the same module-info.java
, without the library dependencies, and modified the main
method of the generated app as follows:
@SpringBootApplication
public class MyBootApplication {
public static void main(String[] args) {
System.out.println("Module name: " + MyBootApplication.class.getModule().getName());
SpringApplication.run(MyBootApplication.class, args);
}
}
Running this from IntelliJ correctly prints out the module name as:
"Module name: my.module"
However, when I run the generated jar from the spring gradle plugin, I get no name at all:
"Module name: null"
Disclosure: I am fairly new when it comes to gradle and packaging spring apps. From my brief search on the topic, I found some potentially conflicting information:
- that fat jars cannot (or should not?) be modularized.
- that spring 6 (spring boot version 3) has full support for jigsaw modules.
How should I build a spring boot fat jar so that it is modularized? Barring that, is it possible to get SpringBoot to work with modular libraries, such that I have access to their ModuleLayer
?
Note: I know that adding a src/resources/META-INF/services files to the modular libraries can fix the issue with ServiceLoader
not being able to find an implementation, and I am indeed currently using this as a workaround for that particular problem. However, I am using an overloaded method, load(ModuleLayer, Class<S>
in the services, to impose an order to the implementations found by ServiceLoader
, and since the spring boot app is not modularized, this throws an exception.