Skip to main content
Skip table of contents

Volume Counter FAQs

FAQs and information about the counting mechanism for volume licenses used by iText 7 and newer.

Important!

As of the release of iText 7.2, the old licensing system was replaced by a new Unified Licensing Mechanism, which allows users to more easily check their usage and extended the event counting capabilities. This replaces the old itext-licensekey and itext-licensekey-volume dependencies with the new licensing-base and licensing-remote dependencies and the version number for the licensing libraries was increased from version 3.x to 4.x. In addition, it replaces the old XML license file with a new one in JSON format.

Simply put, if your license key file is an XML file you should follow the instructions marked iText 7.1.x in this FAQ. However, if you have a JSON license key file, see the instructions for iText 7.2.x and newer.

How do I enable volume counting?

You don't need to enable anything. If you have a valid volume license file and if you have loaded the iText license key volume dependency, then iText will have automatically enabled this for you. Both the license key library and volume license key library are required, see the Volume Counter installation guide for details on installation.

In iText 7.1.11 we introduced version 3.1 of the License Key and iText License Key Volume libraries which required "Volume Subscription" license holders to specify if they want to do volume counting locally, or remote (our most convenient way for usage reporting). As of the release of iText 7.1.15 and version 3.1.4 of the License Key and iText License Key Volume libraries, remote reporting is enabled by default, making reporting your usage much easier.

How do I get a volume license key file?

If you are an existing customer, you should contact your sales rep.
Otherwise, you will need to contact our sales department. You can fill out a form here and one of our sales representatives will contact you.

What if my Internet connection fails?

Don't worry, your code or program will not be affected if iText cannot connect to our server.

If your internet connection fails, then iText will cache all failed requests during its runtime and will try to send them with the next event. For the requests to be sent you will need to have a connection during the current runtime of your application. Statistics won't be carried over if the application shuts down.

Is the volume counting interface safe to use in a fully closed environment?

For volume counting to work, you will need to allow access for the volume counting server on port 443 to the following hosts:

For iText 7.2.x and newer

kinesis.eu-central-1.amazonaws.com, cognito-identity.eu-central-1.amazonaws.com, sdk-requests.licensing.itextpdf.comsdk-login.licensing.itextpdf.com and sdk-health.licensing.itextpdf.com.

For iText 7.1.x

kinesis.us-east-1.amazonaws.com, sdk-requests.licensing.itextpdf.comsdk-login.licensing.itextpdf.com and sdk-health.licensing.itextpdf.com

However, access is only required “one-way” as information is only sent, not received.

What kind of data does iText collect?

We collect information about the version of iText and its executable environment, the license key, the event type (which product) and the time it occurred. This information is useful for the development of iText, to gauge usage of certain functions and what versions of Java and .NET we should target.

If you want to know more details about the information that we collect, refer to your Software License Agreement (SLA).

Can I use my volume license keys with iText 5?

You can use your volume license keys with iText 5, but you won't be able to use the automated mechanism. To keep track of your statistics, you'll need to implement the Counter interface and register it to the CounterFactory. You can see an example implementation detailed here.

How can I use volume counting with my AGPL project?

Non-paying users can use a bare-bones counter for their projects. It doesn't involve our server but is a custom implementation of the Counter interface provided by iText. You can set your implementation by calling the following code:

Java

JAVA
IEventCounterFactory counterFactory = new SimpleEventCounterFactory(new CustomEventCounter());
EventCounterHandler.getInstance().register(counterFactory);

.NET

C#
IEventCounterFactory counterFactory = new SimpleEventCounterFactory(new CustomEventCounter());
EventCounterHandler.GetInstance().Register(counterFactory);

Why do Volume reporting requests sometimes get a 403 response?

This just means that the access token needs to be refreshed. It’s something that should only happen roughly once a day for any running instance.

How does volume counting work?

iText Core

The volume counting system used by iText is based on distinct instances of the PdfDocument class, which we count as "events". An event is recorded whenever a document operation includes a PdfDocument instance, or when a PdfDocument instance is used in the background by other classes such as PdfMerger or PdfSplitter. Therefore, any operation that involves reading, writing, or otherwise modifying a PDF contributes towards volume counting.

