JSON-RPC reference

Table of Contents

The Dev UI talks to the runtime through a JSON-RPC service (GoblinJsonRPCService). The same service is available over the Dev UI’s built-in JSON-RPC bridge, which is useful for scripting or CI validation. All methods read or mutate the in-memory MutableAssaultConfig; in dev mode every mutation is also written to .goblin-state.json, which replaces the quarkus.goblin.assault.* and quarkus.goblin.target.level properties at the next start (the active flag is never persisted).

Method Effect

getStatus

Returns active, inactiveReason (why chaos is off: manual, auto-off, disabled for quarkus.goblin.enabled=false, test-mode for a test run without quarkus.goblin.test.enabled, launch-mode outside dev/test; absent while chaos is active, the Dev UI JSON-RPC transport leaving out null fields), autoOffRemainingMs (the time left before the pending auto-off, 0 when none), the active profile, the four assault toggles, the two client-side assault toggles, the responseBodyEnabled and responseHeaderEnabled toggles, the armed chaos layers (array of layer names), the availableLayers backed by an installed hook in this application (SERVICE, HTTP_OUT, HTTP_IN, plus DATABASE with a JDBC datasource and MESSAGING with Quarkus Messaging), and the target level.

getConfig

Returns the full mutable config (profile, toggles, client-side toggles, latency range, exception config, HTTP status config, response body config, response header rules under headers, armed chaos layers, availableLayers, level) plus exceptionPresets, the exception classes offered as quick picks in the Dev UI. Every preset is validated by the engine (loadable, extends RuntimeException, has a single-String constructor) so picking one never triggers the fallback.

toggleActive / setActive(boolean)

Enable/disable chaos entirely. Emits a WARN log. Deactivating chaos also cancels any pending auto-off; activating it keeps a pending one. When the auto-off deadline has just elapsed, toggleActive leaves chaos off (the caller meant to turn it off) instead of switching it back on; setActive with the intended state avoids the ambiguity. In dev mode the decision belongs to the session and survives a live reload in both directions: a deactivation is not undone by the next file save, and an activation is not undone either. It is deliberately kept out of .goblin-state.json, so a new process still starts from quarkus.goblin.enabled. Returns the full config plus ok and active.

startAutoOff(minutes)

Schedule chaos to switch itself off after minutes (between 1 and 1440, i.e. 24 hours), replacing any pending auto-off. The engine applies the deadline on its own, whether or not the Dev UI is open. In dev mode a pending auto-off survives a live reload (not a new start), and once it has fired chaos stays off across live reloads until it is switched on again; a test application started by continuous testing keeps its own, separate auto-off. Returns { ok, autoOffRemainingMs }; a delay out of range returns { ok: false, error }.

cancelAutoOff()

Cancel the pending auto-off; chaos stays in its current state. Returns { ok, autoOffRemainingMs: 0 }.

setProfile(profile)

Activate a predefined composite assault mode (NONE, SLOW_FAILURE, INTERMITTENT, TIMEOUT, case-insensitive; a blank value means NONE). A profile other than NONE turns every server-side assault off (response body and response header included), then enables its own assaults and overwrites their parameters: latency 100-5000 ms and java.lang.RuntimeException with the default message for SLOW_FAILURE, HTTP 500 with Internal Server Error (Goblin chaos) for INTERMITTENT, a fixed 30000 ms latency for TIMEOUT. NONE leaves every toggle as it is. Client-side toggles and chaos layers are never touched. Each toggle stays overridable afterwards. An unknown profile returns { ok: false, error }.

toggleLatency / toggleException / toggleHttpStatus / toggleDependencyDegradation / toggleResponseBody / toggleResponseHeader

Flip the corresponding assault toggle on/off.

toggleClientLatency / toggleClientException

Flip the corresponding client-side assault toggle on/off (outgoing REST Client and Vert.x WebClient calls).

setResponseBodyConfig(mode, percentage)

Set the response body transformation (TRUNCATE or INFLATE) and its target size in percent. Invalid modes are rejected; out-of-range percentages are clamped with the correction surfaced in warning.

setResponseHeaderInfo(name, action, value)

