SDK Utilization for Workday API Developers: Client Libraries for .NET and Java

Jason Walisser
Jason Walisser
Principal Consultant, Integrations
22 min read

Stop writing SOAP boilerplate by hand. Here is what you actually need to know to use the Workday client libraries in .NET and Java , project setup, authentication, making calls, and handling what goes wrong.

What This Article Is Actually About

Workday exposes a large set of SOAP-based web services , around 400+ operations across HCM, Payroll, Financials, Recruiting, and more. If you are writing custom integrations, building middleware connectors, or automating administrative tasks against a Workday tenant, you will need to call these APIs from application code.

You have two options: construct raw SOAP XML envelopes and parse the responses yourself, or use a generated client library that handles the wire protocol so you can focus on the actual business logic. This article is about the second option , specifically, how to generate and use Workday API client libraries in .NET (C#) and Java.

We will cover what Workday actually provides, how to generate strongly-typed clients from the WSDL files, how authentication works in code, and how to make real API calls without the common mistakes that cost teams days of debugging.

Prerequisite

You need access to a Workday tenant (sandbox is fine) and an Integration System User (ISU) account with the appropriate domain security permissions for the operations you intend to call. The examples in this article target Workday Web Services (WWS) v42.0, though the same approach applies to any supported version.

How Workday’s API Surface Is Structured

Workday’s web services are grouped by functional domain. Each domain has its own WSDL, its own namespace, and its own set of request/response types. You do not get one unified API , you get a collection of domain-specific services, and you interact with each one independently.

Workday Domain WSDL / Service Name
Human Capital Management Human_Resources.wsdl , covers workers, positions, organisations, job changes
Payroll (US) Payroll.wsdl , payroll runs, earnings, deductions, period schedules
Recruiting Recruiting.wsdl , job requisitions, candidates, offers, background checks
Benefits Benefits_Administration.wsdl , plans, elections, eligibility events
Time Tracking Time_Tracking.wsdl , time entries, schedules, absence requests
Financial Management Financial_Management.wsdl , ledger accounts, cost centres, journals
Talent Talent.wsdl , performance reviews, development plans, succession

 

Each WSDL is downloadable directly from your tenant at:

# WSDL URL pattern
https://{your-tenant}.workday.com/ccx/service/{tenant-name}/{service-name}/{version}?wsdl# Examples
https://wd2-impl-services1.workday.com/ccx/service/acmecorp/Human_Resources/v42.0?wsdl
https://wd2-impl-services1.workday.com/ccx/service/acmecorp/Payroll/v42.0?wsdl

 

The tenant name appears in the URL path. The version string (v42.0) controls which WSDL schema you get , Workday increments this with each release twice per year. Picking your version intentionally matters: if you generate your client against v42.0 and then change nothing, your code keeps working even when the tenant is updated to a newer release, because Workday maintains backwards compatibility per version endpoint.

Ready to Accelerate Workday API Development with the Right SDK Strategy?

From REST/SOAP API authentication and Workday Studio integrations to custom SDK tooling, Sama Integrations helps your team build faster, reduce errors, and ship reliable Workday solutions. Let's streamline your API development workflow.

Authentication: Getting This Right Before Anything Else

Every Workday API call requires authentication. Workday supports three authentication mechanisms, and which one you use depends on what you are building:

1. Username / Password (WS-Security UsernameToken)

This is the simplest option and the one most integrations start with. You include a WS-Security header in the SOAP envelope with the ISU credentials. The username format matters: it must include the tenant name as a suffix, separated by an @ sign.

// .NET , setting up WS-Security UsernameToken
// Username format: integrationuser@tenantname
// NOT just: integrationuservar binding = new BasicHttpsBinding();
binding.Security.Mode = BasicHttpsSecurityMode.Transport;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;var endpoint = new EndpointAddress(
“https://wd2-impl-services1.workday.com/ccx/service/acmecorp/Human_Resources/v42.0”
);var client = new Human_ResourcesPortClient(binding, endpoint);

// Apply WS-Security header via custom endpoint behaviour (see below)
// Username MUST be: username@tenantname
client.ClientCredentials.UserName.UserName = “isvc_hrfeed@acmecorp”;
client.ClientCredentials.UserName.Password = “your_secure_password”;

 

One thing that catches developers out: the standard WCF/BasicHttpBinding in .NET does not include a WS-Security SOAP header by default , it uses HTTP Basic Auth instead. Workday expects the credentials inside the SOAP envelope, not in the HTTP Authorization header. You need a custom message inspector or a WSHttpBinding with the appropriate security mode. We cover this in the .NET section below.

2. OAuth 2.0 (Recommended for New Integrations)

Workday supports OAuth 2.0 with the Client Credentials flow for server-to-server integrations. This is the right choice if you are building something that will run long-term in production, because it avoids embedding a password in your configuration and supports token refresh without human intervention.

The OAuth token endpoint is tenant-specific:

POST https://{your-tenant}.workday.com/ccx/oauth2/{tenant-name}/token
Content-Type: application/x-www-form-urlencodedgrant_type=client_credentials
&client_id={your_client_id}
&client_secret={your_client_secret}

 

The access token returned is a Bearer token. For SOAP API calls, you attach it as a WS-Security BinarySecurityToken in the SOAP header , Workday does not use HTTP Bearer headers for SOAP services. The Java section below shows the exact header structure.

3. X.509 Certificate Authentication

Certificate-based auth is available but is mainly used by Workday’s own Studio integrations and by enterprise middleware platforms that have a Workday connector. For custom .NET or Java code, username/password or OAuth 2.0 covers most use cases.

Generating the .NET Client Library

Step 1 , Download the WSDL

Download the WSDL file for the service you need. You can let Visual Studio fetch it via URL, but downloading it locally first is better practice , it means your build does not depend on Workday tenant availability, and you have a versioned copy that you can check into source control.

# Download WSDL using curl (or just open the URL in a browser and Save As)
curl -o Human_Resources_v42.wsdl \
“https://wd2-impl-services1.workday.com/ccx/service/acmecorp/Human_Resources/v42.0?wsdl”# The WSDL imports an XSD schema file from the same server , you may need
# to download that too if you want a fully offline build:
curl -o Human_Resources_v42.xsd \
“https://wd2-impl-services1.workday.com/ccx/service/acmecorp/Human_Resources/v42.0?xsd”

Step 2 , Generate C# Client with dotnet-svcutil

The modern tool for generating WCF clients from WSDL in .NET Core / .NET 5+ is dotnet-svcutil. Do not use the Visual Studio ‘Add Service Reference’ wizard for production code , it hides too much and makes the generated code harder to version-control.

# Install dotnet-svcutil globally (one-time setup)
dotnet tool install –global dotnet-svcutil# Generate the client proxy from your downloaded WSDL
dotnet-svcutil Human_Resources_v42.wsdl \
–outputDir ./Generated \
–outputFile HumanResourcesClient \
–namespace “*,Sama.Workday.HumanResources” \
–sync# This produces:
# ./Generated/HumanResourcesClient.cs , generated proxy classes
# ./Generated/dotnet-svcutil.params.json , params file for regeneration

The --namespace flag maps the generated types into your own namespace rather than a generic proxy namespace. The --sync flag generates synchronous method wrappers alongside the async ones , useful if you are integrating into an older codebase that does not use async/await throughout.

Step 3 , Add Required NuGet Packages

# Core WCF packages for .NET 6+
dotnet add package System.ServiceModel.Http
dotnet add package System.ServiceModel.Security# For WS-Security UsernameToken support
dotnet add package CoreWCF.Http

Step 4 , Making Your First Call (.NET)

Here is a complete, working example that calls Get_Workers to retrieve a single worker record by Employee ID. This is one of the most commonly used operations in the Human Resources service.

using System.ServiceModel;
using System.ServiceModel.Security;
using Sama.Workday.HumanResources;public class WorkdayHrClient
{
private readonly string _tenantUrl;
private readonly string _username; // must be user@tenantname
private readonly string _password;public WorkdayHrClient(string tenantUrl, string username, string password)
{
_tenantUrl = tenantUrl;
_username = username;
_password = password;
}private Human_ResourcesPortClient BuildClient()
{
// Use BasicHttpsBinding , Workday SOAP runs over HTTPS only
var binding = new BasicHttpsBinding(BasicHttpsSecurityMode.Transport)
{
MaxReceivedMessageSize = 10_000_000, // 10 MB , increase for large responses
SendTimeout = TimeSpan.FromSeconds(120),
ReceiveTimeout = TimeSpan.FromSeconds(120)
};

var endpoint = new EndpointAddress(_tenantUrl);
var client = new Human_ResourcesPortClient(binding, endpoint);

// CRITICAL: Workday expects WS-Security in the SOAP header.
// This requires a custom endpoint behaviour , the built-in
// ClientCredentials does not produce the right header structure.
client.Endpoint.EndpointBehaviors.Add(
new WorkdayUsernameTokenBehaviour(_username, _password)
);

return client;
}

public async Task<Get_Workers_ResponseType> GetWorkerAsync(string employeeId)
{
var client = BuildClient();

var request = new Get_Workers_RequestType
{
version = “v42.0”,
Request_Criteria = new Worker_Request_CriteriaType
{
Worker_Reference = new[]
{
new WorkerObjectType
{
ID = new[]
{
new WorkerObjectIDType
{
type = “Employee_ID”,
Value = employeeId
}
}
}
}
},
Response_Filter = new Response_FilterType
{
Count = 1,
CountSpecified = true
},
Response_Group = new Worker_Response_GroupType
{
Include_Personal_Information = true,
Include_Personal_InformationSpecified = true,
Include_Employment_Information = true,
Include_Employment_InformationSpecified = true
}
};

try
{
var response = await client.Get_WorkersAsync(
new Get_WorkersInput(null, request)
);
return response.Get_Workers_Response;
}
catch (FaultException<Validation_FaultType> fault)
{
// Workday returns structured validation faults , catch them specifically
var errors = string.Join(“; “,
fault.Detail.Validation_Error.Select(e => e.Message));
throw new WorkdayApiException(
$“Workday validation fault for worker {employeeId}: {errors}”, fault);
}
catch (FaultException ex)
{
throw new WorkdayApiException(
$“SOAP fault calling Get_Workers: {ex.Message}”, ex);
}
finally
{
// Always close or abort , never let a faulted channel sit open
if (client.State == CommunicationState.Faulted)
client.Abort();
else
await client.CloseAsync();
}
}
}

Ready to Accelerate Workday API Development with the Right SDK Strategy?

From REST/SOAP API authentication and Workday Studio integrations to custom SDK tooling, Sama Integrations helps your team build faster, reduce errors, and ship reliable Workday solutions. Let's streamline your API development workflow.

The WS-Security Header Problem in .NET

WorkdayUsernameTokenBehaviour referenced above is a custom IEndpointBehavior / IClientMessageInspector that injects a wsse:Security header into every outbound SOAP message. Workday validates this header strictly , the password must be sent as PasswordText (not PasswordDigest), and the namespace prefix must match what Workday expects. If you get a SOAP fault with code E:AuthenticationError and no other detail, this header is almost certainly the issue.

 

Below is the custom message inspector that generates the correct WS-Security header:

using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.Xml;public class WorkdayUsernameTokenBehaviour : IEndpointBehavior
{
private readonly string _username;
private readonly string _password;public WorkdayUsernameTokenBehaviour(string username, string password)
{
_username = username;
_password = password;
}public void ApplyClientBehavior(ServiceEndpoint ep, ClientRuntime cr)
=> cr.ClientMessageInspectors.Add(new UsernameTokenInspector(_username, _password));

// Other IEndpointBehavior methods are no-ops for this use case
public void AddBindingParameters(ServiceEndpoint ep, BindingParameterCollection bp) { }
public void ApplyDispatchBehavior(ServiceEndpoint ep, EndpointDispatcher ed) { }
public void Validate(ServiceEndpoint ep) { }
}

public class UsernameTokenInspector : IClientMessageInspector
{
private const string WssNs = “http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd”;
private const string PwType = “http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText”;

private readonly string _username;
private readonly string _password;

public UsernameTokenInspector(string username, string password)
{
_username = username;
_password = password;
}

public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
var header = MessageHeader.CreateHeader(
“Security”, WssNs,
new UsernameTokenBody(_username, _password),
new UsernameTokenSerializer()
);
request.Headers.Add(header);
return null;
}

public void AfterReceiveReply(ref Message reply, object correlationState) { }
}

