Shipping and Logistics: Integrating UPS APIs with Java Applications

The UPS Developer Kit provides comprehensive APIs for shipping, tracking, rates, and address validation. Java applications can leverage these APIs to build powerful logistics solutions, e-commerce shipping integrations, and supply chain management systems. Let's explore how to implement UPS API integrations in Java.

Why UPS APIs for Java Applications?

Key integration scenarios:

  • E-commerce Shipping - Calculate rates and create shipments
  • Order Fulfillment - Automate shipping label generation
  • Package Tracking - Real-time shipment tracking
  • Address Validation - Verify shipping addresses
  • Supply Chain Visibility - Monitor logistics operations

UPS API Configuration and Authentication

1. UPS Configuration

@Configuration
@ConfigurationProperties(prefix = "ups")
@Data
public class UPSConfig {
private String clientId;
private String clientSecret;
private String baseUrl = "https://wwwcie.ups.com"; // Sandbox
// private String baseUrl = "https://onlinetools.ups.com"; // Production
private String accountNumber;
private String userId;
private String password;
private int timeoutSeconds = 30;
public String getAccessTokenUrl() {
return baseUrl + "/security/v1/oauth/token";
}
public String getShippingUrl() {
return baseUrl + "/api/shipments/v1/ship";
}
public String getRatingUrl() {
return baseUrl + "/api/rating/v1/Shop";
}
public String getTrackingUrl() {
return baseUrl + "/api/track/v1/details";
}
public String getAddressValidationUrl() {
return baseUrl + "/api/addressvalidation/v1/1";
}
}

2. OAuth Authentication Service

@Service
@Slf4j
public class UPSAuthService {
private final UPSConfig config;
private final OkHttpClient httpClient;
private final ObjectMapper objectMapper;
public UPSAuthService(UPSConfig config) {
this.config = config;
this.httpClient = new OkHttpClient();
this.objectMapper = new ObjectMapper();
}
public UPSAccessToken getAccessToken() throws UPSApiException {
try {
String credentials = Credentials.basic(config.getClientId(), config.getClientSecret());
FormBody formBody = new FormBody.Builder()
.add("grant_type", "client_credentials")
.build();
Request request = new Request.Builder()
.url(config.getAccessTokenUrl())
.header("Authorization", credentials)
.header("Content-Type", "application/x-www-form-urlencoded")
.post(formBody)
.build();
Response response = httpClient.newCall(request).execute();
if (!response.isSuccessful()) {
throw new UPSApiException("Failed to get access token: " + response.code());
}
String responseBody = response.body().string();
UPSAccessToken token = objectMapper.readValue(responseBody, UPSAccessToken.class);
token.setIssuedAt(Instant.now());
log.info("UPS access token obtained, expires in: {} seconds", token.getExpiresIn());
return token;
} catch (Exception e) {
throw new UPSApiException("UPS authentication failed", e);
}
}
@Data
public static class UPSAccessToken {
private String accessToken;
private String tokenType;
private Integer expiresIn;
private String issuedAt;
private String scope;
private Instant issuedAtInstant;
public void setIssuedAt(Instant issuedAt) {
this.issuedAtInstant = issuedAt;
}
public boolean isExpired() {
if (issuedAtInstant == null || expiresIn == null) {
return true;
}
return issuedAtInstant.plusSeconds(expiresIn - 300).isBefore(Instant.now()); // 5 min buffer
}
public Instant getExpiryTime() {
return issuedAtInstant != null ? issuedAtInstant.plusSeconds(expiresIn) : null;
}
}
}

Core UPS API Client

1. Base UPS API Client

