skip to content

Java Launcher Builder

A short Java class chains builder calls to pick features, tags and environment, then hands a thread count to Karate's own runner. Interviewers ask how a feature file actually gets started.

on this pageshow

explore

questions

6

In Karate, what does the Java line `Runner.path("classpath:animals").tags("@smoke").parallel(5)` actually do, and why does this class need no step-definition or glue package?

level: juniorimportance: must knowfreq 76%

answer

  1. A builder, then one terminal call
  2. Nothing runs until the last call
  3. The argument is thread width
  4. No glue package to point at
  5. It returns counts, it does not throw

basics

~20 s

Runner.path() opens Karate's builder, each chained call sets one option, and parallel(n) is the terminal call that discovers the features, runs them on n threads and returns a result object. No glue package is involved.

solid answer

~40 s

`Runner.path(...)` returns a `Runner.Builder`. Every chained call — `tags`, `karateEnv`, `configDir`, `outputJunitXml` — just stores a field and returns the builder, so nothing has run yet. `parallel(int)` is the **terminal** call: it resolves the paths into feature files, applies the tag selector, executes scenarios across that many threads, writes the reports and returns a result object carrying the pass/fail counts. Because Karate parses and executes `.feature` files with its own lexer and closed keyword set, there is no step-definition registry and no glue path to point at — the launcher only says *which files* and *with what options*, never *which Java methods implement the steps*. The surrounding class is an ordinary JUnit test whose body asserts that the returned failure count is zero.

code

java · 17 lines
java
import com.intuit.karate.Results;
import com.intuit.karate.Runner;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;

class ApiSuiteTest {

    @Test
    void runAll() {
        Results results = Runner.path("classpath:animals")
                .tags("@smoke")
                .karateEnv("qa")
                .outputJunitXml(true)
                .parallel(5);
        assertEquals(0, results.getFailCount(), results.getErrorMessages());
    }
}

go deeper

for a junior

Recall the shape: Runner.path(...) starts a builder, chained calls configure it, and parallel(n) runs it and hands back counts you assert on.

for a middle

Explain that every call before parallel() only sets a field, and that path resolution, tag filtering, execution and report writing all happen inside that one terminal call.

for a senior

Judge what belongs in the launcher versus outside it: hard-coding tags and environment in Java means one class per pipeline job, whereas a single runner steered externally serves them all.

for a principal

Weigh the launcher as an interface: it is the only contract your pipeline has with the suite, so decide deliberately how much selection logic lives in compiled Java versus in the job definition.

## What the chain is `Runner.path("classpath:animals")` is a static factory that creates a `Runner.Builder` and seeds it with one path. Every method after it is a plain setter that returns the builder, so the whole expression is one object being configured: | Call | What it sets | |---|---| | `path("classpath:animals")` | a directory, a single `.feature` file, or `file.feature:12` for one scenario — repeated calls **accumulate** | | `tags("@smoke")` | the tag selector applied to every discovered scenario | | `karateEnv("qa")` | the value features read back as `karate.env` | | `configDir("classpath:cfg")` | the directory the runner looks in for `karate-config.js` | | `outputJunitXml(true)` | opt in to JUnit XML output (off by default) | | `parallel(5)` | **terminal** — resolve, run, report, return | Nothing executes until the terminal call. That is worth saying out loud in an interview, because it is what makes the launcher trivially testable in an IDE: you can build the chain, print it, and nothing has touched the network. ## What `parallel(n)` does Reading it as "turn on parallelism" is the common misreading. It is the run trigger, and the argument is only the width: 1. **Resolve** every path into feature files — a directory is scanned recursively; `classpath:` resolves against the test classpath. 2. **Compile** the `tags(...)` array into a selector expression. 3. **Build a suite** and execute it, features and scenarios spread across `n` threads. 4. **Write the reports** into the output directory. 5. **Return** a result object holding feature/scenario counts, failure counts and the collected error messages. `parallel(1)` therefore still runs the suite — sequentially. There is no separate `run()` or `build()` to call afterwards, and no exception is thrown for a failing scenario: the failure count comes back on the returned object and it is the surrounding JUnit method that turns it into a red test. ## Why there is no glue This is the answer that separates Karate from a Cucumber-style runner. Karate reads `.feature` files with its **own** lexer and parser, not Cucumber's Gherkin library, and dispatches each step against a **closed, built-in keyword set** — `def`, `url`, `path`, `method`, `status`, `match`, `call`, `configure` and the rest. There is: - **no step-definition registry** to populate, - **no glue package** or class list to point the runner at, - **no** Cucumber-Expression or regex parameter machinery, - **no** "undefined step / here is a snippet to paste" flow — an unrecognised leading keyword is a hard error. So the builder has no `glue(...)` method to offer. `path(...)` is the *only* thing that says what to run; everything else is configuration. `Given`, `When` and `Then` are interchangeable labels in a Karate feature — `*` works everywhere — because dispatch never looks at the prefix. ## The result object, and the version to name The accessor names are the one part of this chain that is **not** stable across Karate's two lines, and naming the wrong one is a common interview slip: | | Karate 1.x | Karate 2.x | |---|---|---| | package | `com.intuit.karate` | `io.karatelabs.core` | | returned by `parallel(n)` | `Results` | `SuiteResult` | | failure count | `getFailCount()` | `getScenarioFailedCount()` | | error text | `getErrorMessages()` | `getErrors()` (a `List<String>`) | Karate 2 ships a `com.intuit.karate` compatibility shim so the 1.x snippet above still compiles and runs — but code resolving through that shim is using the old API, not the new one. ## What the class is *not* - It is **not** a JUnit `TestEngine`. Karate ships none, and it has no `@Suite`-style annotation of its own. The launcher is just Java that happens to be invoked from a `@Test` method so that Maven or Gradle will run it. - It is **not** where assertions about the API live. Those are `match` steps inside the feature files; the launcher only asserts the aggregate failure count. - It is **not** the place to encode which scenarios a particular pipeline runs, if you can avoid it — the same class can be steered from outside by system properties, which keeps one runner serving several jobs. ## The shape you will be asked to write ```java @Test void testParallel() { Results results = Runner.path("classpath:animals") .tags("@smoke") .outputJunitXml(true) .parallel(5); assertEquals(0, results.getFailCount(), results.getErrorMessages()); } ``` Four lines of Java for an entire API suite, and not one of them mentions a step implementation. That is the whole pitch of the glue-free model, and it is exactly what the interviewer is checking you can explain.

  • Does `parallel(1)` skip the suite, or run it sequentially?
    It runs it, on a single thread. `parallel(int)` is the terminal call that executes the suite; the argument only sets the width. There is no separate `run()` — dropping the call means the builder is configured and nothing ever executes, which is the usual cause of a runner class that passes instantly with zero scenarios.
  • A scenario fails. Does `parallel(n)` throw?
    No. It completes normally and reports the failure on the returned object — `getFailCount()` on Karate 1.x, `getScenarioFailedCount()` on 2.x. The launcher method must assert on that count itself; a runner that calls `parallel(n)` and ignores the return value is a green test over a red suite.
  • What does calling `path(...)` twice do?
    The paths accumulate — both are scanned. That matters because the same is *not* true of an external override: a path supplied through the `karate.options` system property replaces the builder's paths outright rather than adding to them.

