Version: 1.0.0 Compatibility: 1.21 - 1.21.11 (Paper) Java: 21 Author: cd3daddy
Centralized database and Redis connection pooling for Minecraft plugins.
SharedDB provides shared MariaDB (HikariCP) and Redis (Lettuce) connection pools that multiple plugins can use. Instead of each plugin managing its own database and Redis connections, they all share one pool — reducing resource usage, simplifying configuration, and enabling read/write split architectures.
- Database connection pooling via HikariCP with read/write split (separate read replica and write primary pools)
- Redis connection pooling via Lettuce with read/write split
- Simple static API — plugins call
SharedDB.redisGet(),SharedDB.getWriteConnection(), etc. - Simple Redis API covering strings, hashes, sets, sorted sets, and pub/sub — no need to interact with Lettuce directly
- Binary Redis API for storing compressed or serialized data
- Caller tracking and debug logging — see which plugins are making which calls
- Health monitoring via
/shareddb statusand/shareddb test - All dependencies shaded and relocated — no conflicts with other plugins
# config.yml
database:
# Read connection - point to your local replica for fast reads
# For single-server setups, use the same host for both read and write
read:
host: 127.0.0.1
port: 3306
# Write connection - point to your primary database
write:
host: 127.0.0.1
port: 3306
username: minecraft
password: "yourpassword"
parameters: "?autoReconnect=true&useSSL=false&useUnicode=true&characterEncoding=UTF-8"
pool:
maximum-size: 20
minimum-idle: 5
connection-timeout: 10000
max-lifetime: 1800000
idle-timeout: 300000
keepalive-time: 30000
redis:
password: ""
timeout: 10000
# Read connection - point to your local replica for fast reads
# For single-server setups, use the same host for both read and write
read:
host: 127.0.0.1
port: 6379
# Write connection - point to your primary Redis
write:
host: 127.0.0.1
port: 6379
threads:
io: 4
computation: 4For single-server setups, set both read and write hosts to the same address. The read/write split is designed for multi-server or multi-region deployments where you have a primary database/Redis with local read replicas.
Maven (pom.xml):
<dependency>
<groupId>com.c3smp</groupId>
<artifactId>SharedDB</artifactId>
<version>1.0.0</version>
<scope>provided</scope>
</dependency>Since SharedDB is not published to a Maven repository, install the jar to your local Maven repo:
mvn install:install-file -Dfile=SharedDB-1.0.0.jar -DgroupId=com.c3smp -DartifactId=SharedDB -Dversion=1.0.0 -Dpackaging=jarOr reference it as a system dependency:
<dependency>
<groupId>com.c3smp</groupId>
<artifactId>SharedDB</artifactId>
<version>1.0.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/libs/SharedDB-1.0.0.jar</systemPath>
</dependency>Gradle (build.gradle.kts):
compileOnly(files("libs/SharedDB-1.0.0.jar"))plugin.yml:
# Required dependency
depend: [SharedDB]
# Or optional dependency
softdepend: [SharedDB]SharedDB exposes static methods that hide the underlying Lettuce/JDBC complexity.
// Read connection — use for SELECT queries (fast, local replica)
Connection conn = SharedDB.getReadConnection();
Connection conn = SharedDB.getReadConnection("database_name");
// Write connection — use for INSERT/UPDATE/DELETE (primary)
Connection conn = SharedDB.getWriteConnection();
Connection conn = SharedDB.getWriteConnection("database_name");
// IMPORTANT: Always close connections when done (use try-with-resources)
try (Connection conn = SharedDB.getWriteConnection("mydb")) {
PreparedStatement ps = conn.prepareStatement("INSERT INTO ...");
ps.executeUpdate();
}
// Check availability
boolean ready = SharedDB.isReady();
boolean dbRead = SharedDB.isDatabaseReadAvailable();
boolean dbWrite = SharedDB.isDatabaseWriteAvailable();
boolean redisRead = SharedDB.isRedisReadAvailable();
boolean redisWrite = SharedDB.isRedisWriteAvailable();String value = SharedDB.redisGet(key);
SharedDB.redisSet(key, value);
SharedDB.redisSetex(key, seconds, value);
long deleted = SharedDB.redisDel(key1, key2, ...);
boolean exists = SharedDB.redisExists(key);
List<String> keys = SharedDB.redisKeys(pattern);
SharedDB.redisExpire(key, seconds);String value = SharedDB.redisHget(key, field);
boolean isNew = SharedDB.redisHset(key, field, value);
Map<String, String> all = SharedDB.redisHgetall(key);
SharedDB.redisHmset(key, map);
long deleted = SharedDB.redisHdel(key, field1, field2, ...);long added = SharedDB.redisSadd(key, member1, member2, ...);
long removed = SharedDB.redisSrem(key, member1, member2, ...);
Set<String> members = SharedDB.redisSmembers(key);long added = SharedDB.redisZadd(key, score, member);
List<String> range = SharedDB.redisZrangeByScore(key, minScore, maxScore);
long removed = SharedDB.redisZremrangeByScore(key, minScore, maxScore);long receivers = SharedDB.redisPublish(channel, message);
String pong = SharedDB.redisPing();For storing compressed or serialized binary data:
SharedDB.redisSetBytes("mykey", byteArray);
SharedDB.redisSetexBytes("mykey", 3600, byteArray);
byte[] data = SharedDB.redisGetBytes("mykey");
SharedDB.redisDelBytes("mykey");For advanced use cases like setting up pub/sub listeners:
RedisCommands<String, String> sync = SharedDB.redis(); // read replica
RedisCommands<String, String> writeSync = SharedDB.writeRedis(); // primary
RedisAsyncCommands<String, String> async = SharedDB.redisAsync(); // async read
RedisAsyncCommands<String, String> writeAsync = SharedDB.writeRedisAsync(); // async write
// Pub/sub
StatefulRedisPubSubConnection<String, String> pubsub = SharedDB.pubsub();
// ... set up listeners ...
SharedDB.closePubSub(pubsub); // close when doneIf your plugin shades its own copy of Lettuce or other libraries that SharedDB also shades, you may hit classloader conflicts. In that case, use reflection:
private Class<?> sharedDBClass;
private boolean initSharedDB() {
try {
Plugin sharedDBPlugin = Bukkit.getPluginManager().getPlugin("SharedDB");
if (sharedDBPlugin == null || !sharedDBPlugin.isEnabled()) return false;
sharedDBClass = Class.forName("com.c3smp.shareddb.SharedDB", true,
sharedDBPlugin.getClass().getClassLoader());
String pong = (String) sharedDBClass.getMethod("redisPing").invoke(null);
getLogger().info("SharedDB connected: " + pong);
return true;
} catch (Exception e) {
getLogger().warning("SharedDB init failed: " + e.getMessage());
return false;
}
}
private String redisGet(String key) {
try {
return (String) sharedDBClass.getMethod("redisGet", String.class).invoke(null, key);
} catch (Exception e) { return null; }
}This is only necessary if you have classloader conflicts. Most plugins can use the direct static API without issues.
/shareddb status Permission: shareddb.admin Show connection status for all pools.
/shareddb test db Permission: shareddb.admin Test database connection.
/shareddb test redis Permission: shareddb.admin Test Redis connections.
/shareddb test all Permission: shareddb.admin Test all connections.
/shareddb debug on|off Permission: shareddb.admin Toggle debug logging (shows which plugins make which calls).
/shareddb stats Permission: shareddb.admin Show per-plugin usage statistics.
/shareddb clearstats Permission: shareddb.admin Clear usage statistics.
shareddb.admin (default: op) — Access to all /shareddb commands.
MariaDB Driver Version: SharedDB uses MariaDB JDBC 2.7.12 (not 3.x) for compatibility with libraries that use PreparedStatement.setObject() with Long and Date types. MariaDB 3.x is stricter about these type conversions.
Why Reflection? Each Bukkit plugin has its own classloader. When plugins shade libraries (like Lettuce), their shaded classes are incompatible with SharedDB's shaded classes even though they're functionally identical. Using reflection with SharedDB's classloader avoids ClassCastException errors. Most plugins that don't shade Lettuce themselves can use the direct static API.
Pub/Sub Listeners: Use SharedDB.pubsub() to create pub/sub connections. The connection is tracked and will be closed on shutdown, but you should close it yourself when done via SharedDB.closePubSub().
mvn clean packageOutput: target/SharedDB-1.0.0.jar