ScreenshotNeo

BlogHow-to

How to Use a Screenshot API in a Java Spring Boot App in India

Call a screenshot API from Spring Boot with RestClient, handle image bytes and errors, and compare a hosted service with Playwright Java.

By the ScreenshotNeo team4 October 202612 min read

A Spring Boot app can use a screenshot API like any other HTTP service: send the provider’s documented request and authentication, then read the response as image bytes or a response entity. For a synchronous Spring MVC application, Spring’s RestClient is a straightforward fit; use WebClient when the application already follows a reactive, non-blocking model. The endpoint, parameters, authentication, output type, and limits vary by provider, so use that provider’s current API contract.

This guide shows a complete Spring Boot integration, including configuration, saving or returning the image, error handling, and a self-managed Playwright alternative. The India-specific deployment considerations are covered below; the available provider documentation does not establish any particular service’s India availability, processing location, retention, support, or INR pricing.

1. Choose the HTTP client for your Spring application

Spring’s RestClient provides a synchronous, fluent API for requests, headers, response conversion, and response entities. It suits a typical Spring MVC request flow. Spring Framework 7.0 deprecates RestTemplate in favor of RestClient; for an existing application, check the version you use before changing clients. WebClient is Spring’s non-blocking reactive client and is a better fit when the application already uses reactive processing. See the Spring REST client reference.

Approach Use it when Operational responsibility
Hosted screenshot API with RestClient Your app needs a normal synchronous HTTP call and an image response. Provider operates the browser rendering service; your app handles credentials, calls, and returned data.
Hosted screenshot API with WebClient Your app already uses reactive flows or needs the non-blocking request model. Same hosted service considerations, with reactive request and response handling in your app.
Playwright Java You need browser automation under your deployment and operational control. Your team runs and maintains the browser automation environment.

Do not choose WebClient just because the app uses Spring Boot. Match the HTTP client to the application’s execution model.

2. Configure the provider contract and credentials

Before writing the request, check the provider’s current documentation for the HTTP method, endpoint, authentication scheme, required URL parameter, output format, error response, size limits, and usage limits. A screenshot API example may use a URL and output format, but those fields are not universal. For production, keep credentials outside source control using environment configuration or a secret store.

The following example uses ScreenshotNeo’s documented GET endpoint and query parameters. It requests a WebP screenshot of https://stripe.com. See the ScreenshotNeo API documentation for the API contract and available options.

SCREENSHOTNEO_ACCESS_KEY=YOUR_API_KEY

For example, expose these values through Spring configuration:

# application.properties
screenshot.api-url=https://api.screenshotneo.com/v1/shot
screenshot.access-key=${SCREENSHOTNEO_ACCESS_KEY}

Never commit a real key, include it in a browser-side application, or log the complete request URL if it contains the key.

3. Call the API with Spring RestClient

This compact example uses Spring Boot 3.2 or later, where RestClient is available. It downloads the screenshot bytes to a file. The provider’s response is binary image data on success, so the request asks for a byte array rather than attempting JSON deserialization.

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatusCode;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;

@Service
public class ScreenshotService {
    private final RestClient restClient;
    private final String endpoint;
    private final String accessKey;

    public ScreenshotService(
            RestClient.Builder builder,
            @Value("${screenshot.api-url}") String endpoint,
            @Value("${screenshot.access-key}") String accessKey) {
        this.restClient = builder.build();
        this.endpoint = endpoint;
        this.accessKey = accessKey;
    }

    public byte[] capture(String targetUrl) {
        byte[] image = restClient.get()
                .uri(uriBuilder -> uriBuilder
                        .path(endpoint)
                        .queryParam("access_key", accessKey)
                        .queryParam("url", targetUrl)
                        .build())
                .retrieve()
                .onStatus(HttpStatusCode::isError, (request, response) -> {
                    throw new ScreenshotApiException(
                            "Screenshot API returned HTTP " + response.getStatusCode());
                })
                .body(byte[].class);

        if (image == null || image.length == 0) {
            throw new ScreenshotApiException("Screenshot API returned an empty body");
        }
        return image;
    }