// Serialiser writes the exact XML structure Workday expects:
// <wsse:Security>
// <wsse:UsernameToken>
// <wsse:Username>user@tenant</wsse:Username>
// <wsse:Password Type=”…#PasswordText”>password</wsse:Password>
// </wsse:UsernameToken>
// </wsse:Security>

 

Generating the Java Client Library

Step 1 , Generate with wsimport (JAX-WS)

Java’s built-in JAX-WS tooling generates client stubs from a WSDL via wsimport. For Java 11+, wsimport was removed from the JDK standard distribution , you need to pull it in via the jakarta.xml.ws dependency or use the jaxws-maven-plugin.

# Java 8 , wsimport ships with the JDK
wsimport -keep -verbose \
-p com.samaintegrations.workday.hr \
-s ./src/main/java \
-b ./bindings.xml \
https://wd2-impl-services1.workday.com/ccx/service/acmecorp/Human_Resources/v42.0?wsdl# Java 11+ , use the jaxws-maven-plugin instead (see pom.xml below)
# or download jaxws-ri and use its wsimport directly:
/path/to/jaxws-ri/bin/wsimport -keep -p com.samaintegrations.workday.hr \
-s ./src/main/java Human_Resources_v42.wsdl

 

The -keep flag preserves the generated .java source files so you can inspect and version-control them. The -p flag sets the Java package for generated classes.

