Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
java.io.IOException: Error: End-of-File, expected line usually means PDFBox reached the end of the supplied input while parsing PDF syntax. The cause is often not PDFBox itself: the input may be an HTML or JSON error response, an empty or truncated download, the wrong file, a consumed stream, or a malformed PDF.
Start by preserving and inspecting the exact bytes passed to PDFBox. Check the HTTP response, file size, beginning of the file, and structural integrity before changing parser settings or suppressing the exception.
What “expected line” means
PDFBox parses a PDF as structured syntax rather than ordinary Java text. Internally, its parser tries to read a line and throws this exception when the input source is already at EOF. The current parser source may also report the byte offset where parsing stopped: COSParser source.
java.io.IOException: Error: End-of-File, expected line
This message does not prove that the file is zero bytes, that the entire PDF is invalid, that PDFBox is defective, or that the final newline is missing. If the stack trace contains parseHeader, parsePDFHeader, or PDDocument.load, first investigate the beginning and completeness of the input.
The fastest diagnostic sequence
- Save the exact bytes supplied to PDFBox.
- For a download, record the final URL, HTTP status, and
Content-Type. - Check that the input is nonempty and begins plausibly as a PDF.
- Run an independent structural check such as
qpdf --check. - Load the saved file with an API matching your PDFBox major version.
- Compare the result with a known-good PDF and, if necessary, repair or reject the failing document.
Step 1: Confirm that the input is really a PDF
Do not trust a .pdf extension or HTTP content type. A server can save a login page, access-denied page, CAPTCHA, or JSON error response under a PDF filename. A normal PDF generally contains the signature %PDF- near the beginning.
file document.pdf
head -c 16 document.pdf | xxd
ls -l document.pdf
The first bytes should include:
25 50 44 46 2d
Those hexadecimal values represent %PDF-. This is only an initial diagnostic, not a complete validator: some files can contain leading bytes, and a file can have the signature yet still be truncated or malformed.
Look for an HTML or JSON prefix such as <html, <!DOCTYPE, an access-denied message, or {"error": ...}. Also treat an unusually small file as suspicious.
Bounded Java prefix check
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HexFormat;
public final class PdfDiagnostics {
private PdfDiagnostics() {}
public static void inspect(Path path) throws IOException {
Path absolute = path.toAbsolutePath().normalize();
byte[] bytes = Files.readAllBytes(absolute);
System.out.println("Path: " + absolute);
System.out.println("Exists: " + Files.exists(absolute));
System.out.println("Size: " + bytes.length);
int length = Math.min(bytes.length, 32);
System.out.println("First bytes: " +
HexFormat.of().formatHex(bytes, 0, length));
boolean startsAsPdf = bytes.length >= 5 &&
bytes[0] == '%' && bytes[1] == 'P' &&
bytes[2] == 'D' && bytes[3] == 'F' &&
bytes[4] == '-';
System.out.println("Starts with %PDF-: " + startsAsPdf);
}
}
For very large files, inspect only a bounded prefix rather than reading the entire file solely for diagnostics. If the response is already held in a byte array, inspect that array before writing or parsing it.
Recommended Free Tools
Step 2: Inspect HTTP responses before calling PDFBox
Remote URLs add several failure points: redirects may not be followed, authentication may be missing, a token may have expired, or an intermediary may return an incomplete body. HTTP status 200 is not enough because an application can return an error page with status 200.
Rank #2
With Java’s built-in HTTP client, download the complete response first and validate it:
import java.io.IOException;
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 DownloadPdf {
public static Path downloadPdf(URI uri, Path destination)
throws IOException, InterruptedException {
HttpClient client = HttpClient.newBuilder()
.followRedirects(HttpClient.Redirect.NORMAL)
.build();
HttpRequest request = HttpRequest.newBuilder(uri)
.header("Accept", "application/pdf")
.GET()
.build();
HttpResponse<byte[]> response = client.send(
request, HttpResponse.BodyHandlers.ofByteArray());
int status = response.statusCode();
String contentType = response.headers()
.firstValue("Content-Type").orElse("");
byte[] bytes = response.body();
if (status < 200 || status >= 300) {
throw new IOException("PDF download failed: HTTP " + status);
}
if (bytes.length < 5 || bytes[0] != '%' || bytes[1] != 'P' ||
bytes[2] != 'D' || bytes[3] != 'F' || bytes[4] != '-') {
throw new IOException(
"Response is not a PDF. Content-Type: " + contentType);
}
Files.write(destination, bytes);
return destination;
}
}
In production, also apply connection and body-size limits, handle authentication deliberately, and log the final response URL and relevant headers without exposing credentials. Do not accept arbitrary remote URLs without SSRF protections.
To isolate the HTTP layer, temporarily save the response exactly as received:
System.out.println("Status: " + response.statusCode());
System.out.println("Content-Type: " +
response.headers().firstValue("Content-Type").orElse(""));
System.out.println("Bytes: " + response.body().length);
Files.write(Path.of("debug-download.bin"), response.body());
Inspect debug-download.bin independently. If it begins with HTML or JSON, fix the URL, redirect handling, authorization, or server-side error handling rather than PDFBox.
Step 3: Load the file with the correct PDFBox API
Match the example to the major version in your dependency file. PDFBox 2.x normally uses PDDocument.load(...); PDFBox 3.x uses Loader.loadPDF(...). See the PDFBox 3.x migration guide and the 2.x Javadocs.
PDFBox 2.x
import org.apache.pdfbox.pdmodel.PDDocument;
import java.nio.file.Path;
try (PDDocument document =
PDDocument.load(Path.of("document.pdf").toFile())) {
System.out.println(document.getNumberOfPages());
}
For bytes or a stream:
byte[] pdfBytes = Files.readAllBytes(Path.of("document.pdf"));
try (PDDocument document = PDDocument.load(pdfBytes)) {
System.out.println(document.getNumberOfPages());
}
try (InputStream input = Files.newInputStream(Path.of("document.pdf"));
PDDocument document = PDDocument.load(input)) {
System.out.println(document.getNumberOfPages());
}
PDFBox 3.x
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import java.nio.file.Files;
import java.nio.file.Path;
byte[] pdfBytes = Files.readAllBytes(Path.of("document.pdf"));
try (PDDocument document = Loader.loadPDF(pdfBytes)) {
System.out.println(document.getNumberOfPages());
}
Use try-with-resources so the document and input stream are closed. A direct URL or response stream should not be treated as trustworthy input merely because it came from a URL; download and inspect it first.
Step 4: Check for truncation or malformed structure
Compare the bytes produced by your application with a known-good download. Useful commands are:
sha256sum document.pdf
qpdf --check document.pdf
qpdf is an independent PDF validation and repair utility, not part of PDFBox. Its check can reveal damaged cross-reference data or premature EOF. A checksum comparison can show whether a temporary file differs from the original response or a known-good copy.
Rank #4
Run a control test with a PDF known to work:
try (PDDocument document =
PDDocument.load(Path.of("known-good.pdf").toFile())) {
System.out.println("PDFBox works; pages = " +
document.getNumberOfPages());
}
- If the known-good file also fails, investigate the dependency version, classpath, runtime, and loading code.
- If only one file fails, suspect that file or the way it was acquired.
- If the local copy works but the URL path fails, investigate HTTP handling or authentication.
- If qpdf reports structural errors, repair or reject the file.
- If a browser opens it but PDFBox fails, assume the viewer may be applying recovery heuristics; successful display is not proof of strict conformance.
Step 5: Fix upload and stream lifecycle problems
An upload stream may already have been consumed by MIME detection, antivirus scanning, hashing, logging, or another parser. Calling reset() is unsafe unless the stream supports marking and the mark remains valid. Other common problems include reading only part of a multipart request, closing the stream too early, or deleting a temporary file before parsing completes.
For modest uploads, buffer the bytes once:
byte[] bytes = inputStream.readAllBytes();
if (bytes.length == 0) {
throw new IOException("Uploaded file is empty");
}
try (PDDocument document = PDDocument.load(bytes)) {
// Process the document
}
For large PDFs, write the upload to a controlled temporary file and load that file using PDFBox’s appropriate memory-management options. This avoids unnecessary heap use and gives you a stable, repeatable input for diagnostics.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Step 6: Fix shell-script and path problems
A path can be correct in an interactive test but wrong when passed through a shell script. Quote shell variables:
Windows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallCrashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minutejava -jar app.jar "$PDF_PATH"
Do not use the unquoted form java -jar app.jar $PDF_PATH when paths may contain spaces, wildcard characters, or shell metacharacters.
Best Value
In Java, log and verify the resolved path:
Path path = Path.of(args[0]).toAbsolutePath().normalize();
System.out.println("Reading: " + path);
System.out.println("Exists: " + Files.exists(path));
System.out.println("Size: " + Files.size(path));
Also check the process working directory, permissions, URL-encoded or escaped filenames, concurrent overwrites, and whether the script downloaded an HTML error page. Apache issue PDFBOX-4443 illustrates why filename and invocation handling deserve separate attention.
Step 7: Repair or reject a malformed PDF
Preserve the original before attempting repair. A possible qpdf workflow is:
qpdf --check damaged.pdf
qpdf damaged.pdf repaired.pdf
qpdf --check repaired.pdf
Repair may fail or may discard damaged objects, change metadata, alter incremental-update history, or affect digital signatures. Re-saving through a trusted PDF application can also change the document. For signed, evidentiary, archival, or legally important files, do not silently repair: retain the original and follow your document policy. If the file is severely truncated, no repair tool may be able to reconstruct missing bytes.
Free tools Windows power users keep installed
One-click scans. No signup required.
If policy permits, retry PDFBox against the repaired copy. Otherwise reject the document or route it through a controlled conversion service.
Should you upgrade PDFBox?
Upgrading is sensible when you use an old release, the failure is reproducible with a complete and structurally valid PDF, or a relevant parser fix is documented for your version. Test the upgrade against representative documents and your runtime.
It cannot fix an HTML error page, empty response, wrong path, consumed stream, or transfer truncated before PDFBox receives it. Reports such as PDFBOX-4736, PDFBOX-5006, and PDFBOX-5089 show that this exception can arise from remote input, malformed files, and download-related conditions. Their outcomes do not establish a universal PDFBox defect.
Decision table
| Finding | Likely cause | Remedy |
|---|---|---|
| Zero bytes | Empty upload, failed download, or wrong stream | Fix acquisition and validate length |
| HTML or JSON prefix | Error, login, or authorization response | Check status, redirects, authentication, and body |
| No plausible PDF signature | Wrong file or corrupt/nonstandard input | Obtain the actual PDF |
| Signature present but file is tiny | Truncated transfer | Re-download and verify completion |
| Local file works but URL fails | HTTP or authentication path | Save and inspect response bytes |
| Only one PDF fails | File-specific corruption | Repair or reject it |
| qpdf reports errors | Malformed PDF structure | Repair, convert, or reject |
| Every PDF fails | Dependency, API, runtime, or classpath problem | Check version and loading code |
| Shell invocation fails | Argument expansion or wrong working directory | Quote arguments and log the absolute path |
| Upload fails after earlier processing | Consumed or closed stream | Buffer once or use a seekable temporary file |
Important edge cases
- Leading bytes: A simplistic “must start at byte zero” test should not be the sole acceptance rule.
- Encryption: A correctly structured encrypted PDF generally produces a password or encryption-related failure, which should not automatically be conflated with EOF parsing.
- Linearization: Progressive display by a viewer does not prove that your application received the complete body.
- HTTP compression: Ensure the client handles response encoding correctly and does not save an unsuitable transport representation.
- Security: Apply timeouts, size limits, content validation, and SSRF controls when fetching remote documents.
Bottom line
Treat this exception as a clue that PDFBox reached EOF while parsing the bytes it received. Preserve those bytes, inspect the HTTP response or resolved path, validate the content, run an independent structural check, and then load the file with the correct PDFBox 2.x or 3.x API. Only after those checks should you consider repairing the document or upgrading PDFBox.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

