Doc Strings and Multiline Data in Cucumber JVM

What Is a Doc String in Cucumber?

A Doc String in Cucumber is a Gherkin feature used to pass a large block of multiline text from a feature file into a step definition. Instead of sending one small value through a quoted parameter or arranging data into rows and columns with a Data Table, a Doc String sends one continuous document-like value. Cucumber captures the complete block and provides it to the matching Java step definition as a single String. This makes Doc Strings one of the cleanest ways to express JSON payloads, XML requests, SQL queries, email bodies, HTML snippets, SOAP messages, long descriptions, and other text-heavy inputs in a Cucumber JVM project.

In simple terms, a Doc String is multiline text input. It is written directly below a Gherkin step and surrounded by triple double quotes. Everything between the opening and closing triple quotes belongs to the Doc String. The text can span many lines, contain spaces, line breaks, indentation, braces, tags, quotes, and other characters that would be painful to escape inside a normal step parameter. The result is a feature file that remains readable for testers, developers, business analysts, and product owners.

Doc Strings are important because BDD scenarios often need examples that are larger than a single word or sentence. A login role can be passed as "Admin". A list of users can be passed as a Data Table. But a JSON request body, an email template, or an XML document should keep its original shape. If that content is squeezed into a quoted string, the scenario becomes hard to read and harder to maintain. Doc Strings solve that problem by letting the example appear in the feature file almost exactly as it would appear in the real application, API request, message body, or database script.

In Cucumber JVM, the step definition does not need special parsing just to receive the Doc String. If the step pattern matches the text before the Doc String, Cucumber automatically passes the block to the Java method as the last argument. That argument is normally a String. From there, the automation code can log it, validate it, send it as an API request body, compare it with actual content, parse it as JSON or XML, or pass it to a service class. This keeps the feature file expressive and the Java code flexible.

Why Doc Strings Are Needed

Doc Strings are needed because large text values become unreadable when they are forced into normal step parameters. For example, imagine passing a JSON body as a single quoted value. The JSON must often contain escaped quotes, compact formatting, and backslashes. A simple request body can quickly become difficult to understand. When scenarios become hard to read, they lose one of the main benefits of BDD. A feature file should communicate behavior and examples clearly; it should not look like a compressed programming string.

Given the request body is "{\"name\":\"John\",\"age\":30}"

This step may technically work, but it is not pleasant to read. The intent is hidden behind escape characters. If more fields are added, the line becomes longer. If nested JSON is needed, the step becomes worse. A reviewer has to mentally remove the escaping before understanding the payload. This is unnecessary work and a common sign that the scenario needs a Doc String.

Given the request body is
"""
{
  "name": "John",
  "age": 30
}
"""

The Doc String version is cleaner. The payload keeps its natural shape. The quotes inside the JSON do not need escaping. The indentation makes the structure visible. Anyone reading the feature file can quickly see what data is being sent. This is exactly the kind of readability improvement Doc Strings are designed to provide.

Doc Strings also help avoid mixing technical string-building code into step definitions. Instead of constructing large JSON or XML documents in Java, the example can live in the feature file where the behavior is described. The step definition can receive the text and pass it to the layer that knows what to do with it. This is useful in API testing, integration testing, message validation, email testing, and document generation scenarios.

How Doc Strings Work Internally

The execution flow is straightforward. Cucumber reads the feature file, finds a matching step, detects the Doc String block attached to that step, captures the entire block as text, and passes it into the step definition method. The step definition receives one string value that contains the content between the triple quotes. Cucumber handles the transport of the text from Gherkin to Java. The automation code decides how to use that text.

Feature File
      |
      v
Read Doc String
      |
      v
Capture Entire Text Block
      |
      v
Pass as String
      |
      v
Execute Step Definition

This behavior is different from Data Tables. A Data Table is parsed into a table-like structure and can be converted into lists, maps, or custom objects. A Doc String is not table data. It is one text block. Even if the text contains many lines, Cucumber treats it as one argument. That is why Doc Strings are ideal for documents and payloads, while Data Tables are ideal for structured records.