Step 2 , Maven Configuration (Recommended)

For a team project, using the Maven plugin is cleaner than running wsimport manually. Add this to your pom.xml:

<dependencies>
<!– JAX-WS API and runtime for Java 11+ –>
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-rt</artifactId>
<version>4.0.2</version>
</dependency>
<!– WS-Security handler –>
<dependency>
<groupId>com.sun.xml.wss</groupId>
<artifactId>xws-security</artifactId>
<version>3.0</version>
</dependency>
</dependencies><build>
<plugins>
<plugin>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-maven-plugin</artifactId>
<version>4.0.2</version>
<executions>
<execution>
<id>generate-hr-client</id>
<goals><goal>wsimport</goal></goals>
<configuration>
<!– Point to your locally saved WSDL, not the live URL –>
<wsdlFiles>
<wsdlFile>${project.basedir}/src/main/wsdl/Human_Resources_v42.wsdl</wsdlFile>
</wsdlFiles>
<packageName>com.samaintegrations.workday.hr</packageName>
<sourceDestDir>${project.basedir}/src/main/java</sourceDestDir>
<keep>true</keep>
<verbose>true</verbose>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

 

Step 3 , Making Your First Call (Java)

Here is the equivalent Get_Workers call in Java, with WS-Security handled via a JAX-WS SOAPHandler:

