Shipping Integration: Using FedEx API with Java for Logistics Automation


Article

In e-commerce and logistics applications, shipping integration is crucial for providing real-time rates, tracking, and label generation. FedEx API provides comprehensive web services for shipping operations, while Java offers the robustness needed for enterprise logistics applications. Integrating FedEx with Java enables automated shipping processes, real-time tracking, and seamless logistics management.

What is FedEx Web Services?

FedEx provides several APIs for different shipping needs:

  • Rate Service: Get real-time shipping rates and transit times
  • Ship Service: Create shipments and generate labels
  • Track Service: Real-time package tracking
  • Address Validation: Verify shipping addresses
  • Pickup Service: Schedule package pickups
  • Return Service: Manage return shipments

Why Integrate FedEx API with Java?

  1. Automated Shipping: Generate labels and documents programmatically
  2. Real-time Rates: Provide accurate shipping costs to customers
  3. Tracking Integration: Offer real-time package tracking
  4. Address Validation: Reduce failed deliveries
  5. Enterprise Scale: Handle high-volume shipping operations

FedEx API Architecture for Java

Java Application → FedEx SOAP/REST Client → FedEx Web Services → Shipping Operations
↓
Authentication → Security Token
↓
Response Processing → Business Logic

Setting Up FedEx Integration

1. Add Dependencies:

<!-- pom.xml -->
<dependencies>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web-services</artifactId>
</dependency>
<!-- SOAP Client -->
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-ri</artifactId>
<version>4.0.1</version>
<type>pom</type>
</dependency>
<!-- Alternatively, use REST with WebClient -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- XML Processing -->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
<!-- PDF Generation for Labels -->
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>2.0.29</version>
</dependency>
</dependencies>

2. Configuration Properties:

