ADVANCED
Java API & Utilities
Invoke Karate from Java code, configure comprehensive logging, mask sensitive data, and optimize test execution for CI/CD pipelines.
On this page:
- Runner API - Programmatic test execution
- Runner.runFeature() - Call features from Java code
- karate.callSingle() - Run code once across parallel tests
- Logging - Logback configuration
- Logging and report control - Mask sensitive data, hide scenarios, control verbosity
- CI/CD - Pipeline-friendly configuration
- Utilities - Dynamic ports, reusable functions
- Java integration - Thread-safe utilities and hooks
Quick Start — the karate CLI
To run tests without Maven, Gradle, or any Java setup, install the Karate CLI. The Rust launcher bootstraps the JRE and the Karate fatjar for you — zero manual Java install, zero build tool.
See Standalone Execution for installation, and Command Line for the full CLI reference. For the scenarios below, the entry point is simply karate run.
Java DSL (embedding Karate in your code)
When you need Java code instead of .feature files — one-off scripts, quick API checks, or embedding Karate inside an existing Java app — use the Java DSL. The Http, Json, and Match classes work standalone without a test runner:
import io.karatelabs.http.Http;
import io.karatelabs.common.Json;
import io.karatelabs.match.Match;
import java.util.List;
public class Example {
public static void main(String[] args) {
List users = Http.to("https://jsonplaceholder.typicode.com/users")
.get().json().asList();
Match.that(users.get(0)).contains("{ name: 'Leanne Graham' }");
String city = Json.of(users).get("$[0].address.city");
Match.that("Gwenborough").isEqualTo(city);
System.out.println("\n*** second user: " + Json.of(users.get(1)).toString());
}
}
Add io.karatelabs:karate-core to your Maven or Gradle build to use these classes.
Runner API Basics
The Runner API lets you execute Karate tests from within Java code, giving you full programmatic control. This is the standard approach for running tests in CI/CD pipelines, where you need to specify which tests to run, configure parallel execution, and capture results for reporting.
The key components are:
Runner.path()- Specify which feature files or directories to runSuiteResult- Object containing pass/fail counts and timing data
import io.karatelabs.core.Runner;
import io.karatelabs.core.SuiteResult;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
@Test
void runKarateTests() {
SuiteResult results = Runner.path("classpath:features")
.tags("~@ignore")
.parallel(5);
assertFalse(results.isFailed());
}
Advanced Runner Configuration
Chain multiple builder methods to customize test execution. The example below runs tests from two locations, filters by tags, sets the environment to "staging", enables both Cucumber JSON and JUnit XML reports for CI integration, and runs with 10 parallel threads:
@Test
void advancedRunnerConfig() {
SuiteResult results = Runner.path("classpath:api", "classpath:smoke.feature")
.tags("@regression", "~@wip")
.karateEnv("staging")
.outputCucumberJson(true)
.outputJunitXml(true)
.outputDir("target/karate-reports")
.parallel(10);
assertFalse(results.isFailed(),
"Found " + results.getScenarioFailedCount() + " failures");
}
Runner Builder Methods
| Method | Description |
|---|---|
path(String...) | Feature files or directories to run |
tags(String...) | Include/exclude by tags (~@ignore to exclude) |
karateEnv(String) | Set the Karate environment |
systemProperty(String, String) | Set a Java system property |
parallel(int) | Number of threads (must be last — returns SuiteResult) |
outputDir(String) | Output directory for reports (default: target/karate-reports) |
outputJunitXml(boolean) | Generate JUnit XML reports for CI |
outputCucumberJson(boolean) | Generate Cucumber JSON reports |
outputHtmlReport(boolean) | Enable/disable HTML reports (default: true) |
outputJsonLines(boolean) | Emit JSONL event stream |
dryRun(boolean) | Skip step execution and hooks while still generating a full report |
listener(RunListener) | Add a run event listener (replaces v1 hook()) |
Dry Run Mode
Produce a full report without executing any steps — useful for feature-file validation, tag-coverage review, and CI smoke passes that don't need real I/O:
SuiteResult results = Runner.path("classpath:features")
.tags("@smoke")
.dryRun(true)
.parallel(1);
Under dry run, every step on a normal scenario is recorded as passed with 0ms duration. Config JS (karate-base.js, karate-config.js) and beforeScenario / afterScenario hooks are skipped. @setup scenarios still execute fully so dynamic scenario outlines resolve their rows. Inside an @setup scenario, read karate.suite.dryRun to branch on mode — e.g. return placeholder rows instead of hitting a real database.
See Dry Run for the full behavior matrix.
Invoking Features from Java
Sometimes you need to call a Karate feature file from within Java code - for example, to set up test data before a UI test, or to verify database state after an operation. Runner.runFeature() executes a single feature and returns a FeatureResult whose getResultVariables() gives you response, custom variables, or any other data set during execution.
import io.karatelabs.core.Runner;
import io.karatelabs.core.FeatureResult;
import java.util.Map;
import java.util.HashMap;
public class JavaApiExample {
public static void main(String[] args) {
// Pass variables to the feature
Map<String, Object> vars = new HashMap<>();
vars.put("userId", 1);
// Run the feature
FeatureResult result = Runner.runFeature(
"classpath:features/get-user.feature",
vars
);
// Access variables set in the feature
Map<String, Object> out = result.getResultVariables();
Object user = out.get("response");
System.out.println("User: " + user);
}
}
Use Runner.runFeature() to integrate Karate API calls into larger test suites (e.g., Selenium tests) for data setup or verification.
karate.callSingle()
When running tests in parallel, karate-config.js executes once per thread, which can cause problems for expensive operations like authentication. If you have 10 parallel threads, your login feature would run 10 times unnecessarily.
karate.callSingle() solves this by guaranteeing code runs exactly once across all threads, with the result cached and shared. This is essential for:
- Authentication tokens - Login once, share the token across all tests
- Test data setup - Create a user once, use it everywhere
- Database seeding - Initialize data once before all tests
function fn() {
var config = {};
// This runs only once, even with parallel execution
var authResult = karate.callSingle('classpath:auth/get-token.feature');
config.authToken = authResult.token;
return config;
}
callSingle with Arguments
You can pass data to callSingle() using a second parameter. However, since callSingle() caches results by the feature file path, calling the same feature with different arguments would return the same cached result.
To cache different argument combinations separately, add a ?key suffix to the feature path. Each unique key creates a separate cache entry:
function fn() {
// Different cache entries for admin vs user tokens
var adminToken = karate.callSingle('classpath:auth/get-token.feature?admin',
{ username: 'admin', password: 'admin123' });
var userToken = karate.callSingle('classpath:auth/get-token.feature?user',
{ username: 'user', password: 'user456' });
return { adminToken: adminToken.token, userToken: userToken.token };
}
callSingleCache
During local development, you often restart tests frequently. By default, callSingle() only caches in memory, so every restart triggers a fresh login. This slows down development.
Enable disk caching to persist results between test runs. The cache is automatically invalidated after the specified duration:
function fn() {
if (karate.env == 'local') {
// Cache for 15 minutes during local development
karate.configure('callSingleCache', { minutes: 15 });
}
var auth = karate.callSingle('classpath:auth/login.feature');
return { authToken: auth.token };
}
callSingle should return pure JSON data. Complex objects like Java instances or JS functions with closures may cause issues in parallel execution.
Logging Configuration
Karate uses Logback for logging. By default, logs go to the console, but you can customize log levels, formats, and destinations by creating a configuration file.
Basic Logback Setup
Create logback-test.xml in src/test/resources/. Karate will automatically detect and use this file. The example below logs Karate messages at DEBUG level (showing HTTP request/response details) while keeping other libraries at INFO:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="io.karatelabs" level="DEBUG"/>
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
Logger Categories
Karate v2 emits logs under named SLF4J categories so you can tune verbosity per concern instead of flipping the whole engine to DEBUG:
| Category | What it covers |
|---|---|
karate.runtime | Feature/scenario lifecycle, step execution, suite orchestration |
karate.http | HTTP client — request/response bodies, headers, retries |
karate.mock | Mock server — incoming requests, matched scenarios, responses |
karate.scenario | User output — print, karate.log(), scenario-scoped messages |
karate.console | CLI console output — summary, colors, progress |
Enable selectively — for example, DEBUG only for HTTP traffic while keeping the rest at INFO:
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="karate.http" level="DEBUG"/>
<logger name="karate.mock" level="DEBUG"/>
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
Skip logback entirely with karate run --log-console debug. For reports (independent of console output), use --log-report <level> or configure logging = { report: 'warn' } inside a feature. See Logging.
Environment-Specific Logging
You may want verbose DEBUG logs during local development but quieter INFO logs in CI pipelines. Logback supports conditional configuration using Janino (add it as a dependency). This checks the karate.env system property and adjusts log levels accordingly:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<if condition='property("karate.env").equals("ci")'>
<then>
<logger name="io.karatelabs" level="INFO"/>
</then>
<else>
<logger name="io.karatelabs" level="DEBUG"/>
</else>
</if>
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
</configuration>