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