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
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");
}
}
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);
}
}
}
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();
}
}
}
Test it:
curl -X POST "http://localhost:8080/api/screenshots?url=https://example.com" \
> screenshot.png
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());
}
}
}
Maven Setup
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.7</version>
</dependency>
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
}
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");
}
}
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)