DEV Community

Custodia-Admin
Custodia-Admin

Posted on • Originally published at pagebolt.dev

How to Take a Website Screenshot in Java (Without Selenium)

How to Take a Website Screenshot in Java (Without Selenium)

You're building a Java app that needs screenshots. You reach for Selenium and suddenly you're:

  • Managing headless browser instances
  • Handling timeouts and crashes
  • Scaling across application servers
  • Maintaining complex WebDriver code

There's a better way: use a screenshot API.

One HTTP request. Binary PNG response. No browser overhead. Works in Spring Boot, Quarkus, or plain Java.

The Basic Pattern: HttpClient Binary Download

curl -X POST https://api.pagebolt.dev/v1/screenshot \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com","format":"png","fullPage":true}' \
  > screenshot.png
Enter fullscreen mode Exit fullscreen mode

But in Java, you want type-safe code.

Java Implementation: HttpClient Binary Response

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

public class PageBoltClient {
    private final String apiKey;
    private final HttpClient httpClient;
    private static final String BASE_URL = "https://api.pagebolt.dev/v1";

    public PageBoltClient(String apiKey) {
        this.apiKey = apiKey;
        this.httpClient = HttpClient.newHttpClient();
    }

    public void takeScreenshot(String url, String outputPath) throws Exception {
        String payload = String.format("""
            {
              "url": "%s",
              "format": "png",
              "width": 1280,
              "height": 720,
              "fullPage": true,
              "blockBanners": true
            }
            """, url);

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(BASE_URL + "/screenshot"))
            .POST(HttpRequest.BodyPublishers.ofString(payload))
            .header("Authorization", "Bearer " + apiKey)
            .header("Content-Type", "application/json")
            .build();

        HttpResponse<byte[]> response = httpClient.send(
            request,
            HttpResponse.BodyHandlers.ofByteArray()
        );

        if (response.statusCode() != 200) {
            throw new Exception("API error: HTTP " + response.statusCode());
        }

        // Response is binary PNG — write directly to file
        Files.write(Path.of(outputPath), response.body());
        System.out.println("Screenshot saved to " + outputPath);
    }

    public static void main(String[] args) throws Exception {
        PageBoltClient client = new PageBoltClient(System.getenv("PAGEBOLT_API_KEY"));
        client.takeScreenshot("https://example.com", "screenshot.png");
    }
}
Enter fullscreen mode Exit fullscreen mode

Key points:

  • HttpResponse.BodyHandlers.ofByteArray() handles binary PNG data
  • Files.write() writes bytes directly to disk
  • No JSON decoding — response is raw PNG bytes

Production Pattern: Reusable Client Class

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.CompletableFuture;

public class PageBoltClient {
    private final String apiKey;
    private final HttpClient httpClient;
    private static final String BASE_URL = "https://api.pagebolt.dev/v1";

    public PageBoltClient(String apiKey) {
        this.apiKey = apiKey;
        this.httpClient = HttpClient.newBuilder()
            .version(HttpClient.Version.HTTP_2)
            .build();
    }

    public byte[] screenshot(ScreenshotOptions options) throws Exception {
        String payload = buildPayload(options);

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(BASE_URL + "/screenshot"))
            .POST(HttpRequest.BodyPublishers.ofString(payload))
            .header("Authorization", "Bearer " + apiKey)
            .header("Content-Type", "application/json")
            .timeout(java.time.Duration.ofSeconds(30))
            .build();

        HttpResponse<byte[]> response = httpClient.send(
            request,
            HttpResponse.BodyHandlers.ofByteArray()
        );

        if (response.statusCode() != 200) {
            throw new ScreenshotException("API error: HTTP " + response.statusCode());
        }

        return response.body();
    }

    public void screenshotToFile(ScreenshotOptions options, String outputPath) throws Exception {
        byte[] data = screenshot(options);
        Files.write(Path.of(outputPath), data);
    }

    public CompletableFuture<byte[]> screenshotAsync(ScreenshotOptions options) {
        String payload = buildPayload(options);

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(BASE_URL + "/screenshot"))
            .POST(HttpRequest.BodyPublishers.ofString(payload))
            .header("Authorization", "Bearer " + apiKey)
            .header("Content-Type", "application/json")
            .build();

        return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofByteArray())
            .thenApply(response -> {
                if (response.statusCode() != 200) {
                    throw new RuntimeException("API error: HTTP " + response.statusCode());
                }
                return response.body();
            });
    }

    private String buildPayload(ScreenshotOptions options) {
        return String.format("""
            {
              "url": "%s",
              "format": "%s",
              "width": %d,
              "height": %d,
              "fullPage": %b,
              "blockBanners": %b,
              "blockAds": %b,
              "darkMode": %b
            }
            """,
            options.url,
            options.format,
            options.width,
            options.height,
            options.fullPage,
            options.blockBanners,
            options.blockAds,
            options.darkMode
        );
    }

    public static class ScreenshotOptions {
        public String url;
        public String format = "png";
        public int width = 1280;
        public int height = 720;
        public boolean fullPage = true;
        public boolean blockBanners = true;
        public boolean blockAds = false;
        public boolean darkMode = false;
    }

    public static class ScreenshotException extends RuntimeException {
        public ScreenshotException(String message) {
            super(message);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Spring Boot Integration

Use PageBolt in a Spring REST controller:

import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/screenshots")
public class ScreenshotController {
    private final PageBoltClient pagebot;

    public ScreenshotController(@Value("${pagebolt.api-key}") String apiKey) {
        this.pagebot = new PageBoltClient(apiKey);
    }

    @PostMapping(produces = MediaType.IMAGE_PNG_VALUE)
    public ResponseEntity<byte[]> screenshot(@RequestParam String url) {
        try {
            PageBoltClient.ScreenshotOptions options = new PageBoltClient.ScreenshotOptions();
            options.url = url;
            options.fullPage = true;

            byte[] image = pagebot.screenshot(options);

            return ResponseEntity.ok()
                .contentType(MediaType.IMAGE_PNG)
                .contentLength(image.length)
                .body(image);
        } catch (Exception e) {
            return ResponseEntity.status(500).build();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Test it:

curl -X POST "http://localhost:8080/api/screenshots?url=https://example.com" \
  > screenshot.png
Enter fullscreen mode Exit fullscreen mode

Real Use Case: Report Generation

Generate PNG reports asynchronously:

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
public class ReportService {
    private final PageBoltClient pagebot;

    public ReportService(@Value("${pagebolt.api-key}") String apiKey) {
        this.pagebot = new PageBoltClient(apiKey);
    }

    @Async
    public void generateReportScreenshot(long reportId, String reportUrl) {
        try {
            PageBoltClient.ScreenshotOptions options = new PageBoltClient.ScreenshotOptions();
            options.url = reportUrl;
            options.fullPage = true;

            pagebot.screenshotToFile(
                options,
                "reports/report-" + reportId + ".png"
            );

            // Update database
            reportRepository.updateScreenshotPath(reportId, "reports/report-" + reportId + ".png");
        } catch (Exception e) {
            logger.error("Report screenshot failed: " + e.getMessage());
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Maven Setup

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>2.0.7</version>
</dependency>
Enter fullscreen mode Exit fullscreen mode

Java 11+ HttpClient is built-in — no additional dependencies needed.

Gradle Setup

dependencies {
    implementation 'org.slf4j:slf4j-api:2.0.7'
}

java {
    sourceCompatibility = JavaVersion.VERSION_11
}
Enter fullscreen mode Exit fullscreen mode

Error Handling Patterns

public class PageBoltClient {
    public byte[] screenshotWithRetry(ScreenshotOptions options, int maxRetries) throws Exception {
        for (int attempt = 1; attempt <= maxRetries; attempt++) {
            try {
                return screenshot(options);
            } catch (Exception e) {
                if (e.getMessage().contains("429") && attempt < maxRetries) {
                    long waitMs = 1000 * (long) Math.pow(2, attempt - 1);
                    System.out.println("Rate limited. Waiting " + waitMs + "ms...");
                    Thread.sleep(waitMs);
                    continue;
                }
                throw e;
            }
        }
        throw new Exception("Max retries exceeded");
    }
}
Enter fullscreen mode Exit fullscreen mode

Pricing

Plan Requests/Month Cost Best For
Free 100 $0 Learning, low-volume projects
Starter 5,000 $29 Small teams, moderate use
Growth 25,000 $79 Production apps, frequent calls
Scale 100,000 $199 High-volume automation

Summary

  • ✅ Idiomatic Java with HttpClient (Java 11+)
  • ✅ Binary response handling with HttpResponse.BodyHandlers
  • ✅ File writing with Files.write()
  • ✅ Spring Boot integration examples
  • ✅ Async/concurrent screenshot handling
  • ✅ Error handling and retry logic
  • ✅ No Selenium, no browser management
  • ✅ Works in web apps, batch jobs, microservices

Get started free: pagebolt.dev — 100 requests/month, no credit card required.

Top comments (0)