@Service
@Slf4j
public class UPSApiClient {
private final UPSConfig config;
private final UPSAuthService authService;
private final OkHttpClient httpClient;
private final ObjectMapper objectMapper;
private UPSAuthService.UPSAccessToken currentToken;
public UPSApiClient(UPSConfig config, UPSAuthService authService) {
this.config = config;
this.authService = authService;
this.objectMapper = new ObjectMapper();
this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
this.httpClient = new OkHttpClient.Builder()
.addInterceptor(new UPSAuthInterceptor())
.addInterceptor(new HttpLoggingInterceptor(log::info).setLevel(HttpLoggingInterceptor.Level.BASIC))
.connectTimeout(config.getTimeoutSeconds(), TimeUnit.SECONDS)
.readTimeout(config.getTimeoutSeconds(), TimeUnit.SECONDS)
.build();
}
// Generic API call with authentication
protected <T> T executePost(String endpoint, Object request, Class<T> responseType) throws UPSApiException {
ensureValidToken();
try {
String jsonBody = objectMapper.writeValueAsString(request);
Request httpRequest = new Request.Builder()
.url(endpoint)
.header("Authorization", "Bearer " + currentToken.getAccessToken())
.header("Content-Type", "application/json")
.header("transId", UUID.randomUUID().toString())
.header("transactionSrc", "your-application-name")
.post(RequestBody.create(jsonBody, MediaType.parse("application/json")))
.build();
Response response = httpClient.newCall(httpRequest).execute();
if (!response.isSuccessful()) {
handleErrorResponse(response, endpoint);
}
String responseBody = response.body().string();
return objectMapper.readValue(responseBody, responseType);
} catch (Exception e) {
throw new UPSApiException("UPS API request failed: " + endpoint, e);
}
}
protected <T> T executeGet(String endpoint, Class<T> responseType) throws UPSApiException {
ensureValidToken();
try {
Request httpRequest = new Request.Builder()
.url(endpoint)
.header("Authorization", "Bearer " + currentToken.getAccessToken())
.header("Content-Type", "application/json")
.get()
.build();
Response response = httpClient.newCall(httpRequest).execute();
if (!response.isSuccessful()) {
handleErrorResponse(response, endpoint);
}
String responseBody = response.body().string();
return objectMapper.readValue(responseBody, responseType);
} catch (Exception e) {
throw new UPSApiException("UPS API request failed: " + endpoint, e);
}
}
private void ensureValidToken() throws UPSApiException {
if (currentToken == null || currentToken.isExpired()) {
log.info("UPS access token expired or missing, obtaining new token");
currentToken = authService.getAccessToken();
}
}
private void handleErrorResponse(Response response, String endpoint) throws IOException, UPSApiException {
int code = response.code();
String body = response.body() != null ? response.body().string() : "No response body";
log.error("UPS API error - Status: {}, Endpoint: {}, Body: {}", code, endpoint, body);
try {
UPSError error = objectMapper.readValue(body, UPSError.class);
throw new UPSApiException("UPS API Error: " + error.getResponse().getErrors().get(0).getMessage(), code);
} catch (Exception e) {
throw new UPSApiException("UPS API error - Status: " + code + ", Endpoint: " + endpoint, code);
}
}
// Authentication interceptor for automatic token refresh
private class UPSAuthInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
try {
ensureValidToken();
} catch (UPSApiException e) {
throw new IOException("Failed to obtain UPS access token", e);
}
Request authenticatedRequest = originalRequest.newBuilder()
.header("Authorization", "Bearer " + currentToken.getAccessToken())
.build();
return chain.proceed(authenticatedRequest);
}
}
}

Shipping Service

1. Shipment Creation and Management

