Screenshot API Spring Boot Guide 2026 — RestTemplate, WebClient & async

Integrate screenshot and PDF generation into Spring Boot applications. RestTemplate, WebClient reactive, @Async service, Kubernetes deployment, and scheduled screenshot jobs.

Get Free API Key

Why Spring Boot Applications Use a Screenshot API

Spring Boot microservices that need screenshot or PDF generation face the same headless browser challenge as any other backend: adding Playwright or Selenium creates a heavyweight dependency that conflicts with the clean microservice architecture Spring applications typically use. A Spring Boot service is usually a compact 50–100 MB JAR that starts in under 10 seconds and deploys to Kubernetes with tight resource limits. Adding a headless Chrome dependency inflates the Docker image by 400+ MB, increases startup time by 5–10 seconds while waiting for the browser process, and requires 400–600 MB of additional RAM allocation in the pod spec. SnapAPI integrates as a standard REST call — Spring's RestTemplate or WebClient handles it identically to any other external API call — keeping the microservice small, fast, and resource-efficient.

RestTemplate Screenshot Service

@Service
public class ScreenshotService {

    @Value("${snapapi.key}")
    private String apiKey;

    private final RestTemplate restTemplate;

    public ScreenshotService(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    public byte[] screenshot(String url, String format, boolean fullPage) {
        UriComponentsBuilder builder = UriComponentsBuilder
            .fromHttpUrl("https://snapapi.pics/screenshot")
            .queryParam("access_key", apiKey)
            .queryParam("url", url)
            .queryParam("format", format)
            .queryParam("full_page", fullPage ? "1" : "0")
            .queryParam("viewport_width", "1280")
            .queryParam("viewport_height", "800");

        ResponseEntity response = restTemplate.exchange(
            builder.toUriString(),
            HttpMethod.GET,
            null,
            byte[].class
        );

        if (!response.getStatusCode().is2xxSuccessful()) {
            throw new RuntimeException("SnapAPI error: " + response.getStatusCode());
        }
        return response.getBody();
    }

    public byte[] pdf(String url) {
        return screenshot(url, "pdf", true);
    }
}

// Configuration bean
@Configuration
public class RestTemplateConfig {
    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder builder) {
        return builder
            .setConnectTimeout(Duration.ofSeconds(10))
            .setReadTimeout(Duration.ofSeconds(60))
            .build();
    }
}

WebClient Reactive Integration

@Service
public class ReactiveScreenshotService {

    @Value("${snapapi.key}")
    private String apiKey;

    private final WebClient webClient;

    public ReactiveScreenshotService(WebClient.Builder builder) {
        this.webClient = builder
            .baseUrl("https://snapapi.pics")
            .build();
    }

    public Mono screenshotAsync(String url, String format) {
        return webClient.get()
            .uri(uriBuilder -> uriBuilder
                .path("/screenshot")
                .queryParam("access_key", apiKey)
                .queryParam("url", url)
                .queryParam("format", format)
                .queryParam("full_page", "1")
                .build())
            .retrieve()
            .onStatus(status -> !status.is2xxSuccessful(),
                res -> Mono.error(new RuntimeException("SnapAPI error: " + res.statusCode())))
            .bodyToMono(byte[].class);
    }

    public Flux batchScreenshots(List urls) {
        return Flux.fromIterable(urls)
            .flatMap(url -> screenshotAsync(url, "png"), 5);  // max 5 concurrent
    }
}

Async Screenshot with @Async and CompletableFuture

@Service
@EnableAsync
public class AsyncScreenshotService {

    private final ScreenshotService screenshotService;
    private final PageRepository pageRepository;

    @Async("screenshotExecutor")
    public CompletableFuture captureAndSave(Long pageId, String url) {
        try {
            byte[] png = screenshotService.screenshot(url, "png", true);
            pageRepository.updateScreenshot(pageId, png, Instant.now());
            return CompletableFuture.completedFuture(null);
        } catch (Exception e) {
            return CompletableFuture.failedFuture(e);
        }
    }
}

// Thread pool configuration
@Configuration
public class AsyncConfig implements AsyncConfigurer {
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor exec = new ThreadPoolTaskExecutor();
        exec.setCorePoolSize(5);
        exec.setMaxPoolSize(20);
        exec.setQueueCapacity(200);
        exec.setThreadNamePrefix("screenshot-");
        exec.initialize();
        return exec;
    }
}

Spring Boot REST Controller — PDF Download

@RestController
@RequestMapping("/api/v1")
public class ScreenshotController {

    private final ScreenshotService screenshotService;

