COMPARISON

Troubleshooting Common Issues When Selecting PDF Libraries for .NET in 2025-2026

Choosing a PDF library for .NET is a deployment decision as much as a feature decision. The library that works on your Windows development machine may leak memory on Linux, fail in Docker containers, carry licensing terms that disqualify it for commercial use, or require infrastructure management that costs more than a commercial license.

This article evaluates PDF libraries through the lens of .NET deployment realities: cross-platform behavior, containerized operation, memory stability under production load, and total licensing cost. Each library is assessed through concrete scenarios rather than feature checklists.

How .NET Deployment Requirements Shape Library Choice

The .NET ecosystem in 2026 deploys to Windows servers, Linux containers, macOS development machines, Azure App Service, AWS Lambda, ARM-based infrastructure (Apple Silicon, Graviton), and Docker in every combination. A PDF library must work across these targets — or you need to know exactly where it fails before committing.

Three deployment factors cause the most production incidents:

System.Drawing.Common dependency: Microsoft deprecated this for non-Windows platforms in .NET 6. Libraries that depend on it (PdfSharp, Aspose.PDF) require libgdiplus on Linux — an unmaintained library with documented memory leaks. This isn't a theoretical concern; it surfaces as gradually increasing memory consumption that eventually crashes your container.

Native binary management: Libraries wrapping external tools (wkhtmltopdf, Puppeteer Sharp) require deploying and managing platform-specific binaries. Docker images gain 200–400MB in dependencies. Path configuration, sandbox permissions, and process lifecycle management become your problem.

Licensing hidden costs: AGPL (iText) requires either open-sourcing your entire application or purchasing commercial licensing with unpublished pricing. Revenue thresholds (QuestPDF) create licensing cliffs at $1M. "Contact sales" pricing (iText, Apryse) makes budgeting impossible.

Library Evaluations

IronPDF — Embedded Chromium, Cross-Platform

IronPDF embeds Chromium in the NuGet package. HTML rendering matches Chrome because it uses the same engine. CSS Flexbox, Grid, custom properties, and JavaScript all work as expected.

using IronPdf;

var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.CssMediaType = IronPdf.Rendering.PdfCssMediaType.Print;