package com.samaintegrations.workday.hr;import javax.xml.ws.BindingProvider;
import javax.xml.ws.handler.Handler;
import java.util.List;
import java.util.Map;public class WorkdayHrService {private static final String ENDPOINT =
“https://wd2-impl-services1.workday.com/ccx/service/acmecorp/Human_Resources/v42.0”;

private final String username; // user@tenantname
private final String password;

public WorkdayHrService(String username, String password) {
this.username = username;
this.password = password;
}

private Human_ResourcesPort buildPort() {
// The generated service class is named after the WSDL service element
Human_Resources service = new Human_Resources();
Human_ResourcesPort port = service.getHuman_ResourcesPort();

BindingProvider bp = (BindingProvider) port;

// Override the WSDL-defined endpoint with your tenant’s URL
bp.getRequestContext().put(
BindingProvider.ENDPOINT_ADDRESS_PROPERTY, ENDPOINT);

// Increase timeout defaults , Workday report-style calls can be slow
bp.getRequestContext().put(“com.sun.xml.ws.request.timeout”, 120_000);
bp.getRequestContext().put(“com.sun.xml.ws.connect.timeout”, 30_000);

// Attach WS-Security handler
List<Handler> handlers = bp.getBinding().getHandlerChain();
handlers.add(new WorkdaySecurityHandler(username, password));
bp.getBinding().setHandlerChain(handlers);

return port;
}

public GetWorkersResponseType getWorker(String employeeId) throws Exception {
Human_ResourcesPort port = buildPort();

GetWorkersRequestType request = new GetWorkersRequestType();
request.setVersion(“v42.0”);

// Build criteria , filter to a specific employee
WorkerRequestCriteriaType criteria = new WorkerRequestCriteriaType();
WorkerObjectType workerRef = new WorkerObjectType();
WorkerObjectIDType workerId = new WorkerObjectIDType();
workerId.setType(“Employee_ID”);
workerId.setValue(employeeId);
workerRef.getID().add(workerId);
criteria.getWorkerReference().add(workerRef);
request.setRequestCriteria(criteria);

// Response filter , limit to 1 record
ResponseFilterType filter = new ResponseFilterType();
filter.setCount(new java.math.BigDecimal(1));
request.setResponseFilter(filter);

// Response group , include personal and employment data
WorkerResponseGroupType group = new WorkerResponseGroupType();
group.setIncludePersonalInformation(true);
group.setIncludeEmploymentInformation(true);
request.setResponseGroup(group);

try {
return port.getWorkers(request);
} catch (ValidationFaultMsg fault) {
// Workday validation faults carry structured detail
String errors = fault.getFaultInfo()
.getValidationError()
.stream()
.map(e -> e.getMessage())
.collect(java.util.stream.Collectors.joining(“; “));
throw new RuntimeException(
“Workday validation fault for worker “ + employeeId + “: “ + errors, fault);
}
}
}

