Skip to main content

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

pom.xml — before (v1)
<dependency>
<groupId>io.karatelabs</groupId>
<artifactId>karate-junit5</artifactId>
<version>1.5.2</version>
<scope>test</scope>
</dependency>
pom.xml — after (v2)
<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>
note

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.

pom.xml
<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 ClassStatus
com.intuit.karate.RunnerWorks — delegates to v2
com.intuit.karate.ResultsWorks — wraps v2 SuiteResult
com.intuit.karate.core.MockServerWorks — delegates to v2
com.intuit.karate.junit5.KarateWorks — 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:

v1v2 equivalent
configure logPrettyRequest = true/falseconfigure logging = { pretty: true/false } — single boolean for both directions
configure logPrettyResponse = true/false(same — pretty covers both)
configure printEnabled = falseconfigure logging = { report: 'warn' } — raise the threshold to drop INFO print/karate.log lines
configure lowerCaseResponseHeaders = trueDropped. 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 wantreportconsoleNotes
Bodies in HTML report, quiet consoledebuginfoDefault — no config needed. One-liner per request on stdout, full headers + body in the HTML report.
Bodies on console toodebugtracev2 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=false is gone — use @lock instead. See Parallel Execution Control below.
  • Cookie domain assertions: RFC 6265 compliance means leading dots are stripped (.example.comexample.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 intentv2 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.

v1 — no longer works in v2 (silently ignored)
@parallel=false
Feature: order workflow
v2 — same effect
@lock=order-workflow
Feature: order workflow
# every scenario in this feature serializes against every other
# scenario tagged @lock=order-workflow
v2 — run this feature exclusively (no other scenarios anywhere run concurrently)
@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 Java List/Map. Just pass values straight through. See Java Function References.
  • JS function passed to a Java method that expects a @FunctionalInterface now works directly — e.g. dbUtils.waitFor(rows, expected, timeout, row => row.status === 'READY') where waitFor declares Predicate<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

Areav1v2
Primary driverWebDriver (chromedriver, etc.)Chrome DevTools Protocol (CDP)
Cross-browserVia WebDriverW3C WebDriver (chromedriver, geckodriver, safaridriver)
Browser poolingManual setupAutomatic via PooledDriverProvider
Auto-waitNot availableBuilt-in before element operations
showDriverLogWorkedNo 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
scope: 'caller' removed

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

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-junit5karate-junit6 dependency
  • Add explicit junit-jupiter dependency
  • Update Java version to 21+
  • Remove scope: 'caller' from driver config if present (no longer needed)
  • Replace delay(millis) with karate.pause(millis) if used before the driver starts
  • Replace JsonUtils with Json class (if used directly)
  • Remove code using WebSocketClient or Driver Java APIs (if used)
  • Replace any @parallel=false tags with @lock=<name> (or @lock=* for full exclusion) — @parallel=false is 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.


Getting Help