Java runs the financial world. With over 90% of Fortune 500 companies using Java and the language holding steady at third in the TIOBE Index with an 8.71% rating, it remains the default for enterprise backend systems. Now those same systems are moving on-chain: 84% of fintech companies include blockchain in their payment infrastructure, and enterprise blockchain spending hit $19 billion in 2025. This guide shows you how to add token swap functionality to a Spring Boot application using a free REST API that requires no SDK, no API key, and no account. You send a GET request, receive executable calldata, and broadcast it to the blockchain.
What You'll Need
- Java 17+ (LTS recommended)
-
Spring Boot 3.x with
spring-boot-starter-webflux(for WebClient) - Jackson (included with Spring Boot) for JSON deserialization
- A wallet address for the
senderparameter (the API returns calldata scoped to this address) - No API key — the swap API is free and supports 46 EVM chains
Add the WebFlux starter to your pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
Step 1: Define Response DTOs
The swap API returns a JSON envelope with success, data, and timestamp fields. Map these to Java records for type-safe deserialization. The API returns BigInt values as strings to avoid precision loss, so amountIn, expectedAmountOut, and minAmountOut are all String fields.
package com.example.swap.dto;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import java.util.List;
@JsonIgnoreProperties(ignoreUnknown = true)
public record SwapResponse(
boolean success,
SwapData data,
SwapError error,
String timestamp
) {}
@JsonIgnoreProperties(ignoreUnknown = true)
public record SwapData(
String status,
TokenInfo tokenFrom,
TokenInfo tokenTo,
Double swapPrice,
Double priceImpact,
String amountIn,
String expectedAmountOut,
String minAmountOut,
List<TokenInfo> tokens,
SwapTransaction tx,
String rpcUrl,
List<String> rpcUrls
) {}
@JsonIgnoreProperties(ignoreUnknown = true)
public record TokenInfo(
String address,
String symbol,
String name,
int decimals
) {}
@JsonIgnoreProperties(ignoreUnknown = true)
public record SwapTransaction(
String from,
String to,
String data,
String value,
Long gasPrice
) {}
@JsonIgnoreProperties(ignoreUnknown = true)
public record SwapError(
String code,
String message
) {}
These records handle all three response statuses: Successful, Partial, and NoRoute. Fields absent in NoRoute responses deserialize to null safely thanks to @JsonIgnoreProperties.
Step 2: Build the Swap Service
The service layer encapsulates the HTTP call to api.swapapi.dev. Spring's WebClient handles non-blocking I/O, which matters when the upstream API takes 1-5 seconds to compute optimal routes across decentralized exchange pools. With over 17,700 companies using Spring Boot in production and Spring WebClient replacing the deprecated RestTemplate as the recommended HTTP client, this pattern fits modern Spring conventions.
package com.example.swap.service;
import com.example.swap.dto.SwapResponse;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import java.time.Duration;
@Service
public class SwapService {
private static final String BASE_URL = "https://api.swapapi.dev";
private final WebClient webClient;
public SwapService(WebClient.Builder builder) {
this.webClient = builder
.baseUrl(BASE_URL)
.build();
}
public Mono<SwapResponse> getSwapQuote(
int chainId,
String tokenIn,
String tokenOut,
String amount,
String sender,
Double maxSlippage
) {
return webClient.get()
.uri(uriBuilder -> {
var uri = uriBuilder
.path("/v1/swap/{chainId}")
.queryParam("tokenIn", tokenIn)
.queryParam("tokenOut", tokenOut)
.queryParam("amount", amount)
.queryParam("sender", sender);
if (maxSlippage != null) {
uri.queryParam("maxSlippage", maxSlippage);
}
return uri.build(chainId);
})
.retrieve()
.bodyToMono(SwapResponse.class)
.timeout(Duration.ofSeconds(15));
}
}
The 15-second timeout matches the API's recommended HTTP timeout. The maxSlippage parameter is optional and defaults to 0.005 (0.5%) server-side if omitted.
Step 3: Add Response Validation
Before exposing swap data to callers, validate the response. The API returns HTTP 200 for all three statuses (Successful, Partial, NoRoute), so you must check data.status programmatically. Stablecoins processed $4+ trillion in transaction volume in the first half of 2025 alone — validating price impact protects your users from executing swaps into illiquid pools.
package com.example.swap.service;
import com.example.swap.dto.SwapData;
import com.example.swap.dto.SwapResponse;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
public class SwapValidator {
private static final double MAX_PRICE_IMPACT = -0.05;
public static void validate(SwapResponse response) {
if (!response.success()) {
String code = response.error() != null
? response.error().code() : "UNKNOWN";
String msg = response.error() != null
? response.error().message() : "Swap request failed";
throw new SwapException(code, msg);
}
SwapData data = response.data();
if ("NoRoute".equals(data.status())) {
throw new SwapException("NO_ROUTE",
"No swap route found for this token pair");
}
if (data.priceImpact() != null
&& data.priceImpact() < MAX_PRICE_IMPACT) {
throw new SwapException("HIGH_PRICE_IMPACT",
String.format(
"Price impact %.2f%% exceeds threshold",
data.priceImpact() * 100
));
}
}
public static BigDecimal humanReadableAmount(
String rawAmount, int decimals
) {
return new BigDecimal(new BigInteger(rawAmount))
.divide(
BigDecimal.TEN.pow(decimals),
MathContext.DECIMAL128
);
}
}
package com.example.swap.service;
public class SwapException extends RuntimeException {
private final String code;
public SwapException(String code, String message) {
super(message);
this.code = code;
}
public String getCode() { return code; }
}
The humanReadableAmount method converts raw token amounts (like "2500000000") to human-readable values using the token's decimal count. USDC on Ethereum uses 6 decimals, so 2500000000 becomes 2500.0. Note that USDC and USDT on BSC use 18 decimals instead of 6 — always read the decimals field from the response rather than hardcoding.
Step 4: Create the Controller
Expose the swap functionality through a REST endpoint. This controller accepts swap parameters, calls the service, validates the response, and returns a clean DTO. With 6 in 10 Fortune 500 executives actively working on blockchain initiatives, this pattern fits enterprise integration scenarios where a backend service mediates between frontends and on-chain execution.
package com.example.swap.controller;
import com.example.swap.dto.SwapResponse;
import com.example.swap.service.SwapService;
import com.example.swap.service.SwapValidator;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
@RestController
public class SwapController {
private final SwapService swapService;
public SwapController(SwapService swapService) {
this.swapService = swapService;
}
@GetMapping("/api/swap/{chainId}")
public Mono<ResponseEntity<SwapResponse>> getSwapQuote(
@PathVariable int chainId,
@RequestParam String tokenIn,
@RequestParam String tokenOut,
@RequestParam String amount,
@RequestParam String sender,
@RequestParam(required = false) Double maxSlippage
) {
return swapService
.getSwapQuote(
chainId, tokenIn, tokenOut,
amount, sender, maxSlippage
)
.map(response -> {
SwapValidator.validate(response);
return ResponseEntity.ok(response);
});
}
}
Test it with curl — swap 1 ETH for USDC on Ethereum:
curl "http://localhost:8080/api/swap/1?tokenIn=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE&tokenOut=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48&amount=1000000000000000000&sender=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
Or swap 0.1 ETH for USDC on Arbitrum:
curl "http://localhost:8080/api/swap/42161?tokenIn=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE&tokenOut=0xaf88d065e77c8cC2239327C5EDb3A432268e5831&amount=100000000000000000&sender=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
Step 5: Handle Errors and Partial Fills
Production systems need structured error handling. The API returns four error codes (INVALID_PARAMS, UNSUPPORTED_CHAIN, RATE_LIMITED, UPSTREAM_ERROR) plus the Partial status for trades where liquidity only covers part of the requested amount. Add a global exception handler:
package com.example.swap.controller;
import com.example.swap.service.SwapException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.time.Instant;
import java.util.Map;
@RestControllerAdvice
public class SwapExceptionHandler {
@ExceptionHandler(SwapException.class)
public ResponseEntity<Map<String, Object>> handleSwapException(
SwapException ex
) {
HttpStatus status = switch (ex.getCode()) {
case "RATE_LIMITED" -> HttpStatus.TOO_MANY_REQUESTS;
case "NO_ROUTE", "HIGH_PRICE_IMPACT"
-> HttpStatus.UNPROCESSABLE_ENTITY;
default -> HttpStatus.BAD_GATEWAY;
};
return ResponseEntity.status(status).body(Map.of(
"success", false,
"error", Map.of(
"code", ex.getCode(),
"message", ex.getMessage()
),
"timestamp", Instant.now().toString()
));
}
}
For Partial responses, the amountIn and expectedAmountOut in the response reflect the partial fill, not your original request. Compare response.data().amountIn() to your input to detect partial fills. You can execute the partial swap as-is or adjust your amount and re-request.
Step 6: Add Token Address Constants
Maintain a constants class for commonly used token addresses. Using 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE as the input address swaps the chain's native gas token (ETH, BNB, MATIC) on any chain.
package com.example.swap.constants;
public final class TokenAddresses {
private TokenAddresses() {}
public static final String NATIVE_TOKEN =
"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
public static final String ETH_WETH =
"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
public static final String ETH_USDC =
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
public static final String ETH_USDT =
"0xdAC17F958D2ee523a2206206994597C13D831ec7";
public static final String ARB_WETH =
"0x82aF49447D8a07e3bd95BD0d56f35241523fBab1";
public static final String ARB_USDC =
"0xaf88d065e77c8cC2239327C5EDb3A432268e5831";
}
API Comparison
| Feature | swapapi.dev | 0x API | 1inch API |
|---|---|---|---|
| Auth required | None | API key | API key |
| Chains supported | 46 | 9 | 12 |
| Free tier | Unlimited | Paid only | Rate limited |
| Response format | {tx.to, tx.data, tx.value} |
{to, data, value} |
{tx.to, tx.data, tx.value} |
| Java SDK | REST (any HTTP client) | REST (any HTTP client) | REST (any HTTP client) |
| Rate limit | ~30 req/min per IP | Varies by plan | Varies by plan |
All three return calldata compatible with any web3 library (web3j, ethers-java). The difference: swapapi.dev requires zero setup and covers the most chains.
FAQ
How do I use a token swap API with Java Spring Boot?
Add spring-boot-starter-webflux to your project, create a WebClient pointed at https://api.swapapi.dev, and call the /v1/swap/{chainId} endpoint with tokenIn, tokenOut, amount, and sender parameters. Deserialize the JSON response into Java records with Jackson. The response includes a tx object with to, data, and value fields ready for on-chain execution via web3j.
Do I need an API key to swap tokens?
No. The swap API at swapapi.dev is free with no registration, no API key, and no account. It rate-limits at approximately 30 requests per minute per IP address.
What chains does the API support?
The API supports 46 EVM chains including Ethereum, Arbitrum, Base, Polygon, BSC, Optimism, Avalanche, zkSync Era, Linea, Scroll, Blast, Mantle, Sonic, Berachain, Monad, and MegaETH. Pass the chain ID as a path parameter (e.g., 1 for Ethereum, 42161 for Arbitrum).
How do I handle BigInt values in Java?
The API serializes BigInt values (amountIn, expectedAmountOut, minAmountOut, tx.value) as JSON strings to avoid precision loss. In Java, deserialize these as String fields and convert to BigInteger or BigDecimal when performing arithmetic. Never use long — token amounts in wei exceed its range.
Is the calldata safe to execute directly?
The calldata is scoped to the sender address you provide. Only that address can execute the transaction. Always validate priceImpact before execution (reject swaps worse than -5%), simulate with eth_call first, and submit within 30 seconds — the calldata includes a deadline.
Get Started
swapapi.dev is free, requires no API key, and supports 46 EVM chains. The OpenAPI specification documents every parameter and response field. Add a single WebClient call to your Spring Boot service and start swapping tokens in minutes.
Top comments (0)