    @GetMapping("/screenshot")
    public ResponseEntity screenshot(
            @RequestParam String url,
            @RequestParam(defaultValue = "png") String format) {

        byte[] data = screenshotService.screenshot(url, format, true);
        String contentType = "pdf".equals(format) ? "application/pdf" : "image/png";
        String filename = "pdf".equals(format) ? "page.pdf" : "screenshot.png";

        return ResponseEntity.ok()
            .header(HttpHeaders.CONTENT_TYPE, contentType)
            .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename="" + filename + """)
            .header(HttpHeaders.CACHE_CONTROL, "public, max-age=3600")
            .body(data);
    }
}

Scheduled Screenshot Job with @Scheduled

Spring's @Scheduled annotation makes it straightforward to run periodic screenshot capture jobs without a separate job scheduler. Combine it with the async screenshot service to capture screenshots of monitored URLs on a cron schedule and store results to an S3 bucket or database:

@Component
public class ScreenshotMonitorJob {

    private final ScreenshotService screenshotService;
    private final MonitoredPageRepository monitorRepo;
    private final StorageService storage;

    @Scheduled(cron = "0 0 */6 * * *")  // every 6 hours
    public void captureMonitoredPages() {
        List pages = monitorRepo.findAllActive();
        pages.parallelStream().forEach(page -> {
            try {
                byte[] png = screenshotService.screenshot(page.getUrl(), "png", true);
                String key = storage.save(page.getId(), png);
                monitorRepo.updateLastCapture(page.getId(), key, Instant.now());
            } catch (Exception e) {
                log.error("Screenshot failed for {}: {}", page.getUrl(), e.getMessage());
            }
        });
    }
}

Environment Configuration for Spring Boot

Store the SnapAPI key in Spring's externalized configuration. Add snapapi.key=${SNAPAPI_KEY} to application.properties and set the SNAPAPI_KEY environment variable in your deployment environment. In Kubernetes, create a Secret for the API key and reference it as an environment variable in the pod spec rather than embedding the key in a ConfigMap. For local development, set the environment variable in your IDE run configuration or in a .env file loaded by a dotenv plugin. Never commit the API key to version control — use Spring's encrypted property support or HashiCorp Vault integration for production secret management in environments that require secrets rotation or audit trails.

Spring Boot Screenshot Use Cases and Patterns

Spring Boot applications use screenshot and scraping APIs in several recurring architectural patterns. The most common is the scheduled monitoring job: a @Scheduled method runs every few minutes, iterates a list of monitored URLs from a database or configuration file, calls SnapAPI for a screenshot of each, saves the result to S3 or a file system, and compares the new screenshot against the previous one to detect visual changes. Another common pattern is the on-demand screenshot endpoint: a REST controller accepts a URL parameter from a frontend or internal API client, proxies the screenshot request to SnapAPI, and returns the binary image directly in the HTTP response with the appropriate Content-Type header, enabling the frontend to embed the screenshot without exposing the SnapAPI key to the browser. The third pattern is the document generation pipeline: a scheduled job or event listener processes a queue of document generation requests, calls SnapAPI's PDF endpoint for each URL, stores the resulting PDF in S3 or a document storage service, and updates a database record with the PDF location and generation timestamp. Spring's @Async and CompletableFuture support makes all three patterns straightforward to implement with non-blocking execution and configurable thread pool sizing. The RestTemplate and WebClient integration examples shown above provide the building blocks for all of these patterns, with the specific scheduling, queuing, and storage integration handled by standard Spring components.

Error Handling and Resilience in Spring Boot

Production Spring Boot integrations with external APIs need explicit error handling and retry logic to remain resilient under network instability and temporary API unavailability. RestTemplate throws HttpClientErrorException for 4xx responses and HttpServerErrorException for 5xx responses. Catch these exceptions separately: a 4xx error usually indicates a problem with the request parameters (an invalid URL, an unsupported format, or a missing required parameter) and should not be retried, while a 5xx error indicates a temporary server-side issue that is a candidate for retry with exponential backoff. Spring Retry provides @Retryable annotation support that applies retry logic declaratively without cluttering the method body: annotate the screenshot service method with @Retryable(value = {HttpServerErrorException.class}, maxAttempts = 3, backoff = @Backoff(delay = 1000, multiplier = 2)) to automatically retry on server errors with 1s, 2s, 4s delays. Add @Recover to handle the failure case after retries are exhausted, logging the failure and returning an appropriate fallback response to the caller. For WebClient reactive implementations, use retryWhen with Retry.backoff from the Reactor Retry library to achieve equivalent retry behavior in the reactive pipeline without blocking. Circuit breaker patterns using Resilience4j or Spring Cloud Circuit Breaker further improve resilience by stopping request floods to a failing API and failing fast with a fallback response until the API recovers. These resilience patterns are especially important for screenshot services used in synchronous request paths where latency directly affects user experience.

Spring Boot Configuration and Secret Management

Managing the SnapAPI key in Spring Boot follows standard Spring configuration practices. Store the key in application.properties or application.yml under a custom property like snapapi.key, inject it into service classes with @Value("${snapapi.key}"), and provide the actual secret value through environment variables in production rather than hardcoding it in the configuration file. In Kubernetes deployments, create a Secret resource containing the SnapAPI key and mount it as an environment variable in the Pod spec: the Spring application reads the environment variable at startup and injects it into the property, keeping the secret out of source control and version history. In AWS deployments, use AWS Secrets Manager or Parameter Store with the AWS Spring Boot starter to fetch the secret at application startup and bind it to the Spring property namespace. For local development, use Spring Boot's support for .env files via a library like spring-dotenv, or define the environment variable in the IDE run configuration. This approach ensures the API key is never committed to version control, rotatable without code changes, and auditable through your secrets management system. A secondary benefit of property injection over hardcoding is that the same application binary can use different SnapAPI keys for development, staging, and production environments simply by varying the environment variable value at deployment time.