Ready to Accelerate Workday API Development with the Right SDK Strategy?

From REST/SOAP API authentication and Workday Studio integrations to custom SDK tooling, Sama Integrations helps your team build faster, reduce errors, and ship reliable Workday solutions. Let's streamline your API development workflow.

The WS-Security SOAPHandler (Java)

The SOAPHandler implementation injects the WS-Security UsernameToken header into every outbound message. This is the cleanest way to handle it in JAX-WS , the handler is attached at the port level and fires automatically on every call.

package com.samaintegrations.workday.hr;import javax.xml.namespace.QName;
import javax.xml.soap.*;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
import java.util.Set;public class WorkdaySecurityHandler implements SOAPHandler<SOAPMessageContext> {private static final String WSSE_NS =
“http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd”;
private static final String PW_TYPE =
“http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText”;

private final String username;
private final String password;

public WorkdaySecurityHandler(String username, String password) {
this.username = username;
this.password = password;
}

@Override
public boolean handleMessage(SOAPMessageContext ctx) {
Boolean outbound = (Boolean) ctx.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
if (!outbound) return true; // only process outbound messages

try {
SOAPMessage message = ctx.getMessage();
SOAPEnvelope envelope = message.getSOAPPart().getEnvelope();
SOAPHeader header = envelope.getHeader();
if (header == null) header = envelope.addHeader();

// <wsse:Security>
SOAPElement security = header.addChildElement(
“Security”, “wsse”, WSSE_NS);
security.addAttribute(
envelope.createName(“mustUnderstand”, “soap”,
envelope.getNamespaceURI()),
“1”);

// <wsse:UsernameToken>
SOAPElement token = security.addChildElement(
“UsernameToken”, “wsse”, WSSE_NS);

// <wsse:Username>user@tenantname</wsse:Username>
SOAPElement user = token.addChildElement(“Username”, “wsse”, WSSE_NS);
user.addTextNode(username);

// <wsse:Password Type=”…#PasswordText”>password</wsse:Password>
SOAPElement pw = token.addChildElement(“Password”, “wsse”, WSSE_NS);
pw.addAttribute(envelope.createName(“Type”), PW_TYPE);
pw.addTextNode(password);

message.saveChanges();

} catch (SOAPException e) {
throw new RuntimeException(“Failed to attach WS-Security header”, e);
}

return true;
}

@Override public boolean handleFault(SOAPMessageContext ctx) { return true; }
@Override public void close(MessageContext ctx) { }
@Override public Set<QName> getHeaders() { return null; }
}

 