@Service
@Slf4j
public class UPSShipmentService extends UPSApiClient {
public UPSShipmentService(UPSConfig config, UPSAuthService authService) {
super(config, authService);
}
public ShipmentResponse createShipment(ShipmentRequest shipmentRequest) throws UPSApiException {
return executePost(config.getShippingUrl(), shipmentRequest, ShipmentResponse.class);
}
public VoidResponse voidShipment(String shipmentIdentificationNumber) throws UPSApiException {
VoidShipmentRequest voidRequest = new VoidShipmentRequest();
voidRequest.setVoidShipment(createVoidShipment(shipmentIdentificationNumber));
String endpoint = config.getShippingUrl().replace("/ship", "/void");
return executePost(endpoint, voidRequest, VoidResponse.class);
}
public ShipmentResponse createShipmentWithDetails(Address fromAddress, Address toAddress, 
PackageDetails packageDetails, 
ShipmentOptions options) throws UPSApiException {
ShipmentRequest request = createShipmentRequest(fromAddress, toAddress, packageDetails, options);
return createShipment(request);
}
public byte[] getShippingLabel(String shipmentIdentificationNumber) throws UPSApiException {
try {
// First get shipment details
ShipmentResponse shipment = getShipment(shipmentIdentificationNumber);
// Extract label URL from response
String labelUrl = shipment.getShipmentResponse().getShipmentResults()
.getPackageResults().get(0).getShippingLabel().getGraphicImage();
// Download label image
return downloadLabelImage(labelUrl);
} catch (Exception e) {
throw new UPSApiException("Failed to get shipping label", e);
}
}
private ShipmentRequest createShipmentRequest(Address fromAddress, Address toAddress,
PackageDetails packageDetails, ShipmentOptions options) {
ShipmentRequest request = new ShipmentRequest();
Shipment shipment = new Shipment();
// Set shipper information
shipment.setShipper(createParty(fromAddress, options.getShipperName(), options.getShipperNumber()));
// Set ship to information
shipment.setShipTo(createParty(toAddress, options.getReceiverName(), null));
// Set service
Service service = new Service();
service.setCode(options.getServiceCode());
service.setDescription(options.getServiceDescription());
shipment.setService(service);
// Set package
shipment.setPackage(createPackage(packageDetails));
// Set payment information
shipment.setPaymentInformation(createPaymentInformation());
request.setShipment(shipment);
// Set request options
Request requestOptions = new Request();
requestOptions.setRequestOption("validate");
request.setRequest(requestOptions);
return request;
}
private Party createParty(Address address, String name, String shipperNumber) {
Party party = new Party();
party.setName(name);
party.setAddress(address);
if (shipperNumber != null) {
party.setShipperNumber(shipperNumber);
}
return party;
}
private Package createPackage(PackageDetails details) {
Package pkg = new Package();
// Packaging type
PackagingType packagingType = new PackagingType();
packagingType.setCode("02"); // Customer supplied package
packagingType.setDescription("Package");
pkg.setPackagingType(packagingType);
// Package weight
PackageWeight weight = new PackageWeight();
weight.setWeight(String.valueOf(details.getWeightLbs()));
UnitOfMeasurement uom = new UnitOfMeasurement();
uom.setCode("LBS");
weight.setUnitOfMeasurement(uom);
pkg.setPackageWeight(weight);
// Package dimensions
Dimensions dimensions = new Dimensions();
dimensions.setLength(String.valueOf(details.getLengthInches()));
dimensions.setWidth(String.valueOf(details.getWidthInches()));
dimensions.setHeight(String.valueOf(details.getHeightInches()));
UnitOfMeasurement dimUom = new UnitOfMeasurement();
dimUom.setCode("IN");
dimensions.setUnitOfMeasurement(dimUom);
pkg.setDimensions(dimensions);
return pkg;
}
private PaymentInformation createPaymentInformation() {
PaymentInformation paymentInfo = new PaymentInformation();
ShipmentCharge shipmentCharge = new ShipmentCharge();
// Bill shipper
BillShipper billShipper = new BillShipper();
billShipper.setAccountNumber(config.getAccountNumber());
shipmentCharge.setBillShipper(billShipper);
// Charge type
shipmentCharge.setType("01"); // Transportation
paymentInfo.setShipmentCharge(new ShipmentCharge[]{shipmentCharge});
return paymentInfo;
}
private VoidShipment createVoidShipment(String shipmentIdentificationNumber) {
VoidShipment voidShipment = new VoidShipment();
voidShipment.setShipmentIdentificationNumber(shipmentIdentificationNumber);
return voidShipment;
}
private byte[] downloadLabelImage(String labelUrl) throws Exception {
Request request = new Request.Builder()
.url(labelUrl)
.get()
.build();
Response response = httpClient.newCall(request).execute();
if (!response.isSuccessful()) {
throw new UPSApiException("Failed to download label image: " + response.code());
}
return response.body().bytes();
}
}

