Moises Gamio
Moises Gamio Software Engineer. Comprehensive experience in all phases of the software development lifecycle for several economic sectors.

Break Out of Nested Loops in Java: Automating a Real REST API Client

Break Out of Nested Loops in Java: Automating a Real REST API Client

Testing an order API by hand means clicking through buyers, suppliers, customer numbers and articles until you find a combination that works. This program does it in seconds: it walks the API, finds the first valid combination, places the order and stops. Along the way it shows the right way to escape four nested loops in Java.

We will create an API Client to automate the creation of an Order based on a combination of Buyers, Suppliers, Customer Numbers, and Articles.

You can find the API specification in the following link.

Order API.

A note for 2026: this client uses OAuth2RestTemplate from the Spring Security OAuth project, which is deprecated, and the password grant, which Spring Security 7.0 removed. It still works in projects on those older versions. For new code, see the follow-up: Replace OAuth2RestTemplate with RestClient. The loop logic in this post stays exactly the same.

Test Program

Once users are logged in, they can access a pool of Buyers. A Buyer can order articles from different Suppliers. But first, every Buyer must be identified by a unique customer number to proceed with creating orders.

Each call needs the IDs returned by the previous one, because they are required parameters:

Step Call Needs
1 GET /api/v2/buyers nothing
2 GET /api/v2/suppliers buyerId
3 GET /api/v2/customerNumbers buyerId, supplierId
4 GET /api/v2/articles buyerId, supplierId, customerNumberId
5 POST /api/v2/orders all of the above + one article


Swagger UI: GET /api/v2/articles requires buyerId, supplierId and customerNumberId

Once the program finds at least one article by navigating through these entities, it builds an order, sends the request, and stops.

What we need to use in the Program

continue statement

The continue statement skips the rest of the current iteration and moves on to the next one. It works in all Java loops: for, while, and do-while.

break keyword

The break keyword terminates a loop or switch statement early. Control moves to the statement immediately after the enclosing loop or switch.

OAuth2RestTemplate

A RestTemplate that makes OAuth2-authenticated REST requests with the credentials of the provided resource.

We define a boolean variable to control when an article is found.

A test helper today, a production tool tomorrow. Small design choices like these decide whether code stays readable as it grows:

codersite

Here is the program code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
public final class CreateOrderRandomTest {

  private static final Logger logger = LoggerFactory.getLogger(CreateOrderRandomTest.class);

  private static ResourceOwnerPasswordResourceDetails resourceDetails;
  private static OAuth2RestTemplate restTemplate;
  private static HttpHeaders headers;

  static String apiHost = "http://localhost:41231/cloud";
  static String tokenUri = apiHost + "/oauth/token";
  static String url = apiHost + "/api/v2/";
  static String clientId = System.getenv("API_CLIENT_ID");
  static String secret = System.getenv("API_CLIENT_SECRET");

  public static void main(String[] args) {

    headers = new HttpHeaders();
    restTemplate = buildRestTemplate(clientId, secret);

    try {

      String urlRequest = url + "buyers";

      HttpEntity<String> entity = new HttpEntity<String>(headers);
      ResponseEntity<List<Buyer>> listOfBuyersResponse = restTemplate.exchange(urlRequest,
          HttpMethod.GET, entity, new ParameterizedTypeReference<List<Buyer>>(){});

      List<Buyer> listOfBuyers = listOfBuyersResponse.getBody();

      boolean atLeastOneCustomerNumberWithArticles = false;

      for (Buyer buyer : listOfBuyers) {

        urlRequest = url + "suppliers";

        ResponseEntity<List<Supplier>> listOfSuppliersResponse = restTemplate.exchange(urlRequest + "?buyerId=" + buyer.getBuyerId(),
            HttpMethod.GET, entity, new ParameterizedTypeReference<List<Supplier>>(){});

        List<Supplier> listOfSuppliers = listOfSuppliersResponse.getBody();

        if (listOfSuppliers.size() == 0)
          continue;

        for (Supplier supplier : listOfSuppliers) {

          urlRequest = url + "customerNumbers";

          ResponseEntity<List<CustomerNumber>> listOfCustomerNumbersResponse = restTemplate.exchange(
              urlRequest + "?buyerId=" + buyer.getBuyerId() + "&supplierId=" + supplier.getSupplierId(),
              HttpMethod.GET, entity, new ParameterizedTypeReference<List<CustomerNumber>>(){});

          List<CustomerNumber> listOfCustomerNumbers = listOfCustomerNumbersResponse.getBody();

          if (listOfCustomerNumbers.size() == 0)
            continue;

          for (CustomerNumber customerNumber : listOfCustomerNumbers) {

            urlRequest = url + "articles";

            ResponseEntity<List<Article>> listOfArticlesResponse = restTemplate.exchange(urlRequest + "?buyerId=" + buyer.getBuyerId() +
                "&supplierId=" + supplier.getSupplierId() + "&customerNumberId=" + customerNumber.getCustomerNumberId(),
                HttpMethod.GET, entity, new ParameterizedTypeReference<List<Article>>(){});

            List<Article> listOfArticles = listOfArticlesResponse.getBody();

            if (listOfArticles.size() == 0)
              continue;

            for (Article article : listOfArticles) {

              urlRequest = url + "orders";

              Order order = buildOrder(buyer, supplier, customerNumber, article);
              HttpEntity<Order> orderEntity = new HttpEntity<Order>(order, headers);
              ResponseEntity<Order> responseEntity = restTemplate.exchange(urlRequest, HttpMethod.POST, orderEntity, Order.class);

              Order orderResponse = responseEntity.getBody();
              logger.info(responseEntity.getStatusCode().toString());
              break;
            }

            atLeastOneCustomerNumberWithArticles = true;
            logger.info("buyerId = " + buyer.getBuyerId() + ", supplierId = " + supplier.getSupplierId() +
                ", customerNumberId = " + customerNumber.getCustomerNumberId());
            break;

          } //end-for listOfCustomerNumbers

          if (atLeastOneCustomerNumberWithArticles == true)
            break;

        } //end-for listOfSuppliers

        if (atLeastOneCustomerNumberWithArticles == true)
          break;

      } //end-for listOfBuyers

    } catch (HttpClientErrorException e) {
      //code omitted for brevity
    } catch (Exception e) {
      logger.error("error:  " + e.getMessage());
    }
  }

  private static Order buildOrder(Buyer buyer, Supplier supplier, CustomerNumber customerNumber, Article article) {
    Order order = new Order();
    order.setBuyerId(buyer.getBuyerId());
    order.setSupplierId(supplier.getSupplierId());
    //code omitted for brevity

    return order;
  }

  private static OAuth2RestTemplate buildRestTemplate(String clientId, String secret) {
    resourceDetails = new ResourceOwnerPasswordResourceDetails();
    //code omitted for brevity

    DefaultOAuth2ClientContext clientContext = new DefaultOAuth2ClientContext();
    restTemplate = new OAuth2RestTemplate(resourceDetails, clientContext);
    restTemplate.setMessageConverters(asList(new MappingJackson2HttpMessageConverter()));

    return restTemplate;
  }
}

The client ID and secret come from environment variables. Never hard-code credentials in source code: they end up in version control.

Why does the program need continue? If a customer number has no articles, continue skips to the next one. Without it, the program would set atLeastOneCustomerNumberWithArticles to true and stop before placing any order.

Refactor: One Labeled Break Instead of Three Flags

The flag variable and the three if (... == true) break; checks exist only to escape four nested loops. Java has a cleaner tool for exactly this: a labeled break.

1
2
3
4
5
6
7
8
9
10
11
12
search:
for (Buyer buyer : getBuyers()) {
  for (Supplier supplier : getSuppliers(buyer)) {
    for (CustomerNumber customerNumber : getCustomerNumbers(buyer, supplier)) {
      List<Article> articles = getArticles(buyer, supplier, customerNumber);
      if (articles.isEmpty())
        continue;
      createOrder(buyer, supplier, customerNumber, articles.get(0));
      break search;   // leaves all three loops at once
    }
  }
}

The get… helpers wrap the restTemplate.exchange calls shown above. The flag variable and the three if checks disappear, and an empty list simply means the inner loop never runs.

Escaping nested loops, early exits and API-driven test data are classic interview topics. Practice them on real questions:

java interview

“If this article helped you, help me create the next one.”

☕ Buy me a coffee