In my previous company we faced some critical issues regarding the regulations.
We have had some official documents which were sent to the customers with some mistakes, and in order to provide a new one, it was important to add an add-on with some explanations about the changes we brought to their documents.

I was looking at a means to add automatically a PDF page to existing PDF documents.

Apache PDFBox is an open-source Java library that supports the development and conversion of PDF documents. Using this library, you can develop Java programs that create, convert and manipulate PDF documents.

In addition to this, PDFBox also includes a command line utility for performing various operations over PDF using the available Jar file.

PDFBox has several notable features.

  • Extract Text − Using PDFBox, you can extract Unicode text from PDF files.
  • Split & Merge − Using PDFBox, you can divide a single PDF file into multiple files, and merge them back as a single file.
  • Fill Forms − Using PDFBox, you can fill the form data in a document.
  • Print − Using PDFBox, you can print a PDF file using the standard Java printing API.
  • Save as Image − Using PDFBox, you can save PDFs as image files, such as PNG or JPEG.
  • Create PDFs − Using PDFBox, you can create a new PDF file by creating Java programs and, you can also include images and fonts.
  • Signing− Using PDFBox, you can add digital signatures to the PDF files.

In my sample, I have used the Merge utility, but several samples using other functions could easily be found in the internet, I will provide a link later.

Project Setup

First I have created a small java project in Eclipse, but the choice of the editor is not important.

You need to download the require jar files from here https://pdfbox.apache.org/

The list of jar file I have added to my small java project is below

As it was a bunch of files to update, I have create the following folders

The main folder contains documents which need to have an additional letter. The “addon” folder contain the letter explaining the correction we brought to the new version of the document.

I have added a property file to specify the input pdfs, the add on and the result output.

A simple class to manage the property file, a singleton pattern is used even if it is not necessary here. However we do not want to have plenty of instances for a configuration file do you?


for (File pdfFile : files) {

   PDFMergerUtility ut = new PDFMergerUtility();
   ut.addSource(pdfFile);
   for (File pdfAddOn : addonfiles) {
      ut.addSource(pdfAddOn);
   }
			ut.setDestinationFileName(PropertiesCache.getInstance().getProperty("pdfOutput") + BACK_SLASH + pdfFile.getName());
			logInfo("Starting merge at : " + directoryName + BACK_SLASH + mergedDir + BACK_SLASH + pdfFile.getName());
			ut.mergeDocuments(null);
			logInfo("Created merged pdf at : " + PropertiesCache.getInstance().getProperty("pdfOutput") + BACK_SLASH + pdfFile.getName());
		}

The main method looks very simple. I first read data from the config file, and pass the source as parameter to the merAllPages(source of pdfs) method.

	public static void main(String[] args) {

		try
		{
			final Properties prop = new Properties();
			InputStream input = MergeUtility.class.getClassLoader().getResourceAsStream("config.properties");        

			if (input == null) {
				System.out.println("Sorry, unable to find config.properties");
				return;
			}

			//load a properties file from class path, inside static method
			prop.load(input);

			//get the property value and print it out
			mergeAllPages(PropertiesCache.getInstance().getProperty("pdfSources"));            

		} catch (IOException ex) {
			ex.printStackTrace();
		}

	}

In the mergeAllPages, an instance of the PDFmergerUtility() is created, for each PDF sources.
We add all additional add-on documents and so one.
As we can see it is really straight forward, and easy to use.



import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import java.util.Set;
 
public class PropertiesCache {

	private final Properties configProp = new Properties();
    
	   private PropertiesCache()
	   {
	      //Private constructor to restrict new instances
		   try {
	      InputStream in = this.getClass().getClassLoader().getResourceAsStream("config.properties");
	      System.out.println("Read all properties from file");
	          configProp.load(in);
	          
	      } catch (IOException e) {
	          e.printStackTrace();
	      }
	   }
	 
   
	   //Bill Pugh Solution for singleton pattern
	   private static class LazyHolder
	   {
	      private static final PropertiesCache INSTANCE = new PropertiesCache();
	   }
	 
	   public static PropertiesCache getInstance()
	   {
	      return LazyHolder.INSTANCE;
	   }
	    
	   public String getProperty(String key){
	      return configProp.getProperty(key);
	   }
	    
	   public Set<String> getAllPropertyNames(){
	      return configProp.stringPropertyNames();
	   }
	    
	   public boolean containsKey(String key){
	      return configProp.containsKey(key);
	   }
	}