Screenshot a Website with Selenium Java on a Headless Linux Server
Capture and save a website screenshot with Selenium Java on headless Linux. Set up Chrome, wait for the right page state, and troubleshoot common failures.
To screenshot a website with Selenium Java on a headless Linux server, launch Chrome with ChromeOptions and --headless=new, navigate to the target URL, wait for the specific content the image needs, capture it through TakesScreenshot, and save the returned file to a writable path. Always close the WebDriver session in a finally block. Use matching Chrome and ChromeDriver major versions.
The example below captures the browser viewport as a PNG. Headless mode removes the need for a visible desktop session; it does not remove the need to install Chrome, choose the right page wait, or make sure the server process can write the output file. See the official Selenium Chrome documentation and Selenium screenshot API documentation.
1. Install Java, Chrome, and Selenium
Install a supported Java runtime and Google Chrome or Chromium on the Linux host. Add Selenium’s Java library to your project. With Maven, include the Selenium Java dependency in pom.xml; use the current version listed by the Selenium project when setting up or upgrading your build:
<dependencies>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>YOUR_SELENIUM_VERSION</version>
</dependency>
</dependencies>
Selenium Manager can manage driver setup in current Selenium releases when a compatible browser is installed. In locked-down or reproducible server environments, provision Chrome and ChromeDriver explicitly and verify their major versions match. If Chrome is installed in a nonstandard location, set its path with ChromeOptions.setBinary(...). Selenium’s Chrome page documents the binary option and the browser-driver major-version requirement.
2. Capture and save a screenshot in Java
This complete class accepts an optional URL and output path. It waits for the document’s body element, captures the current browser viewport, creates the output directory if needed, overwrites an existing file, and quits the driver even if navigation or capture fails.
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.time.Duration;
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.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
public class WebsiteScreenshot {
public static void main(String[] args) throws IOException {
String url = args.length > 0 ? args[0] : "https://example.com";
Path output = Path.of(args.length > 1 ? args[1] : "output/shot.png");
ChromeOptions options = new ChromeOptions();
options.addArguments("--headless=new");
options.addArguments("--window-size=1440,1000");
WebDriver driver = new ChromeDriver(options);
try {
driver.manage().timeouts().pageLoadTimeout(Duration.ofSeconds(60));
driver.get(url);
// Replace this with a selector or state that matters to your page.
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(20));
wait.until(ExpectedConditions.presenceOfElementLocated(By.tagName("body")));
Files.createDirectories(output.toAbsolutePath().getParent());
File screenshot = ((TakesScreenshot) driver)
.getScreenshotAs(OutputType.FILE);
Files.copy(screenshot.toPath(), output,
StandardCopyOption.REPLACE_EXISTING);
System.out.println("Saved screenshot to " + output.toAbsolutePath());
} finally {
driver.quit();
}
}
}
Run it with the URL and destination as arguments after compiling with your Maven project:
mvn compile
mvn exec:java -Dexec.mainClass=WebsiteScreenshot -Dexec.args="https://example.com output/example.png"
The exec:java command requires Maven’s Exec plugin configured in the project. Alternatively, run the compiled class using your IDE or the Java launcher with the project’s dependency classpath.
3. Wait for the content the screenshot needs
driver.get(url) normally waits for the document’s ready state to reach complete. That does not guarantee a single-page application has finished rendering data, images, animations, or client-side components. Selenium documents this distinction in its browser options and page-load strategy guide.
Wait for the actual target condition rather than adding an arbitrary sleep:
// A result card has appeared
wait.until(ExpectedConditions.visibilityOfElementLocated(
By.cssSelector(".result-card")));
// A loading indicator has gone away
wait.until(ExpectedConditions.invisibilityOfElementLocated(
By.cssSelector(".loading-spinner")));
// A button became enabled
wait.until(ExpectedConditions.elementToBeClickable(
By.id("continue")));
Use presenceOfElementLocated when existence in the DOM is enough, visibilityOfElementLocated when it must be visible, and invisibilityOfElementLocated when a loading overlay must disappear. Choose a condition that represents the state you want rendered, including any app-specific signal for loaded data.
4. Configure the capture for your page
Viewport size
Set the viewport before navigation when the page’s responsive layout depends on it:
options.addArguments("--window-size=1440,1000");
Choose dimensions that match the layout you need to document. A standard WebDriver screenshot captures the current viewport. Selenium also supports screenshots of individual elements through the element screenshot API. Full-page screenshots are not a portable guarantee of the standard viewport capture; if the whole page is required, use an approach supported by your browser and Selenium setup, and verify the resulting image dimensions and content.
Page load strategy and timeouts
The default navigation strategy waits for complete. Selenium also defines eager (waits until interactive) and none (does not block on page readiness). Faster navigation completion can be useful when you explicitly wait for a later page condition, but it does not make the page itself render faster.
import org.openqa.selenium.PageLoadStrategy;
options.setPageLoadStrategy(PageLoadStrategy.EAGER);
driver.manage().timeouts().pageLoadTimeout(Duration.ofSeconds(60));
Keep an explicit wait for the image’s real content when changing the strategy. A timeout should reflect the target site’s expected behavior and your job’s overall deadline; no single value suits every page.
Custom Chrome binary or driver service
For a browser installed outside the default location:
options.setBinary("/path/to/chrome");
If the driver executable is not discoverable, configure the driver service or system property according to your Selenium version and deployment. Keep browser and driver updated together, and ensure the Linux user running Java can execute them.
Element capture
When only one component matters, capture its element instead of the viewport:
import org.openqa.selenium.WebElement;
WebElement chart = wait.until(ExpectedConditions.visibilityOfElementLocated(
By.cssSelector(".report-chart")));
File image = chart.getScreenshotAs(OutputType.FILE);
Files.copy(image.toPath(), Path.of("output/chart.png"),
StandardCopyOption.REPLACE_EXISTING);
5. Run headless Chrome safely on Linux
- Check versions and installation. Confirm the Chrome binary is available and ChromeDriver’s major version matches Chrome’s.
- Use a writable output path. Create the destination directory and run Java as a user with permission to write there.
- Allow enough resources. Browser startup and rendering need memory, CPU, and temporary disk space. Avoid running more concurrent sessions than the host can support.
- Close every session. Keep
driver.quit()infinallyso failures do not leave browser processes behind. - Use explicit waits. Synchronize on the content or state shown in the screenshot, not a fixed delay where a reliable condition is available.
In containers or restricted Linux environments, Chrome startup may fail because of sandbox or shared-memory restrictions. First inspect the ChromeDriver and browser error output, container security settings, and available shared memory. Selenium’s Chrome documentation lists common command-line options, but the correct container configuration depends on how the host is secured. Avoid disabling Chrome’s sandbox as a blanket fix; only change isolation settings when the environment’s security model has been reviewed.
6. Save screenshots reliably
- Use deterministic filenames. Include a test identifier or timestamp when preserving multiple captures; otherwise a fixed path will overwrite the previous file.
- Make output directories before copying. The example creates parent directories and replaces an existing file.
- Keep cleanup in
finally. A timeout, missing selector, or filesystem exception should still close the browser session. - Record useful failure context. Log the target URL, output path, exception, and relevant browser/driver versions. Avoid logging secrets in URLs or headers.
- Validate the artifact when required. Check that the file exists and is nonempty; a successful WebDriver call does not prove the screenshot contains the expected application state.
7. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
SessionNotCreatedException or Chrome fails to start |
Chrome and ChromeDriver major versions differ, Chrome is missing, or its path is wrong. | Install or point to the intended Chrome binary and align the ChromeDriver major version. Check startup logs. |
| Chrome exits immediately on a server | Missing system libraries, restricted container settings, insufficient resources, or sandbox/shared-memory constraints. | Inspect browser and driver logs, verify the host’s Chrome dependencies and container configuration, and check available memory and shared memory. |
| Screenshot is blank or misses dynamic content | Capture happened after document load but before the app rendered the needed state. | Wait for a meaningful visible element, disappearance of a loader, or application-specific readiness signal. |
| Screenshot is the wrong size or layout | The viewport was not set as expected, or responsive rendering uses a different viewport. | Set --window-size before startup and verify the browser’s actual viewport and screenshot dimensions. |
| Screenshot file is missing | Destination directory does not exist, path is relative to an unexpected working directory, or the process cannot write there. | Use an absolute path, create parent directories, and check ownership and permissions for the Java process. |
| Navigation times out | The page or one of its resources did not reach the configured load state in time. | Inspect target availability and network access; choose an appropriate page-load timeout and use a deliberate page-load strategy plus explicit content wait. |
| Intermittent capture failures in a batch | Concurrent browsers may exceed available CPU, memory, or file capacity, or the target may respond inconsistently. | Limit concurrency, give each run its own output filename, capture diagnostic context, and retry only failures that are safe to repeat. |
8. Performance, reliability, and cost
Each local capture starts or uses a browser session, loads the target page and its dependencies, and writes an image. The dominant time and resource use usually depend on the page, browser startup, network, and host capacity; the supplied Selenium documentation gives no universal screenshot speed or reliability benchmark. Reuse a session only when your workflow benefits from it, and isolate state when cookies, local storage, or navigation from a prior capture could affect results.
Set explicit navigation and condition waits, cap parallel sessions to what the machine can sustain, and always quit sessions. For repeatable output, control the browser version, viewport, target state, and output naming. Selenium is software-based; this workflow does not require a physical product purchase. Infrastructure cost depends on where you run the Linux host and is not specified by the Selenium sources used here.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. Its one-request API returns a PNG, JPEG, WebP, or PDF. This curl example captures the target page as WebP; see the ScreenshotNeo API documentation for parameters and response details:
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://example.com \
-o shot.webp
Equivalent requests from Python and Node.js:
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://example.com'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);
ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed, and response headers report the page verdict and billing status. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. [ScreenshotNeo plan details supplied for this article.]
Sign up for ScreenshotNeo and get 1,000 screenshots a month free with no card.
FAQ
Does headless Chrome need an X server?
No. Chrome’s headless mode runs without a visible desktop window, which is why it is suitable for Linux servers without a graphical session.
Does driver.get() mean the page is ready for a screenshot?
It means the configured navigation readiness condition was met. JavaScript applications can continue rendering after that, so wait for the content that should appear in the capture.
Can Selenium capture just one element?
Yes. Locate the element and call getScreenshotAs(OutputType.FILE) on it. This is useful for a chart or component when the rest of the viewport is irrelevant.
Why should I use driver.quit() instead of only closing a window?
quit() ends the WebDriver session. Put it in cleanup so browser resources are released whether capture succeeds or throws an error.


