After focusing my career on Fintech, I decided it was time to understand how to introduce web3 into an existing Java-based RESTful API
![](https://cdn-images-1.medium.com/max/1024/1*FgmW5KuJb9wNWkHJtMS5Nw.jpeg)
It’s funny how a seemingly meaningless event in one’s life can lead to an unexpected change. For me, one of those events happened in July 2021 when my flight home was delayed so much that I paid for my first Uber.
I was so impressed by the experience that I wrote about the underlying payment processor in my “Build an Uber-Like Payment Service Using Spring Boot” article. I continued to dive deeper into the Marqeta platform, writing about how to create a rewards card and even an article about building a buy now, pay later payment solution.
At that point, I started talking with Marqeta about becoming a part of their engineering team. About six weeks later, I hung up my consultant hat and started as a full-time employee of a Fintech-based market disruptor that has created the world’s first modern card issuing platform.
It’s been an exciting journey along a challenging, winding road. I love it!
In my spare time, I have continued to dive deeper into the world of web3, and I am always eager to learn more about Fintech too. So, exploring where web3 and financial services intersect is natural for me!
For this article, I wanted to see how easy it is for a web2 developer to use Java to perform some Fintech transactions using web3 and USDC over the Ethereum blockchain. I plan to use the Circle Java SDK, Java 17, and a Spring Boot 3 RESTful API.
About Circle and USDC
We can’t talk about USDC (USD Coin) without first talking about Circle since they are the company that manages the stablecoin. Circle was founded in 2013 (around the same time as Marqeta), focusing on peer-to-peer payments.
As a private company, Circle has reached some key milestones:
- Circle Pay (mobile payments) allowed users to hold, send, and receive traditional fiat currencies. Later, they became a Bitcoin digital wallet service — allowing consumers to buy/sell Bitcoins.
- By 2015, a Circle account could be funded in USD via US-issued Visa and Mastercard credit and debit cards. Similar functionality was extended to Europe a year later.
- In 2018, Circle raised venture capital to establish USDC with the promise that fully reserved assets back its coin.
USDC is a digital (crypto) currency pegged to (and backed by) the US dollar. This means that 1 USDC is always equal to 1 US dollar.
So why USDC then, instead of US dollars?
- USDC can be sent, in any amount, for just a couple of dollars.
- USDC can be sent globally and nearly instantly.
- Because USDC is a digital currency, it can be sent, received, and settled anytime. You don’t have to worry about banking hours.
- And since USDC is digital — there’s an API and SDK to work with it (which is what we’ll explore in this article).
A private entity issues USDC (it’s not a central bank digital currency) and is primarily available as an Ethereum ERC-20 token.
With USDC, Circle aims to be a disruptor by allowing customers to avoid bank hours, processing times, and costly fees — all while building a business with the USDC digital currency.
Using USDC to Make and Receive Payments with Java? Yes, Please!
For this publication, let’s assume your service caters to those interested in using USDC to perform financial transactions. From what you’ve observed, the current fees associated with using the Circle platform will allow your service to still be profitable.
Still trying to keep this scenario realistic, let’s assume that your infrastructure has its roots in Java-based microservices written in Spring Boot. Your infrastructure supports a proven infrastructure of web2 applications and services.
To keep things simple, we will introduce a new service — called circle-sdk-demo — which will act as the integration with the Circle platform. As a result, we will plan to explore the Java SDK by Circle — which is still currently in a beta state.
Demonstration
Before getting started with the service, we need to navigate to the Circle’s Developer site and obtain an API key for sandbox use:
https://app-sandbox.circle.com/signup/sandbox
All I had to do was fill out this form:
![](https://cdn-images-1.medium.com/max/1024/1*sUogWB74cOdRy110DX9lDw.png)
And then, I received an API key for their sandbox:
![](https://cdn-images-1.medium.com/max/1024/1*5ekIeRwpMyB7Q0STjR1ulA.png)
Keep the API key value handy, as it will be required before we start our new service.
Creating a Spring Boot service
For this demonstration, I plan to use Spring Boot 3 for the first time. While I used the Spring Initializr in IntelliJ IDEA, the results of my dependencies are noted in the following pom.xml file for the circle-sdk-demo service:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.circle</groupId>
<artifactId>circle</artifactId>
<version>0.1.0-beta.3</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
For this demonstration, I had to manually add the circle and commons-lang3 artifacts.
Externalizing Circle Configuration
To externalize the API key value, the CircleConfigurationProperties class was created:
@Data
@Validated
@Configuration("circleConfigurationProperties")
@ConfigurationProperties("circle")
public class CircleConfigurationProperties {
@NotBlank()
private String apiKey;
}
For reference, here is a copy of my application.yaml configuration file:
circle:
api-key:
server:
port: 8585
error:
include-message: always
You can use this link to determine how to externalize the population of the circle.api-key value with the API key created above. I do not recommend directly populating the API key value into the configuration file above.
Next, I created the following CircleConfiguration:
@Slf4j
@RequiredArgsConstructor
@Component
public class CircleConfiguration {
private final CircleConfigurationProperties circleConfigurationProperties;
@Bean
public Circle getCircle() {
log.info("=======================");
log.info("Initializing Circle SDK");
log.info("=======================");
log.info("basePath={}", Circle.SANDBOX_BASE_URL);
log.info("circle.api-key={}",
SecurityUtils.maskCredentialsRevealPrefix(
circleConfigurationProperties.getApiKey(), 7, '*'));
Circle circle = Circle.getInstance()
.setBasePath(Circle.SANDBOX_BASE_URL)
.setApiKey(circleConfigurationProperties.getApiKey());
log.info("circle={}", circle);
log.info("==================================");
log.info("Circle SDK Initialization Complete");
log.info("==================================");
return circle;
}
}
With the API key value set, starting the circle-sdk-demo Spring Boot 3.x service appears as shown below:
![](https://cdn-images-1.medium.com/max/1024/1*kGh2_rZNykxCo81CzxA6UQ.png)
Adding a Circle Integration Service
To communicate with the Circle platform, I created a simple pass-through service called CircleIntegrationService:
@Slf4j
@Service
public class CircleIntegrationService {
private final BalancesApi balancesApi = new BalancesApi();
private final CryptoPaymentIntentsApi cryptoPaymentIntentsApi = new CryptoPaymentIntentsApi();
public ListBalancesResponse getBalances() throws ApiException {
ListBalancesResponse listBalancesResponse = balancesApi.listBalances();
log.info("listBalancesResponse={}", listBalancesResponse);
return listBalancesResponse;
}
public CreatePaymentIntentResponse createPayment(SimplePayment simplePayment) throws ApiException {
CreatePaymentIntentRequest createPaymentIntentRequest = new CreatePaymentIntentRequest(new PaymentIntentCreationRequest()
.idempotencyKey(UUID.randomUUID())
.amount(
new CryptoPaymentsMoney()
.amount(simplePayment.getAmount())
.currency(simplePayment.getCurrency())
)
.settlementCurrency(simplePayment.getSettlementCurrency())
.paymentMethods(
Collections.singletonList(
new PaymentMethodBlockchain()
.chain(Chain.ETH)
.type(PaymentMethodBlockchain.TypeEnum.BLOCKCHAIN)
)
));
CreatePaymentIntentResponse createPaymentIntentResponse = cryptoPaymentIntentsApi.createPaymentIntent(createPaymentIntentRequest);
log.info("createPaymentIntentResponse={}", createPaymentIntentResponse);
return createPaymentIntentResponse;
}
public GetPaymentIntentResponse getPayment(String id) throws ApiException {
UUID paymentIntentId = UUID.fromString(id);
log.info("paymentIntentId={} from id={}", paymentIntentId, id);
GetPaymentIntentResponse getPaymentIntentResponse = cryptoPaymentIntentsApi.getPaymentIntent(paymentIntentId);
log.info("getPaymentIntentResponse={}", getPaymentIntentResponse);
return getPaymentIntentResponse;
}
}
This service allows the following functionality to be performed:
- Obtain a list of balances for my API key
- Create a new payment
- Get an existing payment by ID
Creating RESTful URIs
In the example scenario, the circle-sdk-demo will act as middleware between my existing services and the Circle platform. Next, basic controllers were created for the following URIs:
- GET /balances
- POST /payments
- GET /payments/{id}
For this example, I simply created the BalancesController and PaymentsController classes to house these URIs. A more realistic design would employ an API First approach, similar to what I noted in my “Exploring the API-First Design Pattern” publication.
circle-sdk-demo Service In Action
With the circle-sdk-demo service running, I could perform some cURL commands against my local service, which interacted with the Circle platform via the Java SDK.
Getting a list of balances:
curl --location 'localhost:8585/balances'
Results in the following payload response from Circle:
{
"data": {
"available": [],
"unsettled": []
}
}
Creating a payment:
curl --location 'localhost:8585/payments'
--header 'Content-Type: application/json'
--data '{
"currency" : "USD",
"amount" : "1.67",
"settlement_currency": "USD"
}'
Results in the following payload response from Circle:
{
"data": {
"id": "60b9ff8b-f28c-40cf-9a1c-207d12a5350b",
"amount": {
"amount": "1.67",
"currency": "USD"
},
"amountPaid": {
"amount": "0.00",
"currency": "USD"
},
"amountRefunded": {
"amount": "0.00",
"currency": "USD"
},
"settlementCurrency": "USD",
"paymentMethods": [
{
"type": "blockchain",
"chain": "ETH",
"address": null
}
],
"fees": null,
"paymentIds": [],
"refundIds": [],
"timeline": [
{
"status": "created",
"context": null,
"reason": null,
"time": "2023-03-28T12:26:39.607607Z"
}
],
"expiresOn": null,
"updateDate": "2023-03-28T12:26:39.604637Z",
"createDate": "2023-03-28T12:26:39.604637Z",
"merchantWalletId": "1013833795"
}
}
Getting an existing payment by ID:
curl --location 'localhost:8585/payments/60b9ff8b-f28c-40cf-9a1c-207d12a5350b'
--header 'Content-Type: application/json'
Results in the following response payload from Circle:
{
"data": {
"id": "60b9ff8b-f28c-40cf-9a1c-207d12a5350b",
"amount": {
"amount": "1.67",
"currency": "USD"
},
"amountPaid": {
"amount": "0.00",
"currency": "USD"
},
"amountRefunded": {
"amount": "0.00",
"currency": "USD"
},
"settlementCurrency": "USD",
"paymentMethods": [
{
"type": "blockchain",
"chain": "ETH",
"address": "0xa7fa0314e4a3f946e9c8a5f404bb9819ed442079"
}
],
"fees": [
{
"type": "blockchainLeaseFee",
"amount": "0.00",
"currency": "USD"
}
],
"paymentIds": [],
"refundIds": [],
"timeline": [
{
"status": "pending",
"context": null,
"reason": null,
"time": "2023-03-28T12:26:42.346901Z"
},
{
"status": "created",
"context": null,
"reason": null,
"time": "2023-03-28T12:26:39.607607Z"
}
],
"expiresOn": "2023-03-28T20:26:42.238810Z",
"updateDate": "2023-03-28T12:26:42.345054Z",
"createDate": "2023-03-28T12:26:39.604637Z",
"merchantWalletId": "1013833795"
}
}
From the Circle developer logs screen, I can see a summary of all of my requests, including the response payload:
![](https://cdn-images-1.medium.com/max/1024/1*BNZ8LDMhsGHC_x1V-t3OBw.png)
Conclusion
Readers of my publications may recall that I have been focused on the following mission statement, which I feel can apply to any IT professional:
“Focus your time on delivering features/functionality that extend the value of your intellectual property. Leverage frameworks, products, and services for everything else.”
— J. Vester
When I look back on each of my web3 publications, I am always taken aback by the number of steps or complexity involved in getting from point A to point B. While I am sure this was the case when I started working with web2, I feel like the learning cost is much higher now. This is where Circle really seems to bridge the gap.
In the example illustrated above, I could leverage Java and Spring Boot to integrate a RESTful API into the Circle platform and start making real-time, online, secure payments. As a result, Circle is helping me adhere to my mission statement.
Things tend to move fast in technology fields, and early adopters are often faced with challenges like:
- Documentation that is not polished, accurate, or even available
- Tools and technologies with steep learning curves
- Inability to easily integrate with existing frameworks, services, and applications
From what I found in this exercise, Circle has avoided these pitfalls — allowing me to avoid bank hours, processing times, and costly fees — while building my business with the USDC digital currency. In addition to USDC, it supports card payments, cryptocurrencies, and other digital payment methods. And it has distinct advantages over other payment technologies, such as Apple Pay, PayPal, and Google Pay.
If you are interested in the source code for this publication, it can be found over at GitLab:
https://gitlab.com/johnjvester/circle-sdk-demo
Have a really great day!
(Spring) Booting Java to Accept Digital Payments with USDC was originally published in Better Programming on Medium, where people are continuing the conversation by highlighting and responding to this story.