Java powers a massive share of enterprise backends, but capturing website screenshots from Java code is surprisingly underdocumented. Running headless Chrome on a JVM server is possible but painful -- you are dealing with process management, resource leaks, and version mismatches between Chrome and ChromeDriver.
A screenshot API sidesteps all of that. Send an HTTP request with a URL, get back a PNG or JPEG. No browser binaries, no native dependencies, no memory spikes. This guide covers three approaches in Java: the built-in HttpClient (Java 11+), Spring's RestTemplate, and the reactive WebClient.
Before writing Java code, verify your API key works:
curl "https://grabshot.dev/api/screenshot?url=https://example.com&width=1280&height=720" \
-H "X-API-Key: YOUR_API_KEY" \
--output screenshot.png
If that returns a valid PNG, you are ready to integrate. Get a free API key here -- the free tier gives you 25 screenshots per month, enough for development and testing.
Java 11 introduced java.net.http.HttpClient, which is all you need for a simple screenshot call. Zero external dependencies.
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.Path;
public class ScreenshotCapture {
private static final String API_KEY = System.getenv("GRABSHOT_API_KEY");
private static final String BASE_URL = "https://grabshot.dev/api/screenshot";
public static Path captureScreenshot(String targetUrl, int width, int height)
throws Exception {
String encoded = URLEncoder.encode(targetUrl, StandardCharsets.UTF_8);
String endpoint = String.format(
"%s?url=%s&width=%d&height=%d&format=png",
BASE_URL, encoded, width, height
);
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(endpoint))
.header("X-API-Key", API_KEY)
.GET()
.build();
Path outputPath = Path.of("screenshot.png");
HttpResponse<Path> response = client.send(
request, HttpResponse.BodyHandlers.ofFile(outputPath)
);
if (response.statusCode() != 200) {
throw new RuntimeException("Screenshot failed: HTTP " + response.statusCode());
}
return outputPath;
}
public static void main(String[] args) throws Exception {
Path result = captureScreenshot("https://example.com", 1280, 720);
System.out.println("Screenshot saved to: " + result);
}
}
This streams the response directly to a file, so even full-page screenshots that produce large images will not blow up your heap.
If you are already in a Spring Boot project, RestTemplate is the path of least resistance. Here is a service class that wraps the screenshot API:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
@Service
public class ScreenshotService {
@Value("${grabshot.api-key}")
private String apiKey;
private final RestTemplate restTemplate = new RestTemplate();
public byte[] capture(String url, int width, int height) {
String endpoint = UriComponentsBuilder
.fromHttpUrl("https://grabshot.dev/api/screenshot")
.queryParam("url", url)
.queryParam("width", width)
.queryParam("height", height)
.queryParam("format", "png")
.toUriString();
HttpHeaders headers = new HttpHeaders();
headers.set("X-API-Key", apiKey);
ResponseEntity<byte[]> response = restTemplate.exchange(
endpoint,
HttpMethod.GET,
new HttpEntity<>(headers),
byte[].class
);
if (response.getStatusCode() != HttpStatus.OK) {
throw new RuntimeException("Screenshot API returned " + response.getStatusCode());
}
return response.getBody();
}
}
Add the API key to your application.yml:
grabshot:
api-key: ${GRABSHOT_API_KEY}
Now inject ScreenshotService anywhere you need it -- controllers, scheduled tasks, event handlers.
For high-throughput applications where you need to capture many screenshots concurrently, Spring's WebClient is the better choice. It is non-blocking, so your threads are not sitting idle waiting for the API to render a page.
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 webClient;
public ReactiveScreenshotService(
@Value("${grabshot.api-key}") String apiKey) {
this.webClient = WebClient.builder()
.baseUrl("https://grabshot.dev/api")
.defaultHeader("X-API-Key", apiKey)
.build();
}
public Mono<byte[]> capture(String url, int width, int height) {
return webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/screenshot")
.queryParam("url", url)
.queryParam("width", width)
.queryParam("height", height)
.queryParam("format", "png")
.build())
.retrieve()
.bodyToMono(byte[].class);
}
}
Capturing multiple URLs in parallel becomes straightforward:
List<String> urls = List.of(
"https://example.com",
"https://news.ycombinator.com",
"https://github.com"
);
List<byte[]> screenshots = Flux.fromIterable(urls)
.flatMap(url -> screenshotService.capture(url, 1280, 720), 5) // concurrency of 5
.collectList()
.block();
A common pattern: expose your own endpoint that proxies to the screenshot API, adding authentication, caching, or rate limiting on top.
@RestController
@RequestMapping("/api/screenshots")
public class ScreenshotController {
private final ScreenshotService screenshotService;
public ScreenshotController(ScreenshotService screenshotService) {
this.screenshotService = screenshotService;
}
@GetMapping(produces = MediaType.IMAGE_PNG_VALUE)
public ResponseEntity<byte[]> screenshot(
@RequestParam String url,
@RequestParam(defaultValue = "1280") int width,
@RequestParam(defaultValue = "720") int height) {
byte[] image = screenshotService.capture(url, width, height);
return ResponseEntity.ok()
.header("Cache-Control", "public, max-age=3600")
.body(image);
}
}
When users paste a URL in your app, generate a thumbnail on the fly. Cache the result in S3 or your database so you only call the API once per URL. This is how Slack, Discord, and Twitter generate their link previews -- yours can work the same way.
Use Spring's @Scheduled to capture screenshots of critical pages every hour. Store them with timestamps and compare against baselines to detect visual regressions or defacements. GrabShot also offers a full-page capture mode that scrolls the entire page.
Combine screenshots with PDFMagic (HTML to PDF API) to generate visual reports. Capture dashboards, embed them in an HTML template, then convert the whole thing to PDF. Your stakeholders get a polished report without ever opening a browser.
Capture every product page after a deployment. Feed the screenshots into an image diff tool to catch broken layouts, missing images, or CSS regressions before customers see them.
Screenshot APIs depend on external websites loading correctly. Pages time out, return errors, or load slowly. Build retries into your service:
public byte[] captureWithRetry(String url, int width, int height) {
int maxRetries = 3;
for (int attempt = 1; attempt <= maxRetries; attempt++) {
try {
return capture(url, width, height);
} catch (Exception e) {
if (attempt == maxRetries) throw e;
try {
Thread.sleep(1000L * attempt); // exponential-ish backoff
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException(ie);
}
}
}
throw new RuntimeException("Unreachable");
}
| Parameter | Type | Description |
|---|---|---|
| url | string | The URL to screenshot (required) |
| width | int | Viewport width in pixels (default: 1280) |
| height | int | Viewport height in pixels (default: 720) |
| format | string | Output format: png, jpeg, webp |
| fullPage | boolean | Capture the full scrollable page |
| delay | int | Wait N ms before capture (for JS-heavy pages) |
| darkMode | boolean | Emulate dark mode preference |
GrabShot's free tier includes 25 screenshots/month. No credit card required.
Get Your Free API KeyCapturing website screenshots from Java does not require running a headless browser on your server. With a screenshot API like GrabShot, it is a single HTTP call -- whether you use vanilla HttpClient, Spring's RestTemplate, or reactive WebClient.
The approach scales from a single screenshot in a cron job to thousands of concurrent captures in a monitoring pipeline. Pick the HTTP client that matches your stack, add retry logic, and you are set.
Related guides: Screenshot API in Python | Screenshot API in PHP & Laravel | Screenshot API in Go | Screenshot API in React & Next.js