Java Pro Programming: Printing

Learn how to use the print service API

Java matured very quickly in most respects after it was first introduced, but for a long time, printing was one of Java's weakest points. In fact, Java 1.0 didn't offer any support for printing at all. Java 1.1 included a class called PrintJob in the java.awt package, but the printing capabilities supported by that class were somewhat crude and unreliable. When Java 1.2 (or "Java 2") was introduced, it included a completely separate mechanism (called the Java 2D printing API) for printing designed around PrinterJob and other classes and interfaces defined in the new java.awt.print package. This rendered the PrintJob-based printing mechanism (also known as AWT printing) largely obsolete, although PrintJob has never been deprecated and, at least of this writing, is still technically a supported class.

Additional changes were made in J2SE 1.3 when PrintJob's capabilities expanded to allow the setting of job and page attributes using the appropriately named JobAttributes and PageAttributes classes within the java.awt package. With the release of J2SE 1.3, the printing capabilities were reasonably robust, but some problems still existed aside from the confusion associated with having two completely separate printing facilities. For one thing, both facilities used an implementation of the java.awt.Graphics class for rendering the content to be printed, which meant anything that needed to be printed had to be rendered as a graphical image. In addition, the newer and generally more robust PrinterJob facility provided only limited support for setting attributes associated with the job. Finally, neither facility provided a way to programmatically select the target printer.

The biggest change in Java's printing capabilities to date came with the release of J2SE 1.4, when the Java print service API was introduced. This third implementation of printing support in Java addressed the limitations that were just described using an implementation of the PrintService and DocPrintJob interfaces defined in the javax.print package. Because this new API represents a superset of the functionality defined by the two older printing facilities, it's the one you should normally use and will be the focus of this article.

At a high level, the steps involved in using the Java print service API are straightforward:

  1. Locate print services (printers), optionally limiting the list of those returned to the ones that support the capabilities your application needs. Print services are represented as instances of PrintService implementations.
  2. Create a print job by calling the createPrintJob() method defined in the PrintService interface. The print job is represented by an instance of DocPrintJob.
  3. Create an implementation of the Doc interface that describes the data you want to print. You also have the option of creating an instance of PrintRequestAttributeSet that describes the printing options you want.
  4. Initiate printing by calling the print() method defined in the DocPrintJob interface, specifying the Doc you created in the previous step and the PrintRequestAttributeSet or a null value.

You'll now examine each of these steps and see how to achieve them.

Note
Within this article, I'll use the terms printer and print service interchangeably because, in most cases, a print service is nothing more than a representation of a physical printer. The more generic print service reflects that the output can theoretically be sent to something other than a printer. For example, a print service might not print the output at all but instead write it to a disk file. In other words, all printers are represented by a print service, but not every print service necessarily corresponds to a physical printer. In practice, though, it's likely you'll almost always send your content to a printer, which is why I'll sometimes use the simpler printer term instead of the more technically accurate print service.

Locating print services

You locate a printer using one of three static methods defined in the PrintServiceLookup class. The simplest of the three methods is lookupDefaultPrintService(), and, as its name implies, it returns a reference to the service that represents your default printer:

 PrintService service = PrintServiceLookup.lookupDefaultPrintService(); 

Although this method is simple and convenient, using it to select which printer to send output to means you're implicitly assuming that the user's default printer will always be able to support the capabilities your application needs in order to be able to print its output correctly. In practice, you'll typically want to select only those printers that are able to handle the type of data you want to print and that support the features your application needs, such as color or two-sided printing. To retrieve the list of all defined printers or to retrieve a list that's limited to printers supporting certain capabilities, you'll want to use one of two other static methods defined in PrintServiceLookup: either lookupPrintServices() or lookupMultiDocPrintServices().

The lookupPrintServices() method accepts two parameters: an instance of DocFlavor and an instance of some implementation of the AttributeSet interface. As you'll see shortly, you can use both of these to limit the list of printers returned by the method, but lookupPrintServices() allows you to specify a null value for either or both of the two parameters. By specifying a null value for both parameters, you're effectively requesting that the method return a PrintService instance for every printer that's available. At this point, you haven't really examined the methods defined in PrintService, but one of them is the getName() method, which returns a String representing the name of the printer. You can display a list of all printers available on your system by compiling and running code like this:

 PrintService[] services = PrintServiceLookup.lookupPrintServices(null, null);
for (int i = 0; i < services.length; i++) {
   System.out.println(services[i].getName());
}

For example, if you have access to printers named Alpha, Beta, and Gamma that are attached to a server named PrintServer, running the previous code produces this output:

 \\PrintServer\Alpha
\\PrintServer\Beta
\\PrintServer\Gamma 

Now let's examine the parameters you can pass to the lookupPrintServices() method and see how they allow you to limit the printers returned to those with only certain capabilities.

DocFlavor

The first parameter you can specify on a call to lookupPrintServices() is an instance of the DocFlavor class, which describes the type of data to be printed and how that data is stored. In most cases, it won't be necessary for you to create a new instance of DocFlavor because Java includes many predefined instances, allowing you to simply pass a reference to one of those instances to lookupPrintServices(). However, let's look at the DocFlavor constructor and methods to understand how an instance is used by a print service.

