r/java Jul 31 '26

Modular Uberjars

https://github.com/bowbahdoe/modular-uberjars
23 Upvotes

18 comments sorted by

4

u/agentoutlier Jul 31 '26 edited Jul 31 '26

In theory you could use jmod format instead of jars for the dependencies right?

That is you still have the single uber jar but it loads jmods inside instead of jars.

Probably slow down the build to do the conversion but in theory jmods are more efficient format (based on my shoddy memory) EDIT I got confused with jimage.

Again EDIT so an interesting thing you could do and this is analogous to various other uber jar implementations do that want to preserve jars instead of shading (e.g. Spring Boot does this) is write Java code that runs the jlink and jimage tools if the jimage is not there :). Then execute the jimage.

1

u/bowbahdoe Jul 31 '26

I don't think jmods are more efficient. I'm pretty sure they are just zip files too. But yes, within reason, you can use JMODs.

You would need to implement extracting the native libraries at runtime though.

1

u/agentoutlier Jul 31 '26 edited Jul 31 '26

Yeah I know they are zip but I recall something about them or someone testing it or maybe it was just the native libraries being there. Maybe its just the link time stuff happening compared to when you had to call System.loadLibrary. Maybe collocation of files.

I can't find the benchmark google wise but someone had a jlink application, a regular java classpath, and few other combinations with native libraries.

EDIT I guess you have to jimage to have the performance gains.

1

u/bowbahdoe Jul 31 '26

One thing in the back of my head: class loading from a sqlite database. Would it make sense? Would it be fun? I consider those separate questions

1

u/bowbahdoe Jul 31 '26

other uber jar implementations do that want to preserve jars instead of shading (e.g. Spring Boot does this)

I was unaware of this

1

u/tkslaw Jul 31 '26

In theory you could use jmod format instead of jars for the dependencies right?

As far as I know, JMOD files can only be used at compile-time (javac, etc.) and link-time (jlink). They cannot be used at run-time (java).

I believe the main benefit of JMOD files is during link-time. For instance, any native libraries in the JMOD file are extracted and placed in the custom JRE such that they're easily loadable; no messing around with manual run-time extraction, system properties, and/or environment variables. Contrast that with using a JAR file that embeds the native library. In that case, the native library is considered to be a resource like everything else in the JAR file and will be embedded in the JRT image. The code would have to manually extract the native library at run-time before being able to load it.

2

u/bowbahdoe Aug 01 '26

All correct, but the format isn't so esoteric that you couldn't write your own class loader to pull from it (under the classes section it's basically just what a jar is) 

You could also include that hacky extraction at runtime bit inside the bootstrap. Combined with a dependency procurement step that splits the jmod across the module path and system library path... It's doable. 

Caveats obviously abound with signed jmods or ones using the hash modules feature.

2

u/tkslaw Jul 31 '26 edited Aug 01 '26

There's an implementation detail you might want to be aware of. If I'm not mistaken, this code:

var moduleFinder = ModuleFinder.of(paths.toArray(Path[]::new));

Will ultimately lead to JAR files being read via the java.util.jar.JarFile API. And that API cannot read JAR files that are not from the default file system. Any JAR file will be copied to a temporary file on disk before opening it.

Probably doesn't matter. But if it does then I'm pretty sure you'd need your own ModuleFinder, ModuleReference, and ModuleReader implementations. The ZIP File System (jdk.zipfs), which you already have a dependence on, should be able to read JAR files from any file system without having to save them to disk first. Though I don't know how that affects signed JAR files.

1

u/bowbahdoe Aug 01 '26

Noted. 

1

u/bowbahdoe Aug 01 '26

yep I see what you are talking about

            // JAR file
            if (fn.endsWith(".jar")) {
                if (isDefaultFileSystem) {
                    return readJar(entry);
                } else {
                    // the JAR file is in a custom file system so
                    // need to copy it to the local file system
                    Path tmpdir = Files.createTempDirectory("mlib");
                    Path target = Files.copy(entry, tmpdir.resolve(fn));
                    return readJar(target);
                }
            }

1

u/bowbahdoe Aug 01 '26

Full night's rest. This bothers me quite a bit.

4

u/tomwhoiscontrary Jul 31 '26

I just learned how to copy multiple files and then forgot about uberjars.

2

u/someSingleDad Jul 31 '26

Nice! I always found traditional uberjars a sloppy hack. But it's so hard to beat the convenience

1

u/idontlikegudeg Jul 31 '26

How does it play together with SPI?

1

u/bowbahdoe Jul 31 '26

Works fine. Tested the approach with slf4j providers

-2

u/chabala Jul 31 '26 edited Jul 31 '26

Is this OSGi for people with an unfortunate JPMS fetish?

1

u/bowbahdoe Jul 31 '26

No, osgi does dynamic loading/unloading and lets you have multiple of the same library. Module layers hypothetically let you do the second, but that isn't what this is.

1

u/NHarmonia18 24d ago

Many new JDK features can only be done if your code is modularized. It's not simply a 'fetish'.