A Java library + Gradle plugin for dynamically loading Maven dependencies at runtime. Dramatically reduce your JAR size by downloading large dependencies on first launch.
- Ultra-light JAR: Reduce size from ~40MB to ~2MB by loading dependencies dynamically
- Simple API: One line in your
main()is enough - Child-First ClassLoader: Proper isolation of dynamic dependencies
- Maven Resolver: Reliable resolution via Apache Maven Resolver
- No Java Agent: No agent, no process restart required
- Gradle Plugin: Easy integration with existing projects
Perfect for applications with large dependencies like:
- Discord bots using JDA
- Applications using heavy libraries (Guava, Apache Commons, etc.)
- Microservices with optional features
build.gradle.kts:
plugins {
java
application
id("fr.traqueur.bootstrap")
}
dependencies {
// Core library
implementation(project(":bootstrap-core"))
// Mark dependencies as dynamic
// They will be available at compile time but NOT included in the JAR
bootstrap("net.dv8tion:JDA:6.1.2")
bootstrap("com.google.guava:guava:32.1.3-jre")
}Main.java:
public class Main {
public static void main(String[] args) {
BootstrapLoader.bootstrap(args, MyApp.class);
}
}MyApp.java:
public class MyApp implements DynamicApplication {
private JDA jda; // You can use dynamic dependency types directly!
@Override
public void start(String[] args) throws Exception {
jda = JDABuilder.createDefault(token).build();
System.out.println("Bot started!");
}
}./gradlew :example:jar
java -jar example/build/libs/example-1.0.0-SNAPSHOT.jarOn first run, dependencies will be downloaded to .dynamic-loader/cache/.
- The Gradle plugin creates a
boostrapconfiguration extendingcompileOnly - Dynamic dependencies are available during compilation
- A manifest file
META-INF/bootstrap-dependencies.jsonis generated - Dynamic dependencies are excluded from the JAR
BootstrapLoader.bootstrap()loads the manifest- Maven Resolver downloads dependencies and their transitive dependencies
- An IsolatedClassLoader (child-first) is created with the dependencies
- Your application class is loaded and instantiated via the isolated ClassLoader
- Your
start()method is called
The IsolatedClassLoader uses a child-first strategy (not Java's default parent-first):
- System packages (
java.*,javax.*,sun.*,jdk.*,fr.traqueur.bootstrap.*) → parent - Everything else: try child first, then delegate to parent
This is crucial for allowing your application classes to reference types from dynamic dependencies.
For more control over initialization:
BootstrapLoader.bootstrap(args, ctx -> {
MyApp app = ctx.create(MyApp.class);
// Access the ClassLoader
ClassLoader cl = ctx.getClassLoader();
// Load classes dynamically
Class<?> someClass = ctx.loadClass("com.example.SomeClass");
app.start(ctx.args());
});Via system property or environment variable:
# System property
java -Dbootstraploader.cache.dir=/custom/cache -jar app.jar
# Environment variable
export BOOTSTRAP_LOADER_CACHE_DIR=/custom/cache
java -jar app.jarThe plugin automatically includes all repositories from your Gradle project:
repositories {
mavenCentral()
maven {
url = uri("https://repo.example.com/maven2")
}
}These repositories are included in the generated manifest.
Bootstrap/
├── bootstrap-core/ # Runtime library (Java 21)
│ ├── BootstrapLoader.java # Main entry point
│ ├── BootstrapApplication.java # Simple interface
│ ├── BootstrapEntrypoint.java # Callback interface
│ ├── config/
│ │ └── DependencyManifest.java # JSON parser
│ ├── loader/
│ │ ├── IsolatedClassLoader.java # Child-first ClassLoader
│ │ └── LoaderContext.java # Context for callbacks
│ └── resolver/
│ ├── ArtifactResolver.java # Maven Resolver integration
│ └── SimpleTransferListener.java
│
├── bootstrap-gradle/ # Gradle plugin (Kotlin)
│ ├── BootstrapLoaderPlugin.kt
│ ├── BootstrapLoaderExtension.kt
│ └── GenerateDynamicManifestTask.kt
│
└── example/ # Discord bot example
META-INF/bootstrap-dependencies.json:
{
"dependencies": [
"net.dv8tion:JDA:6.1.2",
"com.google.guava:guava:32.1.3-jre"
],
"repositories": [
{ "id": "MavenRepo", "url": "https://repo.maven.apache.org/maven2/" }
]
}- Java 21 or higher
- Gradle 8.x
- Accessible Maven repositories
See the example/ directory for a complete Discord bot implementation using JDA.
# Build the project
./gradlew build
# Run the example (with Discord token)
java -jar example/build/libs/example-1.0.0-SNAPSHOT.jar YOUR_DISCORD_TOKEN
# Or set environment variable
export DISCORD_TOKEN=your_token_here
java -jar example/build/libs/example-1.0.0-SNAPSHOT.jarTo publish to your local Maven repository:
./gradlew publishToMavenLocalTo use in other projects:
repositories {
mavenLocal()
}
dependencies {
implementation("fr.traqueur.bootstrap:bootstrap-core:1.0.0-SNAPSHOT")
}The plugin is configured as a composite build in settings.gradle.kts:
includeBuild("bootstrap-gradle") // Plugin available for local development
include("bootstrap-core")
include("example")- No Java agent support (by design)
- Internet connection required on first run
- Dependencies must be available in configured Maven repositories
- Minimal JSON parser (no external JSON library)
Contributions are welcome! Please ensure:
- Java 21 compatibility
- Proper Javadoc documentation
- Clean code structure
- Avoid unnecessary dependencies
MIT License - feel free to use in your projects!
Built with:
- Apache Maven Resolver
- Gradle Plugin API
- Kotlin DSL
Questions? Open an issue on GitHub!