Skip to main content

ADVANCED

Java API & Utilities

Invoke Karate from Java code, configure comprehensive logging, mask sensitive data, and optimize test execution for CI/CD pipelines.

Benefits of Java API

  • Programmatic test execution: Full control over test runs from Java code
  • Enterprise logging integration: Configure Logback for compliance and monitoring
  • Security compliance: Mask passwords, tokens, and sensitive data automatically
  • CI/CD optimization: Reduce noise in pipeline logs while capturing failures
  • Performance monitoring: Track execution time and memory usage

Quick Start with jbang

Run Karate tests without a build tool using jbang for rapid prototyping and simple projects.

// Install jbang: https://www.jbang.dev

// Run a single feature file
jbang karate@karatelabs test.feature

// Run with specific environment
jbang karate@karatelabs --env=qa test.feature

// Run with tags
jbang karate@karatelabs --tags=@smoke features/

// Parallel execution
jbang karate@karatelabs --threads=5 features/

Runner API Basics

Execute Karate tests programmatically from Java code using the Runner API.

import com.intuit.karate.Results;
import com.intuit.karate.Runner;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;

@Test
void runKarateTests() {
Results results = Runner.path("classpath:features")
.tags("~@ignore")
.parallel(5);

assertEquals(0, results.getFailCount(),
results.getErrorMessages());
}

Advanced Runner Configuration

@Test
void advancedRunnerConfig() {
Results results = Runner.path("classpath:api", "classpath:smoke.feature")
.tags("@regression", "~@wip")
.karateEnv("staging")
.outputCucumberJson(true)
.outputJunitXml(true)
.reportDir("target/karate-reports")
.parallel(10);

assertTrue(results.getFailCount() == 0,
"Found " + results.getFailCount() + " failures");
}

Logging Configuration

Basic Logback Setup

Create logback-test.xml in src/test/resources for complete logging control.

logback-test.xml
<?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="com.intuit.karate" level="DEBUG"/>

<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
</configuration>

Environment-Specific Logging

Use Janino for conditional logging based on environment.

logback-test.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<if condition='property("karate.env").equals("ci")'>
<then>
<logger name="com.intuit.karate" level="INFO"/>
</then>
<else>
<logger name="com.intuit.karate" level="DEBUG"/>
</else>
</if>

<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
</configuration>

File and Console Logging

Send logs to both console and file for comprehensive debugging and archival.

logback-test.xml
<configuration>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>target/karate.log</file>
<encoder>
<pattern>%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>WARN</level>
</filter>
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>

<logger name="com.intuit.karate" level="DEBUG"/>

<root level="INFO">
<appender-ref ref="FILE"/>
<appender-ref ref="STDOUT"/>
</root>
</configuration>

Log Masking for Security

Custom Log Modifier

Implement HttpLogModifier to mask sensitive data in logs and reports.

public class SecureLogModifier implements HttpLogModifier {

@Override
public boolean enableForUri(String uri) {
// Enable masking for auth endpoints
return uri.contains("/auth") || uri.contains("/login");
}

@Override
public String request(String msg) {
// Mask passwords in request logs
return msg.replaceAll(
"\"password\"\\s*:\\s*\"[^\"]+\"",
"\"password\":\"***MASKED***\""
);
}

@Override
public String response(String msg) {
// Mask tokens in response logs
return msg.replaceAll(
"\"token\"\\s*:\\s*\"[^\"]+\"",
"\"token\":\"***MASKED***\""
);
}
}

Using Log Modifier in Tests

Feature: Secure logging

Background:
* def LogModifier = Java.type('com.example.SecureLogModifier')
* configure logModifier = new LogModifier()

Scenario: Login with masked credentials
Given url authUrl
And path 'login'
And request { username: 'user@test.com', password: 'secret123' }
When method post
Then status 200
# Password is masked in logs and reports

Report Verbosity Control

Configure Report Output