saying these in an interview costs you the question

  • Claiming parallel(n) only enables threading, not the run itself
  • Looking for a glue or step-definition package on the builder
  • Saying a failing scenario makes parallel() throw an exception
  • Naming getFailCount() and SuiteResult together as one API
  • Thinking Karate registers a JUnit Platform TestEngine
  • Believing Given/When/Then choose which Java method runs
open as a page

In a Karate Java runner, what does `Runner.path("classpath:api").tags("@smoke,@sanity", "~@wip")` select, and how do the comma, the tilde and the two separate arguments each combine?

level: middleimportance: must knowfreq 68%

basics

~10 s

That chain runs scenarios tagged @smoke or @sanity but not @wip. Separate arguments are ANDed, a comma inside one argument means OR, and a leading tilde negates that whole argument.

open as a page

In Karate's JUnit integration, what is the `@Karate.Test` annotation, where may it be placed, and what must the annotated method return?

level: juniorimportance: should knowfreq 42%

basics

~10 s

Karate's @Karate.Test is a method-level annotation meta-annotated with JUnit's @TestFactory. The method must return a Karate instance, an Iterable of dynamic nodes, so each feature and scenario becomes its own test.

open as a page

A Karate runner calls `Runner.path("classpath:api").tags("~@ignore").parallel(4)`. Does the `~@ignore` do anything, and can any tag expression make an `@ignore`d Scenario run?

level: middleimportance: should knowfreq 47%

basics

~20 s

Karate skips an @ignore scenario before the tag selector is evaluated, so ~@ignore is redundant, and no tag expression can select one back in. Only a line filter, a scenario-name filter or a call reaches it.

open as a page

A suite launched with `Runner.path("classpath:api").parallel(4)` writes an HTML report, but the CI job reports "no tests found" because it can find no XML. Which Karate builder defaults explain that, and where does Karate write its output?

level: middleimportance: should knowfreq 52%

basics

~20 s

Karate's HTML report is on by default but JUnit XML and Cucumber JSON are off; you must call outputJunitXml(true). Output goes to karate-reports under the build directory, not to the surefire directory CI usually reads.

open as a page

A Karate runner class hard-codes `Runner.path("classpath:api").tags("@regression").karateEnv("dev").parallel(4)`. How do you get one CI job to run only `@smoke` against `qa` on eight threads without editing that class?

level: seniorimportance: should knowfreq 45%

basics

~10 s

Pass the karate.options system property, for example -Dkarate.options="--tags @smoke --env qa --threads 8". Karate reads it inside parallel() and overrides the builder's values, so the compiled class stays untouched.

open as a page