Server Quickstart (Java)
This guide will help you build a payment-protected REST API server using Java and OpenLibx402.
Prerequisites
Java 11 or higher
Maven 3.6+ or Gradle 7+
Basic understanding of Java web frameworks
Solana wallet with some SOL (for transaction fees)
Overview
While the Java implementation currently focuses on client-side functionality, you can build payment-protected servers using popular Java frameworks with custom middleware. This guide shows you how to integrate X402 payment verification into your Java server.
Project Setup
Using Maven
Create a new Maven project:
<!-- pom.xml -->
<project>
<modelVersion> 4.0.0</modelVersion>
<groupId> com.example</groupId>
<artifactId> x402-server</artifactId>
<version> 1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source> 11</maven.compiler.source>
<maven.compiler.target> 11</maven.compiler.target>
</properties>
<dependencies>
<!-- OpenLibx402 Core -->
<dependency>
<groupId> xyz.openlib</groupId>
<artifactId> openlibx402-core</artifactId>
<version> 0.1.0</version>
</dependency>
<!-- Web Framework (Spring Boot example) -->
<dependency>
<groupId> org.springframework.boot</groupId>
<artifactId> spring-boot-starter-web</artifactId>
<version> 3.1.0</version>
</dependency>
<!-- Solana Java SDK -->
<dependency>
<groupId> com.mmorrell</groupId>
<artifactId> solanaj</artifactId>
<version> 1.17.0</version>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId> com.google.code.gson</groupId>
<artifactId> gson</artifactId>
<version> 2.10.1</version>
</dependency>
</dependencies>
</project>
Using Gradle
// build.gradle
plugins {
id 'java'
id 'org.springframework.boot' version '3.1.0'
}
group = 'com.example'
version = '1.0-SNAPSHOT'
sourceCompatibility = '11'
repositories {
mavenCentral()
}
dependencies {
implementation 'xyz.openlib:openlibx402-core:0.1.0'
implementation 'org.springframework.boot:spring-boot-starter-web:3.1.0'
implementation 'com.mmorrell:solanaj:1.17.0'
implementation 'com.google.code.gson:gson:2.10.1'
}
Spring Boot Server Example
1. Payment Request Model
package com.example.x402.model ;
import java.time.Instant ;
public class PaymentRequest {
private String maxAmountRequired ;
private String assetType ;
private String assetAddress ;
private String paymentAddress ;
private String network ;
private Instant expiresAt ;
private String nonce ;
private String paymentId ;
private String resource ;
private String description ;
// Constructors
public PaymentRequest () {}
public PaymentRequest ( String amount , String paymentAddress ,
String tokenMint , String network ,
String resource , String description ) {
this . maxAmountRequired = amount ;
this . assetType = "SPL" ;
this . assetAddress = tokenMint ;
this . paymentAddress = paymentAddress ;
this . network = network ;
this . expiresAt = Instant . now (). plusSeconds ( 300 );
this . nonce = java . util . UUID . randomUUID (). toString ();
this . paymentId = java . util . UUID . randomUUID (). toString ();
this . resource = resource ;
this . description = description ;
}
// Getters and setters
public String getMaxAmountRequired () { return maxAmountRequired ; }
public void setMaxAmountRequired ( String maxAmountRequired ) {
this . maxAmountRequired = maxAmountRequired ;
}
public String getAssetType () { return assetType ; }
public void setAssetType ( String assetType ) { this . assetType = assetType ; }
public String getAssetAddress () { return assetAddress ; }
public void setAssetAddress ( String assetAddress ) {
this . assetAddress = assetAddress ;
}
public String getPaymentAddress () { return paymentAddress ; }
public void setPaymentAddress ( String paymentAddress ) {
this . paymentAddress = paymentAddress ;
}
public String getNetwork () { return network ; }
public void setNetwork ( String network ) { this . network = network ; }
public Instant getExpiresAt () { return expiresAt ; }
public void setExpiresAt ( Instant expiresAt ) { this . expiresAt = expiresAt ; }
public String getNonce () { return nonce ; }
public void setNonce ( String nonce ) { this . nonce = nonce ; }
public String getPaymentId () { return paymentId ; }
public void setPaymentId ( String paymentId ) { this . paymentId = paymentId ; }
public String getResource () { return resource ; }
public void setResource ( String resource ) { this . resource = resource ; }
public String getDescription () { return description ; }
public void setDescription ( String description ) {
this . description = description ;
}
public boolean isExpired () {
return Instant . now (). isAfter ( expiresAt );
}
}
2. Payment Verification Service
package com.example.x402.service ;
import com.google.gson.Gson ;
import org.springframework.stereotype.Service ;
import org.springframework.beans.factory.annotation.Value ;
import java.net.http.HttpClient ;
import java.net.http.HttpRequest ;
import java.net.http.HttpResponse ;
import java.net.URI ;
import java.util.Base64 ;
@Service
public class PaymentVerificationService {
@Value ( "${solana.rpc.url}" )
private String rpcUrl ;
@Value ( "${x402.payment.address}" )
private String paymentAddress ;
@Value ( "${x402.token.mint}" )
private String tokenMint ;
private final HttpClient httpClient = HttpClient . newHttpClient ();
private final Gson gson = new Gson ();
public boolean verifyPayment ( String transactionHash ,
String expectedAmount ,
String expectedRecipient ) {
try {
// Get transaction from Solana RPC
String requestBody = String . format (
"{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"getTransaction\"," +
"\"params\":[\"%s\",{\"encoding\":\"json\"}]}" ,
transactionHash
);
HttpRequest request = HttpRequest . newBuilder ()
. uri ( URI . create ( rpcUrl ))
. header ( "Content-Type" , "application/json" )
. POST ( HttpRequest . BodyPublishers . ofString ( requestBody ))
. build ();
HttpResponse < String > response = httpClient . send ( request ,
HttpResponse . BodyHandlers . ofString ());
// Parse and verify transaction
// (Simplified - add proper JSON parsing and verification)
TransactionResponse txResponse = gson . fromJson (
response . body (),
TransactionResponse . class
);
if ( txResponse . getResult () == null ) {
return false ;
}
// Verify:
// 1. Transaction is confirmed
// 2. Recipient matches
// 3. Amount matches
// 4. Token mint matches
return verifyTransactionDetails ( txResponse ,
expectedAmount , expectedRecipient );
} catch ( Exception e ) {
System . err . println ( "Payment verification failed: " +
e . getMessage ());
return false ;
}
}
private boolean verifyTransactionDetails ( TransactionResponse tx ,
String expectedAmount ,
String expectedRecipient ) {
// Implement detailed verification logic
// Check transaction instructions, amounts, addresses
return tx . getResult () != null &&
tx . getResult (). getMeta () != null &&
tx . getResult (). getMeta (). getErr () == null ;
}
// Inner classes for RPC response parsing
static class TransactionResponse {
private Result result ;
public Result getResult () { return result ; }
static class Result {
private Meta meta ;
public Meta getMeta () { return meta ; }
static class Meta {
private Object err ;
public Object getErr () { return err ; }
}
}
}
}
3. Payment Controller
package com.example.x402.controller ;
import com.example.x402.model.PaymentRequest ;
import com.example.x402.service.PaymentVerificationService ;
import org.springframework.beans.factory.annotation.Autowired ;
import org.springframework.beans.factory.annotation.Value ;
import org.springframework.http.HttpStatus ;
import org.springframework.http.ResponseEntity ;
import org.springframework.web.bind.annotation.* ;
import java.util.HashMap ;
import java.util.Map ;
@RestController
@RequestMapping ( "/api" )
public class PremiumDataController {
@Value ( "${x402.payment.address}" )
private String paymentAddress ;
@Value ( "${x402.token.mint}" )
private String tokenMint ;
@Value ( "${x402.network}" )
private String network ;
@Autowired
private PaymentVerificationService paymentService ;
@GetMapping ( "/free-data" )
public ResponseEntity < Map < String , Object >> getFreeData () {
Map < String , Object > response = new HashMap <> ();
response . put ( "message" , "This is free data" );
response . put ( "timestamp" , System . currentTimeMillis ());
return ResponseEntity . ok ( response );
}
@GetMapping ( "/premium-data" )
public ResponseEntity <?> getPremiumData (
@RequestHeader ( value = "X-Payment-Authorization" , required = false )
String paymentAuth ) {
// Check if payment authorization provided
if ( paymentAuth == null || paymentAuth . isEmpty ()) {
// Return 402 Payment Required
PaymentRequest paymentRequest = new PaymentRequest (
"0.10" , // amount in USDC
paymentAddress ,
tokenMint ,
network ,
"/api/premium-data" ,
"Access to premium market data"
);
return ResponseEntity . status ( HttpStatus . PAYMENT_REQUIRED )
. header ( "X-Payment-Required" , "true" )
. header ( "X-Payment-Protocol" , "x402" )
. body ( paymentRequest );
}
// Verify payment
// Parse payment authorization header
// Extract transaction hash and verify
try {
String txHash = extractTransactionHash ( paymentAuth );
boolean isValid = paymentService . verifyPayment (
txHash ,
"0.10" ,
paymentAddress
);
if ( ! isValid ) {
return ResponseEntity . status ( HttpStatus . PAYMENT_REQUIRED )
. body ( Map . of ( "error" , "Payment verification failed" ));
}
// Payment verified - return premium data
Map < String , Object > response = new HashMap <> ();
response . put ( "data" , "Premium content" );
response . put ( "price" , 100.50 );
response . put ( "timestamp" , System . currentTimeMillis ());
response . put ( "paymentVerified" , true );
return ResponseEntity . ok ( response );
} catch ( Exception e ) {
return ResponseEntity . status ( HttpStatus . BAD_REQUEST )
. body ( Map . of ( "error" , "Invalid payment authorization" ));
}
}
private String extractTransactionHash ( String paymentAuth ) {
// Parse payment authorization header
// Format: "signature=<tx-hash>;..."
String [] parts = paymentAuth . split ( ";" );
for ( String part : parts ) {
if ( part . startsWith ( "transactionHash=" )) {
return part . substring ( "transactionHash=" . length ());
}
}
throw new IllegalArgumentException ( "No transaction hash found" );
}
}
4. Application Configuration
// src/main/java/com/example/x402/Application.java
package com.example.x402 ;
import org.springframework.boot.SpringApplication ;
import org.springframework.boot.autoconfigure.SpringBootApplication ;
@SpringBootApplication
public class Application {
public static void main ( String [] args ) {
SpringApplication . run ( Application . class , args );
}
}
# src/main/resources/application.properties
server.port = 8080
# Solana Configuration
solana.rpc.url = https://api.devnet.solana.com
x402.payment.address = YOUR_WALLET_ADDRESS
x402.token.mint = USDC_MINT_ADDRESS_DEVNET
x402.network = solana-devnet
# Server Configuration
spring.application.name = x402-server
Running the Server
# Using Maven
mvn clean install
mvn spring-boot:run
# Using Gradle
gradle clean build
gradle bootRun
The server will start on http://localhost:8080.
Testing the Server
Test Free Endpoint
curl http://localhost:8080/api/free-data
Response:
{
"message" : "This is free data" ,
"timestamp" : 1699545600000
}
Test Premium Endpoint (No Payment)
curl -i http://localhost:8080/api/premium-data
Response:
HTTP/1.1 402 Payment Required
X-Payment-Required: true
X-Payment-Protocol: x402
{
"maxAmountRequired": "0.10",
"assetType": "SPL",
"assetAddress": "USDC_MINT_ADDRESS",
"paymentAddress": "YOUR_WALLET_ADDRESS",
"network": "solana-devnet",
"expiresAt": "2025-11-10T17:00:00Z",
"nonce": "...",
"paymentId": "...",
"resource": "/api/premium-data",
"description": "Access to premium market data"
}
Test Premium Endpoint (With Payment)
curl -H "X-Payment-Authorization: transactionHash=TX_HASH_HERE" \
http://localhost:8080/api/premium-data
Response:
{
"data" : "Premium content" ,
"price" : 100.50 ,
"timestamp" : 1699545600000 ,
"paymentVerified" : true
}
Alternative Frameworks
Micronaut Example
import io.micronaut.http.HttpResponse ;
import io.micronaut.http.annotation.Controller ;
import io.micronaut.http.annotation.Get ;
import io.micronaut.http.annotation.Header ;
@Controller ( "/api" )
public class PremiumController {
@Get ( "/premium-data" )
public HttpResponse <?> getPremiumData (
@Header ( value = "X-Payment-Authorization" , defaultValue = "" )
String paymentAuth ) {
if ( paymentAuth . isEmpty ()) {
PaymentRequest request = new PaymentRequest ( /*...*/ );
return HttpResponse . status ( HttpStatus . PAYMENT_REQUIRED )
. body ( request );
}
// Verify payment and return data
return HttpResponse . ok ( getPremiumContent ());
}
}
Quarkus Example
import javax.ws.rs.GET ;
import javax.ws.rs.HeaderParam ;
import javax.ws.rs.Path ;
import javax.ws.rs.core.Response ;
@Path ( "/api" )
public class PremiumResource {
@GET
@Path ( "/premium-data" )
public Response getPremiumData (
@HeaderParam ( "X-Payment-Authorization" ) String paymentAuth ) {
if ( paymentAuth == null || paymentAuth . isEmpty ()) {
PaymentRequest request = new PaymentRequest ( /*...*/ );
return Response . status ( 402 )
. entity ( request )
. build ();
}
// Verify payment and return data
return Response . ok ( getPremiumContent ()). build ();
}
}
Next Steps
Notes
The Java implementation currently focuses on client-side X402 functionality
Server middleware is implemented as custom Spring Boot controllers
For production use, consider implementing:
Payment caching to avoid repeated verification
Redis for distributed payment state
Webhook support for async payment confirmation
Rate limiting per client
See Also