Rating Service

1. Shipping Rates and Service Options

@Service
@Slf4j
public class UPSRatingService extends UPSApiClient {
public UPSRatingService(UPSConfig config, UPSAuthService authService) {
super(config, authService);
}
public RatingResponse getShippingRates(RatingRequest ratingRequest) throws UPSApiException {
return executePost(config.getRatingUrl(), ratingRequest, RatingResponse.class);
}
public List<ShippingOption> getAvailableServices(Address fromAddress, Address toAddress, 
PackageDetails packageDetails) throws UPSApiException {
RatingRequest ratingRequest = createRatingRequest(fromAddress, toAddress, packageDetails);
RatingResponse response = getShippingRates(ratingRequest);
return extractShippingOptions(response);
}
public ShippingOption getCheapestService(Address fromAddress, Address toAddress,
PackageDetails packageDetails) throws UPSApiException {
List<ShippingOption> options = getAvailableServices(fromAddress, toAddress, packageDetails);
return options.stream()
.min(Comparator.comparing(ShippingOption::getTotalCharges))
.orElseThrow(() -> new UPSApiException("No shipping options available"));
}
public ShippingOption getFastestService(Address fromAddress, Address toAddress,
PackageDetails packageDetails) throws UPSApiException {
List<ShippingOption> options = getAvailableServices(fromAddress, toAddress, packageDetails);
return options.stream()
.min(Comparator.comparing(ShippingOption::getTransitDays))
.orElseThrow(() -> new UPSApiException("No shipping options available"));
}
private RatingRequest createRatingRequest(Address fromAddress, Address toAddress,
PackageDetails packageDetails) {
RatingRequest request = new RatingRequest();
Request requestOptions = new Request();
requestOptions.setRequestOption("Shop");
request.setRequest(requestOptions);
Shipment shipment = new Shipment();
// Shipper address
shipment.setShipper(createAddressParty(fromAddress));
// Ship to address
shipment.setShipTo(createAddressParty(toAddress));
// Package
shipment.setPackage(createRatePackage(packageDetails));
request.setShipment(shipment);
return request;
}
private List<ShippingOption> extractShippingOptions(RatingResponse response) {
List<ShippingOption> options = new ArrayList<>();
if (response.getRateResponse() != null && 
response.getRateResponse().getRatedShipment() != null) {
for (RatedShipment ratedShipment : response.getRateResponse().getRatedShipment()) {
ShippingOption option = new ShippingOption();
option.setServiceCode(ratedShipment.getService().getCode());
option.setServiceName(ratedShipment.getService().getDescription());
option.setTotalCharges(new BigDecimal(ratedShipment.getTotalCharges().getMonetaryValue()));
option.setCurrency(ratedShipment.getTotalCharges().getCurrencyCode());
option.setTransitDays(ratedShipment.getGuaranteedDelivery().getBusinessDaysInTransit());
option.setDeliveryDate(ratedShipment.getGuaranteedDelivery().getDeliveryByDate());
options.add(option);
}
}
return options;
}
}

Tracking Service

1. Package Tracking and Status

