0

I have this Java Gatling simulation:

package com.mycompany.performancetests;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mycompany.openapi.Notification;
import io.gatling.javaapi.core.ChainBuilder;
import io.gatling.javaapi.core.ScenarioBuilder;
import io.gatling.javaapi.core.Simulation;
import io.gatling.javaapi.http.HttpProtocolBuilder;

import static io.gatling.javaapi.core.CoreDsl.*;
import static io.gatling.javaapi.http.HttpDsl.*;

public class MySimulation extends Simulation {
    private final HttpProtocolBuilder httpProtocol = http
            .disableFollowRedirect()
            .baseUrl(BASE_URL)
            .acceptHeader("application/json");
       
    private final ScenarioBuilder mainScn = scenario("MainScenario")
            .exec(getStatus())
            .exec(postStatus())
            );

     private static ChainBuilder getStatus() {
        return exec(http("GetStatus")
                .get(session -> API_PATH + "/notifications/00000000-0000-6000-7000-000000000000/status")
                .check(status().is(200))
                .check(jsonPath("$[0].id").notNull())
                .check(jsonPath("$[0].modelVersion").notNull().saveAs("modelVersion"))
        );
    }

    private static ChainBuilder postStatus() {
        return exec(session -> {
            // Retrieve the current modelVersion from the session
            String currentModelVersion = session.getString("modelVersion");
            // Increment the modelVersion by 1
            int incrementedModelVersion = Integer.parseInt(currentModelVersion) + 1;
            // Update the session with the incremented modelVersion
            session.set("modelVersion", String.valueOf(incrementedModelVersion));
            return session;
        }).exec(http("PostStatus")
                .post(session -> API_PATH + "/notifications/00000000-0000-6000-7000-000000000000/status")
                .header("Content-Type", "application/json")
                .header("Accept", "application/json")
                .body(StringBody(session -> """
                {
                   "comment":"",
                   "fiFlaggedStatus":"FLAGGED",
                   "investigationAssessment":"WORTHY",
                   "investigationStatus":"COMPLETED",
                   "panRiskScore":986,
                   "modelVersion":"%s",
                   "created":"2026-07-02T08:20:38.814Z",
                   "id":""
                }
                """.formatted(session.getString("modelVersion"))))
                .check(status().is(200))
                .check(jsonPath("$.modelVersion").saveAs("modelVersion")) // Add this line
                .check(jsonPath("$.id").notNull()));
    }

    {
        setUp(mainScn.injectOpen(atOnceUsers(2))).protocols(httpProtocol); 
    }
}

which generates the following sequence of curl requests:

curl -X GET "https://myurl/00000000-0000-6000-7000-000000000000/status" \
 -H "accept: application/json" \

curl -X POST "https://myurl/00000000-0000-6000-7000-000000000000/status" \
 -H "accept: application/json" \
 -H "Content-Type: application/json" \
 --data-binary '{"comment":"","fiFlaggedStatus":"FLAGGED","investigationAssessment":"WORTHY","investigationStatus":"COMPLETED","panRiskScore":986,"modelVersion":"1","created":"2026-07-02T08:20:38.814Z","id":""}'

curl -X POST "https://myurl/00000000-0000-6000-7000-000000000000/status" \
 -H "accept: application/json" \
 -H "Content-Type: application/json" \
 --data-binary '{"comment":"","fiFlaggedStatus":"FLAGGED","investigationAssessment":"WORTHY","investigationStatus":"COMPLETED","panRiskScore":986,"modelVersion":"1","created":"2026-07-02T08:20:38.814Z","id":""}'

However, I need the post status to be executed with different model versions (incremented by 1 integer between requests), so that it won't fail with a 409 conflict. For the initial value, it should add 1 on top of what is already in the database, which is retrieved as part of the get status api. This value is subsequently also part of the response for the post status request. However, as it can be seen by the curl requests, the two post requests are being executed both with the same model version and I'm not sure what to do to bypass that.

Thank you.

