---
title: Java
slug: sdks/server-sdks/java
excerpt: Pine Labs Server SDK for Java.
hidden: false
sidebar_order: 5
metadata:
  title: Java SDK — Official Java Library | Pine Labs Gateway
  description: >-
    Official Pine Labs Java SDK for backend payment integration. Maven/Gradle
    installation, authentication, and complete API integration guide for Java
    applications.
  keywords: >-
    Java SDK, Pine Labs Java, Java payment SDK, Maven dependency, Java backend
    integration, Java payment library
  robots: index
---
`pinelabs-java` is the official Java SDK for the **Pine Labs Online Payment Gateway** (Plural). It is generated from the same OpenAPI spec that powers this documentation, so every operation, request, and response model is fully typed.

- **Coordinates:** [`com.pinelabsonline:pinelabs-java`](https://central.sonatype.com/artifact/com.pinelabsonline/pinelabs-java) on Maven Central
- **Runtime:** Java 11+
- **Transport:** OkHttp 4 + Jackson

## Install

<CodeTabs>
  <Tab label="Maven">
    ```xml
    <dependency>
      <groupId>com.pinelabsonline</groupId>
      <artifactId>pinelabs-java</artifactId>
      <version>0.1.0</version>
    </dependency>
    ```
  </Tab>
  <Tab label="Gradle (Kotlin DSL)">
    ```kotlin
    implementation("com.pinelabsonline:pinelabs-java:0.1.0")
    ```
  </Tab>
  <Tab label="Gradle (Groovy)">
    ```groovy
    implementation 'com.pinelabsonline:pinelabs-java:0.1.0'
    ```
  </Tab>
</CodeTabs>

## Quickstart

The Pine Labs API uses **OAuth2 with the `client_credentials` grant**. Exchange your `<client-id>` / `<client-secret>` for an access token, then pass it to `PinelabsApiClient`.

```java
import com.pinelabsonline.api.PinelabsApiClient;
import com.pinelabsonline.api.resources.authentication.requests.GenerateTokenRequest;
import com.pinelabsonline.api.resources.orders.requests.CreateOrderRequest;
import com.pinelabsonline.api.types.Amount;

public class Main {
    public static void main(String[] args) {
        String baseUrl = "https://pluraluat.v2.pinepg.in"; // UAT
        // String baseUrl = "https://api.pluralpay.in";     // Production

        // 1. Bootstrap client used only to fetch a token. The /token endpoint
        //    does not require authentication, so we pass an empty token here.
        PinelabsApiClient auth = PinelabsApiClient.builder()
            .token("")
            .url(baseUrl)
            .build();

        var tokenResponse = auth.authentication().generateToken(
            GenerateTokenRequest.builder()
                .grantType("client_credentials")
                .clientId(System.getenv("PINELABS_CLIENT_ID"))
                .clientSecret(System.getenv("PINELABS_CLIENT_SECRET"))
                .build()
        );

        // 2. Build an authenticated client
        PinelabsApiClient client = PinelabsApiClient.builder()
            .token(tokenResponse.getAccessToken())
            .url(baseUrl)
            .build();

        // 3. Call any operation
        var order = client.orders().createOrder(
            CreateOrderRequest.builder()
                .merchantOrderReference("order-001")
                .orderAmount(Amount.builder()
                    .value(50000)        // ₹500.00
                    .currency("INR")
                    .build())
                .build()
        );

        System.out.println(order);
    }
}
```

## Environments

| Environment | Base URL                            |
| ----------- | ----------------------------------- |
| UAT         | `https://pluraluat.v2.pinepg.in`    |
| Production  | `https://api.pluralpay.in`          |

Pass the URL via `.url(baseUrl)` on the builder. The exported `Environment` enum currently only contains `PRODUCTION`; for UAT, set the URL explicitly.

```java
import com.pinelabsonline.api.core.Environment;

PinelabsApiClient client = PinelabsApiClient.builder()
    .token("<access-token>")
    .environment(Environment.PRODUCTION)
    .build();
```

## Auto-refreshing token

For long-running services, cache the token and refresh it before it expires. The builder accepts a `Supplier<String>` for `token` — it is invoked on every request. Use a separate **bootstrap** `auth` client (without a supplier) to fetch the token, so the supplier never recurses into itself.

```java
import com.pinelabsonline.api.PinelabsApiClient;
import com.pinelabsonline.api.resources.authentication.requests.GenerateTokenRequest;

import java.time.Instant;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Supplier;

public class PinelabsClientFactory {

    private static final String BASE_URL = "https://pluraluat.v2.pinepg.in";

    // Bootstrap client used only to fetch tokens. It has no supplier, so calling
    // generateToken() from inside the supplier cannot recurse.
    private static final PinelabsApiClient AUTH = PinelabsApiClient.builder()
        .token("")
        .url(BASE_URL)
        .build();

    private static final ReentrantLock LOCK = new ReentrantLock();
    private static volatile String cachedToken;
    private static volatile Instant expiresAt = Instant.EPOCH;

    private static final Supplier<String> tokenSupplier = () -> {
        // Refresh ~30s before expiry
        if (cachedToken != null && Instant.now().isBefore(expiresAt.minusSeconds(30))) {
            return cachedToken;
        }
        LOCK.lock();
        try {
            if (cachedToken != null && Instant.now().isBefore(expiresAt.minusSeconds(30))) {
                return cachedToken;
            }
            var r = AUTH.authentication().generateToken(
                GenerateTokenRequest.builder()
                    .grantType("client_credentials")
                    .clientId(System.getenv("PINELABS_CLIENT_ID"))
                    .clientSecret(System.getenv("PINELABS_CLIENT_SECRET"))
                    .build()
            );
            cachedToken = r.getAccessToken();
            expiresAt = Instant.now().plusSeconds(r.getExpiresIn());
            return cachedToken;
        } finally {
            LOCK.unlock();
        }
    };

    public static PinelabsApiClient client() {
        return PinelabsApiClient.builder()
            .token(tokenSupplier)
            .url(BASE_URL)
            .build();
    }
}
```

<Callout type="warning" title="Use a separate bootstrap client">
  Always create a dedicated `auth` client (with `token("")`) to fetch tokens. Calling `generateToken()` on a client that itself uses the supplier callable will recurse and hang.
</Callout>

## Sub-clients

`PinelabsApiClient` exposes one sub-client per API tag via accessor methods:

| Sub-client                                   | Purpose                                            |
| -------------------------------------------- | -------------------------------------------------- |
| `client.authentication()`                    | OAuth token generation                             |
| `client.orders()`                            | Create, capture, cancel, and fetch orders          |
| `client.refunds()`                           | Create and look up refunds                         |
| `client.settlements()`                       | Settlement reports + UTR lookup                    |
| `client.checkout()`                          | Hosted-checkout related operations                 |
| `client.paymentLinks()`                      | Single + bulk payment links                        |
| `client.cardPayments()`                      | Direct card payment + OTP flow                     |
| `client.bnpl()`                              | Buy-Now-Pay-Later eligibility and flows            |
| `client.convenienceFee()`                    | Convenience-fee config and computation             |
| `client.eChallans()`                         | Government e-challan integration                   |
| `client.applePay()`                          | Apple Pay session + decryption                     |
| `client.internationalPayments()`             | Cross-border (DCC / MCC) payments                  |
| `client.customers()`                         | Customer profile management                        |
| `client.tokenization()`                      | Card / network tokenization                        |
| `client.payouts()`                           | Payouts: balance, create, cancel, list             |
| `client.subscriptionsPlans()`                | Recurring-billing plans                            |
| `client.subscriptionsSubscriptions()`        | Subscription lifecycle                             |
| `client.subscriptionsPresentations()`        | Subscription debit presentations                   |
| `client.payByPoints()`                       | Loyalty / points-based payments                    |
| `client.affordabilitySuite()`                | EMI / offer eligibility                            |
| `client.splitSettlements()`                  | Split settlements between sub-merchants            |

For the full operation list and request/response schemas, see the [API reference](/docs/api-reference).

## Recipes

### Create a payment link

```java
import com.pinelabsonline.api.resources.paymentlinks.requests.CreatePaymentLinkRequest;
import com.pinelabsonline.api.types.Amount;

var link = client.paymentLinks().createPaymentLink(
    CreatePaymentLinkRequest.builder()
        .merchantPaymentLinkReference("link-001")
        .amount(Amount.builder().value(50000).currency("INR").build())
        .description("Order #001")
        .build()
);
System.out.println(link.getShortUrl());
```

### Fetch an order

```java
import com.pinelabsonline.api.resources.orders.requests.GetOrderByIdRequest;

var order = client.orders().getOrderById(
    GetOrderByIdRequest.builder()
        .orderId("v1-241010055924-aa-AHbN0s")
        .build()
);
System.out.println(order);
```

### Refund an order

```java
import com.pinelabsonline.api.resources.refunds.requests.CreateRefundRequest;
import com.pinelabsonline.api.resources.refunds.types.CreateRefundRequestOrderAmount;

var refund = client.refunds().createRefund(
    CreateRefundRequest.builder()
        .orderId("v1-241010055924-aa-AHbN0s")
        .merchantOrderReference("refund-001")
        .orderAmount(CreateRefundRequestOrderAmount.builder()
            .value(400)
            .currency("INR")
            .build())
        .build()
);
System.out.println(refund);
```

## Error handling

All HTTP errors derive from `PinelabsApiApiException`. The SDK also exposes typed subclasses per status code so you can handle specific failures cleanly.

```java
import com.pinelabsonline.api.core.PinelabsApiApiException;
import com.pinelabsonline.api.core.PinelabsApiException;
import com.pinelabsonline.api.errors.NotFoundError;
import com.pinelabsonline.api.errors.UnauthorizedError;

try {
    client.orders().getOrderById(
        GetOrderByIdRequest.builder().orderId("missing").build()
    );
} catch (NotFoundError e) {
    System.err.println("order does not exist: " + e.body());
} catch (UnauthorizedError e) {
    System.err.println("token expired or invalid");
} catch (PinelabsApiApiException e) {
    System.err.println("HTTP " + e.statusCode() + ": " + e.body());
} catch (PinelabsApiException e) {
    System.err.println("Error: " + e.getMessage());
}
```

`PinelabsApiApiException` exposes `statusCode()`, `body()` (parsed when the server returns JSON), and `headers()`.

## Per-request options

Every operation accepts a `RequestOptions` argument for ad-hoc overrides — useful for per-call timeouts, propagating trace IDs, or disabling retries on idempotency-sensitive flows.

```java
import com.pinelabsonline.api.core.RequestOptions;
import java.time.Duration;

var options = RequestOptions.builder()
    .timeout(Duration.ofSeconds(10))
    .addHeader("x-correlation-id", "req-abc")
    .build();

client.orders().createOrder(
    CreateOrderRequest.builder()
        .merchantOrderReference("order-002")
        .orderAmount(Amount.builder().value(1000).currency("INR").build())
        .build(),
    options
);
```

### Timeouts

The default timeout is **60 seconds**. Override at the client or per-request level:

```java
PinelabsApiClient client = PinelabsApiClient.builder()
    .token("<access-token>")
    .timeout(20)   // seconds
    .build();
```

### Custom HTTP client

```java
import com.pinelabsonline.api.PinelabsApiClient;
import okhttp3.OkHttpClient;
import java.util.concurrent.TimeUnit;

OkHttpClient http = new OkHttpClient.Builder()
    .connectTimeout(5, TimeUnit.SECONDS)
    .readTimeout(30, TimeUnit.SECONDS)
    .build();

PinelabsApiClient client = PinelabsApiClient.builder()
    .token("<access-token>")
    .httpClient(http)
    .build();
```

## Source & support

- Maven Central: [com.pinelabsonline:pinelabs-java](https://central.sonatype.com/artifact/com.pinelabsonline/pinelabs-java)
- GitHub: [github.com/plural-pinelabs/pinelabs-java](https://github.com/plural-pinelabs/pinelabs-java)
- API reference: [/api](/api)
 