Only one event will be counted per document. So, opening a PDF, editing it, and then closing it will be counted as one event. Similarly, opening a PDF, editing it, and then saving as a separate document is also counted as a single event, as you can read one PDF and write a different one with the same PdfDocument instance.

Merging or splitting PDF documents will generate multiple events. For example, merging two separate documents would be counted as two distinct events (as there are separate PdfDocument instances for each of the two documents you merge). Splitting uses PdfSplitter which calls a PdfDocument instance. Therefore, splitting a two-page document counts as three events, as you read a file in one instance and then write two split files, each in a separate instance.

Rather than immediately sending every event which is counted, the counting mechanism will bundle events and send them to the remote server. This will happen approximately every 10 seconds, although only as needed.

In the following examples we show various iText operations that cause events to be added to the volume counter.

Example 1 – Reading a PDF document (forms)

Java

JAVA
final PdfDocument pdfDocument = new PdfDocument(new PdfReader(SRC));
final PdfAcroForm acroForm = PdfAcroForm.getAcroForm(pdfDocument, false);

if (acroForm != null) {
    Map fields = acroForm.getFormFields();
    for (Map.Entry entry : fields.entrySet()) {
        System.out.println(entry.getKey() + " is " + entry.getValue().getFormType());
    }
}

pdfDocument.close();

.NET

C#
PdfDocument pdfDocument = new PdfDocument(new PdfReader(SRC));
PdfAcroForm acroForm = PdfAcroForm.GetAcroForm(pdfDocument, false);
if (acroForm != null)
{
            foreach (KeyValuePair entry in acroForm.GetFormFields())
            {
                        Console.WriteLine(entry.Key + " is " + entry.Value.GetFormType());
                        pdfDocument.Close();
            }
}

This is counted as 1 event.

Example 2 – Writing a PDF document

Java

JAVA
javafinal PdfWriter pdfWriter = new PdfWriter(DEST);
final PdfDocument pdfDocument = new PdfDocument(pdfWriter);
 
try (final Document document = new Document(pdfDocument)) {
    document.add(new Paragraph("Hello World!"));
}

.NET

C#
PdfWriter pdfWriter = new PdfWriter(DEST);
PdfDocument pdfDocument = new PdfDocument(pdfWriter);
using (Document document = new Document(pdfDocument)) {
            document.Add(new Paragraph("Hello World!"));
}

This is also counted as 1 event.

Example 3 – Splitting a PDF document

Java

JAVA
private void splitPDF(final String SRC) throws IOException {
        final int maxPageCount = 2; // create a new PDF per 2 pages from the original file
        new PdfSplitter(new PdfDocument(new PdfReader(new File(SRC)))) {
            int partNumber = 1;

            protected PdfWriter getNextPdfWriter(final PageRange documentPageRange) {
                try {
                    return new PdfWriter("out/splitDocument_" + partNumber++ + ".pdf");
                } catch (final FileNotFoundException ignored) {
                    throw new RuntimeException();
                }
            }
        }.splitByPageCount(maxPageCount, new PdfSplitter.IDocumentReadyListener() {
            @Override
            public void documentReady(final PdfDocument pdfDocument, final PageRange pageRange) {
                pdfDocument.close();
            }
        });
    }

.NET

C#
class CustomSplitter : PdfSplitter
        {
            int PartNumber = 1;
            public CustomSplitter(PdfDocument pdfDocument) : base(pdfDocument)
            {
            }
            protected override PdfWriter GetNextPdfWriter(PageRange documentPageRange)
            {
                return new PdfWriter("out/splitDocument_" + PartNumber++ + ".pdf");
            }
            
        }
        private static void Split()
        {
            int MaxPageCount = 2;
            using (var pdfDoc = new PdfDocument(new PdfReader(SRC)))
            {
                var splitter = new CustomSplitter(pdfDoc);
                var splittedDocs = splitter.SplitByPageCount(MaxPageCount);
                foreach (var splittedDoc in splittedDocs)
                {
                    splittedDoc.Close();
                }
            }
        }