Control what appears in HTML reports for cleaner, more focused test documentation.

Feature: Report configuration

Background:
# Full verbose reporting
* configure report = { showLog: true, showAllSteps: true }

Scenario: Detailed reporting
* print 'This appears in report'
* def data = 'visible variable'
* match response == '#present'

Scenario: Minimal reporting
* configure report = { showLog: false, showAllSteps: false }
* print 'Only print statements visible'
* def internal = 'hidden from report'

Feature-Level Report Control

Hide utility features from reports using tags.

@report=false
Feature: Utility functions

Scenario: Helper function (hidden from reports)
* def helper = function(x) { return x * 2 }
* def result = helper(5)
* match result == 10

Logging in Tests

Using karate.log()

Add structured logging within test scenarios for debugging and monitoring.

Feature: Test logging

Scenario: Structured logging
* karate.log('Starting user creation test')
* def userData = { name: 'Alice', email: 'alice@example.com' }
* karate.log('User data prepared:', userData)

Given url baseUrl
And path 'users'
And request userData
When method post
* karate.log('Response status:', responseStatus)
* karate.log('Created user ID:', response.id)

Then status 201

CI/CD Integration

Pipeline-Friendly Configuration

Reduce log noise in CI while capturing failures for troubleshooting.

// karate-config.js
function fn() {
var config = { baseUrl: 'https://api.example.com' };

if (karate.env === 'ci') {
// Minimal logging in CI
karate.configure('printEnabled', false);
karate.configure('report', {
showLog: false,
showAllSteps: false
});

// Capture failures
karate.configure('afterScenario',
function() {
if (karate.info.errorMessage) {
karate.log('FAILED:', karate.info.scenarioName);
karate.log('ERROR:', karate.info.errorMessage);
}
}
);
}

return config;
}

Parallel Execution Stats

Monitor parallel execution efficiency and identify bottlenecks.

Results results = Runner.path("classpath:features")
.parallel(5);

System.out.println("Scenarios passed: " + results.getPassCount());
System.out.println("Scenarios failed: " + results.getFailCount());
System.out.println("Total time: " + results.getElapsedTime() + "ms");
System.out.println("Features: " + results.getFeaturesPassed() + "/" +
results.getFeaturesTotal());

if (results.getFailCount() > 0) {
System.err.println("Failures:\n" + results.getErrorMessages());
}

Commonly Needed Utilities

Dynamic Port Numbers

Configure dynamic ports for parallel test execution to avoid conflicts.

// karate-config.js
function fn() {
var port = karate.properties['server.port'] || '8080';
var config = {
baseUrl: 'http://localhost:' + port + '/api'
};
return config;
}

// Run tests with dynamic port
// mvn test -Dserver.port=8081

Multiple Functions in One File

Organize reusable JavaScript functions efficiently.

// utils.js
function() {
return {
randomEmail: function() {
return 'user' + Math.floor(Math.random() * 10000) +
'@test.com';
},

randomString: function(length) {
var chars = 'abcdefghijklmnopqrstuvwxyz';
var result = '';
for (var i = 0; i < length; i++) {
result += chars.charAt(
Math.floor(Math.random() * chars.length)
);
}
return result;
},

formatDate: function(date) {
return new Date(date).toISOString().split('T')[0];
}
};
}

Use the utilities in tests:

Background:
* def utils = call read('classpath:utils.js')

Scenario: Use utility functions
* def email = utils.randomEmail()
* def password = utils.randomString(12)
* def today = utils.formatDate(new Date())

* print 'Generated email:', email
* print 'Password length:', password.length
* print 'Today:', today

Java Function References

Call Java static methods directly from Karate tests.

Feature: Java interop

Background:
* def Utils = Java.type('com.example.TestUtils')
* def UUID = Java.type('java.util.UUID')

Scenario: Use Java utilities
* def randomId = UUID.randomUUID().toString()
* def hash = Utils.generateHash('password123')
* def timestamp = Utils.getCurrentTimestamp()

* print 'Random ID:', randomId
* print 'Hash:', hash

When to Use What

Choose the right tool based on your testing needs:

NeedUse ThisWhy
Run tests without build tooljbangZero setup, instant execution
Programmatic test controlRunner APIFull Java integration
Mask passwords/tokensHttpLogModifierSecurity compliance
CI/CD loggingEnvironment configReduce pipeline noise
Performance trackingCustom functionsMonitor bottlenecks
Reusable utilitiesutils.jsDRY principle
Dynamic configurationkarate-config.jsEnvironment switching
Security Best Practice

Always mask sensitive data (passwords, tokens, API keys) in logs and reports. Use HttpLogModifier for production test suites to ensure compliance.

Performance Monitoring

Track Execution Time

Monitor test performance and identify slow operations.

Background:
* def timer =
"""
{
start: function(name) {
karate.set(name + '_start', Date.now());
},
end: function(name) {
var start = karate.get(name + '_start');
var duration = Date.now() - start;
karate.log(name + ' took ' + duration + 'ms');
return duration;
}
}
"""

Scenario: Profile API calls
* timer.start('auth')
* call read('auth.feature')
* def authTime = timer.end('auth')

* timer.start('data')
* url apiUrl + '/data'
* method get
* def dataTime = timer.end('data')

* assert authTime < 1000
* assert dataTime < 5000

Memory Usage Tracking

Monitor memory consumption during test execution.

Background:
* def memory =
"""
function() {
var runtime = java.lang.Runtime.getRuntime();
var used = runtime.totalMemory() - runtime.freeMemory();
return Math.round(used / 1024 / 1024) + ' MB';
}
"""

Scenario: Memory monitoring
* def initialMemory = memory()
* print 'Initial memory:', initialMemory

# Load large dataset
* def largeData = read('classpath:large-file.json')
* def afterLoadMemory = memory()
* print 'After load:', afterLoadMemory

# Process data
* def processed = largeData.map(x => x.value * 2)
* def finalMemory = memory()
* print 'Final memory:', finalMemory

Advanced Java Integration

Thread-Safe Java Functions

Create reusable Java utilities with proper multi-threading support.

package com.example.karate;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

public class TestUtils {
// Thread-safe counter
private static final AtomicInteger counter = new AtomicInteger(0);

// Thread-safe cache
private static final Map<String, Object> cache =
new ConcurrentHashMap<>();

public static int getNextId() {
return counter.incrementAndGet();
}

public static void cacheValue(String key, Object value) {
cache.put(key, value);
}

public static Object getCachedValue(String key) {
return cache.get(key);
}
}

Use in tests:

Background:
* def Utils = Java.type('com.example.karate.TestUtils')

Scenario: Thread-safe operations
* def id1 = Utils.getNextId()
* def id2 = Utils.getNextId()
* assert id2 == id1 + 1

* Utils.cacheValue('token', 'abc123')
* def token = Utils.getCachedValue('token')
* match token == 'abc123'

Custom Hooks

Implement custom hooks for test lifecycle management.

package com.example.karate;

import com.intuit.karate.RuntimeHook;
import com.intuit.karate.core.ScenarioRuntime;

public class CustomHook implements RuntimeHook {

@Override
public boolean beforeScenario(ScenarioRuntime sr) {
System.out.println("Starting: " + sr.scenario.getName());
sr.engine.setVariable("startTime",
System.currentTimeMillis());
return true;
}

@Override
public void afterScenario(ScenarioRuntime sr) {
long start = (Long) sr.engine.getVariable("startTime");
long duration = System.currentTimeMillis() - start;
System.out.println("Completed in: " + duration + " ms");
}
}

Use the hook:

Results results = Runner.path("classpath:features")
.hook(new CustomHook())
.parallel(5);

Next Steps

Master Java integration and continue with: