Go + Java extensions · ABI v1
Write once.
Run in the shared world.
The GoCraft plugin API is designed around one edition-neutral host. Events, commands and permissions are resolved in GoCraft's core, so plugin behavior does not split into separate Java and Bedrock implementations.
Availability
Checked on 10 September 2026: Go/JVM runtimes and custom events are merged into main. The 14-event native API described below is on feat/go-events-api, with missing-items gameplay work included, and is still under review. Do not mix its examples with older released SDKs. GoCraft remains experimental.
.gcpkg scanning and deterministic load order.gocraft-cli v0.2.1 and the Java Gradle plugin build .gcpkg bundles.Two runtimes, one contract
Each Go plugin is a separate executable, not a Go -buildmode=plugin library. Java plugins use a separate Java 25 runtime that hosts multiple plugins. The Minecraft server and shared game state remain in Go.
- Structure is data. Event layouts and command trees cross the ABI; Go function values do not.
- The host owns state. Allowed event mutations return in the verdict; additional host effects are queued for a safe tick.
- One API serves both editions. Protocol-specific encoding stays in the Java and Bedrock adapters.
Getting started today
Use Go 1.26.0, JDK 25 and the checked-in Gradle wrapper. CLI v0.2.1 supports custom-event generation and packaging. For this native-event preview, use matching feat/go-events-api branches in GoCraft, the JVM repository and the examples; the Go example already pins the matching published SDK and ABI commits.
git clone --branch feat/go-events-api https://github.com/GoCraft-MC/gocraft-jvm.git
git clone --branch feat/go-events-api https://github.com/GoCraft-MC/gocraft-plugin-examples.git
go install github.com/GoCraft-MC/gocraft-cli@v0.2.1
# Ensure your Go binary directory is on PATH.
(cd gocraft-jvm && ./gradlew publishToMavenLocal)
cd gocraft-plugin-examples
gocraft-cli gen -lang java -package gocraft.example.greeting \
-o java/src/main/java/gocraft/example/greeting go
(cd java && sh ./gradlew gocraftBundle)
(cd go && SHOP_BUNDLE=../java/build/gocraft/gocraft-example-java.gcpkg ./build.sh)
These are POSIX-shell commands; use Git Bash on Windows and replace wrapper invocations with ./gradlew.bat. The Java example uses locally published feature artifacts named 0.3.0, not the released v0.3.0 API. Copy both bundles into a matching server's plugins/ folder and restart. See the complete example instructions for outputs and local CLI overrides.
Bundle layout
Every language ships the same .gcpkg ZIP format. The host reads and validates metadata before it starts a runtime.
plugin.toml # validated manifest
commands.pb # optional neutral command tree
bin/example-go # Go executable at the manifest entry
payload/*.jar # Java bundles instead carry plugin jars
assets/
... # plugin-owned resources
The packer reads a staged directory; it does not compile source. Java's Gradle task stages jars automatically. The Go example script generates types, builds its binary with GOWORK=off, and checks events.lock.json before packing. Native executables must match the server's OS and architecture.
plugin.toml
The manifest lets GoCraft discover subscriptions and commands without executing plugin code. Unknown keys fail strict decoding.
id = "dev.example.protect"
version = "0.1.0"
api = 1
runtime = "go"
entry = "bin/protect"
[[subscribe]]
event = "block.break"
priority = "high"
perms = ["protect.bypass"]
[[subscribe]]
event = "player.chat"
[commands]
tree = "commands.pb"
idLowercase identifier using letters, numbers, dots, dashes or underscores.RequiredversionPlugin version displayed by the host.RequiredapiABI major. The current and only accepted value is 1.Requiredruntimego for a native executable; jvm for a Java plugin.RequiredentryGo binary path or Java plugin class name.Runtime entrysubscribe.eventOne event per [[subscribe]] block, optionally with a priority.Per eventsubscribe.permsPermission answers requested by this subscription only.Optionalcommands.treePath to the generated tree. Omit this block for a plugin with no commands.OptionalEvents
Native events are generated from the shared ABI schema into typed Go structs and Java classes. Only cancellable handlers receive EventControl. Register listeners and declare each subscription in the manifest.
Cancellable
Go uses control.Cancel(); Java uses control.cancel(). The verdict prevents the original action.
Observational
Scheduled away from the simulation tick with isolated payload copies and health tracking.
Failure policy
ALLOW fails open; DENY fails closed when a verdict is missing.
// import gocraft "github.com/GoCraft-MC/gocraft-api-go"
return ctx.Events().OnPlayerChat(func(event *gocraft.PlayerChatEvent, control gocraft.EventControl) {
if event.Message == "hide-go" {
control.Cancel()
return
}
if event.Message == "hello-go" {
event.Message = "Hello from the typed Go event API."
}
})
import fr.gocraft.api.EventControl;
import fr.gocraft.api.Subscribe;
import fr.gocraft.api.event.PlayerChatEvent;
public final class ChatListener {
@Subscribe
public void onChat(PlayerChatEvent event, EventControl control) {
if ("hide-java".equals(event.message())) {
control.cancel();
return;
}
if ("hello-java".equals(event.message())) {
event.setMessage("Hello from the typed Java event API.");
}
}
}
Go callbacks in one plugin share the event pointer and control. Accepted mutations reach the next plugin; cancellation stops later plugins. Subscriptions use priority order, then plugin ID. Observational notifications are asynchronous and cannot be cancelled.
Only declared mutable fields return through IPC. Changing a Go snapshot such as a player's name does not change server state; Java exposes setters only for mutable values. Chat rewritten to start with / remains chat, while command rewrites are reparsed and permission-checked.
Source: Native event semantics and hook timing. Complete registration and lifecycle code: Go and Java examples.
Native gameplay events
The preview has 14 native event types. The final column indicates whether cancellation is supported; mutable values are listed in the middle. All other values are snapshots.
player.joinAfter the player is reachable; no mutable fields.Notifyplayer.quitAfter removal from online players; no mutable fields.Notifyplayer.chatBefore broadcast; mutable message.Cancelplayer.commandBefore parsing and permission checks; mutable command.Cancelblock.breakBefore block removal, drops and tool wear; no mutable fields.Cancelblock.placeAfter validation, before block writes and item consumption; no mutable fields.Cancelplayer.damageAfter shield/armour checks, before resistance/absorption; mutable damage.Cancelentity.damageNon-player damage before application or queue coalescing; mutable damage.Cancelplayer.deathFatal health transition after totem resolution; no mutable fields.Notifyplayer.respawnAfter authoritative revival and position setup; no mutable fields.Notifyplayer.teleportCommand teleports in the current dimension; mutable X, Y and Z.Cancelplayer.interactMain-hand block/entity use, not attack or dismount; no mutable fields.Cancelinventory.clickJava container clicks or Bedrock inventory transactions; no mutable fields.Cancelitem.useMain-hand use in air before starting/consuming the item; no mutable fields.CancelCancelled placement and inventory actions restore the client's prediction to canonical state. Bedrock multi-slot transactions use slot=-1, not Java's individual click gesture. Zero damage prevents application; non-finite numeric mutations are rejected.
// Inside OnLoad, with block.break declared in plugin.toml.
return ctx.Events().OnBlockBreak(func(event *gocraft.BlockBreakEvent, control gocraft.EventControl) {
if !event.Can("protect.bypass") {
control.Cancel()
_ = event.Player.SendMessage("Protected area.")
}
})
Commands
Commands are a neutral tree. Java renders it as Brigadier data, Bedrock renders it as Available Commands, and the plugin runtime only receives the selected local executor ID and typed values.
func (p *Plugin) Commands() *gocraft.CommandSet {
set := gocraft.NewCommandSet()
set.Command("greet").Permission("example.greet").Runs(func(call *gocraft.CommandContext) error {
call.Reply("Hello, " + call.SenderName + "!")
return nil
})
return set
}
Go's command dump and Java's annotation processor produce neutral metadata; gocraft-cli creates commands.pb. Handlers bind to command paths, not hard-coded IDs. The host namespaces collisions, parses arguments and checks permissions.
Argument types
integerdecimalstringgreedyplayerblock_posblock_stateitemdurationenumcustom
Core command names cannot be replaced. Plugin-versus-plugin collisions are namespaced deterministically, such as /economy:shop.
Permissions
Declare permission reads in the manifest. The host resolves them once against its group and operator model, then injects the answers into an event. Command branches carry their permission node and are pruned before a client can tab-complete them.
- Event permissions come from
subscribe.perms. - Command permissions use Go's
.Permission(...)or Java's@Permissionand command builders. - Invocation rechecks access on the server; client pruning is not treated as security.
- Installing a plugin is trusting it. The manifest is not a process sandbox.
Effects and world writes
There are two return paths. Cancellation and allowed event-field mutations are validated before the guarded action continues. Additional effects, such as sending a message, go through the host's FIFO queue instead of writing game state from a plugin thread.
Calls enqueued while a drain is already running stay queued for the following tick. Closing the queue returns ErrMutationQueueClosed instead of silently accepting data.
Lifecycle
- 1Scan
Read and validate all manifests and command trees without executing code.
- 2Preflight
Provision only the runtime languages required by installed bundles.
- 3Register structure
Resolve command conflicts before a runtime process starts.
- 4Load, warm and ready
Start runtimes, attach subscriptions, perform handler-free JVM warm-up, then signal readiness before listeners open.
- 5Stop in reverse
Revoke subscriptions and commands, unload instances, then stop runtimes.
Any partial startup failure rolls back loaded instances, event subscriptions, command trees and runtime processes.
Go registers callbacks in OnLoad, followed by OnEnable and OnDisable. Java receives Host through its constructor and registers listeners in enable(). Unloading detaches subscriptions; runtime recovery creates fresh plugin state. Persist durable data in the plugin's data directory, not only in memory.
abi/v1
The language-neutral values carried across runtime boundaries.
Value
Tagged language-neutral values: booleans, integers, doubles, strings, bytes and recursive lists. Generated native event types hide this positional transport from normal Go and Java handlers.
ABI source ↗Event definition
The schema declares event fields, their allowed mutations and cancellation. Code generation keeps host, Go and Java representations aligned.
Verdict
A handler returns cancellation, permitted mutations and queued effects through the existing IPC verdict. No extra mutation call is required.
Plugin-defined events
Providers declare events and records in their manifest or Java annotations. Subscribers generate their types from the provider bundle.
Layout locks
events.lock.json rejects field reordering or removal at package time. Appending a field remains compatible.
Go author API
Import github.com/GoCraft-MC/gocraft-api-go as gocraft. Plugins do not import server internals. See the matching SDK source.
Plugin
OnLoad(gocraft.Context) error
OnEnable() error
OnDisable() errorContext
Provides Events(), Commands(), Scheduler(), Logger(), Metadata() and DataDirectory().
Typed listeners
OnPlayerChat and other generated registrations take typed pointers. Observational callbacks such as OnPlayerJoin have no control argument in this preview.
EventControl
Cancel() requests cancellation; Cancelled() reads its state. Only cancellable callbacks receive it.
CommandSet
Return a builder from Commands() to declare command paths, typed arguments, permissions and handlers together.
Run
Start the plugin with gocraft.Run(metadata, plugin). The executable handles its own IPC session and lifecycle.
Java author API
Use fr.gocraft.api through the GoCraft Gradle plugin. The JVM repository contains the API, runtime, annotation processor and Gradle integration.
Plugin
Implement enable() and disable(). Accept Host in the constructor; no static main or Bukkit JavaPlugin is required.
Host
During enable, call host.registerListener(new ChatListener()). The manifest must declare every subscribed event.
@Subscribe
Receive a typed event. Add EventControl only for cancellable events and use generated setters for permitted mutations.
Commands
@Cmd, @Sub and @Permission feed generated command metadata. Builders bind the resulting paths to handlers.
Custom events
@PluginEvent declares a provider. host.emit(event) returns whether it was allowed; read the modified object after it returns.
Build tooling
gocraftBundle compiles, stages and packages the plugin. The Gradle plugin selects the API and processor together.
Limits and guarantees
Custom events, runtimes, SDKs, commands and packaging work today. Native gameplay coverage is still expanding: there is no separate PlayerKill event or structured killer data, and movement, portal/pearl teleports, creative inventory edits and specialised placement are not fully covered. Typed configuration, cross-plugin services and datastore APIs remain separate future work.
Try “events”, “commands”, “manifest” or “lifecycle”.