The previous version works well enough, but it’s fairly inefficient memory-wise and uses a rather strange method of moving data around using streams within the class. So, now we have this one:
/*
 * A Simple PDF Stamper using PDFBox
 *
 * July, 2013
 * @author jack
 */
package simplestamper;
import java.awt.Color;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Properties;
import org.apache.pdfbox.exceptions.COSVisitorException;
import org.apache.pdfbox.exceptions.CryptographyException;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.edit.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
/**
 *
 * @author jack
 */
public class SimpleStamper {
    private static int numPages;
    private static Properties config = new Properties();
    private static String configFile;
    private static String outputFN = null;
    private static String inputFN = null;
    private static String stampString = null;
    private static String fontFamily = null;
    private static Float fontSize = null;
    private static Integer textRot = null;
    private static Color color = null;
    private static Boolean invertY = false;
    /**
     * Our Constructor
     */
    public SimpleStamper() {
        super();
    }
    /**
     * The main class. It's what plants crave.
     *
     * @param args the command line arguments args[0] -> The config file args[1]
     * -> The PDF document to be stamped args[2] -> The string to stamp
     * (optional, falls back to ss.text in the config file) args[3] -> The
     * desired name and path of the stamped PDF (optional, also defined in
     * config file as ss.outputFN)
     */
    public static void main(String[] args) throws IOException, CryptographyException, COSVisitorException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
        // as if we were outside...
        SimpleStamper app = new SimpleStamper();
        // if we don't have any parameters...
        if (args.length == 0) {
            usage();
        } else {
            // the config file path
            configFile = args[0];
            // load the config
            loadConfig(configFile, args);
        }
        // stamp
        app.stamp();
    }
    /**
     * Loads the configuration data.
     * Note: If we add many more config values, this function should be revised as a fetching routine.
     *
     * @param configFile
     * @param args
     * @throws NoSuchFieldException
     * @throws IllegalArgumentException
     * @throws IllegalAccessException
     */
    public static void loadConfig(String configFile, String[] args) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
        // the input stream
        InputStream is;
        try {
            is = new FileInputStream(configFile);
            
            try {
                // try to load the config file
                config.load(is);
                is.close();
            } catch (IOException e) {
                System.out.println(e);
            }
        } catch (IOException e) {
            System.out.println(e);
        }
        // the input PDF filename/path
        if (args.length > 1) {
            inputFN = args[1];
        } else {
            // make sure we have an input filename
            try {
                inputFN = config.getProperty("ss.inputFN");
            } catch (NullPointerException e) {
                System.err.println("You must specify an input filename as the second argument, or in the properties file. (ss.inputFN)");
            }
        }
        // the string to be stamped
        if (args.length > 2) {
            stampString = args[2];
        } else {
            // make sure we have a string to stamp
            try {
                stampString = config.getProperty("ss.text");
            } catch (NullPointerException e) {
                System.err.println("You must specify a string to stamp as the third argument, or in the properties file. (ss.text)");
            }
        }
        // the output PDF filename/path
        if (args.length > 3) {
            outputFN = args[3];
        } else {
            // make sure we have an output filename
            try {
                outputFN = config.getProperty("ss.outputFN");
            } catch (NullPointerException e) {
                System.err.println("You must specify an output filename as the fourth argument, or in the properties file. (ss.outputFN)");
            }
        }
        // make sure we have a font from the prop file
        try {
            fontFamily = config.getProperty("ss.fontFamily");
        } catch (NullPointerException e) {
            System.err.println("You must specify a font in the properties file. (ss.fontFamily)");
        }
        // make sure we have a font size from the prop file
        try {
            fontSize = Float.parseFloat(config.getProperty("ss.fontSize"));
        } catch (NullPointerException e) {
            System.err.println("You must specify a font size in the properties file. (ss.fontSize)");
        }
        // text rotation
        // make sure we have a font size from the prop file
        try {
            textRot = Integer.parseInt(config.getProperty("ss.rotation"));
        } catch (NullPointerException e) {
            System.err.println("You must specify a rotation in the properties file. (ss.rotation)");
        }
        // text color. if not set in the properties file we default to black
        try {
            Field field = Color.class.getField(config.getProperty("ss.fontColor"));
            color = (Color) field.get(null);
        } catch (NullPointerException e) {
            System.err.println("You must specify a font color in the properties file. (ss.fontColor)");
        }
        // the Y value inversion bool. Do we calc yVal from the top or bottom of the page?
        invertY = "true".equals(config.getProperty("ss.invertY")) ? true : false;
    }
    /**
     * The Stamping function. Where the magic lives.
     *
     * @throws IOException
     * @throws COSVisitorException
     */
    public void stamp() throws IOException, COSVisitorException {
        // the document
        PDDocument doc = null;
        try {
            // load the incoming document into a PDDcoument
            doc = PDDocument.load(inputFN);
            // make a list array of all the pages
            List allPages = doc.getDocumentCatalog().getAllPages();
            // Create a new font object selecting one of the PDF base fonts
            PDFont font = PDType1Font.getStandardFont(fontFamily);
            // the x/y coords
            float xVal = Float.parseFloat(config.getProperty("ss.xVal"));
            float yVal = Float.parseFloat(config.getProperty("ss.yVal"));
            // for every page in the incoming doc, stamp
            for (int i = 0; i < allPages.size(); i++) {
                // create an empty page and a geo object to use for calcs
                PDPage page = (PDPage) allPages.get(i);
                PDRectangle pageSize = page.findMediaBox();
                // are we inverting the y axis?
                if (invertY) {
                    yVal = pageSize.getHeight() - yVal;
                }
                // calculate the width of the string according to the font
                float stringWidth = font.getStringWidth(stampString) * fontSize / 1000f;
                // determine the rotation stuff. Is the the loaded page in landscape mode? (for axis and string dims)
                int pageRot = page.findRotation();
                boolean pageRotated = pageRot == 90 || pageRot == 270;
                // are we rotating the text?
                boolean textRotated = textRot != 0 || textRot != 360;
                // calc the diff of rotations so the text stamps
                int totalRot = pageRot - textRot;
                // calc the page dimensions
                float pageWidth = pageRotated ? pageSize.getHeight() : pageSize.getWidth();
                float pageHeight = pageRotated ? pageSize.getWidth() : pageSize.getHeight();
                // determine the axis of rotation
                double centeredXPosition = pageRotated ? pageHeight / 2f : (pageWidth - stringWidth) / 2f;
                double centeredYPosition = pageRotated ? (pageWidth - stringWidth) / 2f : pageHeight / 2f;
                // append the content to the existing stream
                PDPageContentStream contentStream = new PDPageContentStream(doc, page, true, true, true);
                contentStream.beginText();
                // set font and font size
                contentStream.setFont(font, fontSize);
                // set the stroke (text) color
                contentStream.setNonStrokingColor(color);
                // if we are rotating, do it
                if (pageRotated) {
                    // rotate the text according to the calculations above
                    contentStream.setTextRotation(Math.toRadians(totalRot), centeredXPosition, centeredYPosition);
                } else if (textRotated) {
                    // rotate the text according to the calculations above
                    contentStream.setTextRotation(Math.toRadians(textRot), xVal, yVal);
                } else {
                    // no rotate, just move it.
                    contentStream.setTextTranslation(xVal, yVal);
                }
                // stamp the damned text already
                contentStream.drawString(stampString);
                // close and clean up
                contentStream.endText();
                contentStream.close();
            }
            doc.save(outputFN);
        } finally { // you know, PHP 5.5 now has a finally construct...
            // if the document isnt closed, do so
            if (doc != null) {
                doc.close();
            }
        }
    }
    /**
     * Attempts to provide basic usage info.
     */
    private static void usage() {
        System.err.println("usage: java -jar simplestamper \"PdfToStamp.pdf\" \"The text to stamp, in parenthesis.\" \"Output.pdf\"");
    }
}
And the config file:
# the properties for each event #The text to be stamped. ss.text=This is the text to be stamped. #The x value of the stamped text, measured in pixels with the origin at bottom-left. ss.xVal=100 #The y value of the stamped text, measured in pixels with the origin at bottom-left. ss.yVal=200 # The counterclockwise rotation of the text in degrees (converted to radians in the app) ss.rotation=0 #The name/path of the input file ss.inputFN=pdf.pdf # the filename of the stamped pdf ss.outputFN=output.pdf # FONT OPTIONS: # more info: http://pdfbox.apache.org/apidocs/org/apache/pdfbox/pdmodel/font/PDType1Font.html # # Helvetica-BoldOblique # Times-Italic # ZapfDingbats # Symbol # Helvetica-Oblique # Courier # Helvetica-Bold # Helvetica # Courier-Oblique # Times-BoldItalic # Courier-Bold # Times-Roman # Times-Bold # Courier-BoldOblique ss.fontFamily=Helvetica ss.fontSize=18 # # The font color. # Examples: blue, red, green, black, white, yelllow, cyan, gray, lightGray # More Info: http://docs.oracle.com/javase/7/docs/api/java/awt/Color.html ss.fontColor=black # Operational Vars # # Do we want to calc text placement from the top (true) or bottom (false) of the page? # (The PDF spec. dictates that we calc all y values starting from the bottom, this is why top = true) ss.invertY=true