Database
Grim stores violation history, session metadata, player identity, persistent player settings, and future blob-style records in the datastore. The current public configuration is split into one routing file and one file per backend:
plugins/GrimAC/database.ymldecides whether the datastore is enabled and which backend each category uses.plugins/GrimAC/databases/<backend-id>.ymlcontains the connection settings for that backend.
By default, violation, session, player-identity, and setting route to
SQLite; blob routes to none.
database:
enabled: true
routing:
violation: sqlite
session: sqlite
player-identity: sqlite
setting: sqlite
blob: none
The supported backend ids are:
- memory - no setup, process-local, lost on restart. Use only when you intentionally want ephemeral storage.
- sqlite - embedded file, no separate server. Default and recommended for single-server setups.
- mysql - MySQL / MariaDB, good when several Grim instances share history.
- postgres - PostgreSQL, same multi-server use case as MySQL.
- mongo - MongoDB document store, useful if you already operate Mongo.
- redis - Redis KV store; only use for history when Redis persistence is configured and the durability tradeoff is acceptable.
Tip
If you only need history for one server and do not already run a database, stay on SQLite. It needs no driver install on Bukkit/Spigot/Paper, and the default file is
plugins/GrimAC/data/history.v1.db.
Drivers - What's Already On The Classpath
Most Bukkit-family servers ship the SQLite and MySQL JDBC drivers as part of the server itself, so on those platforms Grim can use them directly with no extra install. PostgreSQL, MongoDB, and Redis drivers are never bundled by any server flavour, so those need a holder plugin/mod or another classpath source. Fabric and NeoForge ship no DB drivers at all, so every persistent backend needs a holder mod there.
Each holder mod is a single universal jar that works on Spigot/Paper, Fabric, Forge, and NeoForge from one file. Drop it next to Grim; install nothing else.
| Backend | Bukkit / Spigot / Paper | Fabric / NeoForge |
|---|---|---|
| SQLite | Built in on supported Bukkit-family servers. Optional holder: minecraft-sqlite-jdbc. | Install minecraft-sqlite-jdbc. |
| MySQL / MariaDB | Built in on supported Bukkit-family servers. Optional holder: minecraft-mysql-jdbc. | Install minecraft-mysql-jdbc. |
| PostgreSQL | Install minecraft-postgresql-jdbc. | Install minecraft-postgresql-jdbc. |
| MongoDB | Install minecraft-mongodb-driver. | Install minecraft-mongodb-driver. |
| Redis | Install minecraft-jedis. | Install minecraft-jedis. |
Important
On Bukkit/Spigot/Paper, the bundled
sqlite-jdbcandmysql-connector-jlive on the server's parent classloader. Plugin classloaders delegate to the parent first, so for the default JDBC path (DriverManager.getConnection) the bundled driver normally wins regardless of what is inplugins/. Grim works fine on Bukkit-family servers out of the box using the bundled drivers.If you want a newer SQLite engine than what your server bundles, install the holder mod and Grim will detect it via its public API and route through the holder's child-first classloader when the holder is strictly newer. See Using a newer SQLite engine on a Bukkit-family server.
Tip
Holder plugins are completely Grim-agnostic. They just ship standard JDBC / native drivers as Minecraft plugins/mods. Other plugins using the same drivers can benefit from the same install.
SQLite Engine Versions Across Server Lines
For reference if you are wondering what SQLite version your server actually ships:
| Server line | Bundled SQLite engine | Grim's SQLite backend |
|---|---|---|
| CraftBukkit 1.8 - 1.10 | 3.7.2 | works (legacy dialect) |
| CraftBukkit 1.11 | 3.16.1 | works (legacy dialect) |
| CraftBukkit 1.12 | 3.21.0.1 | works (legacy dialect) |
| CraftBukkit 1.13.2+ | 3.25.2 or newer | works (modern dialect) |
| Paper 1.21.4+ | 3.47.0.0 or newer | works (modern dialect) |
| Paper master / 26.x | 3.49.1.0+ | works (modern dialect) |
Grim's SQLite backend probes sqlite_version() at startup and picks one of
two writer dialects: a single-statement INSERT ... ON CONFLICT DO UPDATE
upsert on engine 3.24+, or a two-statement INSERT OR IGNORE + UPDATE
fallback in a transaction on older engines. Both produce the same row state.
Schema is portable across supported MC versions; see
Database - History
for the file-portability story.
Using A Newer SQLite Engine On A Bukkit-Family Server
If you specifically want an engine version newer than what the server bundles
for the modern UPSERT path on a legacy server, for RETURNING / STRICT
support, or just to track the latest Xerial release, install
minecraft-sqlite-jdbc
alongside Grim.
The holder mod ships a public API class
(dev.axionize.sqlite_jdbc.MinecraftSqliteJdbc) that wraps a child-first
URLClassLoader pointing at its own jar, parented to the platform classloader
so org.sqlite.* comes from the holder rather than the server's parent chain.
A Driver instance returned from this loader has its own class identity,
distinct from the bundled copy.
Grim probes for this API at SQLite-backend init via reflection. If the holder is present and its engine version is strictly newer than the bundled driver's, every JDBC connection Grim opens routes through the holder's classloader. You will see log lines like:
[grim-datastore] minecraft-sqlite-jdbc holder is newer than bundled
(holder=3.53.0, bundled=3.21.0); routing JDBC through holder's child-first classloader
[grim-datastore] SQLite engine 3.53.0 - using modern dialect
This means a Paper 1.8 server with bundled SQLite 3.7.2 plus the holder mod gets the same modern UPSERT writers as a recent Paper server. Same schema, no Grim code changes.
If you want the same trick from your own plugin, softdepend on sqlite-jdbc
(the holder's Bukkit name) and call MinecraftSqliteJdbc.connect(url)
directly. The returned Connection is a standard java.sql.Connection; keep
casts to java.sql.* interfaces, not connector-internal types. See the
holder's Modrinth page for
the full API and caveats. The MySQL holder uses the same pattern.
Picking And Configuring Backends
Edit plugins/GrimAC/database.yml to route categories. Each distinct backend
id in routing: must have a matching file under
plugins/GrimAC/databases/<backend-id>.yml.
Example: shared history on MySQL, local settings on SQLite:
database:
routing:
violation: mysql
session: mysql
player-identity: mysql
setting: sqlite
blob: none
SQLite
plugins/GrimAC/databases/sqlite.yml:
sqlite:
path: "data/history.v1.db"
journal-mode: WAL
synchronous-mode: NORMAL
busy-timeout-ms: 5000
cache-pages: 10000
batch-flush-cap: 256
No driver install needed on Bukkit/Spigot/Paper; the driver is already bundled. On Fabric or NeoForge, install minecraft-sqlite-jdbc.
SQLite is single-server storage. Networked setups should route history to a backend that talks to a shared database.
MySQL / MariaDB
plugins/GrimAC/databases/mysql.yml:
mysql:
host: "localhost"
port: 3306
database: "grim"
user: "root"
password: ""
extra-jdbc-params: ""
batch-flush-cap: 256
tables:
meta: grim_meta
checks: grim_checks
players: grim_players
sessions: grim_sessions
violations: grim_violations
settings: grim_settings
The connection URL Grim builds includes useSSL=false,
allowPublicKeyRetrieval=true, and rewriteBatchedStatements=true
automatically. Append anything else, such as TLS overrides or timeouts, via
extra-jdbc-params.
MariaDB works through the same MySQL backend.
MariaDB support
MariaDB is supported through the mysql backend id and the same
databases/mysql.yml file. There is no separate mariadb backend id and no
separate MariaDB JDBC driver requirement; Grim connects through Connector/J and
then probes SELECT VERSION() during backend initialization.
If the server reports MariaDB 10.6 or newer, Grim selects its MariaDB dialect.
The MariaDB dialect uses MariaDB's supported ON DUPLICATE KEY UPDATE ... VALUES(column) upsert form, while genuine MySQL uses the MySQL 8.0.19+
aliased-row upsert form. Both flavors share the same v8 table shape, including
the stored generated current_name_lower column used for indexed
case-insensitive player-name lookup.
MariaDB older than 10.6 is rejected at startup with a clear datastore error. The hard floor is intentionally conservative: generated columns need MariaDB 10.2.1+, and 10.6 is the oldest MariaDB LTS line Grim treats as supported.
Startup logs identify the chosen flavor:
[grim-datastore] MariaDB engine 10.11.16-MariaDB-... - using MariaDB dialect
PostgreSQL
plugins/GrimAC/databases/postgres.yml:
postgres:
host: "localhost"
port: 5432
database: "grim"
user: "postgres"
password: ""
extra-jdbc-params: ""
batch-flush-cap: 256
tables:
meta: grim_meta
checks: grim_checks
players: grim_players
sessions: grim_sessions
violations: grim_violations
settings: grim_settings
Useful additions in extra-jdbc-params: sslmode=require,
reWriteBatchedInserts=true, ApplicationName=Grim.
MongoDB
plugins/GrimAC/databases/mongo.yml:
mongo:
connection-string: "mongodb://localhost:27017"
database: "grim"
batch-flush-cap: 256
tables:
meta: grim_meta
checks: grim_checks
players: grim_players
sessions: grim_sessions
violations: grim_violations
settings: grim_settings
Use the connection string for replica set members, auth source, read preference, and other Mongo options.
Redis (Jedis)
plugins/GrimAC/databases/redis.yml:
redis:
host: "localhost"
port: 6379
database: 0
user: ""
password: ""
key-prefix: ""
timeout-ms: 2000
batch-flush-cap: 256
warn-on-history: true
tables:
meta: grim_meta
checks: grim_checks
players: grim_players
sessions: grim_sessions
violations: grim_violations
settings: grim_settings
Warning
Redis is appropriate for low-latency writes but trades durability for throughput. If your Redis instance flushes or you hit maxmemory eviction, history can be lost. Use one of the relational or document backends if durability matters.
Datastore Settings
database.yml also controls session reconstruction, the async write path,
migration, name resolution, and /grim history paging:
database:
session:
gap-ms: 600000
scope-per-server: true
heartbeat-interval-ms: 30000
write-path:
queue-capacity: 16384
batch-size: 256
flush-interval-ms: 1000
warn-rate-ms: 10000
shutdown-drain-timeout-ms: 5000
wait-strategy: BLOCKING
retention:
session:
enabled: true
max-age-days: 90
violation:
enabled: true
max-age-days: 365
player-identity:
enabled: false
setting:
enabled: false
blob:
enabled: true
max-age-days: 30
migration:
skip: false
max-duration-ms: 0
name-resolution:
chain: [local-cache, offline-mode-uuid]
history:
entries-per-page: 15
group-interval-ms: 30000
server-name: "Prison"
write-path.queue-capacity must be a positive power of two. Valid wait
strategies are BLOCKING, TIMEOUT_BLOCKING, SLEEPING, YIELDING, and
BUSY_SPIN.
server-name is a display-only tag stamped on new session records and shown
in /grim history. Changing it does not rewrite old sessions.
Verifying It Worked
After restart, check paper.log or your platform equivalent for a line like:
[grim-datastore] SQLite engine 3.49.1 - using modern dialect
For non-SQLite backends, look for the backend connect/init log. If a driver is missing or the backend cannot initialize, Grim logs the backend error and then logs that datastore initialization failed.
Failure Handling
If database.enabled is false, datastore services are skipped. Live
anti-cheat checks still run, but violation history, /grim history, persisted
player toggles, and datastore-backed identity lookup are unavailable.
Current public behavior: if a configured backend's driver is missing or the
database is unreachable during startup, datastore initialization fails and the
datastore is disabled for that run. Grim does not silently fall back to
memory; route a category to memory explicitly if you want ephemeral
storage.
/grim reload tears down the current datastore and rebuilds backends plus
routing from the current database.yml and databases/*.yml. Writes during
the brief teardown/rebuild window can be dropped.
Planned but not shipped: ordered backend chains for a category, such as
session: [mysql, sqlite, memory], with boot-time selection of the first
healthy backend. Hot failover after a backend drops mid-session is not current
behavior.
Subsystems
Different parts of Grim use the database differently. See the dedicated pages:
- History -
/grim history, violation records, sessions - Settings - persisted player toggles and internal key/value settings
More information in the readme.
Available pages
More information in the readme.