Add or replace the rule applied to the named response header. action is case-insensitive (SET or REMOVE, the legacy ADD/OVERRIDE labels are accepted and mapped to SET); blank header names, names that are not RFC 9110 tokens, values containing CR, LF or control characters, and unknown actions are rejected with ok=false. REMOVE ignores the value.

removeResponseHeader(name)

Drop the rule configured for the named response header.

setLatencyRange(minMs, maxMs)

Set the min/max latency range.

setExceptionConfig(type, message)

Set the exception class name and message. The class must extend RuntimeException and have a single-String constructor; a non-RuntimeException or unknown class is kept in the config but flagged in warning (the engine falls back to RuntimeException).

setHttpStatusConfig(code, message)

Set the HTTP status code and response body.

setTargetLevel(level)

Set the percentage of affected requests (clamped to 0-100).

disableAll()

Kill switch: deactivates the engine (which cancels any pending auto-off), disables every assault type and client-side assault, and resets the profile to NONE. The deactivation is reported as inactiveReason: manual and, like the master toggle, survives a live reload of the dev process. Returns { ok, active: false } plus the full config.

resetDefaults()

Restore every toggle and parameter to its built-in default value, not to application.properties: profile NONE, the latency assault on with 100-5000 ms, every other assault and client-side assault off, HTTP status 503, body TRUNCATE 50%, no header rules, layers {HTTP_IN, HTTP_OUT}, level 100. The active flag and any pending auto-off are left unchanged. Returns { ok, warning } plus the full config.

applyConfig(config)

Apply a (partial) config object: profile is applied first, then any provided field overrides — omitted fields keep their current value, except those the profile resets (a profile other than NONE turns every server-side assault off before enabling its own, see setProfile). The optional headers object replaces the whole set of response header rules (rules not listed are dropped); the optional layers array replaces the armed chaos layers (unknown layer names are skipped and surfaced in warning, an empty array restores the default layers). Unknown actions and header names that are not RFC 9110 tokens are skipped and surfaced in warning. Booleans and numbers given as strings ("true", "50") are converted; a value that cannot be converted is skipped and surfaced in warning. A few fields are skipped without a warning: a latency object is only applied when it carries both minMilliseconds and maxMilliseconds, an unknown body.mode is ignored, and non-string entries of layers are dropped. The payload is staged on a copy and published at once: when it is rejected (e.g. an unknown profile) nothing is applied and the response is the unchanged full config plus ok: false and error. Returns { ok, warning } plus the full config. Used by the dashboard’s import, custom-profile and chaos-layer features.

getCounters

Return { total, since, byType, bySource } with the session assault counters: the total number of assaults since the engine started (or the counters were last reset), the epoch timestamp of that window start (since), a per-type breakdown (byType) and a per-source breakdown (bySource, keyed by server, service, rest-client, webclient, database, messaging).

resetCounters()

Reset the assault counters to zero. Returns { ok } only.

getHistory

Return the assault history buffer. Each entry exposes the method, type, timestamp, the applied latency duration (latencyMs, 0 for non-latency assaults), the source (server, service, rest-client, webclient, database, messaging), and the active assault config snapshot at the time of the assault (config).

clearHistory

Empty the assault history buffer. Returns { cleared: true } only.

getMarkdownReport

Return { markdown, generatedAt } with a factual Markdown report of the current configuration and assault history.

listScenarios()

List the saved scenarios (.goblin/scenarios/). Returns { ok, scenarios }, each scenario with its name, savedAt (epoch milliseconds), assaults (what it arms, e.g. latency enabled (100 - 500 ms), or unreadable: …​ for a file that cannot be read), layers and level. See Scenarios.

saveScenario(name, overwrite)

Save the current assault configuration (every toggle and parameter, the armed layers and the target level, never the active flag) as .goblin/scenarios/<name>.json, in the .goblin-state.json format. A name is 1 to 64 letters, digits, spaces, - or _, starting with a letter or a digit; names are case-insensitive. A failure returns { ok: false, error, code }, code being INVALID_NAME, EXISTS (a scenario of that name, whatever its case, exists and overwrite is false; its stored name is then in existingScenario), STORAGE or NOT_INITIALISED. Returns { ok, scenario, saved }: scenario is the stored name and saved the scenario as listScenarios describes it. Across the scenario operations, scenario is always a name, and only set on success.

loadScenario(name)