Update

Tried the below simulation using Atomic increment, but am still facing the issue as the requests don't seem to be executed in the expected order (post status with model version as 1 being called after post status with model version as 2)

import io.gatling.javaapi.core.ChainBuilder;
import io.gatling.javaapi.core.ScenarioBuilder;
import io.gatling.javaapi.core.Simulation;
import io.gatling.javaapi.http.HttpProtocolBuilder;

import java.util.concurrent.atomic.AtomicInteger;

import static io.gatling.javaapi.core.CoreDsl.*;
import static io.gatling.javaapi.http.HttpDsl.http;
import static io.gatling.javaapi.http.HttpDsl.status;

public class MySimulation extends Simulation {
    private static final String BASE_URL = "myurl";
    private static final String CID = "125283";
    private static final String ICAS = "31931,5655,20929,9529,31627";
    private static final String API_PATH = "/notifications/00000000-0000-6000-7000-000000000000/status";

    // Initialize atomic counter
    private static final AtomicInteger atomicModelVersion = new AtomicInteger();

    private final HttpProtocolBuilder httpProtocol = http
            .disableFollowRedirect()
            .baseUrl(BASE_URL)
            .acceptHeader("application/json");

    private final ScenarioBuilder mainScn = scenario("MainScenario")
            .exec(getStatus())
            .exec(pause(1))
            .exec(postStatus());

    private static ChainBuilder getStatus() {
        return exec(http("GetStatus")
                .get(API_PATH)
                .check(status().is(200))
                .check(jsonPath("$[0].id").notNull())
                .check(jsonPath("$[0].modelVersion").optional().saveAs("modelVersion"))
        ).exec(session -> {
            // Retrieve the modelVersion from the session
            String modelVersionStr = session.getString("modelVersion");
            // Set modelVersion to 1 if it is null or empty
            int initialModelVersion = (modelVersionStr == null || modelVersionStr.isEmpty()) ? 0 : Integer.parseInt(modelVersionStr);
            // Set the atomic counter to the initial model version
            atomicModelVersion.set(initialModelVersion);
            return session.set("modelVersion", String.valueOf(initialModelVersion));
        });
    }

    private static ChainBuilder postStatus() {
        return exec(session -> {
            // Get the current modelVersion
            int currentModelVersion = atomicModelVersion.get();
            // Update the session with the current modelVersion
            session = session.set("modelVersion", String.valueOf(currentModelVersion));
            // Increment the atomic counter for the next request
            atomicModelVersion.incrementAndGet();
            return session;
        }).exec(http("PostStatus")
                .post(API_PATH)
                .header("Content-Type", "application/json")
                .header("Accept", "application/json")
                .body(StringBody(session -> """
                {
                   "comment":"",
                   "fiFlaggedStatus":"FLAGGED",
                   "investigationAssessment":"WORTHY",
                   "investigationStatus":"COMPLETED",
                   "panRiskScore":986,
                   "modelVersion":"%s",
                   "id":""
                }
                """.formatted(session.getString("modelVersion"))))
                .check(status().is(200))
                .check(jsonPath("$.modelVersion").saveAs("modelVersion"))
                .check(jsonPath("$.id").notNull()));
    }

    {
        setUp(mainScn.injectOpen(atOnceUsers(2))).protocols(httpProtocol);
    }
}


curl -X GET "https://myurl/00000000-0000-6000-7000-000000000000/status" \
 -H "accept: application/json" \

curl -X GET "https://myurl/00000000-0000-6000-7000-000000000000/status" \
 -H "accept: application/json" \

curl -X POST "https://myurl/00000000-0000-6000-7000-000000000000/status" \
 -H "accept: application/json" \
 -H "Content-Type: application/json" \
 --data-binary '{"comment":"","fiFlaggedStatus":"FLAGGED","investigationAssessment":"WORTHY","investigationStatus":"COMPLETED","panRiskScore":986,"modelVersion":"2","id":""}'

