Step-by-Step Guide to Consume CubeMaster API with Java Spring Boot

Learn how to integrate the CubeMaster API (https://api.cubemaster.net/loads) using Java Spring Boot and RestTemplate.

To use the CubeMaster API, you need an API key (TokenID) for authentication. Here's how to get started:

  1. Visit the CubeMaster website: https://cubemaster.net.
  2. Locate the "Sign In" option (typically found in the top-right corner).
  3. Fill out the registration form with your details (e.g., name, email, password, company information).
  4. After signing up, log in to your account dashboard.
  5. Navigate to the "Settings" - "Integration" section to generate your API key (TokenID).
  6. Generate an API key. Once generated, you’ll receive a unique TokenID (e.g., abc123xyz789). Copy this key and store it securely, as it will be used in the HTTP headers of your API requests.
  7. Copy the TokenID and store it securely.

Note: The TokenID will be used in the HTTP headers of your POST request for authentication.

Create a Spring Boot project to handle API requests using RestTemplate:

Prerequisites
  • Java 17+ installed
  • Maven or Gradle for dependency management
  • An IDE (e.g., IntelliJ IDEA, Eclipse)
Steps
  1. Create a new Spring Boot project using Spring Initializr (start.spring.io):
    • Project: Maven
    • Language: Java
    • Spring Boot: Latest stable version
    • Dependencies: Spring Web
  2. Add the following dependencies to your pom.xml:
  3. <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>
    </dependencies>
  4. Configure your application.properties file in src/main/resources to store the API key and endpoint:
    cubemaster.api.url=https://api.cubemaster.net/loads
    cubemaster.api.token=your_token_id_here

UI Element: Spring Boot’s embedded Tomcat server provides a lightweight environment to test API calls without needing a separate UI framework initially.

A RESTful API (Representational State Transfer) is a way for applications to communicate over the internet using standard HTTP methods:

  • GET: Retrieve data (e.g., fetch a list of loads).
  • POST: Send data to create a resource (e.g., build a load in CubeMaster).
  • PUT: Update an existing resource.
  • DELETE: Remove a resource.

The CubeMaster API uses the POST method for the /loads endpoint to create a new load. RESTful APIs typically:

  • Use JSON or XML for data exchange.
  • Return HTTP status codes (e.g., 200 for success, 400 for bad request).
  • Require authentication (e.g., TokenID in headers).

In this guide, we’ll send a JSON request to https://api.cubemaster.net/loads and receive a JSON response with load details.

Assume your customer’s legacy database stores order/shipment data (e.g., items, dimensions, quantities). Use Spring Data JPA to fetch this data:

Example Entity
package com.example.model;

import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class OrderItem {
    @Id
    private String name;
    private double length;
    private double width;
    private double height;
    private double weight;
    private int quantity;
    private String color;

    // Getters and Setters
}
Repository
package com.example.repository;

import com.example.model.OrderItem;
import org.springframework.data.jpa.repository.JpaRepository;

public interface OrderItemRepository extends JpaRepository<OrderItem, String> {
}
Service to Fetch Data
package com.example.service;

import com.example.model.OrderItem;
import com.example.repository.OrderItemRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class OrderService {
    @Autowired
    private OrderItemRepository repository;

    public List<OrderItem> getOrderItems() {
        return repository.findAll();
    }
}

This data will be mapped to the CubeMaster API’s Cargoes array in the request JSON.

Define Java classes to represent the request JSON structure:

Cargo Model
package com.example.model;

public class Cargo {
    private String name;
    private double length;
    private double width;
    private double height;
    private double weight;
    private String orientationsAllowed;
    private boolean turnAllowedOnFloor;
    private int qty;
    private String colorKnownName;

    // Getters and Setters
}
Container Model
package com.example.model;

public class Container {
    private String vehicleType;
    private String name;
    private double length;
    private double width;
    private double height;
    private String colorKnownName;

    // Getters and Setters
}
Rules Model
package com.example.model;

public class Rules {
    private boolean isWeightLimited;
    private boolean isSequenceUsed;
    private String fillDirection;
    private String calculationType;

    // Getters and Setters
}
Load Request Model
package com.example.model;

import java.util.List;

public class LoadRequest {
    private String title;
    private String description;
    private List<Cargo> cargoes;
    private List<Container> containers;
    private Rules rules;

    // Getters and Setters
}

UI Element: These models act as the data structure for your backend logic, not requiring direct UI components but enabling JSON serialization via Jackson.

Use RestTemplate to send the POST request with TokenID in headers:

Service Class
package com.example.service;

import com.example.model.LoadRequest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class CubeMasterService {
    private final RestTemplate restTemplate;

    @Value("${cubemaster.api.url}")
    private String apiUrl;

    @Value("${cubemaster.api.token}")
    private String tokenId;

    public CubeMasterService(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    public ResponseEntity<String> createLoad(LoadRequest request) {
        HttpHeaders headers = new HttpHeaders();
        headers.set("TokenID", tokenId);
        headers.set("Content-Type", "application/json");

        HttpEntity<LoadRequest> entity = new HttpEntity<>(request, headers);
        return restTemplate.exchange(apiUrl, HttpMethod.POST, entity, String.class);
    }
}
Controller to Trigger Request
package com.example.controller;

import com.example.model.LoadRequest;
import com.example.service.CubeMasterService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class LoadController {
    @Autowired
    private CubeMasterService service;

    @PostMapping("/create-load")
    public ResponseEntity<String> createLoad(@RequestBody LoadRequest request) {
        return service.createLoad(request);
    }
}
Request JSON
{
    "Title": "New Mixed Truck Load",
    "Description": "Hello Web API",
    "Cargoes": [
        {
            "Name": "ITEM001",
            "Length": 72,
            "Width": 30,
            "Height": 75,
            "Weight": 1002.45,
            "OrientationsAllowed": "OrientationsAll",
            "TurnAllowedOnFloor": false,
            "Qty": 16,
            "ColorKnownName": "Brown"
        },
        {
            "Name": "ITEM002",
            "Length": 27.31,
            "Width": 37.5,
            "Height": 76.67,
            "Weight": 521.45,
            "OrientationsAllowed": "OrientationsAll",
            "TurnAllowedOnFloor": false,
            "Qty": 28,
            "ColorKnownName": "Aqua"
        },
        {
            "Name": "SKU0005",
            "Length": 27.31,
            "Width": 9.5,
            "Height": 75.67,
            "Weight": 501.45,
            "OrientationsAllowed": "OrientationsAll",
            "TurnAllowedOnFloor": true,
            "Qty": 24,
            "ColorKnownName": "Beige"
        },
        {
            "Name": "SKU0005",
            "Qty": 23
        },
        {
            "Name": "SKU0008",
            "Qty": 34
        }
    ],
    "Containers": [
        {
            "VehicleType": "Dry",
            "Name": "53FT-Intermodal",
            "Length": 630,
            "Width": 98,
            "Height": 106,
            "ColorKnownName": "Blue"
        }
    ],
    "Rules": {
        "IsWeightLimited": true,
        "IsSequenceUsed": false,
        "FillDirection": "FrontToRear",
        "CalculationType": "MixLoad"
    }
}

UI Element: The @RestController exposes an endpoint (/create-load) that can be triggered via tools like Postman or a simple frontend form.

Process the API response to extract useful data (e.g., load summary, graphics):

Response Model (Simplified)
package com.example.model;

public class LoadResponse {
    private String status;
    private String message;
    private String calculationError;
    private Document document;
    private LoadSummary loadSummary;
    private List<FilledContainer> filledContainers;

    // Getters and Setters

    public static class Document {
        private String title;
        private String description;
        private double calculationTimeInSeconds;
        // Other fields, getters, setters
    }

    public static class LoadSummary {
        private int cargoesLoaded;
        private int piecesLoaded;
        private double volumeLoaded;
        private double weightLoaded;
        // Other fields, getters, setters
    }

    public static class FilledContainer {
        private String name;
        private LoadSummary loadSummary;
        private Graphics graphics;
        // Other fields, getters, setters

        public static class Graphics {
            private Images images;
            // Getters, setters

            public static class Images {
                private String path3DDiagram;
                private String pathComposite;
                // Getters, setters
            }
        }
    }
}
Update Service to Parse Response
public LoadResponse createLoad(LoadRequest request) {
    HttpHeaders headers = new HttpHeaders();
    headers.set("TokenID", tokenId);
    headers.set("Content-Type", "application/json");

    HttpEntity<LoadRequest> entity = new HttpEntity<>(request, headers);
    ResponseEntity<LoadResponse> response = restTemplate.exchange(apiUrl, HttpMethod.POST, entity, LoadResponse.class);
    
    if (response.getStatusCode().is2xxSuccessful()) {
        LoadResponse loadResponse = response.getBody();
        System.out.println("Status: " + loadResponse.getStatus());
        System.out.println("Message: " + loadResponse.getMessage());
        System.out.println("Volume Loaded: " + loadResponse.getLoadSummary().getVolumeLoaded());
        return loadResponse;
    } else {
        throw new RuntimeException("API call failed: " + response.getStatusCode());
    }
}
Response JSON
{
    "status": "succeed",
    "message": "Engine created. 5 cargoes. 1 empty containers. Calculation started. Calculation ended. The load built successfully. The load saved to the cloud database.",
    "calculationError": "InvalidCargoSize",
    "document": {
        "title": "New Mixed Truck Load",
        "description": "Hello Web API",
        "calculationTimeInSeconds": 0.6152743,
        "createdBy": "CHANG@LOGEN.CO.KR",
        "createdAt": "2023-02-11T01:17:01.7392204+09:00"
    },
    "loadSummary": {
        "cargoesLoaded": 68,
        "piecesLoaded": 68,
        "volumeLoaded": 5261723.4606,
        "weightLoaded": 42674.59999999999
    },
    "filledContainers": [
        {
            "name": "#1 53FT-Intermodal",
            "loadSummary": {
                "cargoesLoaded": 68,
                "volumeLoaded": 5261723.4606,
                "weightLoaded": 42674.59999999999
            },
            "graphics": {
                "images": {
                    "path3DDiagram": "https://api.cubemaster.net/runtimes/b28413ca_51ed_44c9_b92e_13147363fd61.PNG",
                    "pathComposite": "https://api.cubemaster.net/runtimes/7eb09974_2f1d_41bc_9371_2002658dce07.PNG"
                }
            }
        }
    ]
}

UI Element: You can extend this to return the response to a frontend (e.g., displaying path3DDiagram images in a web page).

Ensure your API integration is robust by monitoring and debugging:

  1. Logging: Add SLF4J/Logback for detailed logs:
    package com.example.service;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Service;
    
    @Service
    public class CubeMasterService {
        private static final Logger logger = LoggerFactory.getLogger(CubeMasterService.class);
    
        public LoadResponse createLoad(LoadRequest request) {
            logger.info("Sending request to CubeMaster API: {}", request);
            // ... existing code ...
            logger.info("Received response: {}", response.getBody());
            return response.getBody();
        }
    }
  2. Exception Handling: Catch and log errors:
    try {
        return restTemplate.exchange(apiUrl, HttpMethod.POST, entity, LoadResponse.class);
    } catch (Exception e) {
        logger.error("Error calling CubeMaster API: {}", e.getMessage());
        throw new RuntimeException("API call failed", e);
    }
  3. Testing: Use Postman to manually test the /create-load endpoint with the request JSON.
  4. Debugging: Set breakpoints in your IDE (e.g., IntelliJ) at the restTemplate.exchange call to inspect request/response data.

UI Element: Logs can be viewed in the console or integrated into a monitoring tool like ELK Stack for a production environment.