@Service
@Slf4j
public class UPSTrackingService extends UPSApiClient {
public UPSTrackingService(UPSConfig config, UPSAuthService authService) {
super(config, authService);
}
public TrackingResponse trackPackage(String trackingNumber) throws UPSApiException {
String endpoint = config.getTrackingUrl() + "/" + trackingNumber;
return executeGet(endpoint, TrackingResponse.class);
}
public TrackingResponse trackPackageWithDetail(String trackingNumber) throws UPSApiException {
String endpoint = config.getTrackingUrl() + "/" + trackingNumber + "?locale=en_US";
return executeGet(endpoint, TrackingResponse.class);
}
public List<PackageStatus> getActiveShipments(List<String> trackingNumbers) throws UPSApiException {
List<PackageStatus> statuses = new ArrayList<>();
for (String trackingNumber : trackingNumbers) {
try {
TrackingResponse response = trackPackage(trackingNumber);
PackageStatus status = extractPackageStatus(response, trackingNumber);
statuses.add(status);
} catch (UPSApiException e) {
log.warn("Failed to track package: {}", trackingNumber, e);
// Continue with other packages
}
}
return statuses;
}
public Map<String, PackageStatus> getShipmentStatusMap(List<String> trackingNumbers) throws UPSApiException {
return getActiveShipments(trackingNumbers).stream()
.collect(Collectors.toMap(PackageStatus::getTrackingNumber, Function.identity()));
}
public List<PackageStatus> findDelayedShipments(List<String> trackingNumbers) throws UPSApiException {
return getActiveShipments(trackingNumbers).stream()
.filter(status -> "DELAYED".equals(status.getStatus()) || status.isDelayed())
.collect(Collectors.toList());
}
public List<PackageStatus> findDeliveredShipments(List<String> trackingNumbers) throws UPSApiException {
return getActiveShipments(trackingNumbers).stream()
.filter(PackageStatus::isDelivered)
.collect(Collectors.toList());
}
private PackageStatus extractPackageStatus(TrackingResponse response, String trackingNumber) {
PackageStatus status = new PackageStatus();
status.setTrackingNumber(trackingNumber);
if (response.getTrackResponse() != null && 
!response.getTrackResponse().getShipment().isEmpty()) {
Shipment shipment = response.getTrackResponse().getShipment().get(0);
status.setService(shipment.getService().getDescription());
if (!shipment.getPackage().isEmpty()) {
Package pkg = shipment.getPackage().get(0);
status.setStatus(pkg.getActivity().get(0).getStatus().getDescription());
status.setLastUpdate(pkg.getActivity().get(0).getDate() + " " + pkg.getActivity().get(0).getTime());
status.setDeliveryDate(pkg.getDeliveryDate());
status.setSignedBy(pkg.getSignedForByName());
}
}
return status;
}
}

Address Validation Service

1. Address Verification