# application.yml
fedex:
api:
key: ${FEDEX_API_KEY:your-api-key}
password: ${FEDEX_API_PASSWORD:your-password}
account-number: ${FEDEX_ACCOUNT_NUMBER:your-account-number}
meter-number: ${FEDEX_METER_NUMBER:your-meter-number}
base-url: ${FEDEX_BASE_URL:https://ws.fedex.com:443/web-services}
# Use different URLs for testing vs production
test-base-url: ${FEDEX_TEST_BASE_URL:https://wsbeta.fedex.com:443/web-services}
service:
# Service WSDL URLs
rate-wsdl: ${FEDEX_RATE_WSDL:https://wsbeta.fedex.com/web-services/rate}
ship-wsdl: ${FEDEX_SHIP_WSDL:https://wsbeta.fedex.com/web-services/ship}
track-wsdl: ${FEDEX_TRACK_WSDL:https://wsbeta.fedex.com/web-services/track}
defaults:
packaging-type: YOUR_PACKAGING
drop-off-type: REGULAR_PICKUP
service-type: FEDEX_GROUND
label-format: COMMON2D
image-type: PDF
app:
shipping:
default-weight-unit: LB
default-dimension-unit: IN
max-retry-attempts: 3
retry-delay-ms: 1000

3. FedEx Configuration:

@Configuration
@ConfigurationProperties(prefix = "fedex")
@Data
public class FedExConfig {
private String apiKey;
private String password;
private String accountNumber;
private String meterNumber;
private String baseUrl;
private String testBaseUrl;
private Service service;
private Defaults defaults;
@Data
public static class Service {
private String rateWsdl;
private String shipWsdl;
private String trackWsdl;
}
@Data
public static class Defaults {
private String packagingType;
private String dropOffType;
private String serviceType;
private String labelFormat;
private String imageType;
private String weightUnit;
private String dimensionUnit;
}
@Bean
public WebServiceTemplate fedExWebServiceTemplate() {
WebServiceTemplate webServiceTemplate = new WebServiceTemplate();
webServiceTemplate.setDefaultUri(service.getRateWsdl());
webServiceTemplate.setMessageSender(httpComponentsMessageSender());
return webServiceTemplate;
}
@Bean
public HttpComponentsMessageSender httpComponentsMessageSender() {
HttpComponentsMessageSender messageSender = new HttpComponentsMessageSender();
messageSender.setConnectionTimeout(30000);
messageSender.setReadTimeout(30000);
return messageSender;
}
}

Core FedEx Services

1. Authentication Service:

@Service
@Slf4j
public class FedExAuthService {
private final FedExConfig fedExConfig;
public FedExAuthService(FedExConfig fedExConfig) {
this.fedExConfig = fedExConfig;
}
public WebAuthenticationDetail createWebAuthenticationDetail() {
WebAuthenticationDetail authDetail = new WebAuthenticationDetail();
authDetail.setUserCredential(createUserCredential());
return authDetail;
}
public ClientDetail createClientDetail() {
ClientDetail clientDetail = new ClientDetail();
clientDetail.setAccountNumber(fedExConfig.getAccountNumber());
clientDetail.setMeterNumber(fedExConfig.getMeterNumber());
return clientDetail;
}
public TransactionDetail createTransactionDetail(String transactionId) {
TransactionDetail transactionDetail = new TransactionDetail();
transactionDetail.setCustomerTransactionId(transactionId);
return transactionDetail;
}
private WebAuthenticationCredential createUserCredential() {
WebAuthenticationCredential credential = new WebAuthenticationCredential();
credential.setKey(fedExConfig.getApiKey());
credential.setPassword(fedExConfig.getPassword());
return credential;
}
public VersionId createVersionId(String serviceId, String major, String intermediate, String minor) {
VersionId versionId = new VersionId();
versionId.setServiceId(serviceId);
versionId.setMajor(major);
versionId.setIntermediate(intermediate);
versionId.setMinor(minor);
return versionId;
}
}

2. Rate Service:

@Service
@Slf4j
public class FedExRateService {
private final FedExConfig fedExConfig;
private final FedExAuthService authService;
private final RatePortType rateService;
public FedExRateService(FedExConfig fedExConfig, 
FedExAuthService authService) {
this.fedExConfig = fedExConfig;
this.authService = authService;
this.rateService = createRateService();
}
public RateReply getShippingRates(RateRequest request) {
try {
// Set authentication details
request.setWebAuthenticationDetail(authService.createWebAuthenticationDetail());
request.setClientDetail(authService.createClientDetail());
request.setTransactionDetail(authService.createTransactionDetail("RateRequest_" + System.currentTimeMillis()));
request.setVersion(authService.createVersionId("crs", "24", "0", "0"));
RateReply reply = rateService.getRates(request);
if (reply.getHighestSeverity().equals(SeverityType.SUCCESS) || 
reply.getHighestSeverity().equals(SeverityType.NOTE) || 
reply.getHighestSeverity().equals(SeverityType.WARNING)) {
log.info("Rate request successful");
return reply;
} else {
log.error("Rate request failed: {}", formatNotifications(reply.getNotifications()));
throw new FedExServiceException("Rate request failed: " + formatNotifications(reply.getNotifications()));
}
} catch (Exception e) {
log.error("Failed to get shipping rates", e);
throw new FedExServiceException("Failed to get shipping rates", e);
}
}
public RateReply getSimpleRate(String originZip, String destinationZip, 
double weight, String serviceType) {
RateRequest request = new RateRequest();
// Create request details
RequestedShipment shipment = new RequestedShipment();
shipment.setShipTimestamp(Calendar.getInstance());
shipment.setDropoffType(fedExConfig.getDefaults().getDropOffType());
shipment.setServiceType(serviceType);
shipment.setPackagingType(fedExConfig.getDefaults().getPackagingType());
// Set shipper address
shipment.setShipper(createAddress(originZip, "US"));
// Set recipient address
shipment.setRecipient(createAddress(destinationZip, "US"));
// Set shipping charges payment
shipment.setShippingChargesPayment(createShippingChargesPayment());
// Set rate request types
shipment.setRateRequestTypes(new ArrayList<>(Arrays.asList(
RateRequestType.LIST,
RateRequestType.PREFERRED
)));
// Set package details
shipment.setRequestedPackageLineItems(createPackageLineItems(weight));
request.setRequestedShipment(shipment);
return getShippingRates(request);
}
private Party createAddress(String postalCode, String countryCode) {
Party party = new Party();
Address address = new Address();
address.setPostalCode(postalCode);
address.setCountryCode(countryCode);
party.setAddress(address);
return party;
}
private Payment createShippingChargesPayment() {
Payment payment = new Payment();
payment.setPaymentType(PaymentType.SENDER);
Payor payor = new Payor();
payor.setAccountNumber(fedExConfig.getAccountNumber());
payor.setCountryCode("US");
payment.setPayor(payor);
return payment;
}
private List<RequestedPackageLineItem> createPackageLineItems(double weight) {
RequestedPackageLineItem packageLineItem = new RequestedPackageLineItem();
packageLineItem.setSequenceNumber(1);
packageLineItem.setGroupPackageCount(1);
// Set weight
Weight packageWeight = new Weight();
packageWeight.setUnits(WeightUnits.LB);
packageWeight.setValue(weight);
packageLineItem.setWeight(packageWeight);
// Set dimensions (optional)
Dimensions dimensions = new Dimensions();
dimensions.setLength(10.0);
dimensions.setWidth(10.0);
dimensions.setHeight(10.0);
dimensions.setUnits(LinearUnits.IN);
packageLineItem.setDimensions(dimensions);
return Arrays.asList(packageLineItem);
}
private RatePortType createRateService() {
try {
RateService service = new RateService();
return service.getRateServicePort();
} catch (Exception e) {
throw new FedExServiceException("Failed to create Rate service", e);
}
}
private String formatNotifications(List<Notification> notifications) {
return notifications.stream()
.map(notification -> notification.getCode() + ": " + notification.getMessage())
.collect(Collectors.joining("; "));
}
}

3. Ship Service:

@Service
@Slf4j
public class FedExShipService {
private final FedExConfig fedExConfig;
private final FedExAuthService authService;
private final ShipPortType shipService;
public FedExShipService(FedExConfig fedExConfig, 
FedExAuthService authService) {
this.fedExConfig = fedExConfig;
this.authService = authService;
this.shipService = createShipService();
}
public ProcessShipmentReply createShipment(CreateShipmentRequest request) {
try {
ProcessShipmentRequest shipmentRequest = createShipmentRequest(request);
ProcessShipmentReply reply = shipService.processShipment(shipmentRequest);
if (reply.getHighestSeverity().equals(SeverityType.SUCCESS) || 
reply.getHighestSeverity().equals(SeverityType.NOTE)) {
log.info("Shipment created successfully: {}", 
reply.getCompletedShipmentDetail().getMasterTrackingId().getTrackingNumber());
return reply;
} else {
log.error("Shipment creation failed: {}", formatNotifications(reply.getNotifications()));
throw new FedExServiceException("Shipment creation failed: " + formatNotifications(reply.getNotifications()));
}
} catch (Exception e) {
log.error("Failed to create shipment", e);
throw new FedExServiceException("Failed to create shipment", e);
}
}
public byte[] createShippingLabel(ShipmentData shipmentData) {
try {
CreateShipmentRequest request = convertToShipmentRequest(shipmentData);
ProcessShipmentReply reply = createShipment(request);
// Extract label from response
ShippingDocument label = reply.getCompletedShipmentDetail()
.getCompletedPackageDetails().get(0)
.getLabel();
// The label is typically returned as base64 encoded string
return decodeLabelData(label);
} catch (Exception e) {
log.error("Failed to create shipping label", e);
throw new FedExServiceException("Failed to create shipping label", e);
}
}
public Void deleteShipment(String trackingNumber) {
try {
DeleteShipmentRequest deleteRequest = new DeleteShipmentRequest();
// Set authentication
deleteRequest.setWebAuthenticationDetail(authService.createWebAuthenticationDetail());
deleteRequest.setClientDetail(authService.createClientDetail());
deleteRequest.setTransactionDetail(authService.createTransactionDetail("DeleteShipment_" + System.currentTimeMillis()));
deleteRequest.setVersion(authService.createVersionId("ship", "23", "0", "0"));
// Set tracking number
deleteRequest.setTrackingId(createTrackingId(trackingNumber));
DeleteShipmentReply reply = shipService.deleteShipment(deleteRequest);
if (reply.getHighestSeverity().equals(SeverityType.SUCCESS)) {
log.info("Shipment deleted successfully: {}", trackingNumber);
} else {
log.warn("Shipment deletion completed with warnings: {}", trackingNumber);
}
return null;
} catch (Exception e) {
log.error("Failed to delete shipment: {}", trackingNumber, e);
throw new FedExServiceException("Failed to delete shipment", e);
}
}
private ProcessShipmentRequest createShipmentRequest(CreateShipmentRequest request) {
ProcessShipmentRequest shipmentRequest = new ProcessShipmentRequest();
// Set authentication
shipmentRequest.setWebAuthenticationDetail(authService.createWebAuthenticationDetail());
shipmentRequest.setClientDetail(authService.createClientDetail());
shipmentRequest.setTransactionDetail(authService.createTransactionDetail("ProcessShipment_" + System.currentTimeMillis()));
shipmentRequest.setVersion(authService.createVersionId("ship", "23", "0", "0"));
// Set requested shipment
RequestedShipment requestedShipment = new RequestedShipment();
requestedShipment.setShipTimestamp(Calendar.getInstance());
requestedShipment.setDropoffType(request.getDropoffType());
requestedShipment.setServiceType(request.getServiceType());
requestedShipment.setPackagingType(request.getPackagingType());
// Set shipper
requestedShipment.setShipper(createParty(request.getShipper()));
// Set recipient
requestedShipment.setRecipient(createParty(request.getRecipient()));
// Set shipping charges payment
requestedShipment.setShippingChargesPayment(createShippingChargesPayment());
// Set label specification
requestedShipment.setLabelSpecification(createLabelSpecification());
// Set package details
requestedShipment.setRequestedPackageLineItems(createPackageLineItems(request.getPackages()));
// Set total weight
requestedShipment.setTotalWeight(createTotalWeight(request.getPackages()));
shipmentRequest.setRequestedShipment(requestedShipment);
return shipmentRequest;
}
private Party createParty(AddressData addressData) {
Party party = new Party();
// Set contact
Contact contact = new Contact();
contact.setPersonName(addressData.getContactName());
contact.setCompanyName(addressData.getCompanyName());
contact.setPhoneNumber(addressData.getPhoneNumber());
party.setContact(contact);
// Set address
Address address = new Address();
address.setStreetLines(new ArrayList<>(addressData.getStreetLines()));
address.setCity(addressData.getCity());
address.setStateOrProvinceCode(addressData.getState());
address.setPostalCode(addressData.getPostalCode());
address.setCountryCode(addressData.getCountryCode());
address.setResidential(addressData.isResidential());
party.setAddress(address);
return party;
}
private LabelSpecification createLabelSpecification() {
LabelSpecification labelSpec = new LabelSpecification();
labelSpec.setLabelFormatType(LabelFormatType.COMMON2D);
labelSpec.setImageType(ShippingDocumentImageType.PDF);
labelSpec.setLabelStockType(LabelStockType.PAPER_4X6);
return labelSpec;
}
private List<RequestedPackageLineItem> createPackageLineItems(List<PackageData> packages) {
List<RequestedPackageLineItem> lineItems = new ArrayList<>();
for (int i = 0; i < packages.size(); i++) {
PackageData pkg = packages.get(i);
RequestedPackageLineItem lineItem = new RequestedPackageLineItem();
lineItem.setSequenceNumber(i + 1);
lineItem.setGroupPackageCount(1);
// Set weight
Weight weight = new Weight();
weight.setUnits(WeightUnits.LB);
weight.setValue(pkg.getWeight());
lineItem.setWeight(weight);
// Set dimensions
if (pkg.getDimensions() != null) {
Dimensions dimensions = new Dimensions();
dimensions.setLength(pkg.getDimensions().getLength());
dimensions.setWidth(pkg.getDimensions().getWidth());
dimensions.setHeight(pkg.getDimensions().getHeight());
dimensions.setUnits(LinearUnits.IN);
lineItem.setDimensions(dimensions);
}
// Set customer references
if (pkg.getReference() != null) {
CustomerReference customerReference = new CustomerReference();
customerReference.setCustomerReferenceType(CustomerReferenceType.CUSTOMER_REFERENCE);
customerReference.setValue(pkg.getReference());
lineItem.setCustomerReferences(Arrays.asList(customerReference));
}
lineItems.add(lineItem);
}
return lineItems;
}
private Weight createTotalWeight(List<PackageData> packages) {
double totalWeight = packages.stream()
.mapToDouble(PackageData::getWeight)
.sum();
Weight weight = new Weight();
weight.setUnits(WeightUnits.LB);
weight.setValue(totalWeight);
return weight;
}
private TrackingId createTrackingId(String trackingNumber) {
TrackingId trackingId = new TrackingId();
trackingId.setTrackingIdType(TrackingIdType.FEDEX);
trackingId.setTrackingNumber(trackingNumber);
return trackingId;
}
private byte[] decodeLabelData(ShippingDocument label) {
// FedEx returns labels as base64 encoded strings
String encodedData = label.getParts().get(0).getImage();
return Base64.getDecoder().decode(encodedData);
}
private ShipPortType createShipService() {
try {
ShipService service = new ShipService();
return service.getShipServicePort();
} catch (Exception e) {
throw new FedExServiceException("Failed to create Ship service", e);
}
}
private String formatNotifications(List<Notification> notifications) {
return notifications.stream()
.map(notification -> notification.getCode() + ": " + notification.getMessage())
.collect(Collectors.joining("; "));
}
}

4. Track Service:

@Service
@Slf4j
public class FedExTrackService {
private final FedExConfig fedExConfig;
private final FedExAuthService authService;
private final TrackPortType trackService;
public FedExTrackService(FedExConfig fedExConfig, 
FedExAuthService authService) {
this.fedExConfig = fedExConfig;
this.authService = authService;
this.trackService = createTrackService();
}
public TrackReply trackPackage(String trackingNumber) {
try {
TrackRequest trackRequest = new TrackRequest();
// Set authentication
trackRequest.setWebAuthenticationDetail(authService.createWebAuthenticationDetail());
trackRequest.setClientDetail(authService.createClientDetail());
trackRequest.setTransactionDetail(authService.createTransactionDetail("TrackRequest_" + System.currentTimeMillis()));
trackRequest.setVersion(authService.createVersionId("trck", "16", "0", "0"));
// Set tracking selection details
TrackSelectionDetail selectionDetail = new TrackSelectionDetail();
selectionDetail.setPackageIdentifier(createPackageIdentifier(trackingNumber));
selectionDetail.setShipDateRangeSpecified(false);
trackRequest.setSelectionDetails(Arrays.asList(selectionDetail));
TrackReply reply = trackService.track(trackRequest);
if (reply.getHighestSeverity().equals(SeverityType.SUCCESS) || 
reply.getHighestSeverity().equals(SeverityType.NOTE)) {
log.info("Track request successful for: {}", trackingNumber);
return reply;
} else {
log.error("Track request failed: {}", formatNotifications(reply.getNotifications()));
throw new FedExServiceException("Track request failed: " + formatNotifications(reply.getNotifications()));
}
} catch (Exception e) {
log.error("Failed to track package: {}", trackingNumber, e);
throw new FedExServiceException("Failed to track package", e);
}
}
public TrackingInfo getTrackingInfo(String trackingNumber) {
TrackReply reply = trackPackage(trackingNumber);
if (reply.getCompletedTrackDetails() != null && 
!reply.getCompletedTrackDetails().isEmpty()) {
TrackDetail trackDetail = reply.getCompletedTrackDetails().get(0).getTrackDetails().get(0);
return convertToTrackingInfo(trackDetail);
}
throw new FedExServiceException("No tracking information found for: " + trackingNumber);
}
public List<TrackingEvent> getTrackingHistory(String trackingNumber) {
TrackReply reply = trackPackage(trackingNumber);
if (reply.getCompletedTrackDetails() != null && 
!reply.getCompletedTrackDetails().isEmpty()) {
TrackDetail trackDetail = reply.getCompletedTrackDetails().get(0).getTrackDetails().get(0);
return convertToTrackingEvents(trackDetail);
}
return Collections.emptyList();
}
private TrackPortType createTrackService() {
try {
TrackService service = new TrackService();
return service.getTrackServicePort();
} catch (Exception e) {
throw new FedExServiceException("Failed to create Track service", e);
}
}
private PackageIdentifier createPackageIdentifier(String trackingNumber) {
PackageIdentifier packageIdentifier = new PackageIdentifier();
packageIdentifier.setType(TrackIdentifierType.TRACKING_NUMBER_OR_DOOR_TAG);
packageIdentifier.setValue(trackingNumber);
return packageIdentifier;
}
private TrackingInfo convertToTrackingInfo(TrackDetail trackDetail) {
return TrackingInfo.builder()
.trackingNumber(trackDetail.getTrackingNumber())
.status(trackDetail.getStatusDetail().getDescription())
.serviceType(trackDetail.getService().getDescription())
.packageWeight(trackDetail.getPackageWeight().getValue() + " " + 
trackDetail.getPackageWeight().getUnits())
.packageCount(trackDetail.getPackageCount())
.shipTimestamp(trackDetail.getShipTimestamp())
.estimatedDelivery(trackDetail.getEstimatedDeliveryTimestamp())
.actualDelivery(trackDetail.getActualDeliveryTimestamp())
.deliveryLocation(trackDetail.getDeliveryLocationDescription())
.signatory(trackDetail.getDeliverySignatureName())
.events(convertToTrackingEvents(trackDetail))
.build();
}
private List<TrackingEvent> convertToTrackingEvents(TrackDetail trackDetail) {
return trackDetail.getEvents().stream()
.map(this::convertToTrackingEvent)
.collect(Collectors.toList());
}
private TrackingEvent convertToTrackingEvent(TrackEvent fedExEvent) {
return TrackingEvent.builder()
.timestamp(fedExEvent.getTimestamp())
.eventType(fedExEvent.getEventType())
.description(fedExEvent.getEventDescription())
.address(convertToAddressString(fedExEvent.getAddress()))
.build();
}
private String convertToAddressString(Address address) {
if (address == null) return null;
List<String> parts = new ArrayList<>();
if (address.getCity() != null) parts.add(address.getCity());
if (address.getStateOrProvinceCode() != null) parts.add(address.getStateOrProvinceCode());
if (address.getCountryCode() != null) parts.add(address.getCountryCode());
return String.join(", ", parts);
}
private String formatNotifications(List<Notification> notifications) {
return notifications.stream()
.map(notification -> notification.getCode() + ": " + notification.getMessage())
.collect(Collectors.joining("; "));
}
}

Data Models

1. Request/Response Models:

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CreateShipmentRequest {
@NotNull
private AddressData shipper;
@NotNull
private AddressData recipient;
@NotEmpty
private List<PackageData> packages;
private String serviceType;
private String packagingType;
private String dropoffType;
private String paymentType;
@Builder.Default
private LocalDateTime shipDate = LocalDateTime.now();
private String reference;
private String description;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AddressData {
@NotBlank
private String contactName;
private String companyName;
@NotBlank
private String phoneNumber;
@NotEmpty
private List<String> streetLines;
@NotBlank
private String city;
@NotBlank
private String state;
@NotBlank
private String postalCode;
@NotBlank
@Builder.Default
private String countryCode = "US";
@Builder.Default
private boolean residential = false;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PackageData {
@NotNull
private Double weight; // in pounds
private Dimensions dimensions;
private String reference;
private String description;
// Insurance
private Double insuredValue;
private String currency;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Dimensions {
@NotNull
private Double length;
@NotNull
private Double width;
@NotNull
private Double height;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TrackingInfo {
private String trackingNumber;
private String status;
private String serviceType;
private String packageWeight;
private Integer packageCount;
private Calendar shipTimestamp;
private Calendar estimatedDelivery;
private Calendar actualDelivery;
private String deliveryLocation;
private String signatory;
private List<TrackingEvent> events;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TrackingEvent {
private Calendar timestamp;
private String eventType;
private String description;
private String address;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ShippingRate {
private String serviceType;
private String serviceDescription;
private Double totalCharge;
private String currency;
private Calendar deliveryDate;
private String deliveryDay;
private List<String> deliveryMessages;
}

REST Controllers

1. Shipping Controller:

@RestController
@RequestMapping("/api/shipping")
@Slf4j
@Validated
public class ShippingController {
private final FedExRateService rateService;
private final FedExShipService shipService;
private final FedExTrackService trackService;
public ShippingController(FedExRateService rateService,
FedExShipService shipService,
FedExTrackService trackService) {
this.rateService = rateService;
this.shipService = shipService;
this.trackService = trackService;
}
@PostMapping("/rates")
public ResponseEntity<List<ShippingRate>> getShippingRates(@Valid @RequestBody RateRequest request) {
try {
RateReply reply = rateService.getSimpleRate(
request.getOriginPostalCode(),
request.getDestinationPostalCode(),
request.getWeight(),
request.getServiceType()
);
List<ShippingRate> rates = convertToShippingRates(reply);
return ResponseEntity.ok(rates);
} catch (FedExServiceException e) {
log.error("Failed to get shipping rates", e);
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).build();
}
}
@PostMapping("/shipments")
public ResponseEntity<ShipmentResponse> createShipment(@Valid @RequestBody CreateShipmentRequest request) {
try {
ProcessShipmentReply reply = shipService.createShipment(request);
ShipmentResponse response = convertToShipmentResponse(reply);
return ResponseEntity.status(HttpStatus.CREATED).body(response);
} catch (FedExServiceException e) {
log.error("Failed to create shipment", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
@GetMapping("/tracking/{trackingNumber}")
public ResponseEntity<TrackingInfo> trackPackage(@PathVariable String trackingNumber) {
try {
TrackingInfo trackingInfo = trackService.getTrackingInfo(trackingNumber);
return ResponseEntity.ok(trackingInfo);
} catch (FedExServiceException e) {
log.error("Failed to track package: {}", trackingNumber, e);
return ResponseEntity.notFound().build();
}
}
@GetMapping("/tracking/{trackingNumber}/history")
public ResponseEntity<List<TrackingEvent>> getTrackingHistory(@PathVariable String trackingNumber) {
try {
List<TrackingEvent> events = trackService.getTrackingHistory(trackingNumber);
return ResponseEntity.ok(events);
} catch (FedExServiceException e) {
log.error("Failed to get tracking history: {}", trackingNumber, e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
@PostMapping("/labels")
public ResponseEntity<byte[]> createShippingLabel(@Valid @RequestBody ShipmentData shipmentData) {
try {
byte[] labelData = shipService.createShippingLabel(shipmentData);
String filename = "shipping-label-" + shipmentData.getReference() + ".pdf";
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\"")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_PDF_VALUE)
.body(labelData);
} catch (FedExServiceException e) {
log.error("Failed to create shipping label", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
@DeleteMapping("/shipments/{trackingNumber}")
public ResponseEntity<Void> deleteShipment(@PathVariable String trackingNumber) {
try {
shipService.deleteShipment(trackingNumber);
return ResponseEntity.noContent().build();
} catch (FedExServiceException e) {
log.error("Failed to delete shipment: {}", trackingNumber, e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
// Conversion methods
private List<ShippingRate> convertToShippingRates(RateReply reply) {
return reply.getRateReplyDetails().stream()
.map(detail -> {
RateDetail rateDetail = detail.getRatedShipmentDetails().get(0).getShipmentRateDetail();
return ShippingRate.builder()
.serviceType(detail.getServiceType())
.serviceDescription(detail.getServiceDescription())
.totalCharge(rateDetail.getTotalNetCharge().getAmount())
.currency(rateDetail.getTotalNetCharge().getCurrency())
.deliveryDate(detail.getDeliveryTimestamp())
.deliveryDay(detail.getDeliveryDayOfWeek())
.deliveryMessages(detail.getDeliveryMessages())
.build();
})
.collect(Collectors.toList());
}
private ShipmentResponse convertToShipmentResponse(ProcessShipmentReply reply) {
CompletedShipmentDetail shipmentDetail = reply.getCompletedShipmentDetail();
CompletedPackageDetail packageDetail = shipmentDetail.getCompletedPackageDetails().get(0);
return ShipmentResponse.builder()
.trackingNumber(packageDetail.getTrackingIds().get(0).getTrackingNumber())
.masterTrackingNumber(shipmentDetail.getMasterTrackingId().getTrackingNumber())
.serviceType(shipmentDetail.getServiceType())
.packagingType(shipmentDetail.getPackagingType())
.totalNetCharge(shipmentDetail.getShipmentRating().getShipmentRateDetails().get(0).getTotalNetCharge().getAmount())
.currency(shipmentDetail.getShipmentRating().getShipmentRateDetails().get(0).getTotalNetCharge().getCurrency())
.labelFormat(packageDetail.getLabel().getImageType())
.shipTimestamp(shipmentDetail.getShipTimestamp())
.build();
}
}

Error Handling

1. Custom Exceptions:

public class FedExServiceException extends RuntimeException {
public FedExServiceException(String message) {
super(message);
}
public FedExServiceException(String message, Throwable cause) {
super(message, cause);
}
}
@RestControllerAdvice
@Slf4j
public class FedExExceptionHandler {
@ExceptionHandler(FedExServiceException.class)
public ResponseEntity<ErrorResponse> handleFedExException(FedExServiceException e) {
log.error("FedEx service error", e);
ErrorResponse error = ErrorResponse.builder()
.code("FEDEX_SERVICE_ERROR")
.message("Shipping service temporarily unavailable")
.timestamp(Instant.now())
.build();
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body(error);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidationException(MethodArgumentNotValidException e) {
List<String> errors = e.getBindingResult()
.getFieldErrors()
.stream()
.map(error -> error.getField() + ": " + error.getDefaultMessage())
.collect(Collectors.toList());
ErrorResponse error = ErrorResponse.builder()
.code("VALIDATION_ERROR")
.message("Invalid shipping request")
.details(errors)
.timestamp(Instant.now())
.build();
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
}
}
@Data
@Builder
class ErrorResponse {
private String code;
private String message;
private Object details;
private Instant timestamp;
}

Utility Services

1. Address Validation Service:
```java
@Service
@Slf4j
public class FedExAddressService {

private final FedExConfig fedExConfig;
private final FedExAuthService authService;
public FedExAddressService(FedExConfig fedExConfig, 
FedExAuthService authService) {
this.fedExConfig = fedExConfig;
this.authService = authService;
}
public AddressValidationReply validateAddress(AddressData addressData) {
try {
AddressValidationRequest request = new AddressValidationRequest();
// Set authentication
request.setWebAuthenticationDetail(authService.createWebAuthenticationDetail());
request.setClientDetail(authService.createClientDetail());
request.setTransactionDetail(authService.createTransactionDetail("AddressValidation_" + System.currentTimeMillis()));
request.setVersion(authService.createVersionId("aval", "4", "0", "0"));
// Set addresses to validate
List<AddressToValidate> addressesToValidate = new ArrayList<>();
AddressToValidate addressToValidate = new AddressToValidate();
addressToValidate.setAddress(convertToFedExAddress(addressData));

Leave a Reply

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


Macro Nepal Helper