Goblin - Chaos Engineering Extension for Quarkus
Goblin is a chaos engineering extension for Quarkus that lets you inject latency, exceptions, HTTP failures, and dependency degradation into your running application — without touching a single line of source code.
It is designed for one specific purpose: verifying that your resilience mechanisms actually work. You declared @Timeout, @Fallback, @Retry, health probes, or circuit breakers? Goblin gives you the tool to prove they hold under real failure conditions.
| Think of Goblin as Chaos Monkey for the Quarkus ecosystem — but with stronger safety guarantees. |
Why Goblin?
Quarkus has excellent resilience primitives (MicroProfile Fault Tolerance, Mutiny reactive timeouts, health probes), but no built-in way to trigger the failures these mechanisms are supposed to handle. You end up writing fragile integration tests that mock exceptions, or worse, discovering production failure modes for the first time in production.
Goblin fills that gap: inject realistic failures at the HTTP layer during development, observe how your application reacts, and iterate.
Key design decisions:
-
Dev mode only. Chaos artifacts are physically absent from production builds. Not disabled — absent. There is no toggle to forget to switch off.
-
Zero code modification. No
@ChaosLatencyannotations to scatter across your endpoints. Configuration is the only interface. -
Non-destructive. You can target specific packages, exclude annotated methods, and control the percentage of affected requests.
-
Composable. Multiple assault types can be active simultaneously on the same request.
Quick start
Add the dependency
<dependency>
<groupId>io.quarkiverse.goblin</groupId>
<artifactId>quarkus-goblin</artifactId>
<version>${goblin.version}</version>
</dependency>
That’s it. Start your application in dev mode:
./mvnw quarkus:dev
You will see a warning log confirming chaos is active:
WARN Chaos engineering active: 100% of REST requests subject to assault (latency=true, exception=false, httpStatus=false, dependencyDegradation=false)
Verify it works
With the default configuration (latency assault enabled, 100% of requests), every REST endpoint will have an artificial delay applied. Hit any endpoint and observe the added latency:
curl -w "\nTime: %{time_total}s\n" http://localhost:8080/api/hello
You should see a response time well above your normal baseline.
Assault types
Goblin supports four types of chaos assaults. Each type has its own boolean toggle — you can enable multiple types simultaneously on the same request.
When multiple types are active, they are applied in order:
-
Latency — adds delay before processing (applied first).
-
Exception — throws an exception, aborting the request.
-
HTTP Status — returns a specific status code, aborting the request.
-
Dependency Degradation — returns 503, aborting the request.
| If both latency and exception are enabled, the delay is applied first, then the exception is thrown. This simulates a slow failure — realistic for testing timeouts followed by fallbacks. |
LATENCY
Injects a random delay (between min-milliseconds and max-milliseconds) before processing the request. Useful for testing timeouts, circuit breakers, and retry mechanisms.
quarkus.goblin.assault.latency.min-milliseconds=500
quarkus.goblin.assault.latency.max-milliseconds=5000
The delay is applied via Thread.sleep() in the JAX-RS filter, before the request reaches your endpoint logic.
EXCEPTION
Throws a configurable exception before the request reaches the endpoint. The exception class and message are fully configurable.
quarkus.goblin.assault.exception.type=java.io.IOException
quarkus.goblin.assault.exception.message=Connection refused to downstream service
The exception must have a constructor that accepts a single String parameter (the message). RuntimeException is used as a fallback if the specified class cannot be instantiated.
HTTP_STATUS
Returns a specific HTTP status code without executing the endpoint method at all. The request is aborted at the filter level.
quarkus.goblin.assault.http-status.code=503
quarkus.goblin.assault.http-status.message=Service Unavailable (simulated outage)
Common use case: verify that your frontend handles 503 responses gracefully.
Targeting
By default, Goblin affects all REST endpoints in your application. You can narrow or expand this scope with targeting configuration.
Percentage-based targeting
Control what fraction of incoming requests are affected (0-100%):
# Only affect 10% of requests (useful for simulating intermittent failures)
quarkus.goblin.target.level=10
Setting level=0 effectively disables chaos without removing the extension. Setting level=100 (default) affects every request.
Package filtering
Include only specific packages:
quarkus.goblin.target.include-packages=com.example.api,com.example.internal
Exclude specific packages:
quarkus.goblin.target.exclude-packages=com.example.health,com.example.metrics
The filter matches on the declaring class’s package name using startsWith, so com.example will match com.example.api, com.example.internal, etc.
Annotation-based exclusion
Exclude methods (or their declaring classes) that carry specific annotations. This is particularly useful for coexisting with MicroProfile Fault Tolerance:
# Don't inject chaos on endpoints that already have fault tolerance annotations
quarkus.goblin.target.exclude-annotations=org.eclipse.microprofile.faulttolerance.Timeout,org.eclipse.microprofile.faulttolerance.Fallback
This prevents double-interception: you can chaos-test your unprotected endpoints while leaving fault-tolerant ones to their own mechanisms.
Full configuration reference
All configuration keys are under the quarkus.goblin prefix.
| Key | Type | Default | Description |
|---|---|---|---|
|
|
|
Enable/disable the Goblin extension. Only takes effect in dev mode. |
|
|
|
Initial assault type enabled at startup. Values: |
|
|
|
Minimum latency in milliseconds |
|
|
|
Maximum latency in milliseconds |
|
|
|
Fully qualified exception class name |
|
|
|
Exception message |
|
|
|
HTTP status code to return |
|
|
|
HTTP response body |
|
|
|
Percentage of requests to affect (0-100) |
|
|
empty |
Packages to include (empty = all packages) |
|
|
empty |
Packages to exclude from chaos |
|
|
empty |
Exclude methods/classes carrying these annotations |
Dev UI panel
When running in dev mode, Goblin provides a control panel in the Quarkus Dev UI (accessible at /q/dev). You reach it by clicking the Goblin card in the extension grid:
The Goblin card in the Dev UI extension grid.
Chaos Dashboard
The dashboard is the main control surface. It exposes the master toggle, per-type toggles, per-type configuration forms, and the target level.
The Chaos Dashboard with latency enabled at 100% target level.
-
Master toggle — Activate/deactivate all chaos with a single click. When inactive, the status dot turns red and the
Deactivatebutton becomesActivate. -
Assault type toggles — Independent on/off switches for each assault type (Latency, Exception, HTTP Status, Dependency Degradation). Multiple types can be active simultaneously. Enabled types are highlighted with the Quarkus primary color.
-
Config sections — Each assault type has its own configuration section (latency range, exception class/message, HTTP status code/message). Sections are always visible but disabled with placeholder text when the type is off. Enable the type to edit its parameters. Every section has its own
Savebutton. -
Target level — Adjust the percentage of affected requests (0-100%) on the fly, saved with its own button.
-
All changes apply instantly — no restart needed. A WARN log is emitted in the console for every change, and a toast confirms each action in the UI.
| When chaos is deactivated, the configuration sections remain visible but are dimmed and their inputs disabled — you can prepare your assault configuration before turning chaos on. |
History
A real-time log of every assault triggered, including the endpoint method, assault type, and timestamp. Clear the history between test sessions.
The Assault History panel showing past triggered assaults.
The history is an in-memory buffer capped at the latest 1000 assaults. Use it to confirm that your targeting rules and level are applying to the expected endpoints before drawing conclusions about your resilience checks.
How it works
Goblin operates at the JAX-RS filter layer using a ContainerRequestFilter:
-
At build time, the deployment module registers the filter and the
goblinfeature with Quarkus. -
At runtime startup, the
AssaultEngineis initialized with the configuration and activated (in dev/test mode only). -
For every incoming HTTP request, the filter checks:
-
Is chaos active?
-
Should this specific request be affected? (based on the
levelpercentage) -
Is the target endpoint eligible? (based on package/annotation filters)
-
-
If all conditions are met, all enabled assault types are applied in order: latency first (delay), then exception/HTTP-status/dependency-degradation (which abort the request).
-
An assault record is added to the history buffer (capped at 1000 entries).
The filter runs early in the JAX-RS pipeline, before any endpoint logic executes. This means:
-
Exception and HTTP_STATUS assaults completely bypass your endpoint code — no side effects, no partial state mutations.
-
Latency assaults add delay before processing, simulating network or upstream slowness.
Runtime config modification
All assault parameters can be modified at runtime through the Dev UI or via JSON-RPC. The initial config is loaded from application.properties at startup, then held in a mutable in-memory config (MutableAssaultConfig). This means you can:
-
Enable latency AND exception simultaneously to simulate a slow failure.
-
Toggle assault types on/off without restarting.
-
Change the latency range, exception class, or HTTP status code on the fly.
-
Drop the target level from 100% to 10% to test intermittent failures.
Changes take effect on the next request — no restart, no redeployment. A WARN log confirms every change in the console.
Compatibility
MicroProfile Fault Tolerance
Goblin is designed to coexist with Fault Tolerance annotations. The typical workflow:
-
Configure chaos on unprotected endpoints (default behavior).
-
Exclude fault-tolerant endpoints via
quarkus.goblin.target.exclude-annotations. -
Verify that the unprotected endpoints fail as expected.
-
Remove the exclusion, chaos-test the fault-tolerant endpoints to confirm
@Timeoutand@Fallbacktrigger correctly.
Troubleshooting
Chaos is not activating:
-
Verify you are running in dev mode (
quarkus:dev), not in test or production mode. -
Check that
quarkus.goblin.enabled=true(the default). -
Verify at least one assault type is enabled in the Dev UI.
-
Look for the WARN log at startup confirming chaos activation.
Endpoints are returning 500:
-
If using EXCEPTION assault, verify the exception class exists and has a
Stringconstructor. -
Check the Quarkus dev console for stack traces.
Targeting filters not working:
-
Package matching uses
startsWith, socom.examplematchescom.example.apiandcom.example.internal. -
Annotation matching uses fully qualified class names, not simple names. Use
jakarta.ws.rs.GET, notGET.
Dev UI config changes not visible:
-
Check the Quarkus console for WARN logs confirming the change.
-
Changes are in-memory only — they reset on restart. Use
application.propertiesfor persistent defaults.
End-to-end example: prove your @Timeout works
This walkthrough exercises a realistic scenario: verifying that a MicroProfile @Timeout and @Fallback react correctly when a downstream call becomes slow.
-
Start the sample application in dev mode:
./mvnw -pl integration-tests quarkus:dev -
Configure a latency assault that exceeds your
@Timeoutthreshold. If the downstream endpoint has a@Timeout(1000)(1 second), set a latency range well above it:quarkus.goblin.enabled=true quarkus.goblin.assault.type=LATENCY quarkus.goblin.assault.latency.min-milliseconds=2000 quarkus.goblin.assault.latency.max-milliseconds=4000 quarkus.goblin.target.level=100 # Exclude other fault-tolerant endpoints so only the one under test is affected -
Open the Dev UI at
http://localhost:8080/q/devand confirm latency is enabled in the Chaos Dashboard. -
Call the endpoint under test and observe the response time and whether the fallback was invoked:
curl -w "\nstatus=%{http_code} time=%{time_total}s\n" http://localhost:8080/api/hello -
Check the Assault History panel to confirm the latency assault was recorded against the expected method.
-
Repeat with
quarkus.goblin.target.level=10to observe intermittent, non-deterministic failures — the hardest scenario to get right in production.
JSON-RPC reference
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.
| Method | Effect |
|---|---|
|
Returns |
|
Returns the full mutable config (toggles, latency range, exception config, HTTP status config, level). |
|
Enable/disable chaos entirely. Emits a WARN log. |
|
Flip the corresponding assault toggle on/off. |
|
Set the min/max latency range. |
|
Set the exception class name and message. |
|
Set the HTTP status code and response body. |
|
Set the percentage of affected requests (clamped to 0-100). |
|
Return the assault history buffer. |
|
Empty the assault history buffer. |
FAQ
Does Goblin run in production?
No. The chaos implementation lives in the runtime-dev module, which is only attached in dev mode. In a production build the artifacts are physically absent — there is no configuration that can re-enable them.
Do I need to annotate my endpoints?
No. Goblin intercepts at the JAX-RS filter level automatically. The only interface is configuration, either static (application.properties) or dynamic (Dev UI / JSON-RPC).
What is the maximum assault history size?
The history buffer is capped at the latest 1000 entries; the oldest are dropped automatically.
Can I combine latency and exception to simulate a slow failure?
Yes. Multiple assault types can be active on the same request. In that case the latency (delay) is applied first, then the exception is thrown — ideal for verifying that @Timeout triggers before @Fallback, or that a fallback handles an exception raised after a slow response.
What happens if my exception class cannot be constructed?
If the configured class does not exist or has no String constructor, Goblin falls back to a RuntimeException carrying the configured message. See the Troubleshooting section.
Are Dev UI changes persisted?
No. Runtime changes are held in an in-memory MutableAssaultConfig and reset on restart. Use application.properties for any defaults that must survive a restart.
Non-goals (out of scope for V1)
-
Infrastructure chaos (pod killing, network partitioning) — use Chaos Mesh, Litmus, or Pumba for that.
-
Production/staging chaos — may be explored in a future version with a completely different safety model (explicit opt-in, time-bounded windows, audit trail).
-
Native compilation — to be validated separately once the core mechanism is proven on JVM.