Java
Logging, reloading and testing
Java · Updated 09 November 2026
Log with SLF4J, reload your JAR while you work, and unit-test against mocks.
Logging
Use SLF4J. It comes with the API, so there is nothing to add to your build:
private static final Logger log = LoggerFactory.getLogger(MyScript.class);
log.info("Started!");
log.debug("Looping {}", state);
log.warn("No tree found within {} tiles.", CHOPPING_DISTANCE_TILES);
log.error("Error in onLoop", e);
The console and the logs console command tag each line with your script's name. The Logs view in the GUI shows both the script and the client it runs on. Each run of a script is also written to a file under ~/.botwithus/logs/scripts/<script>/.
Catch the errors you can recover from inside onLoop, log them, and return a delay, so one bad loop does not end the run:
@Override
public int onLoop() {
try {
return switch (state) {
case CHOPPING -> handleChopping();
case DROPPING -> handleDropping();
};
} catch (RuntimeException e) {
log.error("Error in onLoop", e);
return LOOP_ON_ERROR_MS;
}
}
Reloading while you work
You do not need to restart the host after every build. Turn on the folder watch and the host reloads a JAR whenever it is added, rebuilt or deleted:
- the
reload --watchconsole command, config set autoReload true, or- the switch in Settings.
A plain reload leaves the reloaded scripts stopped. reload --start starts every script after reloading. The scripts.restartAfterReload setting restarts exactly the scripts that were running before.
If two JARs hold the same module, the newest one wins and the other is reported as a failed load. Delete the old JAR.
Testing
Most of a good script is ordinary Java (state machines, decisions, parsing) that you can unit-test with JUnit like any other code.
The framework also publishes a test-support library, bot-test-support, with mocks for driving a script without a game: MockScriptContext, CannedSnapshot and InMemoryEventBus. It is in the same Maven repository as the API. Add it as a test dependency, at the same version as bot-api:
dependencies {
compileOnly("com.botwithus:bot-api:1.5.0")
testImplementation("com.botwithus:bot-api:1.5.0")
testImplementation("com.botwithus:bot-test-support:1.5.0")
}
Add JUnit the way you normally would. A test with the mocks looks like this, with MyScript from Anatomy of a script standing in for your own script class:
@Test
void lifecycle_completesAgainstEmptySnapshot() {
MockScriptContext ctx = MockScriptContext.builder().build();
MyScript script = new MyScript();
script.onStart(ctx);
int delay = script.onLoop();
script.onStop();
assertTrue(delay > 0, "onLoop should return a positive delay");
}
@Test
void onLoop_queuesNoActionsByDefault() {
List<String> sink = new ArrayList<>();
MockScriptContext ctx = MockScriptContext.builder()
.recordingActionsInto(sink)
.build();
MyScript script = new MyScript();
script.onStart(ctx);
script.onLoop();
script.onStop();
assertTrue(sink.isEmpty(), "MyScript should not queue any actions");
}
MockScriptContext.builder().withSnapshot(...)feeds your script a canned game state, for exampleCannedSnapshot.withSelf(player).recordingActionsInto(list)records the actions your script queues, so you can assert on them.- The mock
GameAPIsupports only a few calls and throws on the rest. Keep game calls behind small methods of your own if you want to test around them.
When you move to a newer bot-api, move bot-test-support to the same version.
Next: Preparing for SDN.