0

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 @Beans. 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.

0 Answers0