Before you get started,

  • Rhino framework is compiled with JDK 11. So, the dependencies attached to your project must be compatible with JDK 11+.
  • Rhino projects are built as Docker containers, so you will need Docker installed on your computer to be able to test your simulations.

Upgrading from 1.x

Please note that there are some major changes beginning from the version 2.0 and if you want to upgrade to the newest version of Rhino, we strongly recommend evaluating your decision before you move on. The biggest change in the new versions is that the scenario mode is not supported anymore. If your load tests rely on scenarios, methods annotated with @Scenario, then you will need to translate the tests to the Load DSL. The reason why we discarded the scenarios is, because of the concurrency management. The scenario mode was implemented in classical blocking model and threads might get blocked instead of doing their jobs, namely generating load.

Let’s imagine a load testing scenario in which our load tests upload big files through the API. If you used scenarios prior to 2.0 and your load test framework is configured to have max. 100 threads, depending on the bandwidth and I/O performance, the threads will be busy with uploading 100 files at a certain point of time. But, in reactive mode the framework needs much less threads to achive a similar job and get the job done more efficently. Because, the reactive pipeline will take new chunks of bytes to upload as the capacity in the reactive pipeline allows so and if the pipeline reaches its limits, the consumers requesting data will signal the providers for on-hold till the capacity becomes available on the consumer side again. So, in reactive mode the number of threads will not define your limits and become the bottleneck and but the capacity of your service under test. It is what we want achive with load tests.

If you want to stick with scenario mode, you must use the Rhino 1.x in your projects. If you plan to write new load tests with Rhino, we highly recommend using the DSL from the reasons which we tried to explain, above.

What is Rhino ?

Rhino Load and Performance Testing Framework is a sub-project of the Rhino umbrella project and an SDK which enables developers to write load and performance tests in JUnit style. With annotation-based development model, the load test developers can provide the framework with metadata required for running tests. The Rhino is an open source project and it is developed under Apache 2.0.

Creating your first project

Rhino Load and Performance Testing projects are plain Maven projects with Rhino dependencies defined in the POM files. You can create a new project by creating a simple Maven project and add required dependencies manually into your POM file or more convenient way, is simply by running Rhino Maven archetype. The Maven Archetype allows developers to create a new project without building up one from the scratch:

$ mvn archetype:generate \
  -DarchetypeGroupId=io.ryos.rhino \
  -DarchetypeArtifactId=rhino-archetype \
  -DarchetypeVersion=${RECENT_RELEASE} \
  -DgroupId=com.acme \

In the example above, for the groupId you need to set your project’s groupId which is an identifier specific to your Maven project and organization e.g com.yourcompany.testing and the artifactId is some artifact id used by your project to identify the artifact e.g my-test-project. After hitting the Maven command above in your terminal, the project will be created automatically which can be imported into your IDE.

You may choose to create a Rhino project without using the Rhino archetype. In this case, it is required to add the Rhino core dependency into your Maven project, manually:


Please note that rhino-hello-world located in the project’s root, might be a good starting point if you want to play around with the project. It includes a baseline set-up to build your custom simulation upon.

Writing your first Simulation

Rhino projects are plain Java applications and consist of a Java main()-method to run simulations and simulation entities which are annotated with Rhino annotations. An example application might look as follows:

import io.ryos.rhino.sdk.Simulation;

public class Rhino {

    private static final String PROPS= "classpath:///"; 

    public static void main(String ... args) { 
        Simulation.create(PROPS, MySimulation.class).start(); 

Simulation is the load testing controller which takes an absolute path to configuration file ❶ as parameter (in the example above, the properties file is in the classpath, therefore classpath://<absolute path to configuration file>) and the class type of the simulation which is to be run. Alternatively, you can pass the fully-qualified name of the class as string, that prevents you from creating new docker artifact for each simulation in the project, instead you can simply pass the fully-qualified name e.g as environment variable to your container. Moreover, You can put the properties file outside of the classpath e.g somewhere on your disk: “file:///etc/init.d/”. The Java properties file contains application configuration, that is needed to run the load testing application. A minimal might look as follows:

# Http client configurations

# Node name

Please refer to Configuration section for the complete list of available configuration options.

Let us have a look at a Simulation example:

@Simulation(name = "Server-Status Simulation") 
public class RhinoEntity {

  @Provider(factory = UUIDProvider.class)
  private UUIDProvider uuidProvider;

  @Dsl(name = "Health") 
  public LoadDsl performHealth() {
    return dsl() 
        .run(http("Health API Call") 
            .header(c -> from(X_REQUEST_ID, "Rhino-" + UUID.randomUUID().toString()))

In the example above, ❶ we mark the simulation entity with @Simulation annotation with a unique name attribute. ❷ The simulation entity is a container for the DSL methods which are materialized and run by the Rhino runtime and annotated with @Dsl annotation. Every DSL method must have a unique name which is used in performance measurements and reporting e.g the DSL method above is called “Health” for healthcheck load test and must return a LoadDsl instance. ❸ To create a new Load DSL instance you use chained method calls ❹ of runners e.g by calling run() method, which takes Spec instances as parameter. By using Specs you can define, for instance, how an HTTP request look like, that is made during the load testing session.

Creating a deployable artifact

A Docker container i s the artifact you will get if you run the Maven package goal:

$ mvn -e clean install

After building the docker artifact, you can run the Docker artifact in CLI:

$ docker run -it your-project:latest

Debugging your Simulations

While developing your simulations, you may want to run the test for only one cycle instead of creating load in your development environment. By setting the environment variable, STOP_AFTER=1, the framework will then run the simulation for a single turn and aftwards it will terminate immediately.

Another useful switch, that you can turn on during development, is the “http.debug” configuration, that can be added to the


It will allow developers to monitor the Http Requests and Response.