// CSS Grid, gradients, custom properties — all render correctly
var pdf = renderer.RenderHtmlAsPdf(@"
    <html>
    <head><style>
        :root { --primary: #2563eb; }
        .grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
        .card { background: linear-gradient(135deg, #f8fafc, #e2e8f0);
                border-radius: 8px; padding: 20px; text-align: center; }
        .card .value { font-size: 2rem; font-weight: 700; color: var(--primary); }
    </style></head>
    <body>
        <div class='grid'>
            <div class='card'><h3>Revenue</h3><div class='value'>$1.2M</div></div>
            <div class='card'><h3>Users</h3><div class='value'>45,230</div></div>
            <div class='card'><h3>Uptime</h3><div class='value'>99.97%</div></div>
        </div>
    </body></html>");
pdf.SaveAs("dashboard.pdf");
using IronPdf;

var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.CssMediaType = IronPdf.Rendering.PdfCssMediaType.Print;

// CSS Grid, gradients, custom properties — all render correctly
var pdf = renderer.RenderHtmlAsPdf(@"
    <html>
    <head><style>
        :root { --primary: #2563eb; }
        .grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
        .card { background: linear-gradient(135deg, #f8fafc, #e2e8f0);
                border-radius: 8px; padding: 20px; text-align: center; }
        .card .value { font-size: 2rem; font-weight: 700; color: var(--primary); }
    </style></head>
    <body>
        <div class='grid'>
            <div class='card'><h3>Revenue</h3><div class='value'>$1.2M</div></div>
            <div class='card'><h3>Users</h3><div class='value'>45,230</div></div>
            <div class='card'><h3>Uptime</h3><div class='value'>99.97%</div></div>
        </div>
    </body></html>");
pdf.SaveAs("dashboard.pdf");
Imports IronPdf

Dim renderer As New ChromePdfRenderer()
renderer.RenderingOptions.CssMediaType = IronPdf.Rendering.PdfCssMediaType.Print

' CSS Grid, gradients, custom properties — all render correctly
Dim pdf = renderer.RenderHtmlAsPdf("
    <html>
    <head><style>
        :root { --primary: #2563eb; }
        .grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
        .card { background: linear-gradient(135deg, #f8fafc, #e2e8f0);
                border-radius: 8px; padding: 20px; text-align: center; }
        .card .value { font-size: 2rem; font-weight: 700; color: var(--primary); }
    </style></head>
    <body>
        <div class='grid'>
            <div class='card'><h3>Revenue</h3><div class='value'>$1.2M</div></div>
            <div class='card'><h3>Users</h3><div class='value'>45,230</div></div>
            <div class='card'><h3>Uptime</h3><div class='value'>99.97%</div></div>
        </div>
    </body></html>")
pdf.SaveAs("dashboard.pdf")
$vbLabelText   $csharpLabel

Trade-offs: The embedded Chromium adds ~200MB to the deployment. First-generation cold start is 2–5 seconds; subsequent generations run 100–500ms. Memory baseline is ~150–200MB. For containers, allocate at least 512MB; 1GB recommended for complex documents.

Licensing: Perpetual — $749 (Lite), $1,499 (Professional), $2,999 (Enterprise). Published at ironpdf.com. No per-document fees, no AGPL, no revenue thresholds.

Where it fits: Applications converting HTML templates to PDF — invoices, reports, dashboards, email archiving. Cross-platform deployments where Docker, Linux, and ARM64 are targets.

QuestPDF — Fluent API, No HTML

A team building a containerized .NET 8 microservice evaluated QuestPDF because of its elegant API and MIT-like community license. The service needed to generate structured reports from database records — no HTML templates involved. QuestPDF was an excellent fit: the fluent API mapped cleanly to their data model, Docker deployment was trivial (no native dependencies), and the Community License covered them at $800K annual revenue.

Two months later, a feature request arrived: export the web dashboard (built in React) as a PDF. QuestPDF can't parse HTML. The team added IronPDF alongside QuestPDF for that specific workflow — but could have avoided the dual-library maintenance cost by selecting a single library that handles both scenarios.

Revenue threshold: The Community License covers businesses under $1M annual gross revenue. At $1,000,001, a commercial license is required regardless of how much you use QuestPDF.

Where it fits: Programmatic document generation from structured data where HTML templates aren't part of the workflow. Startups and teams under the revenue threshold.

PdfSharp — MIT Licensed, Windows Only in Practice

PdfSharp(34.9 million NuGet downloads, MIT license) provides coordinate-based PDF drawing. No HTML parser, no CSS engine. For simple tasks — merging PDFs, adding watermarks, generating invoices from data with programmatic layouts — it works without licensing concerns.

The deployment constraint is System.Drawing.Common. On Linux, this requires libgdiplus, which has unfixed memory leaks. PdfSharp 6.x has been working on removing this dependency, but cross-platform reliability remains incomplete.

Where it fits: Windows-only deployments where you need basic PDF manipulation (merge, split, watermark) or simple document generation from data without HTML.

iText 7 — Capable Library, Licensing Minefield

iText is technically capable for PDF manipulation — forms, signatures, annotations, structured extraction. The pdfHTML add-on provides HTML conversion, though it renders CSS 2.1 at best (no Flexbox, no Grid, no JavaScript).

The licensing is the defining factor. AGPL requires open-sourcing your entire network-accessible application. Commercial licensing is subscription-based with pricing not published— third-party data suggests $15,000–$210,000 annually. iText and parent company Apryse actively enforce compliance.

Where it fits: Organizations that can satisfy AGPL requirements (open-source projects) or have budget for enterprise licensing. PDF manipulation tasks (forms, signatures) where HTML rendering quality isn't critical.

Aspose.PDF — Broad Features, Linux Instability

Aspose.PDF provides extensive PDF functionality with commercial licensing (~$999+/developer). The critical issue is the System.Drawing.Common dependency on Linux:

"Several dozen requests cause the service to run out of memory in the Unix environment, but this does not occur in the Windows based environment" — Aspose Forum, March 2022

These reports span 8+ years. The root cause is libgdiplus — an unmaintained library that doesn't release memory even when objects are disposed. The memory grows with each document processed until the container crashes.

Where it fits: Windows-only deployments where broad PDF manipulation features justify the licensing cost. Not suitable for Linux, Docker, or cloud deployments without accepting ongoing memory management risk.

Syncfusion PDF — Blazor Memory Leak

Syncfusion offers PDF generation and viewing components, including Blazor integration. A free Community License covers individuals and businesses under $1M revenue. The significant issue is documented memory leaks in Blazor PDF components.

The leak manifests when navigating between pages that use SfPdfViewerServer. Static caches in Syncfusion.Pdf.PdfDocument retain references after component disposal. Unmanaged bitmaps from the Pdfium engine aren't cleaned. The Dispose() implementation doesn't release all resources:

@page "/pdf-viewer"
@implements IDisposable

<SfPdfViewerServer DocumentPath="@PdfDocument" />

@code {
    string PdfDocument = "sample.pdf";

    // Memory leak: static cache + unmanaged bitmaps persist
    // after navigation, even with explicit disposal
    public void Dispose()
    {
        // Doesn't free static references or Pdfium bitmaps
    }
}
@page "/pdf-viewer"
@implements IDisposable

<SfPdfViewerServer DocumentPath="@PdfDocument" />

@code {
    string PdfDocument = "sample.pdf";

    // Memory leak: static cache + unmanaged bitmaps persist
    // after navigation, even with explicit disposal
    public void Dispose()
    {
        // Doesn't free static references or Pdfium bitmaps
    }
}
Imports System

@page "/pdf-viewer"
@implements IDisposable

<SfPdfViewerServer DocumentPath="@PdfDocument" />

@code
    Private PdfDocument As String = "sample.pdf"

    ' Memory leak: static cache + unmanaged bitmaps persist
    ' after navigation, even with explicit disposal
    Public Sub Dispose() Implements IDisposable.Dispose
        ' Doesn't free static references or Pdfium bitmaps
    End Sub
End Code
$vbLabelText   $csharpLabel

The recommended mitigation is aggressive disposal with forced garbage collection:

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (!firstRender)
    {
        await PdfViewer.UnloadAsync();
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect(); // Double-collect for finalizable objects
    }
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (!firstRender)
    {
        await PdfViewer.UnloadAsync();
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect(); // Double-collect for finalizable objects
    }
}
Protected Overrides Async Function OnAfterRenderAsync(firstRender As Boolean) As Task
    If Not firstRender Then
        Await PdfViewer.UnloadAsync()
        GC.Collect()
        GC.WaitForPendingFinalizers()
        GC.Collect() ' Double-collect for finalizable objects
    End If
End Function
$vbLabelText   $csharpLabel

Using individual NuGet packages (Syncfusion.Blazor.PdfViewer instead of Syncfusion.Blazor) reduces the surface area. Syncfusion's newer SfPdfViewer2 component has a different architecture, but developers report its own set of issues.

Where it fits: Non-Blazor scenarios where Syncfusion's broader component ecosystem is already in use. For Blazor PDF generation, the memory leak risk is real and documented.

Puppeteer Sharp — Full Rendering, Operational Cost

Puppeteer Sharp renders HTML through headless Chrome — full CSS and JavaScript support. The trade-off is managing external browser processes: downloads, pooling, crash recovery, and Docker configuration with 20+ Chromium dependencies.

using PuppeteerSharp;

await new BrowserFetcher().DownloadAsync(); // ~280MB
await using var browser = await Puppeteer.LaunchAsync(
    new LaunchOptions { Headless = true,
        Args = new[] { "--no-sandbox", "--disable-setuid-sandbox" } });
await using var page = await browser.NewPageAsync();
await page.SetContentAsync(html);
var pdfBytes = await page.PdfAsync(new PdfOptions
    { Format = PaperFormat.A4, PrintBackground = true });
using PuppeteerSharp;

await new BrowserFetcher().DownloadAsync(); // ~280MB
await using var browser = await Puppeteer.LaunchAsync(
    new LaunchOptions { Headless = true,
        Args = new[] { "--no-sandbox", "--disable-setuid-sandbox" } });
await using var page = await browser.NewPageAsync();
await page.SetContentAsync(html);
var pdfBytes = await page.PdfAsync(new PdfOptions
    { Format = PaperFormat.A4, PrintBackground = true });
Imports PuppeteerSharp

Await (New BrowserFetcher()).DownloadAsync() ' ~280MB
Using browser = Await Puppeteer.LaunchAsync(New LaunchOptions With {
    .Headless = True,
    .Args = New String() {"--no-sandbox", "--disable-setuid-sandbox"}
})
    Using page = Await browser.NewPageAsync()
        Await page.SetContentAsync(html)
        Dim pdfBytes = Await page.PdfAsync(New PdfOptions With {
            .Format = PaperFormat.A4,
            .PrintBackground = True
        })
    End Using
End Using
$vbLabelText   $csharpLabel

Production deployments require browser pooling, memory leak monitoring, and a substantially larger Docker image. The operational cost can exceed a commercial license for teams without dedicated DevOps capacity.

Where it fits: Teams with strong infrastructure expertise who need exact Chrome rendering and prefer MIT licensing over commercial.

Apryse (PDFTron) — Enterprise, Contact Sales

Apryse (formerly PDFTron) offers PDF viewing, annotation, and manipulation with commercial licensing. Pricing is not published and requires contacting sales. The SDK is capable for document workflow scenarios — annotation, redaction, form filling, digital signatures — but HTML-to-PDF is not its primary focus.

Where it fits: Enterprise document workflow applications with budget for custom licensing negotiations and requirements focused on PDF viewing/annotation rather than HTML-to-PDF conversion.

Feature Comparison

FeatureIronPDFiText 7PdfSharpQuestPDFAsposeSyncfusionPuppeteer
HTML to PDFFullLimitedNoNoLimitedLimitedFull
Modern CSSYesNoNoNoNoNoYes
JavaScriptYesNoNoNoNoNoYes
Linux (no libgdiplus)YesYesNoYesNoYesYes
Docker (standard image)YesYesNoYesRequires libgdiplusYesComplex
Published pricing$749+NoFreeFree <$1M$999+Free <$1MFree
Perpetual licenseYesNoN/AN/AYesN/AN/A

Decision Framework

The decision depends on three questions:

1. Does your workflow use HTML templates? If yes, only Chromium-based solutions (IronPDF, Puppeteer Sharp) render modern CSS correctly. iText's pdfHTML and Aspose's converter handle basic HTML but break on Flexbox, Grid, and JavaScript.

2. Where do you deploy? If Linux, Docker, or cloud — eliminate PdfSharp and Aspose (System.Drawing.Common dependency). Evaluate remaining libraries against your specific container and serverless constraints.

3. What can you spend? PdfSharp (MIT) and QuestPDF (Community) are free with limitations. IronPDF's perpetual license ($749–$2,999) is a one-time cost. iText's subscription pricing ($15K–$210K/year) is the highest in the category. Factor in operational costs for Puppeteer Sharp (DevOps time managing browser infrastructure).

Before You Commit

  1. Test with your actual HTML content, not "Hello World"
  2. Deploy to your target platform (Linux/Docker) before committing — Windows success doesn't predict Linux behavior
  3. Generate 100+ documents in a loop and monitor memory — single-document tests hide leaks
  4. Read the full license text with your legal team — AGPL implications surprise most teams
  5. Measure cold-start latency if targeting serverless environments