Pagination: The Part Most Examples Skip

Workday’s API uses cursor-based pagination. When a query can return more records than the configured response limit (default is 100, max is typically 999), the response includes a total results count and a next-page token. If you do not handle this, you will silently miss data , and the call will not error, it will just return a partial result set.

Both the .NET and Java clients handle pagination the same way conceptually. Here is the pattern in Java (the .NET equivalent follows the same structure):

public List<WorkerType> getAllWorkers() throws Exception {
Human_ResourcesPort port = buildPort();
List<WorkerType> allWorkers = new ArrayList<>();GetWorkersRequestType request = new GetWorkersRequestType();
request.setVersion(“v42.0”);ResponseFilterType filter = new ResponseFilterType();
filter.setCount(new java.math.BigDecimal(200)); // request 200 records per pageint page = 1;
int totalPages;

do {
filter.setPage(new java.math.BigDecimal(page));
request.setResponseFilter(filter);

GetWorkersResponseType response = port.getWorkers(request);
ResponseResultsType results = response.getResponseResults();

// Total pages tells you when to stop
totalPages = results.getTotalPages().intValue();

if (response.getResponseData() != null) {
allWorkers.addAll(response.getResponseData().getWorker());
}

System.out.printf(“Page %d of %d , retrieved %d workers so far%n”,
page, totalPages, allWorkers.size());

page++;

} while (page <= totalPages);

return allWorkers;
}

 

Performance Note

Retrieving a full worker population for a large enterprise (10,000+ employees) using Get_Workers with all response groups enabled can take several minutes and produce very large XML payloads. In practice, use the most targeted request criteria possible and only include the response groups you actually need. For bulk data extraction, Workday’s RaaS (Report-as-a-Service) API is often faster than paginated Get_Workers calls.

 

Error Handling That Is Actually Useful

Workday SOAP faults come in two flavours that you need to handle differently.

Validation Faults

These are business rule violations , missing required fields, invalid reference IDs, constraint violations. Workday returns them as structured SOAP faults with a detail element containing one or more Validation_Error objects. Each error has a message, an xpath-like field reference, and a detail code. Always catch these specifically , they give you exactly what is wrong and where.

// .NET , catch validation faults specifically
catch (FaultException<Validation_FaultType> fault)
{
foreach (var error in fault.Detail.Validation_Error)
{
// error.Message , human-readable description
// error.Detail , technical detail, often includes field path
// error.SOAP_Fault_Code , structured code for programmatic handling
Console.Error.WriteLine(
$” Field: {error.Detail}, Message: {error.Message}”);
}
}// Java , equivalent
catch (ValidationFaultMsg fault) {
for (ValidationErrorType error : fault.getFaultInfo().getValidationError()) {
System.err.printf(” Field: %s, Message: %s%n”,
error.getDetail(), error.getMessage());
}
}

 

