Skip to content

JExcellence/JExDependency

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

35 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

JExDependency

Ship lightweight Paper & Spigot plugins. Let your players download the libraries.

Runtime dependency resolution, downloading, relocation, and classpath injection β€” purpose-built for modern Minecraft servers.

License: MIT Java 21 Paper Spigot Version GitHub stars

Quick Start β€’ Why JExDependency β€’ Features β€’ Configuration β€’ FAQ


πŸ’‘ Why JExDependency

Minecraft plugin jars keep getting larger. Hibernate, Jackson, Caffeine, JDBC drivers β€” shading them all bloats your jar to 20 MB+ and guarantees classpath collisions the moment another plugin ships a different version.

JExDependency flips that model. Declare dependencies in a tiny YAML file, ship a 50 KB plugin, and let the library download, verify, (optionally) relocate, and inject the JARs at runtime β€” on every flavour of Paper and Spigot, transparently.

Think of it as Paper's libraries block, but portable, relocation-aware, and working on Spigot too.


✨ Features

  • 🧩 Universal loader β€” Paper plugin-loader handshake on 1.20+, legacy bootstrap on Spigot/older Paper. Auto-detected.
  • πŸ“¦ YAML-first descriptors β€” merge generic / Paper-only / Spigot-only files, deduplicate versions, normalise coordinates.
  • πŸ”€ Optional ASM relocation β€” isolate conflicting packages without paying the CPU cost when you don't need it.
  • ⚑ Sync or async bootstrap β€” block onLoad() for simplicity, or return a CompletableFuture for non-blocking startup.
  • πŸ” Checksum-verified downloads β€” corrupted artifacts are retried; temp dirs are wiped on every exit path.
  • β˜• Java 21 ready β€” module de-encapsulation and --add-opens semantics handled for reflective libraries.
  • 🧹 Deterministic cache β€” artefacts live under plugins/<Plugin>/libraries/ and .../libraries/remapped/; safe to prune.
  • πŸͺ΅ First-class logging β€” every bootstrap cycle reports counts, timings, and redacted paths through the plugin logger.

πŸš€ Quick Start

1. Add the dependency

Replace VERSION with the latest release tag.

Gradle (Kotlin DSL)
repositories {
    maven("https://repo.jexcellence.de/releases")
}

dependencies {
    implementation("de.jexcellence.dependency:jexdependency:VERSION")
}
Gradle (Groovy)
repositories {
    maven { url 'https://repo.jexcellence.de/releases' }
}

dependencies {
    implementation 'de.jexcellence.dependency:jexdependency:VERSION'
}
Maven
<repositories>
    <repository>
        <id>jexcellence</id>
        <url>https://repo.jexcellence.de/releases</url>
    </repository>
</repositories>

<dependency>
    <groupId>de.jexcellence.dependency</groupId>
    <artifactId>jexdependency</artifactId>
    <version>VERSION</version>
</dependency>

2. Declare runtime libraries

Create src/main/resources/dependency/dependencies.yml:

dependencies:
  - "com.github.ben-manes.caffeine:caffeine:3.2.2"
  - "com.fasterxml.jackson.core:jackson-databind:2.18.2"
  - "com.mysql:mysql-connector-j:9.2.0"

Need platform-specific sets? Add dependencies-paper.yml or dependencies-spigot.yml β€” they are merged automatically.

3. Bootstrap in your plugin

public final class ExamplePlugin extends JavaPlugin {
    @Override
    public void onLoad() {
        // Synchronous β€” simplest; blocks onLoad while JARs download.
        JEDependency.initialize(this, ExamplePlugin.class);
    }
}

That's it. Libraries land under plugins/ExamplePlugin/libraries/ and are injected into your plugin's classloader before onEnable() fires.


πŸ” Replace your ShadowJar relocate blocks

If your build.gradle.kts currently looks like this:

tasks.named<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>("shadowJar") {
    archiveBaseName.set("RDQ")
    archiveVersion.set(rdqVersion)
    archiveClassifier.set("Free")

    relocate("com.github.benmanes", "de.jexcellence.remapped.com.github.benmanes")
    relocate("me.devnatan.inventoryframework", "de.jexcellence.remapped.me.devnatan.inventoryframework")
    relocate("com.tcoded", "de.jexcellence.remapped.com.tcoded")
    relocate("com.cryptomorin.xseries", "de.jexcellence.remapped.com.cryptomorin.xseries")

    configurations = listOf(project.configurations.getByName("runtimeClasspath"))
    mergeServiceFiles()
}

…you can throw it all away. No shading. No relocate block. No 20 MB fat jar.

With JExDependency, the entire setup becomes:

1) build.gradle.kts β€” no shadow plugin required:

dependencies {
    implementation("de.jexcellence.dependency:jexdependency:2.0.0")

    // These stay as compileOnly β€” they're downloaded at runtime, not shaded.
    compileOnly("com.github.ben-manes.caffeine:caffeine:3.2.2")
    compileOnly("me.devnatan:inventory-framework-paper:3.3.0")
    compileOnly("com.tcoded:FoliaLib:0.5.1")
    compileOnly("com.github.cryptomorin:XSeries:11.3.0")
}

2) src/main/resources/dependency/dependencies.yml:

dependencies:
  - "com.github.ben-manes.caffeine:caffeine:3.2.2"
  - "me.devnatan:inventory-framework-paper:3.3.0"
  - "com.tcoded:FoliaLib:0.5.1"
  - "com.github.cryptomorin:XSeries:11.3.0"

3) Bootstrap with relocation forced on:

public final class RDQ extends JavaPlugin {
    @Override
    public void onLoad() {
        // Equivalent to your old shadow relocate(...) block, at runtime.
        JEDependency.initializeWithRemapping(this, RDQ.class);
    }
}

4) (Optional) Control the relocation prefix via a JVM flag β€” no rebuild needed:

-Djedependency.relocations.prefix=de.jexcellence.remapped

Result: a 50 KB plugin jar instead of 20 MB, a clean Git diff instead of a shaded-classes explosion, and per-server-operator control over relocations.


🎯 Bootstrap modes

Method Blocking Forces relocation When to use
JEDependency.initialize(plugin, anchor) βœ… β€” Default. Safe inside onLoad().
JEDependency.initializeWithRemapping(plugin, anchor) βœ… βœ… You need guaranteed isolation from other plugins' libs.
JEDependency.initializeAsync(plugin, anchor) ❌ β€” Non-blocking startup; await the returned CompletableFuture<Void> before touching injected classes.

All three methods accept an optional String[] of extra Maven coordinates (group:artifact:version[:classifier]) appended to the YAML list.


βš™οΈ Configuration

JExDependency is driven by JVM system properties so server operators can tweak behaviour without recompiling your plugin.

Property Default Purpose
-Djedependency.remap false true / 1 / yes / on forces ASM relocation.
-Djedependency.relocations β€” Comma-separated pattern=target overrides, e.g. com.google.gson=mypkg.libs.gson.
-Djedependency.relocations.prefix β€” Global prefix applied to auto-relocated packages.
-Djedependency.relocations.excludes β€” Packages that must never be relocated.

🧠 How it works

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  onLoad()  β†’  JEDependency.initialize(...)                       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
               β–Ό
   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     Paper 1.20+?
   β”‚  Server detection     │────────┐
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜        β–Ό
               β”‚            β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
               β”‚            β”‚ Inject pre-downloaded β”‚
               β”‚            β”‚ libs via Paper loader β”‚
               β”‚            β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
               β–Ό                        β”‚
   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”            β”‚
   β”‚  Merge YAML sources   β”‚β—€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
   β”‚  (generic + platform) β”‚
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
               β–Ό
   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   β”‚  Resolve + download   β”‚  β†’ checksum verify β†’ cache under
   β”‚  from Maven repos     β”‚    plugins/<Plugin>/libraries/
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
               β–Ό
   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  (only when -Djedependency.remap=true
   β”‚  Optional ASM remap   β”‚   or initializeWithRemapping(...) is used)
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
               β–Ό
   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   β”‚  URLClassLoader       β”‚  β†’ classes visible to your plugin
   β”‚  injection            β”‚    before onEnable() fires.
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ“ Project layout

src/main/java/de/jexcellence/dependency/
β”œβ”€β”€ JEDependency.java          ← public entrypoints
β”œβ”€β”€ manager/DependencyManager  ← core resolution pipeline
β”œβ”€β”€ remapper/                  ← ASM relocation (opt-in)
β”œβ”€β”€ downloader/                ← Maven artefact retrieval + checksum
β”œβ”€β”€ injector/ClasspathInjector ← runtime classloader injection
β”œβ”€β”€ loader/                    ← Paper / Spigot loader adapters
β”œβ”€β”€ repository/                ← repository registry + mirrors
β”œβ”€β”€ resolver/                  ← coordinate + transitive resolution
└── model/                     ← immutable data types

Full Javadoc lives next to the sources. Start from JEDependency and DependencyManager.


πŸ” Observability

  • Every stage logs through plugin.getLogger() β€” no custom appenders required.
  • FINE level reveals per-artifact download progress, checksum results, and relocation summaries.
  • Failure paths sanitise file system roots so logs are safe to share.
  • Start / end timestamps and dependency counts are emitted for automation to diff across restarts.

πŸ” Security practices

  • Maven checksum validation on every downloaded jar; corrupted files trigger a retry and cache purge.
  • Remapping runs inside a sandboxed URLClassLoader so a half-relocated class can never leak into your plugin loader on failure.
  • Temporary directories are wiped on both success and failure β€” no stale bytecode survives restarts.

❓ FAQ

How is this different from Paper's built-in libraries block?
Paper's loader only works on Paper 1.20+ and gives you no relocation support. JExDependency runs on Spigot and older Paper too, adds optional ASM relocation, and β€” when the Paper loader is active β€” cooperates with it instead of duplicating work.
Will it download every startup?
No. Artefacts are cached under the plugin's data folder and reused across restarts. Only missing or checksum-invalid jars are re-downloaded.
Does async mode block onEnable()?
No. initializeAsync returns a CompletableFuture<Void> immediately. You decide whether to .join() before using the dependencies or to schedule work after completion.
What Java versions are supported?
Java 21 is the primary target. Module de-encapsulation and --add-opens semantics for reflective libraries are handled for you.
Does relocation rewrite my plugin's own classes?
No. Only downloaded dependency jars are visited. Your plugin bytecode is never touched.

πŸ’¬ Support & Contact

Need a hand, found a bug, or want to bounce ideas around?


🀝 Contributing

Issues, discussions, and PRs are welcome.

  1. Fork the repo and create a feature branch.
  2. Run ./gradlew build to verify the project compiles.
  3. Add tests or a reproduction case where applicable.
  4. Open a PR describing the change and the motivation.

Please follow the existing code style β€” no wildcard imports, Javadoc on public API, nullability annotations from org.jetbrains.annotations.


πŸ“œ License

Released under the MIT License. Use it, ship it, star it. ⭐


Built with care by JExcellence. If JExDependency saves you a shaded jar today, consider giving it a star β€” it genuinely helps others find the project.

About

No more Shading in bukkit/spigot/paper

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages