GET STARTED
Migration from v1
Karate v2 includes backward compatibility shims that allow most v1 code to work with minimal changes. For most users, the only change required is updating the Maven dependency.
Quick Start
Step 1: Update Maven Dependencies
<dependency>
<groupId>io.karatelabs</groupId>
<artifactId>karate-junit5</artifactId>
<version>1.5.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.karatelabs</groupId>
<artifactId>karate-junit6</artifactId>
<version>2.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.1</version>
<scope>test</scope>
</dependency>
Unlike karate-junit5 which bundled JUnit, karate-junit6 declares JUnit as a provided dependency, giving you control over the JUnit version. You must add junit-jupiter explicitly.
Reference: See the karate-demo migration commit for a complete example of dependency changes.
Step 2: Update Java Version
Karate v2 requires Java 21+ for virtual threads support.
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
That's it for most projects. Run your tests and they should work.
V1 Compatibility Shims
The following v1 APIs work without code changes via deprecated shims:
| v1 Class | Status |
|---|---|
com.intuit.karate.Runner | Works — delegates to v2 |
com.intuit.karate.Results | Works — wraps v2 SuiteResult |
com.intuit.karate.core.MockServer | Works — delegates to v2 |
com.intuit.karate.junit5.Karate | Works — delegates to v2 |
Example: Runner API (no changes needed)
// This v1 code works in v2 without modification
import com.intuit.karate.Results;
import com.intuit.karate.Runner;
Results results = Runner.path("classpath:features")
.tags("~@ignore")
.parallel(5);
assertTrue(results.getFailCount() == 0, results.getErrorMessages());
Example: MockServer API (no changes needed)
// This v1 code works in v2 without modification
import com.intuit.karate.core.MockServer;
MockServer server = MockServer
.feature("classpath:mock.feature")
.arg("key", "value")
.http(0).build();
Gradual Migration to v2 APIs
Each shim provides a toV2*() method if you want to migrate incrementally:
// Get underlying v2 Builder
io.karatelabs.core.Runner.Builder v2Builder = v1Builder.toV2Builder();
// Get underlying v2 MockServer
io.karatelabs.core.MockServer v2Server = v1Server.toV2MockServer();
// Get underlying v2 SuiteResult
io.karatelabs.core.SuiteResult v2Results = v1Results.toSuiteResult();
Logging configuration
V2 unifies the v1 logging knobs under a single configure logging bucket. The full reference is in Logging; here's the migration:
| v1 | v2 equivalent |
|---|---|
configure logPrettyRequest = true/false | configure logging = { pretty: true/false } — single boolean for both directions |
configure logPrettyResponse = true/false | (same — pretty covers both) |
configure printEnabled = false | configure logging = { report: 'warn' } — raise the threshold to drop INFO print/karate.log lines |
configure lowerCaseResponseHeaders = true | Dropped. match header X-Foo is already case-insensitive; or karate.response.header('x-foo') for case-insensitive single-value lookup; or karate.lowerCase(responseHeaders) for direct map access |
configure logModifier = MyImpl (Java) | configure logging = { mask: { headers, jsonPaths, patterns, enableForUri } } — declarative, no Java class |
The four v1 keys above (logPrettyRequest, logPrettyResponse, printEnabled, lowerCaseResponseHeaders) are silent no-ops with a one-line deprecation warning per process pointing at the new shape — your tests still run. logModifier likewise warns; rewrite the masking declaratively.
Where do HTTP bodies show up?
v1 emitted full request/response bodies on the console at DEBUG. v2 splits report capture from console emission and the HTML report has full bodies by default — you do not need to crank the console level.
| What you want | report | console | Notes |
|---|---|---|---|
| Bodies in HTML report, quiet console | debug | info | Default — no config needed. One-liner per request on stdout, full headers + body in the HTML report. |
| Bodies on console too | debug | trace | v2 reserves DEBUG for headers and TRACE for body. Set console: 'trace' only for live local debugging. |
If your v1 muscle memory was "set DEBUG to see bodies in the terminal", switch to looking at target/karate-reports/karate-summary.html — bodies are already there.
Feature File Compatibility
Most feature files work unchanged. Known differences:
@parallel=falseis gone — use@lockinstead. See Parallel Execution Control below.- Cookie domain assertions: RFC 6265 compliance means leading dots are stripped (
.example.com→example.com)
Parallel Execution Control
V2 drops the @parallel=false tag. It is silently ignored — no deprecation warning, no error — and any feature still relying on it will run in parallel as if untagged. The replacement is the more expressive @lock tag.
| v1 intent | v2 equivalent |
|---|---|
@parallel=false on a Feature — run that feature's scenarios sequentially, but let other features still run in parallel | @lock=<unique-name> on the Feature (any name; pick one not used elsewhere) |
| Single-thread the entire run — keep one feature isolated from every other scenario in the suite | @lock=* on the Feature or Scenario |
| Coordinate two unrelated features that mutate the same shared resource | @lock=<shared-name> on both — any scenarios tagged with the same name serialize against each other |
Feature-level tags propagate to every scenario in the feature, so @lock on the Feature: line is the drop-in for v1's feature-level @parallel=false.
@parallel=false
Feature: order workflow
@lock=order-workflow
Feature: order workflow
# every scenario in this feature serializes against every other
# scenario tagged @lock=order-workflow
@lock=*
Feature: db migration smoke
Unlike @parallel=false, @lock=<name> lets unrelated features still run concurrently — only scenarios sharing the same lock name serialize. Use @lock=* only when full exclusion is genuinely required, since it stalls the whole worker pool.
See Parallel Execution > Controlling Parallelism with @lock.
Java Interop
Java.type() and existing v1 patterns work unchanged. Two notes:
karate.toJava()is a deprecated no-op in v2 (logs a warning). It's no longer needed: JS functions auto-coerce to Java functional interfaces (Function,Predicate,Consumer,Supplier,Runnable), and JS arrays/objects already work as JavaList/Map. Just pass values straight through. See Java Function References.- JS function passed to a Java method that expects a
@FunctionalInterfacenow works directly — e.g.dbUtils.waitFor(rows, expected, timeout, row => row.status === 'READY')wherewaitFordeclaresPredicate<Map<String, Object>>. v1 relied on Graal's coercion; v2 supports the same surface natively.
Browser Automation (UI Tests)
V2 uses a rewritten driver with CDP as the primary backend and full W3C WebDriver support for cross-browser testing. The Gherkin syntax is unchanged:
* driver serverUrl + '/login'
* input('#username', 'admin')
* click('button[type=submit]')
* waitFor('#dashboard')
* match driver.title == 'Welcome'
Key Differences from v1
| Area | v1 | v2 |
|---|---|---|
| Primary driver | WebDriver (chromedriver, etc.) | Chrome DevTools Protocol (CDP) |
| Cross-browser | Via WebDriver | W3C WebDriver (chromedriver, geckodriver, safaridriver) |
| Browser pooling | Manual setup | Automatic via PooledDriverProvider |
| Auto-wait | Not available | Built-in before element operations |
showDriverLog | Worked | No effect (TODO) |
Driver in Called Features
V2 preserves v1 behavior: if a called feature creates a driver via a shared-scope call (* call read('feature')), the driver automatically propagates back to the caller. No special configuration is needed.
# login.feature — driver propagates to caller automatically
@ignore
Feature: Login
Background:
* configure driver = { type: 'chrome' }
Scenario: Login
* driver serverUrl + '/login'
* input('#username', 'admin')
* click('#submit')
* waitFor('#dashboard')
# main.feature — driver is available after call returns
Scenario: Full regression
* call read('classpath:pages/login.feature')
* delay(5000) # ✅ works — driver propagated from login
* call read('classpath:pages/dashboard.feature') # ✅ works — driver is shared
Early v2 releases required scope: 'caller' in the driver config for this to work. This is no longer needed and can be safely removed.
Driver-Bound Functions
These functions only work after driver 'url' has been called: click(), input(), delay(), submit(), waitFor(), screenshot(), script(), mouse(), keys(), etc.
If you see <function> is not defined, check that the driver was initialized before that line. For a delay without a driver, use karate.pause(millis) instead.
Browser Pooling (New Default)
V2 automatically pools browser instances — no configuration needed:
Runner.path("features/")
.parallel(4); // Pool of 4 drivers auto-created
Recommended Patterns for UI Test Reuse
Pattern 1: Caller owns the driver (recommended for new projects)
# main.feature — driver starts here
Background:
* configure driver = { type: 'chrome' }
Scenario: Full flow
* driver serverUrl + '/login'
* call read('classpath:pages/login-steps.feature')
* call read('classpath:pages/dashboard-steps.feature')
Called features just perform actions — no driver init needed. This is the cleanest approach.
Pattern 2: Called feature starts the driver (v1-style)
Called features that create a driver will automatically propagate it back to the caller. This works out of the box — no extra configuration needed. See Driver in Called Features above.
Migration Checklist
- Update
karate-junit5→karate-junit6dependency - Add explicit
junit-jupiterdependency - Update Java version to 21+
- Remove
scope: 'caller'from driver config if present (no longer needed) - Replace
delay(millis)withkarate.pause(millis)if used before the driver starts - Replace
JsonUtilswithJsonclass (if used directly) - Remove code using
WebSocketClientor Driver Java APIs (if used) - Replace any
@parallel=falsetags with@lock=<name>(or@lock=*for full exclusion) —@parallel=falseis silently ignored in v2 - Update cookie domain assertions if needed
Reference Migration Commits
karate-demo
Commit: c8fca97ce
This involved additional infrastructure changes beyond what typical end-users need:
- Spring Boot 2.x → 3.x upgrade (required for Java 21)
javax.*→jakarta.*servlet imports- Spring Security 5 → 6 configuration style
- Cookie domain normalization for RFC 6265 compliance
karate-e2e-tests
Commit: 7ffe47509
A simpler migration focused on test dependencies and runner changes.