skip to content

Gatling's reference tells you never to let an IDE optimize the imports of a simulation file — what can that break, and which packages does Gatling treat as its public API?

level: seniorimportance: nice to knowfreq 26%

answer

  1. Copy the block, never tidy it
  2. Some members no call site ever names
  3. Scala implicits and the deploymentInfo field
  4. Only the named packages are public API

basics

~20 s

Optimizing imports drops members no call site names: Scala's implicits on Predef, and Java statics that are read rather than called, such as CoreDsl.deploymentInfo. Gatling's public API is only the packages its import block names; everything else is private.

solid answer

~40 s

An import optimizer deletes what looks unused and narrows a wildcard to the members it can see referenced, and both are unsafe here. In Scala, `io.gatling.core.Predef` carries implicits — the configuration and the duration classes — that no use site mentions by name, so narrowing the wildcard silently removes them. In Java and Kotlin, `HttpDsl.http` is both a static field and an overloaded static method sharing one name — inseparable by import, so the pair is kept or lost whole — `CoreDsl.deploymentInfo` is a field rather than a call, and the DSL methods carry very large overload sets. Gatling's answer is to copy the block verbatim. The same section adds the boundary: any class outside the named packages is private and may change without notice, and `io.gatling.javaapi.core.internal` and `io.gatling.javaapi.http.internal` really exist.

code

scala · 16 lines
scala
import scala.concurrent.duration._

import io.gatling.core.Predef._
import io.gatling.http.Predef._

class CheckoutSimulation extends Simulation {

  private val httpProtocol = http.baseUrl("https://example.com")

  private val scn = scenario("checkout")
    .exec(http("home").get("/"))

  setUp(scn.inject(rampUsers(50).during(30.seconds)))
    .protocols(httpProtocol)
    .assertions(global.failedRequests.percent.lt(1.0))
}

go deeper

for a junior

Recall the instruction itself: copy Gatling's import block into each simulation rather than letting the editor tidy or shorten it.

for a middle

Name at least one member that static analysis cannot see used, such as a Scala implicit, and say why the field-and-method pair named http is instead inseparable by import.

for a senior

Connect the rule to the API boundary: the named packages are the contract, and an import from an internal package is a defect to raise in review.

for a principal

Decide the policy: which editor settings the load-test source set carries, how the preamble is distributed, and what happens when a test needs something the API does not expose.

Gatling's simulation reference carries an unusual instruction in a box next to the import block: *"Do not try to 'optimize imports' with your IDE, you'd break everything. Just copy-paste those imports wherever you want to use Gatling SDK."* The documentation states the rule without explaining it. The source shows what is at stake. ## What the wildcards actually carry An import optimizer does two things: it deletes imports it believes are unused, and it expands a wildcard into the individual members it can see referenced. Both operations are safe only when every use is visible to static analysis. In a Gatling simulation, several are not. **In Scala**, `io.gatling.core.Predef` is a single object holding three different kinds of member: * DSL methods, inherited because `object Predef extends CoreDsl`; * type aliases — `type Simulation`, `type Session`, `type Status`, `type Assertion`; * **implicits** — `implicit def configuration: GatlingConfiguration`, the implicit classes `DurationInteger` and `DurationJLong`, and, through the `CoreDsl` trait, `CoreDefaultImplicits` and `ValidationImplicits`. The first two categories are named at their use sites, so a tool can see them. The implicits are not named anywhere. Narrowing `import io.gatling.core.Predef._` to the members a tool can see used therefore silently drops the implicit machinery the DSL depends on, and the failure surfaces somewhere unrelated to the line that was edited. **In Java and Kotlin**, the risk is different but real. The preamble pairs a package wildcard with a static wildcard, and each carries members that are easy to attribute wrongly: * `HttpDsl.http` is both a static **field** (the protocol builder) and an overloaded static **method** (`http("name")`, the request builder). The two are inseparable by import, because a single static import brings in every accessible static member with that simple name — `import static io.gatling.javaapi.http.HttpDsl.http;` yields the protocol-builder field *and* both `http(...)` overloads together, and no import form admits one while excluding the other. So the risk here is not a split but a deletion, and the field form, being read rather than called, is the one an optimizer is likeliest to judge unused. * `CoreDsl.deploymentInfo` is a static field, not a method call, and is the kind of member a tool is most likely to consider unused. * The DSL methods carry very large overload sets — `pause`, `pace`, `during`, `feed`, `asLongAsDuring` each have a dozen or more forms — so replacing a wildcard with single-member imports produces a long, fragile list that the next edit invalidates. ## The API boundary the same block draws The identical documentation section carries a second warning: *"Beware that any class that doesn't belong to those packages is considered private, not an API, and hence subject to change at any time without notice."* The packages named in the block are the contract: | SDK | public API packages | |---|---| | Java and Kotlin | `io.gatling.javaapi.core`, `io.gatling.javaapi.http`, and `io.gatling.javaapi.jdbc` / `.jms` / `.redis` when used | | Scala | `io.gatling.core.Predef`, `io.gatling.http.Predef`, and the matching `jdbc` / `jms` / `redis` `Predef` objects | The source confirms the line is drawn deliberately: `io.gatling.javaapi.core.internal` and `io.gatling.javaapi.http.internal` are real subpackages, and a Java single-level wildcard import does not reach into them. Anything a simulation pulls in from an `internal` package, or from a Scala core package such as `io.gatling.core.controller`, is machinery Gatling may change in a patch release without a deprecation cycle. ## What to do instead 1. **Treat the preamble as a fixed block.** Copy it into every simulation. It is short, it is the same everywhere, and it is documented as copy-paste material. 2. **Turn the optimizer off for these files**, or configure it not to collapse or expand wildcard imports in the load-test source set. Whichever mechanism your editor offers, the goal is that nobody's save action rewrites the block. 3. **Make it reviewable.** A simulation whose imports differ from the documented set is worth a second look in review, in either direction: a member missing, or a class imported from outside the API packages. 4. **Treat an import from an `internal` package as a defect**, not a shortcut. If a simulation needs something the API does not expose, the honest outcomes are to restructure the test or to accept that the capability is not public — not to reach past the boundary and discover it at the next upgrade. ## Why this is worth an interview question It is a small rule with a revealing answer. A candidate who says "the IDE would remove unused imports" has half of it. A candidate who can say *which* members are invisible to static analysis — implicits in Scala, a static field such as `deploymentInfo` in Java — is describing the mechanism rather than repeating the warning. And a candidate who connects the same import block to Gatling's public-API boundary has understood that the preamble is a contract, not boilerplate.

  • Which members of the Java preamble is an optimizer most likely to get wrong?
    The ones that are not method calls. `CoreDsl.deploymentInfo` is a static field, so an optimizer that reasons about call sites is the likeliest to judge it unused and delete the import that carries it. `HttpDsl.http` is a static field *and* an overloaded static method sharing one name, and the two cannot be separated — a single static import brings in every static member with that simple name — so the risk there is losing both at once rather than splitting the pair.
  • How would you tell whether a simulation has reached outside Gatling's public API?
    Compare its import block to the documented one. Anything from an `internal` subpackage such as `io.gatling.javaapi.core.internal`, or from a Scala core package like `io.gatling.core.controller`, is outside the contract and may change in a patch release without a deprecation cycle.

saying these in an interview costs you the question

  • Treating the Gatling import block as ordinary boilerplate
  • Assuming an unused-looking import is genuinely unused
  • Importing from an internal package to reach a hidden feature
  • Believing a package wildcard also imports its subpackages