@Service
@Slf4j
public class UPSAddressService extends UPSApiClient {
public UPSAddressService(UPSConfig config, UPSAuthService authService) {
super(config, authService);
}
public AddressValidationResponse validateAddress(Address address) throws UPSApiException {
AddressValidationRequest request = createAddressValidationRequest(address);
return executePost(config.getAddressValidationUrl(), request, AddressValidationResponse.class);
}
public AddressValidationResult validateAndCorrectAddress(Address address) throws UPSApiException {
AddressValidationResponse response = validateAddress(address);
return extractValidationResult(response);
}
public boolean isAddressValid(Address address) throws UPSApiException {
AddressValidationResponse response = validateAddress(address);
return response.getXAVResponse().getValidAddressIndicator() != null;
}
public List<Address> getAddressSuggestions(Address address) throws UPSApiException {
AddressValidationResponse response = validateAddress(address);
return extractAddressSuggestions(response);
}
private AddressValidationRequest createAddressValidationRequest(Address address) {
AddressValidationRequest request = new AddressValidationRequest();
Request requestOptions = new Request();
requestOptions.setRequestOption("1"); // Address validation
request.setRequest(requestOptions);
AddressKeyFormat addressKeyFormat = new AddressKeyFormat();
addressKeyFormat.setConsigneeName(address.getConsigneeName());
addressKeyFormat.setAddressLine(address.getAddressLine());
addressKeyFormat.setPoliticalDivision2(address.getCity());
addressKeyFormat.setPoliticalDivision1(address.getState());
addressKeyFormat.setPostcodePrimaryLow(address.getPostalCode());
addressKeyFormat.setCountryCode(address.getCountryCode());
request.setAddressKeyFormat(new AddressKeyFormat[]{addressKeyFormat});
return request;
}
private AddressValidationResult extractValidationResult(AddressValidationResponse response) {
AddressValidationResult result = new AddressValidationResult();
if (response.getXAVResponse() != null) {
result.setValid(response.getXAVResponse().getValidAddressIndicator() != null);
result.setQualityScore(response.getXAVResponse().getAddressClassification().getCode());
if (response.getXAVResponse().getCandidate() != null) {
List<Address> candidates = Arrays.stream(response.getXAVResponse().getCandidate())
.map(this::mapCandidateToAddress)
.collect(Collectors.toList());
result.setSuggestedAddresses(candidates);
}
}
return result;
}
private List<Address> extractAddressSuggestions(AddressValidationResponse response) {
if (response.getXAVResponse() != null && response.getXAVResponse().getCandidate() != null) {
return Arrays.stream(response.getXAVResponse().getCandidate())
.map(this::mapCandidateToAddress)
.collect(Collectors.toList());
}
return Collections.emptyList();
}
private Address mapCandidateToAddress(Candidate candidate) {
Address address = new Address();
address.setAddressLine(candidate.getAddressLine());
address.setCity(candidate.getPoliticalDivision2());
address.setState(candidate.getPoliticalDivision1());
address.setPostalCode(candidate.getPostcodePrimaryLow());
address.setCountryCode(candidate.getCountryCode());
address.setQuality(candidate.getAddressQuality());
return address;
}
}

Data Models

1. Core UPS Data Models

@Data
public class ShipmentRequest {
private Request Request;
private Shipment Shipment;
}
@Data
public class Shipment {
private String Description;
private Party Shipper;
private Party ShipTo;
private Party ShipFrom;
private Service Service;
private Package Package;
private PaymentInformation PaymentInformation;
private String ShipmentRatingOptions;
}
@Data
public class Party {
private String Name;
private String AttentionName;
private String ShipperNumber;
private Address Address;
private String PhoneNumber;
}
@Data
public class Address {
private String[] AddressLine;
private String City;
private String State;
private String PostalCode;
private String CountryCode;
private String ConsigneeName;
public String getAddressLine() {
return AddressLine != null && AddressLine.length > 0 ? AddressLine[0] : null;
}
public void setAddressLine(String addressLine) {
this.AddressLine = new String[]{addressLine};
}
}
@Data
public class Package {
private String Description;
private PackagingType PackagingType;
private Dimensions Dimensions;
private PackageWeight PackageWeight;
}
@Data
public class PackageDetails {
private BigDecimal weightLbs;
private BigDecimal lengthInches;
private BigDecimal widthInches;
private BigDecimal heightInches;
private String description;
}
@Data
public class ShipmentOptions {
private String serviceCode;
private String serviceDescription;
private String shipperName;
private String shipperNumber;
private String receiverName;
private boolean saturdayDelivery;
private boolean insurance;
private BigDecimal declaredValue;
}
@Data
public class ShippingOption {
private String serviceCode;
private String serviceName;
private BigDecimal totalCharges;
private String currency;
private Integer transitDays;
private String deliveryDate;
private boolean saturdayDelivery;
}
@Data
public class PackageStatus {
private String trackingNumber;
private String status;
private String service;
private String lastUpdate;
private String deliveryDate;
private String signedBy;
private boolean isDelivered;
private boolean isDelayed;
public boolean isDelivered() {
return "DELIVERED".equalsIgnoreCase(status);
}
public boolean isDelayed() {
return "DELAYED".equalsIgnoreCase(status) || 
(deliveryDate != null && LocalDate.parse(deliveryDate).isBefore(LocalDate.now()));
}
}
@Data
public class AddressValidationResult {
private boolean valid;
private String qualityScore;
private List<Address> suggestedAddresses;
private String validationMessage;
}
// Response models
@Data
public class ShipmentResponse {
private ShipmentResponseWrapper ShipmentResponse;
@Data
public static class ShipmentResponseWrapper {
private Response Response;
private ShipmentResults ShipmentResults;
}
@Data
public static class ShipmentResults {
private String ShipmentIdentificationNumber;
private List<PackageResults> PackageResults;
}
}
@Data
public class RatingResponse {
private RateResponse RateResponse;
@Data
public static class RateResponse {
private List<RatedShipment> RatedShipment;
}
}
@Data
public class TrackingResponse {
private TrackResponse TrackResponse;
@Data
public static class TrackResponse {
private List<Shipment> Shipment;
}
}
@Data
public class UPSError {
private Response response;
@Data
public static class Response {
private List<Error> Errors;
}
@Data
public static class Error {
private String Code;
private String Message;
}
}

