In Gatling, why do Java, Kotlin and JavaScript scenarios call injectOpen or injectClosed while Scala scenarios call a single inject?
answer
- the split follows the binding layer
- Scala infers, the Java API names
- Kotlin and JavaScript sit on the Java API
- implicit InjectionProfileFactory picks open or closed
- step names are identical in all five languages
basics
~20 sThe entry points sit in different binding layers. Scala's inject infers the model from an implicit InjectionProfileFactory chosen by the step type, while the Java API has no implicits and names the choice instead, as injectOpen and injectClosed.
solid answer
~40 sGatling is authored in five languages over two JVM binding layers plus an npm package family. Scala talks to the core directly, and its `inject` is generic over an implicit `InjectionProfileFactory` — the compiler picks the open factory or the closed factory from the *type of the steps you passed*, so the method name never has to say which model you meant. The Java API cannot rely on implicits, so it publishes the choice as two names, `injectOpen(OpenInjectionStep...)` and `injectClosed(ClosedInjectionStep...)`. Kotlin has no Gatling module of its own and consumes that Java API as-is, and the JavaScript and TypeScript SDK wraps the same shape, which is why all three spell it `injectOpen`. Both paths converge on the same internal call.
code
scala · 17 linesimport scala.concurrent.duration._
import io.gatling.core.Predef._
class ModelChoiceSimulation extends Simulation {
private val arrivals = scenario("arrivals").pause(1.second)
private val concurrency = scenario("concurrency").pause(1.second)
// T infers as OpenInjectionStep, so the open factory is applied
private val open = arrivals.inject(atOnceUsers(10))
// T infers as ClosedInjectionStep, so the closed factory is applied
private val closed = concurrency.inject(constantConcurrentUsers(10).during(30.seconds))
setUp(open, closed)
}go deeper
Be able to write the right entry point for the language in front of you: injectOpen or injectClosed everywhere except Scala, which uses inject.
Be ready to explain the implicit InjectionProfileFactory that lets one Scala method serve both models, and why the Java API cannot do the same.
Be ready to explain why Kotlin and JavaScript follow Java's spelling, so a team can predict which snippets on the internet will port to their codebase.
Own the language choice for a load-testing codebase, weighing the Scala core's implicit machinery against the Java API surface the other four languages share.
Gatling is marketed as having SDKs for Java, Kotlin, Scala, JavaScript and TypeScript. That is true about **authoring languages**, but it is not true about modules, and the difference is exactly what the `inject` split exposes. ## Five languages, three layers | layer | what actually ships | languages it serves | |---|---|---| | Scala core | `gatling-core`, `gatling-http`, `gatling-jms`, `gatling-jdbc`, `gatling-redis` and friends | Scala | | Java API | `gatling-core-java`, `gatling-http-java`, `gatling-jms-java`, `gatling-jdbc-java`, `gatling-redis-java`, all under the package `io.gatling.javaapi` | Java **and** Kotlin | | npm packages | `@gatling.io/core`, `@gatling.io/http`, `@gatling.io/cli` | JavaScript **and** TypeScript | There is **no Kotlin module** in the Gatling repository — Kotlin simply calls the Java API, which is why its idioms and its method names are Java's. The npm packages are a separate product that presents the same Java-API-shaped surface to JavaScript and TypeScript. So the split is not "Java versus everyone else"; it is **Scala core versus the Java API**, with Kotlin, JavaScript and TypeScript all on the Java side of the line. ## What Scala's inject actually does Scala's `ScenarioBuilder` declares two public overloads, both generic: ```scala def inject[T: InjectionProfileFactory](is: T, moreIss: T*): PopulationBuilder def inject[T: InjectionProfileFactory](iss: Iterable[T]): PopulationBuilder ``` `InjectionProfileFactory[T]` is a type class with exactly one job: turn a collection of steps into an injection profile. Two implicit instances exist and they are brought into scope by the Predef import you already have: - `openInjectionProfileFactory: InjectionProfileFactory[OpenInjectionStep]` - `closedInjectionProfileFactory: InjectionProfileFactory[ClosedInjectionStep]` So when you write `scn.inject(atOnceUsers(10))`, the compiler infers `T = OpenInjectionStep`, finds the open factory and builds an open profile. Write `scn.inject(constantConcurrentUsers(10).during(30))` and it infers `T = ClosedInjectionStep` and picks the closed factory. **The model is chosen by the type of your steps, not by the name of the method.** That is the answer to "how does Scala know which one you meant". ## What the Java API does instead Java has no implicit parameters, so the same decision has to be visible in the API surface. `io.gatling.javaapi.core.ScenarioBuilder` publishes four methods — a varargs and a `List` form of each: ```java PopulationBuilder injectOpen(OpenInjectionStep... steps); PopulationBuilder injectClosed(ClosedInjectionStep... steps); ``` Each one reaches into the Scala core, applies the corresponding factory explicitly, and returns a wrapped `PopulationBuilder`. The two languages therefore end up in the same place by different routes: Scala resolves the factory at compile time from a type class, Java resolves it at the call site from a method name. ## Consequences you can be asked about - **Mixing models in one call is a compile error in both.** In Java the parameter type is `OpenInjectionStep...`, so a closed step will not typecheck. In Scala there is no single `InjectionProfileFactory[T]` covering both step types, so the implicit search simply fails. All the steps in one injection call must be of the same model kind, and the compiler is what enforces it. - **The step names themselves do not differ by language.** `atOnceUsers`, `rampUsers`, `constantUsersPerSec`, `constantConcurrentUsers` and the rest are spelled identically across all five languages. Only the entry point differs — do not invent per-language step spellings. - **"The Gatling DSL" is not one thing here.** A sentence about `injectOpen` is false in Scala; a sentence about `inject` is false in the other four. Whenever you write or read a note for a mixed-language team, name the language or name both forms. - **Kotlin's spelling is Java's spelling.** Gatling's own recorder proves it: it emits `setUp(scn.inject(atOnceUsers(1)))` for Scala and `setUp(scn.injectOpen(atOnceUsers(1)))` for Kotlin, Java, JavaScript and TypeScript alike. ## Why it matters beyond trivia Reading a Gatling example off the internet and pasting it into the wrong language is one of the most common ways a simulation fails to compile, because the samples look nearly identical except for this one word. Knowing that the split follows the binding layer rather than the language lets you predict it: if the snippet uses `io.gatling.javaapi` types, it will say `injectOpen`; if it uses `io.gatling.core.Predef._`, it will say `inject`. ## Telling which layer a snippet belongs to You almost never have to guess. The import block gives it away, and so does the way durations are written: | what the imports say | layer | entry point | how a duration is written | |---|---|---|---| | `io.gatling.core.Predef._` | Scala core | `inject` | a `FiniteDuration`, e.g. `30.seconds` | | `io.gatling.javaapi.core.CoreDsl.*` | Java API | `injectOpen` / `injectClosed` | a `java.time.Duration`, or a bare `long` meaning **seconds** | | `@gatling.io/core` | npm SDK | `injectOpen` / `injectClosed` | a number of seconds, or an object such as `{ amount: 2, unit: "minutes" }` | That bare-`long`-means-seconds rule catches people out on its own: `rampUsers(100).during(60)` in Java is a sixty-second ramp, with no unit in the expression to remind you. ## Both entry points take a list as well as varargs Each form has two overloads, one varargs and one taking a collection: Scala's `inject(iss: Iterable[T])` alongside `inject(is: T, moreIss: T*)`, and the Java API's `injectOpen(List<OpenInjectionStep>)` alongside its varargs form. That matters when a profile is assembled at run time from configuration: build the list of steps, then pass it whole. Scala's list form also refuses an empty collection, failing with *Calling inject with empty injection steps*. ## Where the two routes meet Whichever name you called, the result is the same type — a `PopulationBuilder` — and nothing downstream of the entry point knows which language registered it. The split is purely about how the open-or-closed decision is expressed at the call site, and it disappears one line later.
- What stops a Gatling user from mixing open and closed injection steps in one call?The compiler, in every language. The Java API types the parameter as `OpenInjectionStep...` or `ClosedInjectionStep...`, so the wrong step will not typecheck. Scala fails implicit resolution instead, because no single `InjectionProfileFactory` covers both step types. Either way it is caught before the run, not at startup.
- Do injection step names such as rampUsers differ between Gatling's Java and Scala DSLs?No. The step builders are spelled identically across all five authoring languages — `nothingFor`, `atOnceUsers`, `rampUsers`, `constantUsersPerSec`, `constantConcurrentUsers` and the rest. Only the entry point that consumes them differs, and only durations change shape, because Java takes `java.time.Duration` where Scala takes a `FiniteDuration`.
saying these in an interview costs you the question
- Claiming Gatling ships a separate Kotlin SDK module
- Saying injectOpen works in a Scala simulation
- Assuming Scala's inject always builds an open profile
- Inventing per-language spellings for injection step builders