The Doc String is associated with the step immediately above it. It is not a separate step. If the feature file says Given the request body and the next lines contain a Doc String, Cucumber calls the step definition for Given the request body and supplies the Doc String content as an argument. The step text and the Doc String together form the input for that step.

Basic Doc String Syntax

The basic syntax uses triple double quotes before and after the multiline content. The opening delimiter starts the Doc String, and the closing delimiter ends it. The text between those delimiters is the content passed to Java. The Doc String usually appears on the line immediately after the Gherkin step, though indentation may vary depending on formatting style.

Given the following text
"""
Multiline text

Line 2

Line 3
"""

The delimiter is not part of the final string. Cucumber removes the opening and closing triple quotes and passes only the inner content. Line breaks inside the Doc String are preserved. This is important for documents where line breaks matter, such as emails, SQL queries, templates, or text comparisons. If the content must appear in a specific layout, the Doc String allows that layout to be written clearly.

Modern Cucumber versions also support alternate delimiters in some cases, but triple double quotes are the common and widely recognized form. For learning, interviews, and most framework code, triple double quotes are enough. The most important point is to use Doc Strings when the data is a complete block of text rather than a small value or a table of records.

Basic Example of Passing a Message

A simple message example shows the core idea without API or XML complexity. The feature file contains a step that says the following message is available, followed by a Doc String. The step definition receives the full message as a string and can print it, compare it, or pass it to another class.

Scenario: Display Message
  Given the following message
  """
  Welcome to SoftwareTips4U.

  Learn Selenium.

  Learn Cucumber.

  Happy Learning!
  """
@Given("the following message")
public void message(String text) {
    System.out.println(text);
}

The output contains the message content, not the triple quotes. This example demonstrates that the step definition does not need to manually read lines from the feature file. Cucumber already captures the text. The Java method simply declares a String parameter and receives the content.

This is useful beyond demonstration. A product team may define a long message that should appear in an email. A tester may validate the contents of a generated notification. A developer may use a Doc String to define a request template. In all these cases, the feature file remains easy to read because the content is shown as a real block of text.

How Cucumber Passes Doc String Data to Java

Cucumber automatically passes the Doc String into the Java step definition as an argument. If the Gherkin contains a Doc String after the step, the Java method must include a parameter that can receive it. In most practical cases, that parameter is String. No manual parsing is required just to receive the value.

Given the following message
"""
Hello World
"""
@Given("the following message")
public void message(String text) {
    System.out.println(text);
}

There is an important design detail here. The Doc String argument is normally placed after any parameters captured from the step text. For example, if the step says Given the request body for "admin" and also includes a Doc String, the method can receive the captured user type and the Doc String. The Doc String is passed as a separate argument, not as part of the step text.

@Given("the request body for {string}")
public void requestBodyForUser(String userType, String body) {
    System.out.println(userType);
    System.out.println(body);
}

This makes Doc Strings flexible. A scenario can pass a small parameter to describe context and a large text block to provide the actual payload. The step definition remains readable because the method signature shows both inputs clearly.

Using Doc Strings for JSON Payloads

JSON is one of the most common uses for Doc Strings in Cucumber JVM. API automation often requires request bodies, expected response bodies, or partial JSON documents. Writing JSON as a Doc String keeps the feature file readable and avoids unnecessary escaping. The payload can be copied, reviewed, and formatted like normal JSON.

Scenario: Create User
  Given the request body
  """
  {
      "username": "admin",
      "password": "admin123",
      "role": "Admin"
  }
  """
@Given("the request body")
public void requestBody(String json) {
    System.out.println(json);
}

The step definition receives the JSON as a string. It can then send the JSON directly to an API service, parse it with a JSON library, compare it with an expected result, or store it in scenario context. This is much cleaner than building the same JSON document in Java with string concatenation.

JSON Doc Strings are especially useful when the example itself is part of the behavior. For example, a scenario may verify that an API accepts a specific request body, rejects an invalid field, or returns a response matching a contract. Keeping the JSON in the feature file makes the example visible. The team can discuss the exact payload without searching through Java code.

REST API Example with Doc Strings

In REST API automation, a Doc String can be passed directly as the request body. The step definition should usually stay thin. It receives the body and passes it to an API service class. The service class handles Rest Assured, headers, endpoints, authentication, and response storage. This keeps the Cucumber layer focused on behavior rather than implementation details.

When the following request is sent
"""
{
    "name": "John",
    "email": "john@test.com"
}
"""
@When("the following request is sent")
public void request(String body) {
    userApiService.createUser(body);
}
public void createUser(String body) {
    given()
        .contentType("application/json")
        .body(body)
    .when()
        .post("/users");
}

This pattern avoids constructing JSON inside the step definition. It also makes it easy to update the payload when the business example changes. If a new field is added to the request, the scenario can show it directly. The Java code does not need to change unless the service behavior or validation rules change.

Using Doc Strings for XML and SOAP Requests

Doc Strings are also useful for XML payloads. XML often contains nested tags, namespaces, attributes, and line breaks. Passing XML through a quoted parameter is unpleasant because angle brackets and quotes make the step hard to read. A Doc String lets the XML remain in its natural document form.

Given the following XML
"""
<Employee>
  <Name>John</Name>
  <Department>IT</Department>
</Employee>
"""
@Given("the following XML")
public void xml(String xml) {
    employeeService.submit(xml);
}

SOAP requests are another strong use case. SOAP messages can be large and deeply nested. They may include envelope, header, body, and namespace declarations. A Doc String can hold the entire SOAP message and pass it as one string to the step definition.

Given the SOAP request
"""
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
  <soapenv:Header/>
  <soapenv:Body>
    <EmployeeRequest>
      <EmployeeId>101</EmployeeId>
    </EmployeeRequest>
  </soapenv:Body>
</soapenv:Envelope>
"""

In a real framework, the service layer may send this XML to an endpoint and store the response for later validation. The feature file clearly shows the request document, and the Java code does not need to hide it in string constants or builders.

Using Doc Strings for HTML Content

HTML snippets can also be passed with Doc Strings. This is useful when validating generated HTML content, checking email templates, testing rendering behavior, or comparing expected markup with actual output. The Doc String preserves the structure of the markup, which makes the scenario easier to read.

Given the following HTML
"""
<html>
  <body>
    <h1>Hello</h1>
  </body>
</html>
"""

When HTML is passed as a Doc String, remember that it is still a string from Cucumber's perspective. If the framework needs to parse it, compare nodes, or validate accessibility rules, that work belongs in helper classes or service classes. The step definition should not become a large HTML parser. It should receive the content and delegate the correct action.

Using Doc Strings for SQL Queries

SQL queries often benefit from multiline formatting. A complex query may contain selected columns, joins, filters, ordering, and grouping. Writing it on one line inside a quoted parameter makes the scenario difficult to read. A Doc String allows the query to be formatted clearly.

Given the following SQL
"""
SELECT *
FROM Employee
WHERE Department = 'IT';
"""
@Given("the following SQL")
public void sql(String query) {
    databaseHelper.execute(query);
}

Teams should use SQL Doc Strings carefully. Feature files should not become large database scripts unless the scenario genuinely requires that level of setup or validation. For many tests, data setup should be handled by fixtures, builders, or service calls. But when a query itself is the example being tested or reviewed, a Doc String is much clearer than a long quoted line.

Using Doc Strings for Email Bodies and Long Text

Email validation is another practical use case. An email body may contain greetings, paragraphs, order details, support information, and signature text. If the scenario needs to define expected email content, a Doc String gives the content room to breathe. The example can look like the actual message a user receives.

Given the following email body
"""
Dear Customer,

Your order has been shipped.

Thank You.

Regards,
Support Team
"""

The step definition can compare this expected body with an email captured from a test mailbox, mock service, or notification API. It can also normalize whitespace if the application formats emails differently. The main benefit is that the expected text remains readable in the feature file.

Long descriptions are similar. If a scenario must submit or verify a long comment, issue description, product summary, or help article, a Doc String is often better than a normal parameter. The scenario can include line breaks and paragraphs, which makes the example closer to real user input.

Doc Strings Versus Data Tables

Doc Strings and Data Tables both attach extra data to a Gherkin step, but they solve different problems. A Doc String is best for one block of multiline text. A Data Table is best for structured rows and columns. If the data is a document, payload, message, query, or long text field, use a Doc String. If the data is a set of records with fields, use a Data Table.

For example, a JSON request body should usually be a Doc String because JSON is already a structured document. Splitting JSON into a Data Table may make the feature file less accurate because the application does not receive a table; it receives JSON. On the other hand, a list of users with username, password, and role columns is naturally a Data Table because each row represents a user record.

This distinction is important for clean BDD. The feature file should represent the example in the form that best matches the business or system behavior. Do not use a Data Table just because data has fields. Do not use a Doc String just because the content spans multiple lines. Choose the structure that communicates the example most clearly.

Doc Strings Versus Scenario Outlines

Scenario Outlines are used when the same scenario should run multiple times with different values. A Doc String is used when one scenario needs a large text block. These features can sometimes appear similar because both provide data, but their execution behavior is different. A Scenario Outline creates multiple executions based on the Examples table. A Doc String does not create multiple executions. It provides one argument to one step in one execution.

If the goal is to test several usernames and passwords through the same login scenario, use a Scenario Outline. If the goal is to send one JSON payload with several fields, use a Doc String. If the goal is to send multiple different payloads, there are several design options: separate scenarios, a Scenario Outline with payload references, external test data, or carefully structured Doc Strings. The right answer depends on readability and maintainability.

In interviews, a strong answer is to say that Doc Strings are for large text or document-like input, Data Tables are for structured tabular input, and Scenario Outlines are for repeated execution with different datasets. This distinction shows that you understand both syntax and test design.

Doc String Content Type

Cucumber allows an optional content type after the opening triple quotes. For example, a JSON Doc String can start with """json, and an XML Doc String can start with """xml. This content type improves readability because it tells readers and tools what kind of content is inside the block.

Given the request body
"""json
{
    "username": "admin"
}
"""
Given the XML payload
"""xml
<Employee>
  <Name>John</Name>
</Employee>
"""

The content type does not automatically mean the step definition receives a parsed JSON object or XML document. In normal usage, Cucumber still passes the content as a string unless additional transformations or custom handling are configured. The content type is still useful because it documents intent. It can also be helpful for editor tooling, syntax highlighting, reporting, or custom framework behavior.

Indentation Handling in Doc Strings

Indentation is important because feature files are usually indented for readability. Cucumber handles common indentation so the formatting used to align the Doc String in the feature file does not necessarily become unwanted leading spaces in the string. This allows teams to write cleanly indented feature files without polluting the captured content with extra indentation.

Given the message
    """
    Hello

    World
    """

The received content is treated as the meaningful text inside the Doc String, with common indentation removed. This is helpful when feature files use nested indentation under scenarios. However, teams should still be careful when whitespace matters. Email bodies, preformatted text, and exact string comparisons can be sensitive to spaces and line breaks. If exact formatting is important, inspect the received string during development and write assertions intentionally.

Keeping Step Definitions Thin with Doc Strings

Doc Strings should not become an excuse to put large processing logic inside step definitions. The step definition should receive the text and delegate the real work to a page object, service class, parser, validator, or helper. This keeps the Cucumber layer clean. The feature file provides the example, the step definition connects the example to automation, and the framework layer performs the action.

@When("the following JSON request is submitted")
public void submitRequest(String json) {
    apiService.createUser(json);
}
public void createUser(String json) {
    given()
        .contentType("application/json")
        .body(json)
    .when()
        .post("/users");
}

This design is easier to maintain than parsing, sending, validating, and logging everything inside the step definition. If the API client changes, the service class changes. If validation rules change, the assertion helper changes. The Cucumber step remains stable because its responsibility is narrow.

Common Mistakes with Doc Strings

Using Doc Strings for Small Values

A Doc String should not be used for a single simple value. If the scenario only needs a username, role, status, or short label, a normal Cucumber expression is clearer. For example, Given the username is "admin" is better than placing admin inside a multiline block. Doc Strings are valuable when multiline formatting adds readability.

Using Data Tables for Document Payloads

