NetSuite's SuiteTalk web services provide a robust SOAP-based API for integrating external applications with NetSuite ERP. This comprehensive guide covers implementing SuiteTalk in Java, from basic setup to advanced operations.
Architecture Overview
Java Application → SuiteTalk SOAP Client → NetSuite Web Services → WSDL-based Stubs → SOAP API Endpoints → Token-based Authentication → RESTlets (Alternative)
Prerequisites and Setup
Maven Dependencies
<properties>
<suiteTalk.version>2023.2</suiteTalk.version>
</properties>
<dependencies>
<!-- NetSuite SuiteTalk Web Services -->
<dependency>
<groupId>com.netsuite</groupId>
<artifactId>netsuite-web-services</artifactId>
<version>${suiteTalk.version}</version>
</dependency>
<!-- Apache CXF for SOAP Web Services -->
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxws</artifactId>
<version>3.5.5</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
<version>3.5.5</version>
</dependency>
<!-- SOAP Dependencies -->
<dependency>
<groupId>javax.xml.ws</groupId>
<artifactId>jaxws-api</artifactId>
<version>2.3.1</version>
</dependency>
</dependencies>
Configuration Properties
# NetSuite Configuration netsuite.account=YOUR_ACCOUNT_ID [email protected] netsuite.password=your-password netsuite.role=3 netsuite.application-id=your-custom-app-id # Environment URLs netsuite.wsdl.url=https://webservices.netsuite.com/wsdl/v2023_2_0/netsuite.wsdl netsuite.endpoint.url=https://123456-sb1.suitetalk.api.netsuite.com/services/NetSuitePort_2023_2 # Connection Settings netsuite.connection.timeout=30000 netsuite.request.timeout=60000 netsuite.retry.max-attempts=3
Core SuiteTalk Implementation
1. NetSuite Service Factory
package com.yourapp.netsuite;
import com.netsuite.webservices.platform.*;
import com.netsuite.webservices.platform.core.*;
import com.netsuite.webservices.platform.messages.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.xml.ws.BindingProvider;
import java.util.Map;
@Component
public class NetSuiteServiceFactory {
@Value("${netsuite.account}")
private String account;
@Value("${netsuite.email}")
private String email;
@Value("${netsuite.password}")
private String password;
@Value("${netsuite.role}")
private String role;
@Value("${netsuite.application-id}")
private String applicationId;
@Value("${netsuite.endpoint.url}")
private String endpointUrl;
/**
* Create authenticated NetSuite service
*/
public NetSuiteService createNetSuiteService() {
try {
NetSuiteService service = new NetSuiteService();
NetSuitePortType port = service.getNetSuitePort();
// Configure endpoint
BindingProvider bindingProvider = (BindingProvider) port;
Map<String, Object> requestContext = bindingProvider.getRequestContext();
requestContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, endpointUrl);
// Set timeouts
requestContext.put("com.sun.xml.internal.ws.request.timeout", 30000);
requestContext.put("com.sun.xml.internal.ws.connect.timeout", 10000);
return service;
} catch (Exception e) {
throw new RuntimeException("Failed to create NetSuite service", e);
}
}
/**
* Create passport for authentication
*/
public Passport createPassport() {
Passport passport = new Passport();
// Set account
passport.setAccount(account);
// Set email and password
RecordRef roleRef = new RecordRef();
roleRef.setInternalId(role);
passport.setEmail(email);
passport.setPassword(password);
passport.setRole(roleRef);
return passport;
}
/**
* Create application info
*/
public ApplicationInfo createApplicationInfo() {
ApplicationInfo applicationInfo = new ApplicationInfo();
applicationInfo.setApplicationId(applicationId);
return applicationInfo;
}
}
2. Core NetSuite Connector
package com.yourapp.netsuite;
import com.netsuite.webservices.platform.*;
import com.netsuite.webservices.platform.core.*;
import com.netsuite.webservices.platform.messages.*;
import com.netsuite.webservices.setup.customization.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.util.*;
@Service
public class NetSuiteConnector {
private static final Logger logger = LoggerFactory.getLogger(NetSuiteConnector.class);
private final NetSuiteServiceFactory serviceFactory;
public NetSuiteConnector(NetSuiteServiceFactory serviceFactory) {
this.serviceFactory = serviceFactory;
}
/**
* Get record by internal ID
*/
public ReadResponse getRecordById(String recordType, String internalId) {
try {
NetSuiteService service = serviceFactory.createNetSuiteService();
NetSuitePortType port = service.getNetSuitePort();
// Create passport and application info
Passport passport = serviceFactory.createPassport();
ApplicationInfo applicationInfo = serviceFactory.createApplicationInfo();
// Create read request
ReadRequest readRequest = new ReadRequest();
readRequest.setBaseRef(new RecordRef());
readRequest.getBaseRef().setInternalId(internalId);
readRequest.getBaseRef().setType(RecordType.valueOf(recordType));
// Set application info header
setApplicationInfo(port, applicationInfo);
// Execute request
return port.read(readRequest, passport);
} catch (Exception e) {
logger.error("Failed to get record by ID: {} - {}", internalId, e.getMessage());
throw new RuntimeException("NetSuite read operation failed", e);
}
}
/**
* Search records with criteria
*/
public SearchResult searchRecords(SearchRecord searchRecord, int pageSize) {
try {
NetSuiteService service = serviceFactory.createNetSuiteService();
NetSuitePortType port = service.getNetSuitePort();
Passport passport = serviceFactory.createPassport();
ApplicationInfo applicationInfo = serviceFactory.createApplicationInfo();
// Create search request
SearchRequest searchRequest = new SearchRequest();
searchRequest.setSearchRecord(searchRecord);
searchRequest.setPageSize(pageSize);
setApplicationInfo(port, applicationInfo);
SearchResponse searchResponse = port.search(searchRequest, passport);
return searchResponse.getSearchResult();
} catch (Exception e) {
logger.error("Search operation failed: {}", e.getMessage());
throw new RuntimeException("NetSuite search operation failed", e);
}
}
/**
* Add a new record
*/
public WriteResponse addRecord(Record record) {
try {
NetSuiteService service = serviceFactory.createNetSuiteService();
NetSuitePortType port = service.getNetSuitePort();
Passport passport = serviceFactory.createPassport();
ApplicationInfo applicationInfo = serviceFactory.createApplicationInfo();
// Create add request
AddRequest addRequest = new AddRequest();
addRequest.setRecord(record);
setApplicationInfo(port, applicationInfo);
return port.add(addRequest, passport);
} catch (Exception e) {
logger.error("Add record failed: {}", e.getMessage());
throw new RuntimeException("NetSuite add operation failed", e);
}
}
/**
* Update existing record
*/
public WriteResponse updateRecord(Record record) {
try {
NetSuiteService service = serviceFactory.createNetSuiteService();
NetSuitePortType port = service.getNetSuitePort();
Passport passport = serviceFactory.createPassport();
ApplicationInfo applicationInfo = serviceFactory.createApplicationInfo();
// Create update request
UpdateRequest updateRequest = new UpdateRequest();
updateRequest.setRecord(record);
setApplicationInfo(port, applicationInfo);
return port.update(updateRequest, passport);
} catch (Exception e) {
logger.error("Update record failed: {}", e.getMessage());
throw new RuntimeException("NetSuite update operation failed", e);
}
}
/**
* Delete record by internal ID
*/
public WriteResponse deleteRecord(String recordType, String internalId) {
try {
NetSuiteService service = serviceFactory.createNetSuiteService();
NetSuitePortType port = service.getNetSuitePort();
Passport passport = serviceFactory.createPassport();
ApplicationInfo applicationInfo = serviceFactory.createApplicationInfo();
// Create base reference
RecordRef baseRef = new RecordRef();
baseRef.setInternalId(internalId);
baseRef.setType(RecordType.valueOf(recordType));
// Create delete request
DeleteRequest deleteRequest = new DeleteRequest();
deleteRequest.setBaseRef(baseRef);
setApplicationInfo(port, applicationInfo);
return port.delete(deleteRequest, passport);
} catch (Exception e) {
logger.error("Delete record failed: {} - {}", internalId, e.getMessage());
throw new RuntimeException("NetSuite delete operation failed", e);
}
}
private void setApplicationInfo(NetSuitePortType port, ApplicationInfo applicationInfo) {
// Implementation depends on your CXF version
// This sets the application info in SOAP headers
}
}
Business Object Implementations
1. Customer Service
@Service
public class CustomerService {
private final NetSuiteConnector netsuiteConnector;
public CustomerService(NetSuiteConnector netsuiteConnector) {
this.netsuiteConnector = netsuiteConnector;
}
/**
* Create customer in NetSuite
*/
public String createCustomer(CustomerDTO customerDTO) {
try {
Customer customer = new Customer();
// Set basic information
customer.setCompanyName(customerDTO.getCompanyName());
customer.setEmail(customerDTO.getEmail());
customer.setPhone(customerDTO.getPhone());
// Set subsidiary
RecordRef subsidiary = new RecordRef();
subsidiary.setInternalId(customerDTO.getSubsidiaryId());
customer.setSubsidiary(subsidiary);
// Set address
if (customerDTO.getAddress() != null) {
CustomerAddressbook addressbook = new CustomerAddressbook();
addressbook.setDefaultShipping(true);
addressbook.setDefaultBilling(true);
Address address = new Address();
address.setAddr1(customerDTO.getAddress().getLine1());
address.setAddr2(customerDTO.getAddress().getLine2());
address.setCity(customerDTO.getAddress().getCity());
address.setState(customerDTO.getAddress().getState());
address.setZip(customerDTO.getAddress().getZipCode());
address.setCountry(Country._unitedStates);
addressbook.setAddress(address);
customer.setAddressbookList(new CustomerAddressbookList());
customer.getAddressbookList().getAddressbook().add(addressbook);
}
// Set custom fields
if (customerDTO.getCustomFields() != null) {
setCustomFields(customer, customerDTO.getCustomFields());
}
WriteResponse response = netsuiteConnector.addRecord(customer);
return response.getBaseRef().getInternalId();
} catch (Exception e) {
throw new RuntimeException("Failed to create customer", e);
}
}
/**
* Search customers by email
*/
public List<Customer> findCustomersByEmail(String email) {
try {
CustomerSearch customerSearch = new CustomerSearch();
// Create search criteria
CustomerSearchBasic searchBasic = new CustomerSearchBasic();
StringSearchField emailField = new StringSearchField();
emailField.setOperator(SearchStringFieldOperator.is);
emailField.setSearchValue(email);
searchBasic.setEmail(emailField);
customerSearch.setBasic(searchBasic);
SearchResult searchResult = netsuiteConnector.searchRecords(customerSearch, 50);
List<Customer> customers = new ArrayList<>();
if (searchResult.getRecordList() != null) {
for (Record record : searchResult.getRecordList().getRecord()) {
if (record instanceof Customer) {
customers.add((Customer) record);
}
}
}
return customers;
} catch (Exception e) {
throw new RuntimeException("Failed to search customers", e);
}
}
/**
* Update customer currency
*/
public void updateCustomerCurrency(String customerInternalId, String currencyInternalId) {
try {
// First get the customer
ReadResponse readResponse = netsuiteConnector.getRecordById("customer", customerInternalId);
Customer customer = (Customer) readResponse.getRecord();
// Update currency
RecordRef currencyRef = new RecordRef();
currencyRef.setInternalId(currencyInternalId);
customer.setCurrency(currencyRef);
// Update customer
netsuiteConnector.updateRecord(customer);
} catch (Exception e) {
throw new RuntimeException("Failed to update customer currency", e);
}
}
private void setCustomFields(Customer customer, Map<String, Object> customFields) {
CustomFieldList customFieldList = new CustomFieldList();
for (Map.Entry<String, Object> entry : customFields.entrySet()) {
StringCustomField customField = new StringCustomField();
customField.setScriptId(entry.getKey());
customField.setValue(entry.getValue().toString());
customFieldList.getCustomField().add(customField);
}
customer.setCustomFieldList(customFieldList);
}
}
2. Sales Order Service
@Service
public class SalesOrderService {
private final NetSuiteConnector netsuiteConnector;
public SalesOrderService(NetSuiteConnector netsuiteConnector) {
this.netsuiteConnector = netsuiteConnector;
}
/**
* Create sales order
*/
public String createSalesOrder(SalesOrderDTO orderDTO) {
try {
SalesOrder salesOrder = new SalesOrder();
// Set entity (customer)
RecordRef entity = new RecordRef();
entity.setInternalId(orderDTO.getCustomerInternalId());
salesOrder.setEntity(entity);
// Set transaction date
salesOrder.setTranDate(Calendar.getInstance());
// Set items
SalesOrderItemList itemList = new SalesOrderItemList();
for (SalesOrderItemDTO itemDTO : orderDTO.getItems()) {
SalesOrderItem item = new SalesOrderItem();
RecordRef itemRef = new RecordRef();
itemRef.setInternalId(itemDTO.getItemInternalId());
item.setItem(itemRef);
item.setQuantity(itemDTO.getQuantity());
item.setRate(itemDTO.getRate());
itemList.getItem().add(item);
}
salesOrder.setItemList(itemList);
WriteResponse response = netsuiteConnector.addRecord(salesOrder);
return response.getBaseRef().getInternalId();
} catch (Exception e) {
throw new RuntimeException("Failed to create sales order", e);
}
}
/**
* Fulfill sales order
*/
public String fulfillSalesOrder(String salesOrderInternalId) {
try {
ItemFulfillment fulfillment = new ItemFulfillment();
// Create fulfillment from sales order
RecordRef createdFrom = new RecordRef();
createdFrom.setInternalId(salesOrderInternalId);
createdFrom.setType(RecordType.salesOrder);
fulfillment.setCreatedFrom(createdFrom);
WriteResponse response = netsuiteConnector.addRecord(fulfillment);
return response.getBaseRef().getInternalId();
} catch (Exception e) {
throw new RuntimeException("Failed to fulfill sales order", e);
}
}
}
Advanced Features
1. Async Batch Processing
@Service
public class NetSuiteBatchService {
private final NetSuiteConnector netsuiteConnector;
public NetSuiteBatchService(NetSuiteConnector netsuiteConnector) {
this.netsuiteConnector = netsuiteConnector;
}
/**
* Process multiple records in batch
*/
public BatchResult processBatch(List<Record> records, String operation) {
try {
NetSuiteService service = netsuiteConnector.createNetSuiteService();
NetSuitePortType port = service.getNetSuitePort();
Passport passport = netsuiteConnector.createPassport();
ApplicationInfo applicationInfo = netsuiteConnector.createApplicationInfo();
// Create batch request based on operation
switch (operation.toLowerCase()) {
case "add":
return processAddBatch(port, passport, applicationInfo, records);
case "update":
return processUpdateBatch(port, passport, applicationInfo, records);
default:
throw new IllegalArgumentException("Unsupported batch operation: " + operation);
}
} catch (Exception e) {
throw new RuntimeException("Batch processing failed", e);
}
}
private BatchResult processAddBatch(NetSuitePortType port, Passport passport,
ApplicationInfo appInfo, List<Record> records) {
AddListRequest addListRequest = new AddListRequest();
addListRequest.getRecord().addAll(records);
setApplicationInfo(port, appInfo);
AddListResponse response = port.addList(addListRequest, passport);
return processWriteResponseList(response.getWriteResponseList());
}
private BatchResult processWriteResponseList(WriteResponseList responseList) {
BatchResult result = new BatchResult();
for (WriteResponse response : responseList.getWriteResponse()) {
if (response.getStatus().isIsSuccess()) {
result.getSuccessRecords().add(response.getBaseRef().getInternalId());
} else {
result.getFailedRecords().put(
response.getBaseRef().getInternalId(),
response.getStatus().getStatusDetail()[0].getMessage()
);
}
}
return result;
}
public static class BatchResult {
private List<String> successRecords = new ArrayList<>();
private Map<String, String> failedRecords = new HashMap<>();
// Getters and setters
public List<String> getSuccessRecords() { return successRecords; }
public Map<String, String> getFailedRecords() { return failedRecords; }
}
}
2. Error Handling and Retry
@Component
public class NetSuiteErrorHandler {
private static final Logger logger = LoggerFactory.getLogger(NetSuiteErrorHandler.class);
/**
* Handle NetSuite SOAP faults
*/
public void handleSoapFault(Exception e) {
if (e instanceof com.netsuite.webservices.platform.faults.InsufficientPermissionFault) {
logger.error("Insufficient permissions for NetSuite operation");
throw new SecurityException("User lacks required NetSuite permissions");
} else if (e instanceof com.netsuite.webservices.platform.faults.InvalidSessionFault) {
logger.error("Invalid NetSuite session - reauthentication required");
throw new SessionExpiredException("NetSuite session expired");
} else if (e instanceof com.netsuite.webservices.platform.faults.ExceededRequestLimitFault) {
logger.warn("NetSuite request limit exceeded - implementing backoff");
throw new RateLimitException("NetSuite rate limit exceeded");
} else {
logger.error("NetSuite SOAP fault: {}", e.getMessage());
throw new RuntimeException("NetSuite operation failed", e);
}
}
/**
* Check if operation should be retried
*/
public boolean shouldRetry(Exception e, int attempt) {
if (attempt >= 3) return false;
return e instanceof com.netsuite.webservices.platform.faults.ExceededRequestLimitFault ||
e instanceof java.net.SocketTimeoutException ||
e instanceof javax.xml.ws.WebServiceException;
}
/**
* Calculate retry delay with exponential backoff
*/
public long calculateRetryDelay(int attempt) {
return (long) Math.pow(2, attempt) * 1000; // 2s, 4s, 8s
}
}
RESTlet Integration (Alternative)
RESTlet Client
@Service
public class NetSuiteRestletClient {
@Value("${netsuite.restlet.url}")
private String restletUrl;
@Value("${netsuite.account}")
private String account;
private final RestTemplate restTemplate;
public NetSuiteRestletClient(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
/**
* Call NetSuite RESTlet
*/
public <T> T callRestlet(String scriptId, String deployId,
Object payload, Class<T> responseType) {
try {
String url = String.format("%s?script=%s&deploy=%s",
restletUrl, scriptId, deployId);
HttpHeaders headers = createRestletHeaders();
HttpEntity<Object> request = new HttpEntity<>(payload, headers);
ResponseEntity<T> response = restTemplate.exchange(
url, HttpMethod.POST, request, responseType);
return response.getBody();
} catch (Exception e) {
throw new RuntimeException("RESTlet call failed", e);
}
}
private HttpHeaders createRestletHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
// Token-based authentication
String auth = account + ":" + "your-token" + ":" + "your-token-secret";
String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes());
headers.set("Authorization", "Basic " + encodedAuth);
return headers;
}
}
Best Practices
- Connection Management: Use connection pooling and proper timeout settings
- Error Handling: Implement comprehensive error handling for NetSuite-specific faults
- Batch Operations: Use batch operations for bulk data processing
- Pagination: Implement pagination for large search results
- Logging: Log all NetSuite API calls for debugging and auditing
- Performance: Cache frequently accessed reference data
- Security: Secure credentials and use token-based authentication where possible
- Monitoring: Monitor API usage and implement rate limiting
Configuration Tips
@Configuration
public class NetSuiteConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplateBuilder()
.setConnectTimeout(Duration.ofSeconds(30))
.setReadTimeout(Duration.ofSeconds(60))
.build();
}
}
This comprehensive SuiteTalk implementation provides a solid foundation for integrating Java applications with NetSuite, covering both basic CRUD operations and advanced features like batch processing and error handling.