    public Path captureToFile(String targetUrl, Path destination) throws IOException {
        byte[] image = capture(targetUrl);
        Path parent = destination.toAbsolutePath().getParent();
        if (parent != null) {
            Files.createDirectories(parent);
        }
        return Files.write(destination, image);
    }
}
public class ScreenshotApiException extends RuntimeException {
    public ScreenshotApiException(String message) {
        super(message);
    }
}

Depending on the Spring Boot version and dependency management in the project, inject a configured RestClient.Builder or define a RestClient bean explicitly. The URI builder encodes query values, including URLs that contain ampersands or their own query strings.

Use the service from an application component, for example:

Path saved = screenshotService.captureToFile(
        "https://stripe.com",
        Path.of("var", "screenshots", "stripe.webp"));

Choose the file extension from the requested output format. Do not name JPEG or PNG data with a .webp suffix. If the provider returns a content-type header, validate it against the format you requested before storing or serving the bytes.

4. Return the screenshot from a Spring endpoint

If another application or a user needs the image through your Spring service, return a response entity with the correct media type. This keeps the screenshot API key on the server.

import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ScreenshotController {
    private final ScreenshotService screenshots;

    public ScreenshotController(ScreenshotService screenshots) {
        this.screenshots = screenshots;
    }

    @GetMapping("/screenshots")
    public ResponseEntity<byte[]> screenshot(@RequestParam String url) {
        byte[] image = screenshots.capture(url);
        return ResponseEntity.ok()
                .contentType(MediaType.parseMediaType("image/webp"))
                .header(HttpHeaders.CACHE_CONTROL, "no-store")
                .body(image);
    }
}

In a real application, validate or restrict the input URL before fetching it. A publicly reachable endpoint that accepts arbitrary URLs can be abused to make your server request internal or otherwise unintended destinations. Apply the controls appropriate to your application, such as an allowed-host policy and request authorization.

5. Use WebClient in a reactive application

For a reactive Spring application, make the screenshot call with WebClient and keep the response as a reactive byte buffer or byte array. Avoid blocking with .block() on a reactive request thread.

import org.springframework.http.HttpStatusCode;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

@Service
public class ReactiveScreenshotService {
    private final WebClient client;
    private final String accessKey;

    public ReactiveScreenshotService(
            WebClient.Builder builder,
            @org.springframework.beans.factory.annotation.Value("${screenshot.api-url}") String endpoint,
            @org.springframework.beans.factory.annotation.Value("${screenshot.access-key}") String accessKey) {
        this.client = builder.baseUrl(endpoint).build();
        this.accessKey = accessKey;
    }

    public Mono<byte[]> capture(String targetUrl) {
        return client.get()
                .uri(uriBuilder -> uriBuilder
                        .queryParam("access_key", accessKey)
                        .queryParam("url", targetUrl)
                        .build())
                .retrieve()
                .onStatus(HttpStatusCode::isError, response ->
                        response.bodyToMono(String.class)
                                .defaultIfEmpty("No error details")
                                .map(message -> new ScreenshotApiException(
                                        "Screenshot API returned HTTP "
                                                + response.statusCode() + ": " + message)))
                .bodyToMono(byte[].class)
                .filter(bytes -> bytes.length > 0)
                .switchIfEmpty(Mono.error(new ScreenshotApiException(
                        "Screenshot API returned an empty body")));
    }
}

This sample assumes the endpoint is configured as the base URL. For endpoints with a path or query parameters in the base URL, verify how your WebClient base URL and URI builder combine them.

6. Save or process the image safely

  • Keep the response as bytes if another service, object store, or HTTP response consumes it directly.
  • Write to a file only if the application needs local persistence. Create parent directories and handle filesystem errors.
  • Set the correct media type and extension based on the output format requested and, where possible, the response content type.
  • Set a maximum accepted response size appropriate to your use case before buffering untrusted or unusually large pages.
  • Do not assume a successful HTTP response necessarily contains a valid image. Validate the body and content type before downstream use.

7. Configure timeouts, status handling, and retries

Screenshot generation can take longer than a small JSON request because the remote service has to load and render a page. Set connection and response timeouts in the HTTP client to fit the provider’s documented behavior and your own request deadline. Make sure the upstream timeout leaves enough time for your application to return or handle the result.

For RestClient, timeout configuration is typically applied to the underlying request factory. The exact factory and configuration depend on the Spring version and HTTP client on the classpath. Configure connection and read/response timeouts explicitly rather than relying on defaults. Apply equivalent limits to WebClient through its underlying connector.

Do not retry every failure blindly. A retry can repeat a slow capture and increase latency. Retry only transient failures, cap attempts, use backoff, and honor any provider guidance. For jobs that do not need an immediate response, use an asynchronous job mechanism if the provider offers one; ScreenshotNeo supports async jobs with signed webhooks.

8. Browser-managed option with Playwright Java

If your team wants to render pages in its own application environment, Playwright Java can take a screenshot to a path or return the bytes for further processing. It supports full-page screenshots and screenshots of a locator. This gives you browser automation under your operational responsibility rather than a hosted screenshot endpoint. See the Playwright Java screenshot documentation.

import com.microsoft.playwright.Browser;
import com.microsoft.playwright.BrowserType;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.Playwright;

import java.nio.file.Path;

public class LocalScreenshot {
    public static void main(String[] args) {
        try (Playwright playwright = Playwright.create()) {
            Browser browser = playwright.chromium().launch(
                    new BrowserType.LaunchOptions().setHeadless(true));
            try {
                Page page = browser.newPage();
                page.navigate("https://stripe.com");
                page.screenshot(new Page.ScreenshotOptions()
                        .setPath(Path.of("shot.png"))
                        .setFullPage(true));
            } finally {
                browser.close();
            }
        }
    }
}

For an element capture, locate the element and call locator.screenshot(...). For post-processing, omit the path and use the returned byte array. Add the Playwright Java dependency and install the browser runtime using the instructions for the Playwright version you select. In a Spring service, manage browser and page lifetimes deliberately; launching a new browser for every request may be operationally expensive, while sharing browser processes requires careful isolation and cleanup. The cited documentation describes screenshot capabilities, but does not provide a performance or cost benchmark.

9. India-specific provider checks

The fact that the application runs in India does not by itself establish where a hosted screenshot provider processes or stores the target URL or resulting image. Before sending sensitive URLs, page contents, credentials, or personal data to any vendor, verify the provider’s current terms and technical documentation for:

  1. Whether the service is available to your account and users in India.
  2. Where page rendering and any temporary or stored data are processed.
  3. Retention and deletion behavior for screenshots, logs, URLs, and request metadata.
  4. Support channels and response terms for your plan.
  5. Pricing currency, taxes, quotas, and overage behavior relevant to your account.

These are vendor due-diligence questions; the reviewed provider material does not resolve them for India. Do not infer data residency or regulatory status from a product’s website location alone.

10. Troubleshooting common failures

Symptom Likely cause What to check or change
401 or 403 response Missing, invalid, or unauthorized credential; incorrect authentication method. Check the provider’s current authentication contract, secret configuration, and account access. Do not print the key into logs.
400 response Missing or malformed URL or output parameter, or another invalid option. Compare the request with the provider’s exact parameter names and accepted values. URI-encode the target URL.
404 response Wrong API path or obsolete endpoint. Confirm the full endpoint in current provider docs, including version path.
Non-image body or JSON error The provider returned an error document, or the client treated a non-success response as image data. Check status before consuming bytes; inspect a safely truncated error body and response content type.
Empty image bytes Unexpected empty response or application converted the body incorrectly. Check status, body conversion, and provider response details; reject empty images before saving.
Timeout Page load or rendering took longer than the client or proxy deadline. Review client, proxy, and application deadlines; choose provider wait options carefully and avoid unbounded retries.
Image appears blank or incomplete The target may require time to render, load lazy content, or pass an interstitial, or the requested viewport/content differs from expectations. Check the target in a browser, wait behavior and viewport parameters supported by the provider, and whether the page blocks automated access.
Saved file cannot be opened Extension and actual image format differ, or an error response was saved as an image. Verify status and content type, request the intended format, and use a matching extension.
Spring cannot inject RestClient.Builder The project’s Spring Boot version or dependencies do not provide the expected builder bean. Check the Spring Boot version and configure a builder or client bean supported by that version.
Reactive endpoint stalls under load Reactive processing was blocked, often by calling block() on a request thread. Compose and return the Mono or Flux; keep blocking work off the event loop.