The two arguments required when creating an instance of DocFlavor are both String instances, with one representing a MIME (Multipurpose Internet Mail Extensions) type and the other being the name of a representation class. The MIME type is used by a DocFlavor to describe the type of data to be printed. For example, if you're printing a gif file, you'll need to use a DocFlavor that has a MIME type of image/gif. Similarly, you might use a MIME type of text/plain if you're printing text information or text/html for an HTML document.

Representation class

While the MIME type describes the type of data to be printed, the representation class describes how that data is to be made available to the print service. DocFlavor includes seven static inner classes, with each one corresponding to a representation class and each one corresponding to a different way of encapsulating the data that's to be printed.

Table 1 shows the names of the static inner classes defined within DocFlavor and their corresponding representation classes. Note that aside from SERVICE_FORMATTED (which I'll discuss in detail later), each one is described as being associated with either "binary" or "character" data. In reality, the distinction is somewhat artificial because character data is really just a specialized form of binary data, in this case, referring to binary data that contains only human-readable characters and perhaps some formatting characters such as tabs, carriage returns, and so on. However, the distinction is important because it reflects that character-oriented representation classes aren't appropriate for storing binary data that's to be printed.

For example, you wouldn't store a representation of a gif image in a character array or a String, and you wouldn't make it accessible through a Reader implementation. On the other hand, since "character" data is just a specialized type of binary data, it's entirely appropriate to store text information in a byte array or make it accessible through an InputStream or via a URL.

Table 1. DocFlavor's predefined representation classes

Inner class nameRepresentation classData type
BYTE_ARRAY [B (byte[])Binary
CHAR_ARRAY[C (char[]) Character
INPUT_STREAM java.io.InputStreamBinary
READERjava.io.Reader Character
SERVICE_FORMATTEDjava.awt.print.Pageable or java.awt.print.PrintableOther
STRINGjava.lang.StringCharacter
URLjava.net.URL Binary

Each of these static inner classes defined within DocFlavor corresponds to a particular representation class, but remember that I said each DocFlavor instance encapsulates both a representation class and a MIME type that identifies the type of data to be printed. To access an instance of DocFlavor that corresponds to both the representation class and the MIME type of the content you want to print, you'll need to reference an inner class within one of the inner classes listed in Table 1. For example, let's suppose you want to print a gif file that's available on the Web through a URL. In this case, the obvious choice for the representation class is java.net.URL, which is associated with the static class named URL that's defined within DocFlavor. If you browse the documentation for that inner class, you'll find that it, in turn, defines a number of static inner classes, each one corresponding to a particular MIME type representing data types commonly supported by printers. Table 2 shows the inner classes defined within DocFlavor.URL and their corresponding MIME types.

Table 2. The DocFlavor.URL inner classes

Static inner classesMIME type
AUTOSENSEapplication/octet-stream
GIFimage/gif
JPEGimage/jpeg
PCLPCL application/vnd-hp.PCL
PDFapplication/pdf
PNGimage/png
POSTSCRIPTapplication/postscript
TEXT_HTML_HOSTtext/html
TEXT_HTML_US_ASCII text/html;charset=us-ascii
TEXT_HTML_UTF_16text/html;charset=utf-16
TEXT_HTML_UTF_16BEtext/html;charset=utf-16be
TEXT_HTML_UTF_16LEtext/html;charset=utf-16le
TEXT_HTML_UTF_8TEXT_HTML_UTF_8 text/html;charset=utf-8
TEXT_PLAIN_HOSTtext/plain
TEXT_PLAIN_US_ASCIItext/plain;charset=us-ascii
TEXT_PLAIN_UTF_16 text/plain;charset=utf-16
TEXT_PLAIN_UTF_16BEtext/plain;charset=utf-16be
TEXT_PLAIN_UTF_16LEtext/plain;charset=utf-16le
TEXT_PLAIN_UTF_8text/plain;charset=utf-8

Since you'll print a gif image that's available through a URL, you can access an appropriate DocFlavor instance using this code:

 DocFlavor flavor = DocFlavor.URL.GIF; 

This code creates a reference to the static instance of DocFlavor that has a representation class of java.net.URL and a MIME type of image/gif.

The classes listed in Table 2 are defined within the DocFlavor.URL class, but what about the other six inner classes defined within DocFlavor? Again, I'll defer a discussion of SERVICE_FORMATTED until later, but, as for the classes associated with binary data types, all three (BYTE_ARRAY, INPUT_STREAM, and URL) include inner classes with the names shown in Table 2. So, for example, if you had loaded the gif data into a byte array, you might instead choose to use code like this:

 DocFlavor flavor = DocFlavor.BYTE_ARRAY.GIF; 

Just as the three DocFlavor inner classes associated with binary data types include their own inner classes, the three associated with character data types include a different set of inner classes, as shown in Table 3.

Table 3. CHAR_ARRAY, READER, and STRING

Static inner classMIME type
TEXT_HTMLtext/html;charset=utf-16
TEXT_PLAINtext/plain;charset=utf-16

So, for example, if you wanted to print plain text data that's stored in an instance of String, you could use code like the following:

 DocFlavor flavor = DocFlavor.STRING.TEXT_PLAIN; 

Similarly, if the text data represented an HTML document and you wanted to have the data printed as it would appear within a Web browser, you could use the following:

 DocFlavor flavor = DocFlavor.STRING.TEXT_HTML; 
1 2 3 Page
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more