Processing Faults

These are infrastructure or configuration errors , authentication failures, invalid tenant URL, service unavailable, or a malformed request that did not pass schema validation before reaching the business logic layer. These come back as generic SOAP faults with a fault code and fault string but no structured detail.

HTTP-Level Failures

Network timeouts, TLS handshake failures, and 503 responses from Workday’s load balancer are not SOAP faults , they throw as transport exceptions. Your retry logic needs to handle these separately. Do not retry on validation faults (the same request will fail again); do retry with exponential backoff on transport errors and 5xx responses.

// .NET , full exception handling hierarchy
try
{
var result = await client.Get_WorkersAsync(new Get_WorkersInput(null, request));
return result.Get_Workers_Response;
}
catch (FaultException<Validation_FaultType> vf)
{
// Business rule violation , do not retry
LogValidationErrors(vf.Detail.Validation_Error);
throw new WorkdayValidationException(vf);
}
catch (FaultException sf)
{
// SOAP fault , may be auth, may be transient
if (sf.Code?.Name?.Contains(“AuthenticationError”) == true)
throw new WorkdayAuthException(“Authentication failed , check ISU credentials”, sf);
throw new WorkdayApiException($“SOAP fault: {sf.Message}”, sf);
}
catch (CommunicationException ce)
{
// Transport layer , candidate for retry with backoff
throw new WorkdayTransportException(“Transport error , network or TLS issue”, ce);
}
catch (TimeoutException te)
{
// Request timed out , candidate for retry
throw new WorkdayTransportException(“Request timed out”, te);
}

 

Practical SDK Patterns for Production Code

Connection Pooling and Client Reuse

In .NET, WCF channel objects are not thread-safe and cannot be shared across threads. Create a new channel per logical operation, or use a thread-local pattern. In Java, the generated Port proxy is also not thread-safe , the same rule applies.

For high-throughput scenarios, consider implementing a simple factory that creates a pre-configured client on demand rather than passing client instances around:

// .NET , simple thread-safe factory pattern
public class WorkdayClientFactory
{
private readonly WorkdayConfig _config;public WorkdayClientFactory(WorkdayConfig config) => _config = config;// Each call creates a fresh channel , cheap to create, must be closed after use
public Human_ResourcesPortClient CreateHrClient()
{
var binding = BuildBinding();
var endpoint = new EndpointAddress(_config.HrEndpointUrl);
var client = new Human_ResourcesPortClient(binding, endpoint);
client.Endpoint.EndpointBehaviors.Add(
new WorkdayUsernameTokenBehaviour(_config.Username, _config.Password));
return client;
}private static BasicHttpsBinding BuildBinding() =>
new BasicHttpsBinding(BasicHttpsSecurityMode.Transport)
{
MaxReceivedMessageSize = 10_000_000,
SendTimeout = TimeSpan.FromSeconds(120),
ReceiveTimeout = TimeSpan.FromSeconds(120)
};
}

Logging Outbound SOAP Envelopes

During development, being able to see the exact SOAP XML that is going to Workday saves significant debugging time. In .NET, you can attach a message inspector that logs the full envelope before it is sent. In Java, enabling the com.sun.xml.ws.transport.http.client.HttpTransportPipe logger at FINEST level dumps full SOAP traffic to stdout.

# Java , enable full SOAP message logging via JVM argument
java -Dcom.sun.xml.ws.transport.http.client.HttpTransportPipe.dump=true \
-Dcom.sun.xml.ws.transport.http.HttpAdapter.dump=true \
-jar your-application.jar# WARNING: This logs credentials in plaintext , development/debug only.
# Disable via a system property check in your production startup config.