REST Controller

1. UPS Integration API

@RestController
@RequestMapping("/api/ups")
@Slf4j
public class UPSController {
private final UPSShipmentService shipmentService;
private final UPSRatingService ratingService;
private final UPSTrackingService trackingService;
private final UPSAddressService addressService;
public UPSController(UPSShipmentService shipmentService,
UPSRatingService ratingService,
UPSTrackingService trackingService,
UPSAddressService addressService) {
this.shipmentService = shipmentService;
this.ratingService = ratingService;
this.trackingService = trackingService;
this.addressService = addressService;
}
@PostMapping("/shipments")
public ResponseEntity<ShipmentResponse> createShipment(@RequestBody CreateShipmentRequest request) {
try {
ShipmentResponse response = shipmentService.createShipmentWithDetails(
request.getFromAddress(),
request.getToAddress(),
request.getPackageDetails(),
request.getOptions()
);
return ResponseEntity.ok(response);
} catch (UPSApiException e) {
log.error("Failed to create shipment", e);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}
}
@PostMapping("/shipments/{shipmentId}/void")
public ResponseEntity<Void> voidShipment(@PathVariable String shipmentId) {
try {
shipmentService.voidShipment(shipmentId);
return ResponseEntity.ok().build();
} catch (UPSApiException e) {
log.error("Failed to void shipment: {}", shipmentId, e);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}
}
@GetMapping("/rates")
public ResponseEntity<List<ShippingOption>> getShippingRates(@RequestBody RateRequest request) {
try {
List<ShippingOption> options = ratingService.getAvailableServices(
request.getFromAddress(),
request.getToAddress(),
request.getPackageDetails()
);
return ResponseEntity.ok(options);
} catch (UPSApiException e) {
log.error("Failed to get shipping rates", e);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}
}
@GetMapping("/tracking/{trackingNumber}")
public ResponseEntity<PackageStatus> trackPackage(@PathVariable String trackingNumber) {
try {
TrackingResponse response = trackingService.trackPackage(trackingNumber);
PackageStatus status = extractPackageStatus(response, trackingNumber);
return ResponseEntity.ok(status);
} catch (UPSApiException e) {
log.error("Failed to track package: {}", trackingNumber, e);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}
}
@PostMapping("/address/validate")
public ResponseEntity<AddressValidationResult> validateAddress(@RequestBody Address address) {
try {
AddressValidationResult result = addressService.validateAndCorrectAddress(address);
return ResponseEntity.ok(result);
} catch (UPSApiException e) {
log.error("Failed to validate address", e);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}
}
@GetMapping("/labels/{shipmentId}")
public ResponseEntity<byte[]> getShippingLabel(@PathVariable String shipmentId) {
try {
byte[] labelData = shipmentService.getShippingLabel(shipmentId);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.IMAGE_PNG);
headers.setContentDisposition(ContentDisposition.attachment()
.filename("shipping-label-" + shipmentId + ".png")
.build());
return new ResponseEntity<>(labelData, headers, HttpStatus.OK);
} catch (UPSApiException e) {
log.error("Failed to get shipping label: {}", shipmentId, e);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}
}
@PostMapping("/tracking/batch")
public ResponseEntity<Map<String, PackageStatus>> trackMultiplePackages(
@RequestBody List<String> trackingNumbers) {
try {
Map<String, PackageStatus> statusMap = trackingService.getShipmentStatusMap(trackingNumbers);
return ResponseEntity.ok(statusMap);
} catch (UPSApiException e) {
log.error("Failed to track multiple packages", e);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}
}
private PackageStatus extractPackageStatus(TrackingResponse response, String trackingNumber) {
// Implementation to extract status from TrackingResponse
PackageStatus status = new PackageStatus();
status.setTrackingNumber(trackingNumber);
// ... populate status from response
return status;
}
@Data
public static class CreateShipmentRequest {
private Address fromAddress;
private Address toAddress;
private PackageDetails packageDetails;
private ShipmentOptions options;
}
@Data
public static class RateRequest {
private Address fromAddress;
private Address toAddress;
private PackageDetails packageDetails;
}
}

