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.com
, sdk-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.com
, sdk-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
IEventCounterFactory counterFactory = new SimpleEventCounterFactory(new CustomEventCounter());
EventCounterHandler.getInstance().register(counterFactory);
.NET
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
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
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
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
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
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
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
PdfMerger merge = new PdfMerger(new PdfDocument(new PdfReader(DEST)));
merge.merge(new PdfDocument(new PdfWriter(DEST)), 1, 2);
.NET
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
HtmlConverter.convertToPdf(new FileInputStream(ORIG), new FileOutputStream(DEST));
.NET
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
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
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
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
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
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
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:
[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
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
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
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
class CountingHandler : IEventHandler
{
public void OnEvent(IEvent @event)
{
if (@event is ITextCoreProductEvent)
{
Console.WriteLine(((ITextCoreProductEvent)@event).GetEventType());
}
}
}
EventManager.GetInstance().Register(new CountingHandler());