Another common mistake is using a Data Table when the application actually expects a document such as JSON or XML. A table may look organized, but it may not represent the real input. If the system receives JSON, show JSON. If the system receives XML, show XML. Doc Strings allow the feature file to match the real payload more closely.

Escaping Characters Unnecessarily

Escaping quotes inside a Doc String is usually unnecessary. The whole point of a Doc String is to avoid escape-heavy text. A JSON block can contain normal quotes. An XML block can contain normal tags. If the Doc String is filled with backslashes, it may have been copied from a Java string literal instead of written as natural document content.

Putting Multiple Unrelated Documents in One Doc String

A Doc String should usually represent one logical document. If a scenario includes multiple unrelated payloads in one block, the step becomes confusing. Split the scenario, use multiple steps, use Data Tables for structured records, or move complex setup into helpers. One Doc String should have one clear purpose.

Best Practices for Doc Strings

Use Doc Strings for large multiline content, not for simple values. Good candidates include JSON payloads, XML requests, SOAP messages, SQL queries, email bodies, HTML snippets, long descriptions, source code examples, and other document-like inputs. If the content is naturally written across multiple lines, a Doc String is usually appropriate.

Keep the content formatted exactly as it should appear in the application or request whenever possible. This makes the scenario easier to review. If the system expects JSON, write readable JSON. If the system expects an email body, write the message in the same paragraph structure the user would see. If whitespace matters, be deliberate and verify how the framework receives the string.

Use optional content types such as """json or """xml when they improve clarity. They help readers immediately understand the kind of content being passed. They may also help editor tooling or custom framework logic.

Keep step definitions thin. Receive the Doc String, then pass it to the correct layer. API payloads should go to API service classes. HTML should go to validators or page helpers. SQL should go to database helpers. Email bodies should go to notification verification utilities. This keeps Cucumber steps readable and reusable.

Choose the right Gherkin data structure. Use Doc Strings for complete documents or multiline text. Use Data Tables for rows and columns. Use Scenario Outlines for repeated execution with different datasets. This decision has a direct impact on feature file readability and framework maintainability.

Real-Time Framework Example

A realistic Cucumber API scenario may submit a JSON request and then validate the response status. The feature file shows the request body as a Doc String. The step definition receives the body and passes it to an API service. The service sends the request and stores the response. A later step validates the status code or business result.

Feature: User API

Scenario: Create User
  When the following JSON request is submitted
  """json
  {
      "username": "admin",
      "password": "admin123",
      "role": "Admin"
  }
  """
  Then the response status should be 201
@When("the following JSON request is submitted")
public void submitRequest(String json) {
    apiService.createUser(json);
}

This design keeps the scenario readable and the Java code maintainable. The payload is visible where the example is described. The step definition is short. The API service owns request execution. If the payload changes, the feature file can be updated. If request execution changes, the service class can be updated. The responsibilities stay clean.

Interview-Ready Explanation

A Doc String is a Gherkin feature used to pass multiline text to a step definition. Cucumber captures the full block between triple quotes and provides it to the Java method as a single string. It is commonly used for JSON, XML, HTML, SQL, SOAP messages, email bodies, long descriptions, and other document-like input. Doc Strings improve readability because they avoid escaped characters and preserve the natural structure of the content.

Doc Strings should be used when the scenario needs one complete text block. Data Tables should be used when the scenario needs structured rows and columns. Scenario Outlines should be used when the same scenario must run multiple times with different values. A good Cucumber framework receives the Doc String in a thin step definition and passes it to a service, page object, parser, or validator instead of putting heavy logic directly in the step.

Summary

Doc Strings are one of the most useful Gherkin features for handling multiline data in Cucumber JVM. They make large text inputs readable, reduce escaping problems, and preserve the structure of documents such as JSON, XML, SQL, SOAP, HTML, and email content. Cucumber passes the captured block to Java as a single string, which can then be used by the automation framework.

The golden rule is simple: use Doc Strings whenever a scenario needs to pass a complete document or multiline text. If the data is structured into rows and columns, use a Data Table. If the goal is to run the same scenario multiple times with different data, use a Scenario Outline. Choosing the right structure keeps feature files meaningful, step definitions clean, and Cucumber frameworks easier to maintain.