How to Take Selenium Screenshots in AWS Lambda with Java
Package a compatible headless browser, capture with Selenium’s TakesScreenshot, and persist the image from a Java Lambda function.
Short answer: create a headless browser and matching driver inside the Lambda runtime, navigate with Selenium, cast the driver to TakesScreenshot, write the result to /tmp, then upload it to durable storage such as Amazon S3 before the invocation ends. The browser binary, driver, native libraries, CPU architecture and Lambda runtime must be compatible with one another; AWS’s Java documentation does not provide a Chromium distribution or a universal Selenium setup.
Selenium’s Java screenshot contract is the TakesScreenshot interface. Its getScreenshotAs(OutputType) method can return a temporary file, bytes or Base64 data. In Lambda, /tmp is temporary execution-environment storage, so copy the image to a persistent service when another process must retrieve it later.
1. Choose how to package Java, Selenium and the browser
A Lambda Java function can be deployed as a ZIP/JAR archive or as a container image. Both are supported by AWS; neither automatically includes Chromium or ChromeDriver.
| Choice | When it fits | Trade-offs |
|---|---|---|
| ZIP/JAR plus layers | Your team already publishes Lambda layers and wants standard Java archive deployment. | Browser binaries and shared libraries must fit the archive/layer limits and be assembled reproducibly. |
| Container image | The browser and native dependencies make archive packaging awkward, or you want local image parity. | You own the image build and updates. The AWS Java base image supplies the Lambda runtime components, not Chromium. |
AWS’s Java package guide covers dependency packaging with Maven Shade, Gradle and layers. Its Java container-image guide lists AWS base images. Java 21 and later bases use Amazon Linux 2023 and its microdnf/dnf tooling, so commands written for Amazon Linux 2 and yum may not apply. Check the current runtime table before choosing a tag.
2. Build the Java handler
The following is a deployment pattern, not a validated drop-in browser distribution. Replace the executable paths and launch flags with values for the exact Chromium/ChromeDriver build in your image or layer. Test the pair in the actual Lambda architecture and runtime.
Maven dependency
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.XX.X</version>
</dependency>
Pin a Selenium version that your build supports. Pin the browser and driver artifacts separately, and update them together after testing.
Lambda handler example
package example;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import org.openqa.selenium.By;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.time.Duration;
import java.util.Map;
public final class ScreenshotHandler
implements RequestHandler<Map<String, String>, String> {
@Override
public String handleRequest(Map<String, String> event, Context context) {
String url = event.getOrDefault("url", "https://example.com");
Path output = Path.of("/tmp/screenshot.png");
WebDriver driver = null;
try {
ChromeOptions options = new ChromeOptions();
options.addArguments("--headless");
options.addArguments("--no-sandbox");
options.addArguments("--disable-dev-shm-usage");
options.addArguments("--disable-gpu");
options.addArguments("--window-size=1440,900");
// Set these only when your selected distribution uses these paths.
options.setBinary("/opt/chrome/chrome");
System.setProperty("webdriver.chrome.driver", "/opt/chromedriver/chromedriver");
driver = new ChromeDriver(options);
driver.manage().timeouts().pageLoadTimeout(Duration.ofSeconds(60));
driver.get(url);
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(20));
wait.until((ExpectedCondition<Boolean>) d ->
"complete".equals(((org.openqa.selenium.JavascriptExecutor) d)
.executeScript("return document.readyState")));
// Optional, page-specific readiness condition:
// wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("main")));
Path temporary = ((TakesScreenshot) driver)
.getScreenshotAs(OutputType.FILE)
.toPath();
Files.copy(temporary, output, StandardCopyOption.REPLACE_EXISTING);
// Upload output to S3 or another durable store here.
return output.toString();
} catch (IOException e) {
throw new RuntimeException("Could not write screenshot", e);
} finally {
if (driver != null) {
driver.quit();
}
}
}
}
Use OutputType.BYTES when you want to stream the image directly to an upload client, or OutputType.BASE64 when a text transport requires it. The temporary-file form is convenient for copying to /tmp. Always call quit() in a finally block so browser processes do not accumulate during warm invocations.
3. Configure the browser for Lambda
- Executable paths: point Selenium at the browser and driver locations actually present in the image or layer. Do not assume
/usr/bin/google-chromeor a particular/optlayout. - Headless mode: use the headless flag supported by your browser build. Keep other flags only when required by that build; each flag can change behavior.
- Shared memory: Lambda’s container environment may make Chrome’s default shared-memory behavior unsuitable.
--disable-dev-shm-usageis a commonly used pattern, but validate it with your browser package. - Sandbox: some packaged browsers require
--no-sandboxin Lambda. This changes browser isolation, so use the least permissive configuration that works for your deployment. - Window size: set a deterministic viewport when pixel dimensions matter. A normal Selenium screenshot is generally the current viewport; full-page behavior depends on the driver.
- Architecture: an arm64 Lambda needs arm64-compatible browser and native libraries; x86_64 packages cannot be reused unchanged.
The Selenium API cautions that a non-W3C-conformant driver may make a browser-dependent best effort, preferring the entire page, current window, visible frame and finally the display. Confirm what your exact browser/driver pair returns instead of promising full-page output.
4. Wait for the page you actually need
driver.get() returning does not guarantee that application data, fonts or lazy images have rendered. Choose a readiness rule for the target site:
// Wait for a specific application element
wait.until(ExpectedConditions.visibilityOfElementLocated(
By.cssSelector("main.dashboard")));
// Or wait for a JavaScript application flag
wait.until(d -> Boolean.TRUE.equals(((JavascriptExecutor) d)
.executeScript("return window.appReady === true")));
// Or use a bounded delay for a known animation (last resort)
Thread.sleep(1000);
Prefer an element or application-state condition over a long fixed sleep. Keep every wait bounded so a broken page reaches a controlled timeout rather than consuming the entire invocation.
5. Save screenshots beyond the invocation
Lambda’s /tmp directory is temporary and belongs to one execution environment. AWS documents configurable ephemeral storage from 512 MB to 10,240 MB in 1 MB increments, encrypted at rest with an AWS-managed key. Warm environments can retain files between calls, but that is an implementation detail and not durable storage.
- Write the screenshot to a unique path such as
/tmp/<request-id>.png. - Upload it to S3 or another durable destination before returning.
- Store the object key and metadata with the job result.
- Delete large temporary files after upload if the function handles multiple captures per invocation.
Give the Lambda execution role only the object-storage permissions it needs. If a screenshot contains private data, apply the bucket’s encryption, access and retention controls to match your application.
6. ZIP/JAR and container deployment checklists
ZIP/JAR with layers
- Build the shaded Java artifact or dependency directory according to AWS’s Java package guide.
- Put the browser executable, driver and native libraries in the function or a Lambda layer.
- Make binaries executable and verify their dynamic-library dependencies during CI.
- Keep the total uncompressed ZIP deployment, including layers, within the current Lambda quota.
- Test the same architecture and runtime version used in production.
Container image
- Start from an AWS Java base image or include the Lambda Java runtime interface client when using another base.
- Install a browser and driver version pair that supports the image architecture.
- Use the image’s package manager: Java 21+ AWS bases use Amazon Linux 2023 and
microdnf/dnf. - Run the image locally with the Lambda Runtime Interface Emulator before deployment.
- Keep the image below Lambda’s current uncompressed container-image limit and scan/update native dependencies through your normal release process.
AWS currently documents memory from 128 MB to 10,240 MB, function timeout up to 900 seconds, up to five layers and container-image code packages up to 10 GB uncompressed. These are service ceilings, not a recommended Selenium configuration. Size memory, timeout and ephemeral storage from measurements of your pages.
7. Reliability and performance practices
| Concern | Practical approach |
|---|---|
| Cold starts | Keep the image and startup work small, and consider provisioned concurrency only after measuring the latency requirement. |
| Browser startup | Create one driver per invocation unless you have a carefully isolated warm-driver design. Always quit it on every path. |
| Page hangs | Set page-load, script and explicit-wait timeouts. Catch failures and return a useful status to the caller. |
| Concurrency | Set reserved or account concurrency to protect downstream sites and your own storage. Each concurrent browser needs memory and temporary disk. |
| Large pages | Increase memory and /tmp only when measurements show a need. Remove unneeded resources where your test permits it. |
| Repeatability | Fix viewport, timezone, locale, user agent and data state when visual comparisons matter. |
| Diagnostics | Log the target URL, elapsed phases, browser/driver versions, exception type and object key; avoid logging secrets or page contents. |
There is no source-backed universal memory value, startup time or screenshot throughput for Java Selenium on Lambda. Benchmark your browser build, page mix, architecture and concurrency in the deployed environment.
8. Common errors and fixes
| Error or symptom | Likely cause | Fix |
|---|---|---|
SessionNotCreatedException |
Browser and driver versions or architectures do not match. | Inspect both versions inside the deployed image, then install a compatible pair for the selected architecture. |
| “Chrome binary not found” | The configured path is absent or not executable. | List the file in the image/layer, correct setBinary, and verify execute permissions. |
| Process exits immediately | A required shared library is missing, or a launch flag is incompatible. | Run the browser manually in the same image, inspect stderr and native dependencies, and remove unsupported flags. |
| Timeout during navigation | The page, DNS, network path or application never becomes ready. | Set bounded timeouts, verify VPC egress and DNS, and wait for a specific readiness condition. |
| Blank or partial screenshot | Capture happened before rendering, or the driver captured only the viewport. | Wait for application content and images; verify full-page support for the exact driver instead of assuming it. |
| “Read-only file system” | The code writes outside Lambda’s writable directory. | Write temporary files under /tmp and upload them before returning. |
Out of space in /tmp |
Multiple large screenshots or browser caches filled ephemeral storage. | Clean files, use unique paths, and increase configured ephemeral storage within the quota. |
| Function killed or out of memory | Browser plus page exceeds the configured memory. | Measure peak use, raise memory, reduce concurrency or page weight, and investigate leaks. |
| Works locally but not in Lambda | Different OS libraries, architecture, runtime, fonts, network or browser build. | Reproduce with the deployed container image or an equivalent CI image and log versions at startup. |
9. Or skip the browser setup
ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP or PDF. It removes cookie-consent banners, newsletter popups and chat widgets before capture; bot checks, blank pages, timeouts, failed loads and cache hits are not billed, and response headers identify the page verdict and billing result. Its MCP tools let Claude, Cursor and other MCP clients call take_screenshot, get_page_info and capture_pdf.
See the ScreenshotNeo API documentation for all options and response details.
cURL
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://stripe.com \
-o shot.webp
Python
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
Node.js
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://stripe.com'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const bytes = Buffer.from(await res.arrayBuffer());
require('fs').writeFileSync('shot.webp', bytes);
ScreenshotNeo includes full-page capture with lazy images loaded, element selectors, dark mode, device presets and custom viewports, retina scale, PDF controls, custom CSS and JavaScript, clicks, selector/delay/network-idle waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, configurable caching, signed links, asynchronous jobs with signed webhooks, bulk capture for up to 100 URLs per call, a usage API and an OpenAPI specification. Parameter names used by other screenshot APIs also work to ease migration.
The Free plan includes 1,000 shots each month with no card. Paid plans start at $5 for 3,000 shots; every feature is available on every plan. Create a free ScreenshotNeo account.
10. FAQ
Can Selenium guarantee a full-page screenshot?
No. The Selenium API describes browser-dependent best-effort behavior. Verify the exact driver and browser, or capture and stitch page sections yourself when you need deterministic full-page output.
Is /tmp permanent in Lambda?
No. It is temporary execution-environment storage. Upload screenshots to durable storage before the invocation returns.
Should I use a Lambda layer or a container?
Use the format that matches how your team builds and updates native browser dependencies. Layers suit archive-based workflows; containers can make a complete browser environment easier to reproduce.
What browser version should I install?
The research sources do not establish a universal Java-compatible version. Select a browser and driver pair, then validate it in the exact Lambda runtime and architecture you deploy.
Can I reuse one WebDriver across invocations?
A warm environment may persist objects, but reuse complicates isolation, cleanup and failure recovery. Start with one driver per invocation and optimize only after measuring startup cost.