Handling the Generated Class Explosion

Workday WSDLs are large. The Human Resources WSDL alone generates over 1,500 Java classes or C# types when fully expanded. A few practices keep this manageable:

  • Keep generated code in a separate module or project (e.g., workday-hr-client as a Maven module or a separate .NET class library project). Never mix it with your application code.
  • Add the generated source directory to .gitignore and regenerate from the WSDL during the build rather than committing thousands of generated files. The WSDL itself is what you version-control.
  • Write thin wrapper classes around the generated types that only expose the fields your application actually uses. This isolates your application code from WSDL changes , when Workday releases a new API version, only the wrapper needs to update, not every call site.
Ready to Accelerate Workday API Development with the Right SDK Strategy?

From REST/SOAP API authentication and Workday Studio integrations to custom SDK tooling, Sama Integrations helps your team build faster, reduce errors, and ship reliable Workday solutions. Let's streamline your API development workflow.

Choosing Between .NET and Java for Your Workday Integration

Both ecosystems produce functionally equivalent Workday clients. The decision should be based on your existing stack, not on any inherent capability difference. That said, there are practical nuances worth knowing:

Consideration Notes
.NET (C#) WCF’s channel lifecycle management requires more explicit handling than JAX-WS. The dotnet-svcutil tooling is more actively maintained than the JAX-WS reference implementation for modern .NET versions. ASP.NET hosted services integrate well for webhook and callback patterns.
Java The jaxws-ri runtime is mature and stable. Spring Boot integration is straightforward , inject your factory as a @Bean and let Spring manage the lifecycle. Apache CXF is a popular alternative to the reference JAX-WS implementation and adds interceptor chains that simplify WS-Security configuration.
Apache CXF (Java) If you are already using Apache CXF in your project, its WS-Security interceptors are considerably easier to configure than a raw SOAPHandler. CXF’s WSS4JOutInterceptor handles the UsernameToken header with three lines of configuration.
Version upgrades When Workday releases a new API version, regenerate the client stubs from the new WSDL. Test all existing call sites , Workday generally adds rather than removes fields, but required fields can change between major versions.
Large response handling Both platforms struggle with very large XML payloads (100 MB+) when using the default DOM parser. For bulk extraction patterns, consider switching to a StAX-based streaming approach rather than loading the full response into memory.

Quick Reference: Common Workday API Operations

Below is a reference of the operations development teams use most frequently, mapped to their service and the generated method name in both .NET and Java.

Operation Service / Generated Method
Get worker by Employee ID Human_Resources / getWorkers , filter by Worker_Reference with type Employee_ID
Get all active workers Human_Resources / getWorkers , filter by Active_Status_Only = true
Hire a new employee Human_Resources / hire , requires position reference and personal data
Terminate an employee Human_Resources / terminateEmployee , requires reason reference and effective date
Change job / transfer Human_Resources / changeJob , position and organisation changes
Update personal info Human_Resources / changePersonalInformation , name, address, contact
Get open job requisitions Recruiting / getJobRequisitions , filter by status
Get payroll runs Payroll / getPayrollRuns , filter by company and period
Submit time entry Time_Tracking / importTimeBlocks , bulk import pattern
Get benefit elections Benefits_Administration / getWorkerBenefitElections , by worker reference

 

Building a custom Workday integration?

If you are building a .NET or Java client against Workday’s API and running into WS-Security issues, pagination gaps, or WSDL versioning problems, our Workday Integration Services team has dealt with all of these in production environments across dozens of tenant configurations.

If your integration is already live but behaving unpredictably , wrong data, missed records, authentication errors that appear randomly , our Support & Troubleshooting service covers SDK-level diagnostics and API call analysis.

For teams at the design stage deciding how to connect Workday to their application stack, our Custom Integration Development practice builds production-ready .NET and Java Workday clients from the ground up.

 

;