Error Handling

1. Custom Exceptions

public class UPSApiException extends Exception {
private final Integer statusCode;
public UPSApiException(String message) {
super(message);
this.statusCode = null;
}
public UPSApiException(String message, Integer statusCode) {
super(message);
this.statusCode = statusCode;
}
public UPSApiException(String message, Throwable cause) {
super(message, cause);
this.statusCode = null;
}
public Integer getStatusCode() {
return statusCode;
}
}
@ControllerAdvice
public class UPSExceptionHandler {
@ExceptionHandler(UPSApiException.class)
public ResponseEntity<ErrorResponse> handleUPSApiException(UPSApiException e) {
log.error("UPS API error", e);
ErrorResponse error = new ErrorResponse(
"UPS_API_ERROR",
e.getMessage(),
e.getStatusCode(),
LocalDateTime.now()
);
return ResponseEntity.status(
e.getStatusCode() != null ? HttpStatus.valueOf(e.getStatusCode()) : HttpStatus.INTERNAL_SERVER_ERROR
).body(error);
}
@Data
public static class ErrorResponse {
private final String error;
private final String message;
private final Integer statusCode;
private final LocalDateTime timestamp;
}
}

Configuration

1. Application Properties

# application.yml
ups:
client-id: ${UPS_CLIENT_ID}
client-secret: ${UPS_CLIENT_SECRET}
base-url: https://wwwcie.ups.com
account-number: ${UPS_ACCOUNT_NUMBER}
user-id: ${UPS_USER_ID}
password: ${UPS_PASSWORD}
timeout-seconds: 30

Best Practices

  1. Token Management - Implement token caching and refresh logic
  2. Error Handling - Handle UPS-specific error codes and rate limits
  3. Retry Logic - Implement retry with exponential backoff for transient failures
  4. Logging - Comprehensive logging for debugging and audit trails
  5. Validation - Validate all inputs before sending to UPS APIs
  6. Batch Operations - Use batch processing for multiple shipments or tracking requests
  7. Security - Secure API credentials and use HTTPS for all communications

Conclusion

Integrating UPS APIs with Java applications enables powerful shipping and logistics automation. By implementing the patterns shown here, Java developers can:

  • Automate Shipping - Create shipments and generate labels programmatically
  • Calculate Rates - Provide accurate shipping costs to customers
  • Track Packages - Offer real-time shipment tracking
  • Validate Addresses - Ensure delivery address accuracy
  • Streamline Operations - Integrate shipping into e-commerce and fulfillment workflows

The combination of robust authentication, comprehensive service implementations, and proper error handling creates an enterprise-ready UPS integration that can scale to handle high-volume shipping operations.


Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper