Spring-powered dependency injection and lifecycle management for Minecraft plugins
PaperMC · BungeeCord · Velocity
moss is a tiny library that embeds a Spring ApplicationContext inside your Minecraft plugin and wires everything together for you.
Instead of:
- Huge monolithic main classes
- Static singletons everywhere
- Manual wiring of listeners, services, commands
…you get:
- Constructor injection with real DI
- Clear lifecycle hooks (
onLoad,onEnable,onReload,onDisable) - Simple helpers to invoke and register beans across your plugin
This repository contains:
- Core Spring integration (
moss) - Platform bootstraps for Paper, BungeeCord, and Velocity
- Fully working example plugins for each platform
- Requirements
- Installation
- Core Concepts
@SpringComponent- Lifecycle interfaces (
Loadable/Enableable/Reloadable/Disableable) invokeBeans(...)
- Building from Source
- Java: 21
- Build tool: Gradle or Maven
- Minecraft platforms:
- Paper ≥ 1.20+ / 1.21+ (example uses
1.21.4API) - BungeeCord (example uses
1.20-R0.2API) - Velocity (example uses
3.4.0-SNAPSHOTAPI)
- Paper ≥ 1.20+ / 1.21+ (example uses
- Spring:
spring-context6.x
moss is designed to be used as a shaded dependency in your plugin JAR:
- Add
moss-<platform>andspring-contextas implementation dependencies. - Use Shadow (or equivalent) to shade + relocate Moss and Spring into your plugin.
Replace
<version>with the current release (e.g.1.0.0or whatever is published in Negative Games Repo / your repo).
plugins {
id 'java'
id 'com.gradleup.shadow' version '9.2.2'
}
group = 'com.example.myplugin'
version = '1.0.0'
repositories {
mavenCentral()
maven {
name = "negative-games-repo"
url = "https://repo.negative.games/repository/maven-releases/"
}
}
dependencies {
// Platform API
compileOnly "io.papermc.paper:paper-api:1.21.4-R0.1-SNAPSHOT"
// Spring
implementation "org.springframework:spring-context:6.2.13"
// Moss (Paper platform)
implementation "games.negative.moss:moss-paper:<version>"
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
tasks {
build {
dependsOn shadowJar
}
}
shadowJar {
archiveBaseName.set("MyPlugin")
archiveClassifier.set("")
archiveVersion.set("")
// Very important: relocate Moss (and optionally Spring)
relocate "games.negative.moss", "${project.group}.libs.moss"
// relocate "org.springframework", "${project.group}.libs.spring"
}plugins {
java
id("com.gradleup.shadow") version "9.2.2"
}
group = "com.example.myplugin"
version = "1.0.0"
repositories {
mavenCentral()
maven("https://repo.negative.games/repository/maven-releases/")
}
dependencies {
compileOnly("io.papermc.paper:paper-api:1.21.4-R0.1-SNAPSHOT")
implementation("org.springframework:spring-context:6.2.13")
implementation("games.negative.moss:moss-paper:<version>")
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(21))
}
}
tasks {
named("build") {
dependsOn(named("shadowJar"))
}
}
tasks.shadowJar {
archiveBaseName.set("MyPlugin")
archiveClassifier.set("")
archiveVersion.set("")
relocate("games.negative.moss", "${project.group}.libs.moss")
}Conceptually similar:
- Add
moss-paper,moss-bungeecord, ormoss-velocityas a dependency. - Use the Maven Shade plugin to relocate
games.negative.moss(and optionally Spring) into your plugin jar.
<repositories>
<repository>
<id>negative-games-repo</id>
<url>https://repo.negative.games/repository/maven-releases/</url>
</repository>
</repositories><dependency>
<groupId>games.negative.moss</groupId>
<artifactId>moss-paper</artifactId>
<version><version></version>
</dependency>(Then configure maven-shade-plugin with a relocation rule from games.negative.moss → com.example.myplugin.libs.moss.)
package games.negative.moss.spring;
import org.springframework.stereotype.Component;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface SpringComponent {
String value() default "";
}@SpringComponent is a thin wrapper around Spring’s @Component. You use it on any class that should be created and managed by the Spring ApplicationContext inside your plugin:
- Listeners
- Commands
- Services / Managers
- Repositories, etc.
Important: Moss scans the package of your main plugin class and its sub-packages:
private String basePackage() {
return this.getClass().getPackageName();
}So make sure your components live under the same package root as your main class, e.g.:
- Main plugin class:
com.example.myplugin.MyPlugin - Components:
com.example.myplugin.listener.*,com.example.myplugin.service.*, etc.
All lifecycle interfaces live in games.negative.moss.spring:
public interface Loadable {
void onLoad(GenericApplicationContext context);
}-
Called during plugin load, after the Spring context is created and refreshed.
-
Use this for:
- Registering additional beans programmatically
- Early initialization that depends on the
ApplicationContext - Setting up things that must exist before
onEnable(configurations, database connections, etc.)
public interface Enableable {
void onEnable();
}-
Called when the plugin is enabled (Paper/Bungee) or when the proxy finishes initialization (Velocity).
-
Use this for:
- Starting schedulers
- Registering commands
- Registering listeners
- Registering stuff that should exist while the plugin is “up”
public interface Reloadable {
void onReload();
}-
Called when
reload()is invoked on the base Moss class. -
Use this for:
- Config reloads
- Rebuilding caches
- Re-binding configuration-driven services
Each platform base class calls
reload()once duringonEnable/ initialization as an “initial reload”.
public interface Disableable {
void onDisable();
}-
Called when the plugin is disabled / proxy stops.
-
Use this for:
- Flushing data
- Closing connections
- Cleaning up resources
All platform base classes expose:
public <T> void invokeBeans(
Class<T> clazz,
Consumer<T> consumer,
BiConsumer<T, Exception> onFailure
)
public <T> void invokeBeans(
Class<T> clazz,
Consumer<T> consumer
)- Finds all beans of type
clazzin the Spring context. - Invokes
consumer.accept(bean)for each bean. - If
consumerthrows,onFailure.accept(bean, ex)is called.
Typical use cases:
- Register every
Listenerwith the platform’s event system - Register every
CommandExecutoror similar - Run an initialization pass over multiple services
Clone the repository and run:
gradle publishToMavenLocalTo build one of the example plugins (shaded JAR):
# Paper example plugin
gradle :example-plugins:paper-plugin:build
# Bungee example plugin
gradle :example-plugins:bungeecord-plugin:build
# Velocity example plugin
gradle :example-plugins:velocity-plugin:buildThe shaded JARs will be in example-plugins/<module>/build/libs.