Gatling can load test your JMS messaging systems, but it doesn’t actually send messages.
Let’s see it in action. Imagine you have a Java application that publishes messages to a JMS queue named my_request_queue and another application that consumes from my_response_queue. We want to simulate thousands of clients sending requests and waiting for responses.
First, you’ll need the JMS dependencies in your build.sbt (or equivalent):
libraryDependencies ++= Seq(
"io.gatling.highcharts" % "gatling-charts-highcharts" % "3.9.5" % Test,
"io.gatling" % "gatling-core" % "3.9.5" % Test,
"javax.jms" % "javax-jms-api" % "2.0.1",
"org.apache.activemq" % "activemq-client" % "5.18.3" // Or your JMS provider's client
)
Now, the Gatling simulation. This is where the magic happens, or rather, where we orchestrate the magic. Gatling itself won’t be a JMS client in the traditional sense. Instead, it will trigger your client code.
import io.gatling.core.Predef._
import io.gatling.javaapi.core._
import io.gatling.javaapi.jms._
import jakarta.jms._ // For JMS 2.0+
import java.util.UUID
class JmsLoadSimulation extends Simulation {
// This is your actual JMS client logic, encapsulated.
// Gatling will call this.
class MyJmsClient(connectionFactory: ConnectionFactory, requestQueueName: String, responseQueueName: String) {
private val connection: Connection = connectionFactory.createConnection()
connection.start()
private val session: Session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE)
private val requestProducer: MessageProducer = session.createProducer(session.createQueue(requestQueueName))
private val responseConsumer: MessageConsumer = session.createConsumer(session.createQueue(responseQueueName))
// This method is called by Gatling to send a request and wait for a response.
def sendRequest(messagePayload: String): String = {
val correlationId = UUID.randomUUID().toString
val requestMessage = session.createTextMessage(messagePayload)
requestMessage.setJMSCorrelationID(correlationId)
requestProducer.send(requestMessage)
// Blocking wait for the response. In a real high-concurrency scenario,
// you might use a more sophisticated correlation mechanism or async callbacks.
val responseMessage = responseConsumer.receive(5000) // 5 second timeout
if (responseMessage != null && responseMessage.getJMSCorrelationID == correlationId) {
responseMessage.getBody(classOf[String])
} else {
throw new RuntimeException("Response timed out or correlation ID mismatch")
}
}
def close(): Unit = {
session.close()
connection.close()
}
}
// Configure your JMS connection factory.
// For ActiveMQ:
val connectionFactory = new org.apache.activemq.ActiveMQConnectionFactory("tcp://localhost:61616")
val jmsScenario = scenario("JMS Messaging").exec { ctx =>
// Create a new client for each virtual user. This is important
// to avoid resource contention and ensure proper session management.
val client = new MyJmsClient(connectionFactory, "my_request_queue", "my_response_queue")
try {
val response = client.sendRequest(s"Hello from Gatling ${UUID.randomUUID().toString}")
// You'd typically assert something about the response here
// e.g., println(s"Received: $response")
ctx.markAsSucceeded()
} catch {
case e: Exception =>
println(s"Error during JMS operation: ${e.getMessage}")
ctx.markAsFailed()
} finally {
client.close() // Clean up resources for this virtual user
}
ctx // Return the context
}
setUp(
jmsScenario.inject(rampUsers(1000).during(60)) // Inject 1000 users over 60 seconds
).protocols(JmsProtocol.jmsProtocol) // This is a placeholder; JmsProtocol doesn't exist natively.
// Gatling's Java API doesn't have a built-in JmsProtocol.
// The exec block above is how you integrate.
}
The core idea is that Gatling orchestrates instances of your JMS client. Each virtual user created by Gatling gets its own MyJmsClient. This client then establishes a connection, creates producer/consumer pairs, sends a message with a unique correlation ID, and blocks until it receives a response (or times out).
The exec block in the Gatling simulation is where you define the actions for a virtual user. Here, we instantiate MyJmsClient, call its sendRequest method, and then crucially, close the client to release its resources. The JmsProtocol.jmsProtocol line is illustrative; there’s no built-in JmsProtocol in Gatling’s Java API. You integrate JMS directly within exec blocks.
The ConnectionFactory is your gateway to the JMS provider. For ActiveMQ, it’s ActiveMQConnectionFactory. You provide the broker’s URL (e.g., tcp://localhost:61616).
Inside MyJmsClient:
connectionFactory.createConnection(): Establishes a connection to the JMS broker.connection.start(): Crucial! This actually enables message delivery. Without it, you won’t receive messages.connection.createSession(false, Session.AUTO_ACKNOWLEDGE): Creates a session.falsemeans non-transacted.AUTO_ACKNOWLEDGEmeans messages are acknowledged as soon as they are received by the consumer.session.createProducer()andsession.createConsumer(): Create the objects that send to and receive from specific queues.requestMessage.setJMSCorrelationID(correlationId): This is the key to matching requests with responses. Each request gets a unique ID.responseConsumer.receive(5000): The consumer blocks here, waiting for a message that has a matching correlation ID. The5000is the timeout in milliseconds.responseMessage.getBody(classOf[String]): Retrieves the message payload.client.close(): Releases the JMS session and connection. It’s vital to do this for each virtual user to prevent resource exhaustion on the broker.
This setup allows Gatling to measure the latency of sending a message, waiting for processing by your consuming application, and receiving a response, all from the perspective of thousands of simulated clients.
The real power of this approach is that Gatling is measuring the end-to-end latency experienced by your application clients, not just the throughput of your JMS broker.
A common pitfall is forgetting to call connection.start(). If your messages aren’t being received by the consumer, or you’re not getting responses, that’s the first place to check.
The next challenge you’ll face is handling asynchronous responses or scenarios where a single request might generate multiple responses.