curl -X POST "https://myurl/00000000-0000-6000-7000-000000000000/status" \
 -H "accept: application/json" \
 -H "Content-Type: application/json" \
 --data-binary '{"comment":"","fiFlaggedStatus":"FLAGGED","investigationAssessment":"WORTHY","investigationStatus":"COMPLETED","panRiskScore":986,"modelVersion":"1","id":""}'
2
  • 1
    I can't understand well the workflow you want to simulate and the problem you are having. Two users at the same time sending a get but then sending a post with a different value for an specific key and that value depends on the value that one of the users get? You have two endoints. One for GET that returns the modelVersion, one for POST where you want to send the new value of modelVersion which is the previous one you got one plus one. Maybe you can add a diagram or detail step by step what is the process you want to simulate Commented Jul 6, 2024 at 3:44
  • Hi, I only have one user token as it's extracted from a third part service. I want to execute my apis multiple times, but for each time the post status request is executed, it should trigger with a different model version, otherwise I get a 409 conflict error, that's what I'm trying to save it as a session and increment by one between each post requests. For the initial model version value, I get what in already in the database for that item, which I can retrieve as part of the response from the get status api. Commented Jul 7, 2024 at 4:51

2 Answers 2

1

I have doubts about flow and steps in your scenerio:

  1. mainScn.injectOpen(atOnceUsers(2)) It means that it will start two independent users and each of them will make two requests: GET and POST. So, it isn't as you've shown in the example with curl.
  2. You have to rethink your scenario and how it should load the system.
  3. If GET request always returns modelVersion 1, it doesn't make sense to extract this value.
  4. Try to define a global AtomicInteger and get its value each time for your POST request:
AtomicInteger counter = new AtomicInteger(1);
counter.getAndIncrement();
Sign up to request clarification or add additional context in comments.

4 Comments

Hi, I only have one user token as it's extracted from a third part service. I want to execute my apis multiple times, but for each time the post status request is executed, it should trigger with a different model version, otherwise I get a 409 conflict error, that's what I'm trying to save it as a session and increment by one between the request. For the initial model version value, I get what in already in the database for that item, which I can retrieve as part of the response from the get status api.
@FrancislainyCampos Use an AtomicInteger. Define it in a class as I showed before AtomicInteger counter = new AtomicInteger(1); . Then instead of extracting modelVersion from Session use counter.getAndIncrement();. In this way, each request will be with modelVersion + 1
Hi, I'm not sure I can start the model version with the value of 1 as it may be other than that when coming from the database if already initiated by someone else or previous tests. But maybe that's not what you mean? Sorry, if I'm misunderstanding it so maybe if you can show me with more of the simulation code? Gatling is a bit trick for me.
It doesn't matter it returns 1 or any another value. The main idea that you have to extract this value only once. Nothing push you to extract this value with Gatling) You can do it even manually. Or create some method with sends http request and then set value inside AtomicInteger. As I said before - re-think about your scenario.
0

We got the below simulation working for our case. Not sure if this is the ideal solution, but we are now using an atomic integer to count and update the increase for model version for each notification, then we set up a repeat block to force the post and get requests to be triggered one after the other and in the correct sequence. Prior to this, when using multiple atOnceUsers, such as atOnceUsers(3), we would get all the get requests done one after the other, and only then all the post requests also done one after the other, so the retrieval for the modal version wasn't properly updating and the 409 error kept happening. Due to this same error, we also tried to add the synchronization between the requests and more than one notification, to perhaps avoid some race condition between the values retrieved by the api and the proper saving to the db. As we are now using the repeat block, this may not be needed, but we have yet to assert that and test a bit more to be sure, but for the moment just sharing the working version we currently have.

import io.gatling.javaapi.core.ChainBuilder;
import io.gatling.javaapi.core.ScenarioBuilder;

import java.time.Duration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

import static com.mycompany.config.Constants.Notifications.NOTIFICATION_ID;
import static com.mycompany.config.Constants.Notifications.NOTIFICATION_ID_2;
import static io.gatling.javaapi.core.CoreDsl.*;
import static io.gatling.javaapi.http.HttpDsl.http;
import static io.gatling.javaapi.http.HttpDsl.status;

public class MySimulation extends BaseSimulation {

    private static final int REPETITION_AMOUNT = 10;
    private static final int PAUSE_TIME = 100;

    // Pre-existing hardcoded notification IDs
    private static final String[] NOTIFICATION_IDS = {NOTIFICATION_ID.toString(), NOTIFICATION_ID_2.toString()};

    // Map to store an AtomicInteger for each notification ID
    private static final Map<String, AtomicInteger> modelVersionMap = new ConcurrentHashMap<>();

    static {
        for (String id : NOTIFICATION_IDS) {
            modelVersionMap.put(id, new AtomicInteger(0));
        }
    }

    private final ScenarioBuilder mainScn = scenario("MainScenario")
            .exec(session -> {
                // Assign a notification ID from the array in a round-robin fashion
                long index = session.userId() % NOTIFICATION_IDS.length;
                return session.set("notificationId", NOTIFICATION_IDS[(int) index]);
            })
            .exec(addAuthTokenCookie())
            .repeat(REPETITION_AMOUNT, "counter").on(
                    exec(getStatus())
                            .pause(Duration.ofMillis(PAUSE_TIME))
                            .exec(postStatus())
            );

    private static ChainBuilder getStatus() {
        return exec(http("GetStatus")
                .get(session -> API_PATH + "/notifications/" + session.getString("notificationId") + "/status")
                .check(status().is(200))
                .check(jsonPath("$[0].modelVersion").optional().saveAs("modelVersion"))
        ).exec(session -> {
            String notificationId = session.getString("notificationId");
            String modelVersionStr = session.getString("modelVersion");
            int initialModelVersion = (modelVersionStr == null || modelVersionStr.isEmpty()) ? 0 : Integer.parseInt(modelVersionStr);
            synchronized (modelVersionMap) {
                modelVersionMap.get(notificationId).set(initialModelVersion);
            }
            return session.set("modelVersion", String.valueOf(initialModelVersion));
        });
    }

    private static ChainBuilder postStatus() {
        return exec(session -> {
            String notificationId = session.getString("notificationId");
            int currentModelVersion;
            synchronized (modelVersionMap) {
                currentModelVersion = modelVersionMap.get(notificationId).getAndIncrement();
            }
            session = session.set("modelVersion", String.valueOf(currentModelVersion));
            return session;
        }).exec(http("PostStatus")
                .post(session -> API_PATH + "/notifications/" + session.getString("notificationId") + "/status")
                .header("Content-Type", "application/json")
                .header("Accept", "application/json")
                .body(StringBody(session -> """
                        {
                           "comment":"",
                           "fiFlaggedStatus":"FLAGGED",
                           "investigationAssessment":"WORTHY",
                           "investigationStatus":"COMPLETED",
                           "panRiskScore":986,
                           "modelVersion":"%s",
                           "id":""
                        }
                        """.formatted(session.getString("modelVersion"))))
                .check(status().is(200))
                .check(jsonPath("$.modelVersion").saveAs("modelVersion"))
                .check(jsonPath("$.id").notNull())
        );
    }

    {

        SetUp testSetup;
        if (ENV.equals("stage")) {
            testSetup = setUp(loginScn.injectOpen(atOnceUsers(1)).andThen(mainScn.injectOpen(atOnceUsers(1))))
                    .protocols(httpProtocol);
        } else {
            testSetup = setUp(mainScn.injectOpen(rampUsers(1).during(Duration.ofSeconds(1))))
                    .protocols(httpProtocol);
        }

        addAssertions(testSetup);
    }
}

1 Comment

It's overkill to use ConcurrentHashMap and synchronized together. In fact it's overkill to use either as all you're doing is reading from it with multiple threads, but all the writes happen during initialization.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.