11. Performance, reliability, and cost

Rendering a page is usually the dominant work in a screenshot flow, but actual latency depends on the page, provider, options, and network path. The available source material gives no benchmark, so measure with representative target pages and your own timeout and concurrency settings.

  • Performance: Avoid capturing the same URL repeatedly when a cached result is acceptable. Bound concurrent captures so a burst of requests does not overwhelm your app or provider quota. If the provider supports caching or asynchronous jobs, evaluate those options against freshness and response-time needs.
  • Reliability: Treat the screenshot provider as an upstream dependency. Handle non-success status codes, empty bodies, timeouts, and transient network errors. Log a request identifier and outcome where available, but exclude credentials and sensitive page data.
  • Cost: Compare plan quotas, billing rules, and the cost of operating a browser environment. These vary by vendor and deployment; confirm current terms rather than assuming a free tier or fixed request price.
  • Capacity: Establish an application-level maximum response size and concurrency limit. For workloads that exceed an HTTP request’s practical lifetime, prefer a queued or asynchronous workflow when supported.

12. Or skip the browser setup

ScreenshotNeo is a hosted website screenshot API and MCP server for developers. One GET request returns an image or PDF; the service accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets, with each step configurable. Only clean shots are billed: bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response identifies the page verdict and billing status in headers. Its MCP server lets AI agents using Claude, Cursor, or another MCP client call take_screenshot, get_page_info, and capture_pdf. See ScreenshotNeo and its API documentation.

Spring Java example using the JDK HTTP client:

import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;

public class ScreenshotNeoExample {
    public static void main(String[] args) throws Exception {
        String key = System.getenv("SCREENSHOTNEO_ACCESS_KEY");
        if (key == null || key.isBlank()) {
            throw new IllegalStateException("Set SCREENSHOTNEO_ACCESS_KEY");
        }
        String query = "access_key=" + URLEncoder.encode(key, StandardCharsets.UTF_8)
                + "&url=" + URLEncoder.encode("https://stripe.com", StandardCharsets.UTF_8);
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://api.screenshotneo.com/v1/shot?" + query))
                .GET()
                .build();
        HttpResponse<byte[]> response = HttpClient.newHttpClient().send(
                request, HttpResponse.BodyHandlers.ofByteArray());
        if (response.statusCode() / 100 != 2) {
            throw new IllegalStateException("Screenshot API returned HTTP " + response.statusCode());
        }
        Files.write(Path.of("shot.webp"), response.body());
    }
}

ScreenshotNeo also has cURL, Python, and Node.js examples for the same endpoint:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Cookie banners, popups, and chat widgets are removed before the shot. Bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents take screenshots. The Free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Sign up for ScreenshotNeo and get 1,000 free screenshots a month with no card.

Frequently asked questions

Can I put the screenshot API key in frontend JavaScript?

No. Keep the key in the Spring server or a secret manager and expose only an authenticated application endpoint if a browser client needs screenshots.

Should a screenshot endpoint be synchronous?

It can be for requests that comfortably fit within your application’s request deadline. For longer jobs or large batches, use a queue or a provider’s asynchronous workflow when available.

Does using a Java API client mean the screenshot renders in India?

No. The client’s location does not establish the provider’s rendering or storage location. Confirm those details directly with the provider.

Can Playwright capture only one element?

Yes. Its Java API supports screenshots from a locator as well as full-page and page screenshots.

Does every screenshot API use a URL query parameter?

No. Request method, authentication, parameter names, formats, and response behavior are vendor-specific; follow the chosen provider’s current API documentation.