Splitting uses PdfSplitter which calls a PdfDocument instance. Therefore, splitting a 2-page document counts as 3 events, as you read a file in one instance and then write 2 split files, each in a separate instance.

Example 4 – Merging two PDF documents

Java

JAVA
PdfMerger merge = new PdfMerger(new PdfDocument(new PdfReader(DEST)));
merge.merge(new PdfDocument(new PdfWriter(DEST)), 1, 2);

.NET

C#
PdfMerger merge = new PdfMerger(new PdfDocument(new PdfReader(DEST)));
merge.Merge(new PdfDocument(new PdfWriter(DEST)), 1, 2);

Likewise, merging is done with PdfMerger calling PdfDocument, and so this also counts as 2 events, as you are reading two files in one instance and then writing the merged file.

pdfHTML

For pdfHTML each event in PdfDefaultProcessor#processElements and PdfDefaultProcessor#processDocument is counted and added to the volume counter.

Example 5 – HTML conversion to PDF

Java

JAVA
HtmlConverter.convertToPdf(new FileInputStream(ORIG), new FileOutputStream(DEST));

.NET

C#
HtmlConverter.ConvertToPdf(new FileStream(SRC, FileMode.Open), new FileStream(DEST, FileMode
    .Create, FileAccess.Write));

This HTML conversion is counted as 1x pdfHTML event.

pdfSweep

For pdfSweep each PdfCleanUpTool instance is counted. 

Example 6 – PDF redaction

Java

JAVA
htry (PdfDocument pdf = new PdfDocument(new PdfReader(SRC), new PdfWriter(DEST))) {
    final ICleanupStrategy cleanupStrategy = new RegexBasedCleanupStrategy("Creating").setRedactionColor(ColorConstants.PINK);
    final PdfAutoSweep autoSweep = new PdfAutoSweep(cleanupStrategy);
    autoSweep.cleanUp(pdf);
}

.NET

C#
using (var pdf = new PdfDocument(new PdfReader(SRC), new PdfWriter(DEST)))
{
            ICleanupStrategy cleanupStrategy = new RegexBasedCleanupStrategy("Creating")
                        .SetRedactionColor(ColorConstants.PINK);
            PdfAutoSweep autoSweep = new PdfAutoSweep(cleanupStrategy);
            autoSweep.CleanUp(pdf);
}

This redaction example creates 2 events, 1x Core event and 1x pdfSweep event.

How do I query my current volume usage?

For iText 7.2.x and newer

If you have a JSON license key file and are using iText Core 7.2.x (and newer) or any of its add-ons, you can easily query your current volume usage:
Java

JAVA
LicenseKey.loadLicenseFile(new File("license.json"));

for (LicenseInfo licenseInfo : LicenseKey.getLoadedLicensesInfo()) {
    Map<String, LimitInfo> map = licenseInfo.getLimitsInfo().getEventsLimitInfo();
    for (Map.Entry<String, LimitInfo> entry : map.entrySet()) {
        logger.info("Remaining volume: " + entry.getValue().getRemainingVolume() + " out of " + entry.getValue().getTotalVolume());
    }
}

.NET

C#
LicenseKey.LoadLicenseFile(new FileInfo("license.json"));

foreach (var licenseInfo in LicenseKey.GetLoadedLicensesInfo())
{
    foreach (var entry in licenseInfo.GetLimitsInfo().GetEventsLimitInfo())
    {
        LimitInfo limitInfo = entry.Value;
        Console.WriteLine("Remaining volume: " + limitInfo.GetRemainingVolume() + " out of " +
                          limitInfo.GetTotalVolume());
    }
}

How can I count the number of PDF reads and writes in iText 7 and newer?

The DefaultEventCounter class in iText 7.1.4 onwards is used by iText and its add-ons to send statistics. It is based on an event system rather than by file reads and writes, so iText Core operations will only get a Core event. This makes it easier for volume counting purposes. 

However, if you want to implement a custom solution for counting the number of PDF reads and writes in iText 7 and newer, the following example demonstrates a method for telling iText to output a log file that lists all read and write events:

Java

JAVA
static class CustomEvent implements IGenericEvent {
            public static final CustomEvent READ = new CustomEvent("read");
            public static final CustomEvent WRITE = new CustomEvent("write");
            private final String subtype;
            private CustomEvent(String subtype) {
                        this.subtype = subtype;
            }
 
            @Override
            public String getEventType() {
                        return "custom-" + subtype;
            }
            @Override
            public String getOriginId() {
                        return NamespaceConstant.ITEXT;
            }
}
 
static class ReadingWritingEventsAwarePdfDocument extends PdfDocument {
            public ReadingWritingEventsAwarePdfDocument(PdfReader reader) {
                        super(reader);
            }
            public ReadingWritingEventsAwarePdfDocument(PdfWriter writer) {
                        super(writer);
            }
            public ReadingWritingEventsAwarePdfDocument(PdfReader pdfReader, PdfWriter pdfWriter) {
                        super(pdfReader, pdfWriter);
            }
            // ... any other required constructors matching PdfDocument
            @Override
            protected void open(PdfVersion newPdfVersion) {
                        if (this.reader != null) {
                                    EventCounterHandler.getInstance().onEvent(CustomEvent.READ, null, getClass());
                        }
                        if (this.writer != null) {
                                    EventCounterHandler.getInstance().onEvent(CustomEvent.WRITE, null, getClass());
                        }
                        super.open(newPdfVersion);
            }
}
 
public void test() throws IOException {
            // register event-counter factory
            EventCounterHandler.getInstance().register(new SystemOutEventCounterFactory());
            PdfDocument writingDoc = new ReadingWritingEventsAwarePdfDocument(new PdfWriter(DEST_PATH));
            Document document = new Document(writingDoc);
            document.add(new Paragraph("Hello world"));
            document.close();
            System.out.println("--- Document processing separator ---");
            PdfDocument readingDoc = new ReadingWritingEventsAwarePdfDocument(new PdfReader(SOURCE_PATH));
            readingDoc.getPage(1).getPageSize();
            readingDoc.close();
            System.out.println("--- Document processing separator ---");
            PdfDocument stampingDoc = new ReadingWritingEventsAwarePdfDocument(new PdfReader(SOURCE_PATH), new PdfWriter(DEST_PATH));
            new PdfCanvas(stampingDoc.getPage(1)).rectangle(100, 100, 300, 500).fill();
            stampingDoc.close();
}

.NET

C#
class CustomEvent : IGenericEvent
        {
            public static CustomEvent READ = new CustomEvent("read");
            public static CustomEvent WRITE = new CustomEvent("write");
            private string subtype;
            public CustomEvent(string subtype)
            {
                this.subtype = subtype;
            }
            public string GetEventType()
            {
            }
            public string GetOriginId()
            {
                return NamespaceConstant.ITEXT;
            }
        }
        class ReadingWritingEventsAwarePdfDocument : PdfDocument
        {
            protected internal ReadingWritingEventsAwarePdfDocument(PdfReader reader) : base(reader)
            {
            }
            protected internal ReadingWritingEventsAwarePdfDocument(PdfWriter writer) : base(writer)
            {
            }
            protected internal ReadingWritingEventsAwarePdfDocument(PdfReader reader, PdfWriter writer) : base(reader, writer)
            {
            }
            protected override void Open(PdfVersion NewPdfVersion)
            {
                if (this.reader != null)
                {
                    EventCounterHandler.GetInstance().OnEvent(CustomEvent.READ, null, this.GetType());
                }
                if (this.writer != null)
                {
                    EventCounterHandler.GetInstance().OnEvent(CustomEvent.WRITE, null, this.GetType());
                }
                base.Open(NewPdfVersion);
            }
        }
        private static void Test()
        {
            EventCounterHandler.GetInstance().Register(new SystemOutEventCounterFactory());
            PdfDocument writingDoc = new ReadingWritingEventsAwarePdfDocument(new PdfWriter(DEST));
            Document document = new Document(writingDoc);
            document.Add(new Paragraph("Hello world"));
            document.Close();
            Console.WriteLine("--- Document processing separator ---");
            PdfDocument readingDoc = new ReadingWritingEventsAwarePdfDocument(new PdfReader(SRC));
            readingDoc.GetPage(1).GetPageSize();
            readingDoc.Close();
            Console.WriteLine("--- Document processing separator ---");
            PdfDocument stampingDoc = new ReadingWritingEventsAwarePdfDocument(new PdfReader(SRC), new PdfWriter(DEST));
            new PdfCanvas(stampingDoc.GetPage(1)).Rectangle(100, 100, 300, 500).Fill();
            stampingDoc.Close();
        }