Replace the whole assault configuration by a saved scenario, published at once (one onConfigChange notification) and persisted to .goblin-state.json like any Dev UI change; an invalid value in the file falls back with a log, as for any configuration change. It never switches chaos on or off. Emits a WARN log. Returns the full config plus { ok, scenario }, scenario being the name as stored, or { ok: false, error, code } with code NOT_FOUND, INVALID_NAME, STORAGE or NOT_INITIALISED.

deleteScenario(name)

Delete a scenario file, leaving the current configuration untouched; names are case-insensitive. Returns { ok, deleted }, deleted being false when no scenario had that name, or { ok: false, error, code } with code INVALID_NAME or STORAGE.

Every mutating method except resetCounters, clearHistory, startAutoOff, cancelAutoOff, saveScenario and deleteScenario returns the full configuration as JsonObject (the same shape as getConfig) plus ok, so the Dev UI treats the response as a single source of truth; a rejected call returns ok: false and an error (alone, or with the unchanged full config for applyConfig). For backward compatibility the setters also echo the values they changed as top-level keys: setLatencyRange (minMilliseconds, maxMilliseconds), setExceptionConfig and setHttpStatusConfig (type or code, message), setResponseBodyConfig (mode, percentage), setTargetLevel (level), setResponseHeaderInfo (name, action, value); getConfig itself only carries level at the top level.

Dev MCP exposure

Every method above is also a Quarkus Dev MCP tool, so an AI agent connected to /q/dev-mcp can call it (see Driving Goblin from an AI agent).

What decides the exposure is a single rule: a method carrying a @JsonRpcDescription is served to both the Dev UI and MCP, and a method without one stays in the Dev UI only. A blank description is not enough — it hides the method from both. A description is also the only documentation an agent gets, so every method and every parameter describes the action, the accepted values and what comes back.

Being described is not the same as being enabled. A method annotated @DevMCPEnableByDefault is handed to the agent as soon as the Dev MCP server is on; a method without it stays out of the agent’s tool list (tools/list only returns enabled tools) until a developer turns it on explicitly in the Dev MCP tools page of the Dev UI (Settings, the gear icon, then the Dev MCP tab, then Tools), the only place where disabled tools are listed. The descriptions of the read-only tools say so, so an agent that only sees them knows the arming tools exist and must be enabled by a human. The split follows one rule: reading the state or stopping chaos is enabled by default, arming or mutating chaos is opt-in.

Enabled by default Why

getStatus, getConfig, getHistory, getCounters, getMarkdownReport

Read-only. An agent can find out what is armed and what has already happened, which is the minimum needed to work with Goblin at all.

startAutoOff, cancelAutoOff, disableAll

Stopping chaos. An agent must always be able to arm a safety net and to shut everything down, without having to ask a human first.

listScenarios

Read-only. An agent discovers the saved experiments, and what each one arms, before anything is enabled.

Next to the tools, Goblin serves two Dev MCP resources, quarkus-goblin_agentPlaybook and quarkus-goblin_resilienceInventory, both read-only and enabled by default under the same rule. They are build-time data, not JSON-RPC methods: see Agent playbook.

Everything else is opt-in: the master switch (toggleActive and setActive), every assault toggle, every parameter setter, setProfile, resetDefaults, applyConfig, the scenario tools that load, save or delete a scenario (loadScenario arms the assaults the scenario holds, saveScenario and deleteScenario write to disk), and the two methods that throw away evidence (resetCounters and clearHistory). setActive stays opt-in even when it is called with false, because the same tool arms chaos too; disableAll is the tool that turns everything off, and it needs no prior configuration.

applyConfig takes a free-form configuration object, and the MCP schema generated for a free-form object documents none of its keys. Its description therefore spells out every accepted key, its type and its accepted values, which makes it the longest description of the service.
With Quarkus 3.38, a tool that takes two parameters or more shows the description of its first parameter instead of its own, in the Dev MCP tools page and in the tool list an agent receives: Quarkus reads the method description with a lookup that also matches the parameter annotations. This affects setLatencyRange, setExceptionConfig, setHttpStatusConfig, setResponseBodyConfig and setResponseHeaderInfo, which are all opt-in. Their parameter descriptions stay correct, and the tools behave as documented in the table above; none of the tools enabled by default is affected, since they take at most one parameter.