Gatling DSL extensions library — production-ready utilities for feeders, transactions, assertions, templates, config management, JWT generation, Redis integration, and more. Build faster, more reliable performance tests.
Gatling Picatinny is best for teams that write Gatling load tests and need production-grade helpers on top of the core DSL: structured config, realistic data feeders, signed JWTs, Redis-backed scenarios, reusable transaction blocks, and response-time assertions — without writing all of that from scratch.
Pick the snippet for your build tool in Installation, then come back here.
The examples/ folder contains source overlays for all three languages. Each overlay is applied on top of a project generated by the galaxio CLI — none of them are standalone runnable on their own. Generate a project with the CLI first, then open the Debug simulation as your entry point. See Examples for the full table.
| I want to… | Go to |
|---|---|
Share baseUrl, intensity, and custom vars across scenarios |
Config |
| Generate realistic test data (names, phones, UUIDs, CSV rows) | Feeders |
| Wrap request groups into named transactions with latency stats | Transactions |
| Attach a signed JWT to every request | JWT |
| Share state between virtual users via Redis | Redis |
| Assert response-time percentiles after a run | Assertions |
| Render dynamic request bodies from Mustache templates | Templates |
| Picatinny version | Gatling | Scala | Java | Branch |
|---|---|---|---|---|
| 1.12.2 (latest) | 3.13.x | 2.13 | 17+ | main |
| 0.18.2 | 3.11.x | 2.13 | 17+ | — (archived) |
Which version should I use?
- Gatling 3.13 (current) — use
1.12.2from themainbranch.- Gatling 3.11 (legacy) — use
0.18.2; that line is no longer actively developed.
Branch strategy:
maintracks the latest stable Gatling release (currently 3.13.x). There is no separate long-term branch for older Gatling lines.
libraryDependencies += "org.galaxio" %% "gatling-picatinny" % "1.12.2" % Testgatling("org.galaxio:gatling-picatinny_2.13:1.12.2")<dependency>
<groupId>org.galaxio</groupId>
<artifactId>gatling-picatinny_2.13</artifactId>
<version>1.12.2</version>
<scope>test</scope>
</dependency>sbt:
libraryDependencies += "org.galaxio" %% "gatling-picatinny" % "0.18.2" % TestGradle (Kotlin DSL):
gatling("org.galaxio:gatling-picatinny_2.13:0.18.2")Maven:
<dependency>
<groupId>org.galaxio</groupId>
<artifactId>gatling-picatinny_2.13</artifactId>
<version>0.18.2</version>
<scope>test</scope>
</dependency>- Start here
- Compatibility
- Installation
- Config
- Startup Banner and Diagnostics
- Feeders
- Profile
- Redis
- Templates
- JWT
- Assertions
- Transactions
- Examples
- Contributing
- License
The only class that you need from this module is SimulationConfig. It could be used to attach some default variables
such as intensity, baseUrl, baseAuthUrl and some others to your scripts. Also, it provides functions to get custom
variables from config.
Import:
Scala example:
import org.galaxio.gatling.config.SimulationConfig._Java example:
import static org.galaxio.gatling.javaapi.SimulationConfig.*;Kotlin example:
import org.galaxio.gatling.javaapi.SimulationConfig.*
Using default variables:
Scala example:
import org.galaxio.gatling.config.SimulationConfig._
val testPlan: Seq[OpenInjectionStep] = List(
rampUsersPerSec(0).to(intensity).during(rampDuration),
constantUsersPerSec(intensity).during(stageDuration)
)Java example:
import static org.galaxio.gatling.javaapi.SimulationConfig.*;
public class Example {
{
SomeScenario.scn.injectOpen(
incrementUsersPerSec(intensity() / stagesNumber())
.times(stagesNumber())
.eachLevelLasting(stageDuration())
.separatedByRampsLasting(rampDuration())
.startingFrom(0.0)
);
}
}Kotlin example:
import org.galaxio.gatling.javaapi.SimulationConfig.*
SomeScenario.scn.injectOpen(
incrementUsersPerSec(intensity() / stagesNumber())
.times(stagesNumber())
.eachLevelLasting(stageDuration())
.separatedByRampsLasting(rampDuration())
.startingFrom(0.0)
)Using functions to get custom variable:
simulation.conf
stringVariable: "FOO",
intVariable: 1,
doubleVariable: 3.1415,
duration: {
durationVariable: 3600s
}
booleanVariable: true
stringListVariable: ["foo", "bar"]
client {
timeout: 10 seconds
}
Scala example:
import org.galaxio.gatling.config.SimulationConfig._
val stringVariable = getStringParam("stringVariable")
val intVariable = getIntParam("intVariable")
val doubleVariable = getDoubleParam("doubleVariable")
val durationVariable = getDurationParam("duration.durationVariable")
val booleanVariable = getBooleanParam("booleanVariable")
val stringListVariable = getStringListParam("stringListVariable")
val clientConfig = getConfigParam("client")
val optionalValue = getOptStringParam("optionalVariable")Java example:
import static org.galaxio.gatling.javaapi.SimulationConfig.*;
String stringVariable = getStringParam("stringVariable");
int intVariable = getIntParam("intVariable");
double doubleVariable = getDoubleParam("doubleVariable");
Duration durationVariable = getDurationParam("duration.durationVariable");
boolean booleanVariable = getBooleanParam("booleanVariable");
List<String> stringListVariable = getStringListParam("stringListVariable");
Config clientConfig = getConfigParam("client");
Optional<String> optionalValue = getOptStringParam("optionalVariable");Kotlin example:
import org.galaxio.gatling.javaapi.SimulationConfig.*
val stringVariable = getStringParam("stringVariable")
val intVariable = getIntParam("intVariable")
val doubleVariable = getDoubleParam("doubleVariable")
val durationVariable = getDurationParam("duration.durationVariable")
val booleanVariable = getBooleanParam("booleanVariable")
val stringListVariable = getStringListParam("stringListVariable")
val clientConfig = getConfigParam("client")
val optionalValue = getOptStringParam("optionalVariable")
Required getters throw SimulationConfigException when a value is missing or has an invalid type. Optional getters return
None in Scala and Optional.empty() in Java/Kotlin when the path is not defined.
JVM system properties override values from simulation.conf, which is useful for CI and environment-specific runs:
sbt Gatling/test -DbaseUrl=https://test.example.org -Dintensity="120 rpm"Workload defaults are validated when they are first read:
stagesNumbermust be greater than0intensitymust resolve to a value greater than0rpsrampDurationmust be0or greaterstageDurationandtestDurationmust be greater than0
Config values are logged when they are read. Paths containing words like password, secret, token, apiKey, or
credential are masked in logs as ******.
Picatinny provides explicit utility methods for startup output. Put them where it makes sense for your project: in a simulation class, a shared package object, or another project bootstrap point.
Default behavior:
picatinny.startup.banner.enabled = true
picatinny.diagnostics.enabled = false
Disable the banner for quiet runs:
-Dpicatinny.startup.banner.enabled=falseEnable JVM/runtime diagnostics when you need extra debug information:
-Dpicatinny.diagnostics.enabled=truePass the same Gatling injection steps to Utility.banner(...) that you pass to inject/injectOpen. The banner parses
the actual injector objects and renders the effective workload. If no injectors are passed, it falls back to
simulation.conf.
Example simulation.conf:
baseUrl = "http://localhost"
intensity = "60 rpm"
stagesNumber = 2
rampDuration = 1 minute
stageDuration = 5 minutes
picatinny.startup.banner.enabled = true
picatinny.diagnostics.enabled = falseScala usage:
import io.gatling.core.Predef._
import org.galaxio.gatling.config.SimulationConfig._
import org.galaxio.gatling.utils.Utility
class Stability extends Simulation {
val injectionProfile = (
rampUsersPerSec(0).to(intensity).during(rampDuration),
constantUsersPerSec(intensity).during(stageDuration),
)
Utility.banner(injectionProfile)
Utility.diagnostics()
setUp(
scn.inject(injectionProfile._1, injectionProfile._2),
).protocols(http.baseUrl(baseUrl))
}Java usage:
import io.gatling.javaapi.core.OpenInjectionStep;
import io.gatling.javaapi.core.Simulation;
import org.galaxio.gatling.javaapi.Utility;
import static io.gatling.javaapi.core.CoreDsl.constantUsersPerSec;
import static io.gatling.javaapi.core.CoreDsl.rampUsersPerSec;
import static org.galaxio.gatling.javaapi.SimulationConfig.*;
public final class Stability extends Simulation {
{
OpenInjectionStep[] injectionProfile = {
rampUsersPerSec(0).to(intensity()).during(rampDuration()),
constantUsersPerSec(intensity()).during(stageDuration())
};
Utility.banner(injectionProfile);
Utility.diagnostics();
setUp(scn.injectOpen(injectionProfile));
}
}Kotlin usage:
import io.gatling.javaapi.core.CoreDsl.constantUsersPerSec
import io.gatling.javaapi.core.CoreDsl.rampUsersPerSec
import io.gatling.javaapi.core.OpenInjectionStep
import io.gatling.javaapi.core.Simulation
import org.galaxio.gatling.javaapi.Utility
import org.galaxio.gatling.javaapi.SimulationConfig.*
class Stability : Simulation() {
init {
val injectionProfile = arrayOf<OpenInjectionStep>(
rampUsersPerSec(0.0).to(intensity()).during(rampDuration()),
constantUsersPerSec(intensity()).during(stageDuration()),
)
Utility.banner(*injectionProfile)
Utility.diagnostics()
setUp(scn.injectOpen(*injectionProfile))
}
}Startup output:
================================================================================
Picatinny Gatling Run
================================================================================
Simulation : Stability
Base URL : http://localhost
Workload
intensity : 1.00 rps
profile : provided-open-injection
ramp duration : 1m
stage duration : 5m
total duration : 6m
Timeline
00:00 - 01:00 ramp 0.00 -> 1.00 rps
01:00 - 06:00 plateau 1.00 rps
ASCII preview
rps
1.00 | //___________________________________________________________
0.89 | /
0.78 | /
0.67 | /
0.56 | //
0.44 | /
0.33 | /
0.22 | /
0.11 | //
0.00 |/
+------------------------------------------------------------------------
00:00 06:00
================================================================================
Picatinny provides two feeder APIs: the Faker API (composable, domain-oriented generators) and the legacy Random*Feeder helpers (simple one-liner feeders). Both produce standard Gatling feeders. See docs/faker-api.md for the full Faker API reference.
Lazy Generator[A] values that compose with map/flatMap and plug into Gatling via GeneratedFeeder.
Scala:
import org.galaxio.gatling.feeders.faker.Predef._
import org.galaxio.gatling.feeders.faker._
val users = GeneratedFeeder(
"email" -> Faker.internet.email(),
"phone" -> Faker.phone.mobile(Country.RU, PhoneFormatMode.E164),
"inn" -> Faker.ru.inn.person(),
"amount" -> Faker.finance.amount(BigDecimal(100), BigDecimal(5000)),
)Java:
import static org.galaxio.gatling.javaapi.FakerApi.*;
import static org.galaxio.gatling.javaapi.Feeders.GeneratedFeeder;
var users = GeneratedFeeder(
field("email", email()),
field("phone", phoneMobile(countryRU(), phoneFormatE164())),
field("inn", innPerson()),
field("amount", amount(100, 5000))
);Kotlin:
import org.galaxio.gatling.javaapi.FakerApi.*
import org.galaxio.gatling.javaapi.Feeders.GeneratedFeeder
val users = GeneratedFeeder(
field("email", email()),
field("phone", phoneMobile(countryRU(), phoneFormatE164())),
field("inn", innPerson()),
field("amount", amount(100.0, 5000.0)),
)Enrich existing Gatling feeders (Scala):
val enriched = csv("users.csv").circular
.withGenerated("traceId", Faker.uuid.string)
.withGenerated("sessionPhone", Faker.phone.mobile(Country.AR))Deprecated — prefer the Faker API for new projects. Legacy feeders remain fully supported.
Scala:
import org.galaxio.gatling.feeders._
val uuidFeeder = RandomUUIDFeeder("uuid")
val phoneFeeder = RandomPhoneFeeder("phone")
val stringFeeder = RandomStringFeeder("randomString", 10)
val panFeeder = RandomPANFeeder("pan", "421345")
val innFeeder = RandomNatITNFeeder("inn")Java:
import static org.galaxio.gatling.javaapi.Feeders.*;
var uuidFeeder = RandomUUIDFeeder("uuid");
var phoneFeeder = RandomPhoneFeeder("phone");
var stringFeeder = RandomStringFeeder("randomString", 10);
var panFeeder = RandomPANFeeder("pan", "421345");
var innFeeder = RandomNatITNFeeder("inn");Kotlin:
import org.galaxio.gatling.javaapi.Feeders.*
val uuidFeeder = RandomUUIDFeeder("uuid")
val phoneFeeder = RandomPhoneFeeder("phone")
val stringFeeder = RandomStringFeeder("randomString", 10)
val panFeeder = RandomPANFeeder("pan", "421345")
val innFeeder = RandomNatITNFeeder("inn")Creates feeder capable of retrieving secret data from HC Vault
- authorises via approle;
- uses v1 API;
- works with kv Secret Engine;
- does not iterate over keys, returns full map with keys it found on each call;
- params:
- vaultUrl - vault URL e.g. "https://vault.ru"
- secretPath - path to secret data within your vault e.g. "testing/data"
- roleId - approle login
- secretId - approle password
- keys - list of keys you are willing to retrieve from vault
Scala example:
val vaultFeeder = VaultFeeder(vaultUrl, secretPath, roleId, secretId, keys)Java example:
Iterator<Map<String, Object>> vaultFeeder = VaultFeeder(vaultUrl, secretPath, roleId, secretId, keys);Kotlin example:
val vaultFeeder = VaultFeeder(vaultUrl, secretPath, roleId, secretId, keys)Additional one-record data source helpers:
VaultFeeder.withToken(vaultUrl, secretPath, vaultToken, keys)reads Vault data when CI already provides a token.VaultFeeder.fromPaths(vaultUrl, roleId, secretId, paths)merges selected keys from several Vault paths.EnvFeeder(keys, prefix)reads selected environment variables into a feeder record.HttpJsonFeeder(url, keys, headers)reads selected top-level string fields from a JSON HTTP endpoint.
Creates a feeder with separated values from a source String, Seq[String] or Seq[Map[String, Any]].
- params:
- paramName - feeder name
- source - data source
- separator - ",", ";", "\t" or other delimiter which separates values. You can also use following methods for the most common separators: .csv(...), .ssv(...), .tsv(...)
Get separated values from a source: String
Scala example:
val sourceString = "v21;v22;v23"
val separatedValuesFeeder: FeederBuilderBase[String] =
SeparatedValuesFeeder("someValues", sourceString, ';') // Vector(Map(someValues -> v21), Map(someValues -> v22), Map(someValues -> v23))Java example:
String sourceString = "v21;v22;v23";
Iterator<Map<String, Object>> separatedValuesFeeder = SeparatedValuesFeeder.apply("someValues", sourceString, ';');Kotlin example:
val sourceString = "v21;v22;v23"
val separatedValuesFeeder = SeparatedValuesFeeder.apply("someValues", sourceString, ';')Get separated values from a source: Seq[String]
Scala example:
val sourceSeq = Seq("1,two", "3,4")
val separatedValuesFeeder: FeederBuilderBase[String] =
SeparatedValuesFeeder.csv("someValues", sourceSeq) // Vector(Map(someValues -> 1), Map(someValues -> two), Map(someValues -> 3), Map(someValues -> 4))Java example:
List<Map<String, Object>> sourceList = Arrays.asList("1,two", "3,4");
Iterator<Map<String, Object>> separatedValuesFeeder = SeparatedValuesFeeder.csv("someValues", sourceList);Kotlin example:
var sourceList = listOf("1,two", "3,4")
var separatedValuesFeeder1 = SeparatedValuesFeeder.csv("someValues", sourceList)Get separated values from a source: Seq[Map[String, Any]]
Scala example:
val vaultFeeder: FeederBuilderBase[String] = Vector(
Map(
"HOSTS" -> "host11,host12",
"USERS" -> "user11",
),
Map(
"HOSTS" -> "host21,host22",
"USERS" -> "user21,user22,user23",
),
)
val separatedValuesFeeder: FeederBuilderBase[String] =
SeparatedValuesFeeder(None, vaultFeeder.readRecords, ',') // Vector(Map(HOSTS -> host11), Map(HOSTS -> host12), Map(USERS -> user11), Map(HOSTS -> host21), Map(HOSTS -> host22), Map(USERS -> user21), Map(USERS -> user22), Map(USERS -> user23))Java example:
List<Map<String, Object>> vaultData = Arrays.asList(Map.of("HOSTS","host11,host12"), Map.of("USERS", "user21,user22,user23"));
Iterator<Map<String, Object>> separatedValuesFeeder = SeparatedValuesFeeder.apply(Optional.empty(), vaultData, ',');Kotlin example:
var sourceList = listOf(Map.of("HOSTS", "host11,host12"), Map.of("USERS", "user21,user22,user23"))
var separatedValuesFeeder1 = SeparatedValuesFeeder.csv(null, sourceList)Creates a feeder with phone numbers with formats from json file or case class PhoneFormat
Simple phone feeder
Scala example:
val simplePhoneNumber: Feeder[String] = RandomPhoneFeeder("simplePhoneFeeder")Java example:
Iterator<Map<String, Object>> simplePhoneNumber = RandomPhoneFeeder("simplePhoneFeeder");Kotlin example:
val simplePhoneNumber = RandomPhoneFeeder("simplePhoneNumber")Phone feeder with custom formats
Scala example:
val ruMobileFormat: PhoneFormat = PhoneFormat(
countryCode = "+7",
length = 10,
areaCodes = Seq("903", "906", "908"),
prefixes = Seq("55", "81", "111"),
format = "+X XXX XXX-XX-XX")
val randomPhoneNumber: Feeder[String] =
RandomPhoneFeeder("randomPhoneNumber", ruMobileFormat)Java example:
PhoneFormat ruMobileFormat = PhoneFormatBuilder.apply("+7", 10, Arrays.asList("945", "946"), "+X XXX XXX-XX-XX", Arrays.asList("55", "81", "111"));
Iterator<Map<String, Object>> randomPhoneNumber = RandomPhoneFeeder("randomPhoneNumber", ruMobileFormat);Kotlin example:
val ruMobileFormat = PhoneFormatBuilder.apply(
"+7",
10,
listOf("945", "946"),
"+X XXX XXX-XX-XX",
listOf("55", "81", "111")
)
val randomPhoneNumber = RandomPhoneFeeder("randomPhoneNumber", ruMobileFormat)Phone feeder with custom formats with file
Creates file with formats, for example RESOURCES/phoneTemplates/ru.json
{
"formats": [
{
"countryCode": "+7",
"length": 10,
"areaCodes": ["903", "906"],
"prefixes": ["123", "321", "132", "231"],
"format": "+X(XXX)XXXXXXX"
},
{
"countryCode": "8",
"length": 10,
"areaCodes": ["495", "499"],
"prefixes": ["81", "82", "83"],
"format": "X(XXX)XXX-XX-XX"
}
]
}Scala example:
val phoneFormatsFromFile: String = "phoneTemplates/ru.json"
val randomE164PhoneNumberFromJson: Feeder[String] =
RandomPhoneFeeder("randomE164PhoneNumberFile", phoneFormatsFromFile, TypePhone.E164PhoneNumber)Java example:
String phoneFormatsFromFile = "phoneTemplates/ru.json";
Iterator<Map<String, Object>> randomE164PhoneNumberFromJson = RandomPhoneFeeder("randomE164PhoneNumberFile", phoneFormatsFromFile, TypePhone.E164PhoneNumber());Kotlin example:
val phoneFormatsFromFile = "phoneTemplates/ru.json"
val randomE164PhoneNumberFromJson = RandomPhoneFeeder("randomE164PhoneNumberFile", phoneFormatsFromFile, TypePhone.E164PhoneNumber())- Load profile configs from HOCON or YAML files
- Common traits to create profiles for any protocol
- HTTP profile as an example
import org.galaxio.gatling.profile._
import pureconfig.generic.auto._HOCON configuration example:
{
name: test-profile
profile: [
{
name: request-a
url: "http://test.url"
probability: 50.0
method: get
},
{
name: request-b
url: "http://test.url"
probability: 50.0
method: post
body: "{\"a\": \"1\"}"
}
]
}YAML configuration example:
name: test-profile
profile:
- name: request-a
url: "http://test.url"
probability: 50
method: get
- name: request-b
url: "http://test.url"
probability: 50
method: post
body: "{\"a\": \"1\"}"Simulation setUp
class test extends Simulation {
val profileConfigName = "profile.conf"
val someTestPlan = constantUsersPerSec(intensity) during stageDuration
val httpProtocol = http.baseUrl(baseUrl)
val config: HttpProfileConfig = new ProfileBuilder[HttpProfileConfig].buildFromYaml(profileConfigName)
val scn: ScenarioBuilder = config.toRandomScenario
setUp(
scn.inject(
atOnceUsers(10)
).protocols(httpProtocol)
).maxDuration(10)
}New profile YAML configuration example:
apiVersion: link.ru/v1alpha1
kind: PerformanceTestProfiles
metadata:
name: performance-test-profile
description: performance test profile
spec:
profiles:
- name: maxPerf
period: 10.05.2022 - 20.05.2022
protocol: http
profile:
- request: request-1
intensity: 100 rph
groups: ["Group1"]
params:
method: POST
path: /test/a
headers:
- 'Content-Type: application/json'
- 'Connection: keep-alive'
body: '{"a": "b"}'
- request: request-2
intensity: 200 rph
groups: ["Group1", "Group2"]
params:
method: GET
path: /test/b
body: '{"c": "d"}'
- request: request-3
intensity: 200 rph
groups: [ "Group1", "Group2" ]
params:
method: GET
path: /test/c
body: '{"e": "f"}'Optional fields: groups, headers, body.
If there are no required fields, an exception will be thrown for the missing field.
Simulation setUp
Scala example:
class Debug extends Simulation {
val pathToProfile = "path/to/profile.yml"
val scn = ProfileBuilderNew.buildFromYaml(pathToProfile).selectProfile("maxPerf").toRandomScenario
setUp(
scn.inject(
atOnceUsers(10)
).protocols(httpProtocol)
)
.maxDuration(10)
}Java example:
import org.galaxio.gatling.javaapi.profile.ProfileBuilderNew;
public class Debug extends Simulation {
public static ScenarioBuilder scn = ProfileBuilderNew
.buildFromYaml("path/to/profile.yml")
.selectProfile("maxPerf")
.toRandomScenario();
{
setUp(
scn.injectOpen(atOnceUsers(1))
).protocols(httpProtocol);
}
}Kotlin example:
import org.galaxio.gatling.javaapi.profile.ProfileBuilderNew
class Debug : Simulation() {
val scn: ScenarioBuilder = ProfileBuilderNew
.buildFromYaml("path/to/profile.yml")
.selectProfile("maxPerf")
.toRandomScenario()
init {
setUp(
scn.injectOpen(atOnceUsers(1)),
).protocols(httpProtocol)
}
}This module allows you to use Redis commands as Gatling scenario actions.
- Support for 27 Redis commands across 6 data types (Strings, Hashes, Lists, Sets, Keys, Counters)
- Save command results to Gatling session with
.saveAs("variable") - Custom request names for statistics with
.requestName("name") - Support Gatling EL expressions in keys and values
- Methods are not taken into account in Gatling statistics by default. Use
.requestName("name")to track them. - Not intended for load testing of Redis.
Scala:
import com.redis.RedisClientPool
import org.galaxio.gatling.redis.RedisActionBuilder._Java:
import io.gatling.javaapi.redis.RedisClientPool;
import org.galaxio.gatling.javaapi.redis.RedisClientPoolJava;Kotlin:
import io.gatling.javaapi.redis.RedisClientPool
import org.galaxio.gatling.javaapi.redis.RedisClientPoolJavaFirst you need to prepare RedisClientPool:
Scala:
val redisPool = new RedisClientPool("localhost", 6379)Java:
RedisClientPoolJava redisPool = new RedisClientPoolJava("localhost", 6379);Kotlin:
val redisPool = RedisClientPoolJava("localhost", 6379)Add Redis commands to your scenario chain:
Scala:
.exec(redisPool.SET("key", "value"))
.exec(redisPool.GET("key").saveAs("result"))
.exec(redisPool.DEL("key"))Java:
.exec(redisPool.SET("key", "value"))
.exec(redisPool.GET("key").saveAs("result"))
.exec(redisPool.DEL("key"))Kotlin:
.exec(redisPool.SET("key", "value"))
.exec(redisPool.GET("key").saveAs("result"))
.exec(redisPool.DEL("key"))| Category | Commands |
|---|---|
| Strings | GET, SET, GETSET, SETNX, SETEX, MGET, MSET* |
| Counters | INCR, INCRBY, DECR, DECRBY |
| Hashes | HGET, HSET, HDEL, HGETALL, HMSET*, HMGET |
| Lists | LPUSH, RPUSH, LPOP, RPOP, LRANGE, LLEN |
| Sets | SADD, SREM, SMEMBERS, SISMEMBER, SCARD |
| Keys | EXISTS, EXPIRE, TTL, KEYS, DEL |
* MSET and HMSET are available only in the Scala API.
A DSL for building JSON and XML request bodies with Gatling EL expression support, plus file-based template loading. String values are automatically escaped for JSON (", \, newlines) and XML (&, <, >) special characters. jsonBody sets Content-Type: application/json, xmlBody sets Content-Type: application/xml.
| Operator | Description | JSON Output |
|---|---|---|
"field" - "value" |
Literal string | "field": "value" |
"field" - 42 |
Literal number/boolean | "field": 42 |
"field" ~ "var" |
Session variable reference | "field": "#{var}" |
"field" (implicit) |
Session variable with same name | "field": "#{field}" |
"field" > (1, 2, 3) |
Array | "field": [1,2,3] |
"field" - ("a" - 1) |
Nested object | "field": {"a": 1} |
"field" - nullVal |
Null value | "field": null |
Scala example:
import org.galaxio.gatling.templates.HttpBodyExt._
import org.galaxio.gatling.templates.Syntax._
class SampleScenario {
val sendJson: ScenarioBuilder =
scenario("Post some")
.exec(
http("PostData")
.post(url)
.jsonBody(
"id" - 23,
"name", // session variable #{name}
"project" - (
"id" ~ "projectId", // session variable #{projectId}
"name" - "Super Project",
"sub" > (1, 2, 3, 4, 5, 6),
),
"deleted" - nullVal, // null value
)
)
}Java example:
import static org.galaxio.gatling.javaapi.TemplateSyntax.*;
String json = makeJson(
field("id", 23),
sessionVar("name", "name"),
fieldObj("project",
sessionVar("id", "projectId"),
field("name", "Super Project"),
fieldArr("sub", 1, 2, 3, 4, 5, 6)
),
fieldNull("deleted")
);
// Use with Gatling: .body(StringBody(json)).asJsonKotlin example:
import org.galaxio.gatling.javaapi.TemplateSyntax.*
val json = makeJson(
field("id", 23),
sessionVar("name", "name"),
fieldObj("project",
sessionVar("id", "projectId"),
field("name", "Super Project"),
fieldArr("sub", 1, 2, 3, 4, 5, 6)
),
fieldNull("deleted")
)The resulting JSON:
{
"id": 23,
"name": "#{name}",
"project": {
"id": "#{projectId}",
"name": "Super Project",
"sub": [1, 2, 3, 4, 5, 6]
},
"deleted": null
}Scala example:
import org.galaxio.gatling.templates.HttpBodyExt._
import org.galaxio.gatling.templates.Syntax._
http("PostXml")
.post(url)
.xmlBody(
"id" - 23,
"name" ~ "userName",
"tags" > ("alpha", "beta"),
)Java example:
import static org.galaxio.gatling.javaapi.TemplateSyntax.*;
String xml = makeXml(
field("id", 23),
sessionVar("name", "userName"),
fieldArr("tags", "alpha", "beta")
);Produces: <id>23</id><name>#{userName}</name><tags><item>alpha</item><item>beta</item></tags>
Loads template files from resources/templates and sends them as POST request bodies. Template files support Gatling EL expressions. Templates are lazily loaded on first access.
$ tree resources/
.
├── gatling.conf
├── logback.xml
├── simulation.conf
└── templates
└── example_template1.json
└── example_template2.jsonScala example:
class SampleScenario extends Templates {
val sendTemplates: ScenarioBuilder =
scenario("Templates scenario")
.exec(postTemplate("example_template1", "/post_route"))
.exec(postTemplate("example_template2", "/post_route"))
}This sends 2 POST requests: one with body from example_template1.json, second from example_template2.json to $baseUrl/post_route. If a template name is not found, a NoSuchElementException is thrown listing available templates.
- Generate JWT tokens and store them in Gatling sessions for signing requests
- HMAC (HS256/384/512) and RSA/EC (RS256, ES256, etc.) algorithms
- Standard claims DSL (iss, sub, aud, exp, iat, nbf) with automatic time-based claims
- Gatling EL expression support (
#{varName}) for dynamic per-user claims - Claim merging — combine base payload from resource with dynamic claims
- Bearer token helper (
setJwtAsBearer) - PEM key loading utilities
Scala:
import org.galaxio.gatling.utils.jwt._Java:
import org.galaxio.gatling.javaapi.utils.Jwt;
import org.galaxio.gatling.javaapi.utils.JwtKeysJ;
import org.galaxio.gatling.utils.jwt.JwtGeneratorBuilder;
import org.galaxio.gatling.utils.jwt.ClaimsBuilder;Kotlin:
import org.galaxio.gatling.javaapi.utils.Jwt.*
import org.galaxio.gatling.javaapi.utils.JwtKeysJScala:
val jwtGenerator = jwt("HS256", jwtSecretToken)
.defaultHeader
.payloadFromResource("jwtTemplates/payload.json")Java:
JwtGeneratorBuilder jwtGenerator = Jwt.jwt("HS256", "jwtSecretToken")
.defaultHeader()
.payloadFromResource("jwtTemplates/payload.json");Kotlin:
val jwtGenerator = jwt("HS256", jwtSecretToken)
.defaultHeader()
.payloadFromResource("jwtTemplates/payload.json")Payload templates support Gatling EL expressions:
{
"userName": "#{randomString}",
"date": "#{simpleDate}",
"phone": "#{randomPhone}"
}Scala:
val jwtGenerator = jwt("HS256", secret).defaultHeader
.claims(ClaimsBuilder()
.issuer("my-service")
.subject("#{userId}")
.audience("https://api.example.com")
.expiresIn(5.minutes)
.issuedAtNow
.notBeforeNow
.claim("role", "admin")
.claimFromSession("tenantId", "#{tenantId}"))Java:
JwtGeneratorBuilder jwtGenerator = Jwt.jwt("HS256", secret).defaultHeader()
.claims(Jwt.claims()
.issuer("my-service")
.subject("#{userId}")
.audience("https://api.example.com")
.expiresIn(Duration.ofMinutes(5))
.issuedAtNow()
.notBeforeNow()
.claim("role", "admin")
.claimFromSession("tenantId", "#{tenantId}"));Kotlin:
val jwtGenerator = jwt("HS256", secret).defaultHeader()
.claims(claims()
.issuer("my-service")
.subject("#{userId}")
.audience("https://api.example.com")
.expiresIn(Duration.ofMinutes(5))
.issuedAtNow()
.notBeforeNow()
.claim("role", "admin")
.claimFromSession("tenantId", "#{tenantId}"))You can combine a base payload from a resource file with dynamic claims. ClaimsBuilder fields take precedence on conflict:
val gen = jwt("HS256", secret).defaultHeader
.payloadFromResource("jwtTemplates/baseClaims.json")
.claims(ClaimsBuilder().subject("#{userId}").expiresIn(5.minutes))Scala:
val privateKey = JwtKeys.rsaPrivateKeyFromResource("keys/private.pem")
val jwtGenerator = jwt("RS256", privateKey).defaultHeader
.claims(ClaimsBuilder().issuer("auth-service").expiresIn(1.hour))Java:
PrivateKey privateKey = JwtKeysJ.rsaPrivateKeyFromResource("keys/private.pem");
JwtGeneratorBuilder jwtGenerator = Jwt.jwt("RS256", privateKey).defaultHeader()
.claims(Jwt.claims().issuer("auth-service").expiresIn(Duration.ofHours(1)));Kotlin:
val privateKey = JwtKeysJ.rsaPrivateKeyFromResource("keys/private.pem")
val jwtGenerator = jwt("RS256", privateKey).defaultHeader()
.claims(claims().issuer("auth-service").expiresIn(Duration.ofHours(1)))Available key loading methods:
rsaPrivateKeyFromResource/rsaPrivateKeyFromFileecPrivateKeyFromResource/ecPrivateKeyFromFilersaPublicKeyFromResource/rsaPublicKeyFromFile(for verification)ecPublicKeyFromResource/ecPublicKeyFromFile(for verification)
jwt("HS256", secret)
.header("""{"alg": "HS256","typ": "JWT", "customField": "customData"}""")
.headerFromResource("jwtTemplates/header.json")
.defaultHeader
.payload("""{"sub": "#{userId}","scope": "api"}""")
.payloadFromResource("jwtTemplates/payload.json")Scala:
.exec(_.setJwt(jwtGenerator, "jwtToken"))
.exec(addCookie(Cookie("JWT_TOKEN", "#{jwtToken}").withDomain(jwtCookieDomain).withPath("/")))Java:
.exec(Jwt.setJwt(jwtGenerator, "jwtToken"))
.exec(addCookie(Cookie("JWT_TOKEN", "#{jwtToken}").withDomain("jwtCookieDomain").withPath("/")))Kotlin:
.exec(setJwt(jwtGenerator, "jwtToken"))
.exec(addCookie(Cookie("JWT_TOKEN", "#{jwtToken}").withDomain("jwtCookieDomain").withPath("/")))Scala:
.exec(_.setJwtAsBearer(jwtGenerator))
.exec(http("request").get("/api").header("Authorization", "#{Authorization}"))Java:
.exec(Jwt.setJwtAsBearer(jwtGenerator))
.exec(http("request").get("/api").header("Authorization", "#{Authorization}"))Kotlin:
.exec(setJwtAsBearer(jwtGenerator))
.exec(http("request").get("/api").header("Authorization", "#{Authorization}"))You can also specify a custom session key: setJwtAsBearer(jwtGenerator, "X-Auth")
Module helps to load assertion configs from YAML files
import org.galaxio.gatling.assertions.AssertionsBuilder.assertionFromYamlFile nfr contains non-functional requirements.
Requirements supported by Picatinny:
| requirement | key |
|---|---|
| 99th percentile of the responseTime | 99 перцентиль времени выполнения |
| 95th percentile of the responseTime | 95 перцентиль времени выполнения |
| 75th percentile of the responseTime | 75 перцентиль времени выполнения |
| 50th percentile of the responseTime | 50 перцентиль времени выполнения |
| percent of the failedRequests | Процент ошибок |
| maximum of the responseTime | Максимальное время выполнения |
YAML configuration example:
nfr:
- key: '99 перцентиль времени выполнения'
value:
GET /: '500'
MyGroup / MyRequest: '900'
request_1: '700'
all: '1000'
- key: 'Процент ошибок'
value:
all: '5'
- key: 'Максимальное время выполнения'
value:
GET /: '1000'
all: '2000'Scala example
class test extends Simulation {
setUp(
scn.inject(
atOnceUsers(10)
).protocols(httpProtocol)
).maxDuration(10)
.assertions(assertionFromYaml("src/test/resources/nfr.yml"))
}Java example
import static org.galaxio.gatling.javaapi.Assertions.assertionFromYaml;
import static io.gatling.javaapi.core.CoreDsl.atOnceUsers;
import io.gatling.javaapi.core.Simulation;
public class TestSimulation extends Simulation {
public TestSimulation() {
setUp(
scn.injectOpen(
atOnceUsers(10)
).protocols(httpProtocol)
).maxDuration(10)
.assertions(assertionFromYaml("src/test/resources/nfr.yml"));
}
}Kotlin example
import org.galaxio.gatling.javaapi.Assertions.assertionFromYaml
import io.gatling.javaapi.core.Simulation
import io.gatling.javaapi.core.CoreDsl.atOnceUsers
class TestSimulation : Simulation() {
init {
setUp(
scn.injectOpen(
atOnceUsers(10)
).protocols(httpProtocol)
).maxDuration(10)
.assertions(assertionFromYaml("src/test/resources/nfr.yml"))
}
}This extension introduce new syntax (startTransaction/endTransaction) for gatling scenario. Transaction is union of
actions, that able to measure summary response time of actions with pauses. It is same as groups, but response time
measuring include pauses, and you may pass endTime manually. That make possible write something like:
exec(Actions.createEntity())
.startTransaction("transaction1")
.doWhile(_ ("i").as[Int] < 10)(
feed(feeder)
.exec(Actions.insertTest())
.pause(2)
.exec(Actions.selectTest)
)
.endTransaction("transaction1")
.exec(Actions.batchTest)
.exec(Actions.selectAfterBatch)
Java example:
exec(Actions.createEntity())
.exec(startTransaction("transaction1"))
.exec(Actions.insertTest())
.pause(2)
.exec(Actions.selectTest)
.exec(endTransaction("transaction1"))
.exec(Actions.batchTest)
.exec(Actions.selectAfterBatch)Kotlin example:
exec(Actions.createEntity())
.exec(startTransaction("transaction1"))
.exec(Actions.insertTest())
.pause(2)
.exec(Actions.selectTest)
.exec(endTransaction("transaction1"))
.exec(Actions.batchTest)
.exec(Actions.selectAfterBatch)To use this, you need Gatling version >= 3.11.5 and import this in your Scenarios and Simulations:
import org.galaxio.gatling.transactions.Predef._Attention!
Your simulation should inherit the class SimulationWithTransactions instead of Simulation, then the transaction
mechanism will work correctly.
import io.gatling.core.Predef._
import io.gatling.core.structure.ScenarioBuilder
..
.
import org.galaxio.gatling.transactions.Predef._
object DebugScenario {
val scn: ScenarioBuilder = scenario("Debug")
.exec(Actions.createEntity())
.startTransaction("transaction1")
.doWhile(_ ("i").as[Int] < 10)(
feed(feeder)
.exec(Actions.insertTest())
.pause(2)
.exec(Actions.selectTest)
)
.endTransaction("transaction1")
.exec(Actions.batchTest)
.exec(Actions.selectAfterBatch)
}
class DebugTest extends SimulationWithTransactions {
setUp(
DebugScenario.scn.inject(atOnceUsers(1))
).protocols(dataBase)
}Standalone Gatling projects in the examples/ directory:
| Project | Language | Build tool | Feeder API |
|---|---|---|---|
examples/scala-sbt-example |
Scala | sbt | Faker API + legacy feeders |
examples/java-maven-example |
Java | Maven | Faker API (FakerApi) + legacy feeders |
examples/kotlin-gradle-example |
Kotlin | Gradle | Faker API (FakerApi) + legacy feeders |
For a new Scala sbt project, prefer the Galaxio Gatling template:
galaxio template init gatling/scala-sbtlibraryDependencies += "org.galaxio" %% "gatling-picatinny" % "<latest>"To test your changes use sbt test.
All three example folders are source overlays applied on top of a project generated by the galaxio CLI. Generate a project with the CLI first, then the overlay is copied in — none of them are standalone runnable by design.
| Language | Folder | Build tool | Run command |
|---|---|---|---|
| Java | examples/java-maven-example | Maven | mvn gatling:test |
| Kotlin | examples/kotlin-gradle-example | Gradle | ./gradlew gatlingRun |
| Scala | examples/scala-sbt-example | sbt | sbt Gatling/test |
Each project covers the same set of scenarios (Debug, Stability, MaxPerformance, ScenarioCoverage, TransactionCoverage) so you can compare Java / Kotlin / Scala patterns side-by-side. Start with the Debug simulation — it is the smallest runnable entry point. See the Start here section for a guided first-run path.
# Build
sbt compile
# Run unit tests
sbt test
# Run integration tests
sbt IntegrationTest/test
# Check formatting
sbt scalafmtCheckAll
# Format code
sbt scalafmtAllApache License 2.0. See LICENSE for details.