This will produce a log file containing the following information:

BASH
[core.clean.CustomCounter$ReadingWritingEventsAwarePdfDocument]custom-write event
[core.clean.CustomCounter$ReadingWritingEventsAwarePdfDocument]core-process event

Can I use a custom counter in addition to volume counting?

Yes, the volume counting implementation can be used concurrently with a custom counter.

Can I see a code example for custom volume counting?

For iText 7.1.x

Java

JAVA
public void testCoreEvent() {
    // Use com.itextpdf.kernel.counter.IEventCounterFactory implementation.
    // SimpleEventCounterFactory is the most basic one, simply yielding an instance which is passed to the constructor.
    IEventCounterFactory counterFactory = new SimpleEventCounterFactory(new CustomEventCounter());
    EventCounterHandler.getInstance().register(counterFactory);
    LicenseKey.loadLicenseFile(license);
    PdfDocument pdfDocument = new PdfDocument(new PdfWriter(new ByteArrayOutputStream()));
    pdfDocument.addNewPage();
    pdfDocument.close();

    EventCounterHandler.getInstance().unregister(counterFactory);
}

class CustomEventCounter extends EventCounter {

    @Override
    protected void onEvent(IEvent event, IMetaInfo metaInfo) {
        // This implementation is not thread safe!
        System.out.println("Process event: " + event.getEventType());
    }
}

.NET

C#
public void testCoreEvent()
{
    // Use com.itextpdf.kernel.counter.IEventCounterFactory implementation.
    // SimpleEventCounterFactory is the most basic one, simply yielding an instance which is passed to the constructor.
    IEventCounterFactory counterFactory = new SimpleEventCounterFactory(new CustomEventCounter());
    EventCounterHandler.GetInstance().Register(counterFactory);
    LicenseKey.LoadLicenseFile("license path");
    PdfDocument pdfDocument = new PdfDocument(new PdfWriter(new iText.IO.Source.ByteArrayOutputStream()));
    pdfDocument.AddNewPage();
    pdfDocument.Close();

    EventCounterHandler.GetInstance().Unregister(counterFactory);
}

class CustomEventCounter : EventCounter
{
    protected override void OnEvent(IEvent @event, IMetaInfo metaInfo)
    {
        Console.WriteLine("Process Event: ");
    }
}

For iText 7.2.x and newer

Java

JAVA
public class CountingHandler implements IEventHandler {

  private static Logger logger = LoggerFactory.getLogger(CountingHandler.class);

  @Override
  public void onEvent(IEvent iEvent) {
    if (iEvent instanceof ITextCoreProductEvent) {
      ITextCoreProductEvent event = (ITextCoreProductEvent) iEvent;
      logger.info(event.getEventType());
    }
  }
}

EventManager.getInstance().register(new CountingHandler());

.NET

C#
class CountingHandler : IEventHandler
{
    public void OnEvent(IEvent @event)
    {
        if (@event is ITextCoreProductEvent)
        {
            Console.WriteLine(((ITextCoreProductEvent)@event).GetEventType());
        }
    }
}

EventManager.GetInstance().Register(new CountingHandler());

JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.