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; 

Choosing the right printer

Remember that the discussion of DocFlavor began with a desire to make sure the printer you use actually supports the type of data that's to be printed and the delivery mechanism (representation class) you intend to use. This might seem like an unnecessary step, but, in reality, you may be surprised at which document types a given printer supports. For example, the text-oriented types just described might seem as though they'd be the simplest ones to support, so, if your application is printing plain or HTML text, you might be tempted to simply select the first available print service and send the output to that printer. As it turns out, though, many printers don't support the text-based representation classes, and, if you attempt to send output to a printer that doesn't support the DocFlavor you select, an exception will be thrown like the following:

 Exception in thread "main" sun.print.PrintJobFlavorException: invalid flavor
   at sun.print.Win32PrintJob.print(Win32PrintJob.java:290)
   at PrintTest.main(PrintTest.java:11) 

Now that you've seen how to obtain a reference to a DocFlavor and I've discussed the importance of selecting a printer that supports the selected flavor, I'll show how you can use it to make sure you use a printer that supports the flavor you need. As I discussed earlier, the lookupPrintServices() allows you to specify a DocFlavor as its first argument, and, if you specify a non-null value, the method will return only the PrintService instances that correspond to printers that support the specified DocFlavor. For example, the following code will retrieve an array that identifies all printers on your system that can print gif images that are referenced via a URL:

 DocFlavor flavor = DocFlavor.URL.GIF;
PrintService[] services = PrintServiceLookup.lookupPrintServices(flavor, null); 

Alternatively, if your application has already retrieved a reference to a PrintService and you want to determine whether it supports a particular flavor, you can call the isDocFlavorSupported() method. In the following code segment, a reference to the default printer is obtained, and an error message will be displayed if it's not able to print a gif image retrieved via a URL:

 PrintService service = PrintServiceLookup.lookupDefaultPrintService();
DocFlavor flavor = DocFlavor.URL.GIF;
if (!service.isDocFlavorSupported(flavor)) {
   System.err.println("The printer does not support the appropriate DocFlavor");
} 

AttributeSet

As you've now seen, a DocFlavor describes the data to be printed and can be used to ensure that a PrintService supports the corresponding type of data. However, your application may also need to select a printer based upon the features that the printer supports. For example, if you're printing a graph that uses different colors to convey information, you might want to see if a given service supports color printing and, if not, either prevent the printer from being used or render a representation of the graph that doesn't rely on colors.

Characteristics such as the ability to print in color, to print on both sides of a page, or to use different orientations (portrait or landscape) are referred to as a printer's attributes, and the javax.print.attribute package contains many classes and interfaces you can use to describe those attributes. One of those interfaces is AttributeSet, which was mentioned earlier as the second parameter that can be specified on a call to lookupPrintServices(). As you might expect, an implementation of AttributeSet represents a collection of attributes, and specifying a non-null value on the call to lookupPrintServices() will result in only print services being returned that support those attributes. In other words, if you specify both a DocFlavor and an AttributeSet on a call to lookupPrintServices(), the method will return only those printers that support both the specified flavor and the appropriate attributes.

Attribute

Given that an AttributeSet is a collection of attributes, the obvious question is, how do you go about specifying the attribute values that should make up that collection? The javax.print.attribute package also includes an interface named Attribute, and, as you'll see shortly, you create the collection of attributes by adding instances of Attribute to an AttributeSet by calling the add() method. Reviewing the documentation for the Attribute interface reveals that a large number of implementations are defined within the javax.print.attribute.standard package, and it's those classes you'll use. Before you see how that's done, it's helpful to review the other interfaces in the javax.print.attribute package along with their implementations.

Attribute roles

So far, I've described attributes as capabilities of a print service, and while that's largely true, it's really something of an oversimplification, at least in terms of how Java supports attributes. For each different attribute, Java associates it with one or more "roles," and the attribute is valid only in the context of the role(s) with which it's assigned. In other words, various places within the Java print service attributes are used, and not every attribute is valid within every context.

To better understand this, consider the OrientationRequested and ColorSupported implementations of Attribute that are defined within the javax.print.attribute.standard package. The OrientationRequested attribute is one you can specify when creating a document to be printed and allows you to specify the orientation (such as portrait or landscape) that should be used when printing the document. In contrast, ColorSupported is an attribute that can be returned when you call the getAttributes() method of the PrintService interface. In other words, OrientationRequested is an attribute you use to pass information to the print service, and ColorSupported is one that the print service uses to provide you with information about the printer's abilities. You can't specify ColorSupported as an attribute when creating a document to be printed because the printer's ability to print in color isn't something your application is able to control.

Interfaces and implementations

When you first look at the interfaces and classes defined in the javax.print.attribute package, it may appear to present a confusing list of choices when it comes to the interfaces and classes defined there. Aside from the Attribute and AttributeSet interfaces and the HashAttributeSet class that implements AttributeSet, the javax.print.attribute package has four sets of subinterfaces and classes, as shown in Table 4 and Figure 1.

Table 4. Interfaces and classes defined within the javax.print.attribute package

Attribute subinterface AttributeSet subinterfaceAttributeSet subclass
DocAttributeDocAttributeSetHashDocAttributeSet
PrintJobAttributePrintJobAttributeSetHashPrintJobAttributeSet
PrintRequestAttributePrintRequestAttributeSet HashPrintRequestAttributeSet
PrintServiceAttributePrintServiceAttribute PrintServiceAttributeSetHashPrintServiceAttributeSet
Figure 1. The class hierarchy of a portion of the javax.print.attribute package. Click on thumbnail to view full-sized image.

So why do you need all these various interfaces and implementations, particularly since the more generalized Attribute, AttributeSet, and HashAttributeSet are provided? The answer is that these specializations are defined to ensure that only the appropriate attributes are used within the role(s) where they're valid. For example, I mentioned that one place where you can use attributes is when creating a document that's to be printed and that some attributes such as ColorSupported aren't valid within that context. When creating such a document, you'll use the DocAttributeSet interface (or more specifically, its HashDocAttributeSet implementation), and the implementation will allow you to add only attributes that implement the DocAttribute interface. The four different types of roles are as follows:

  • Doc: Specified when creating a document that's to be printed to describe how the document should be printed
  • PrintJob: Attributes returned from the print job to describe the state of the job
  • PrintRequest: Attributes passed to the print job when a request is made to initiate printing
  • PrintService: Returned by a PrintService to describe the capabilities of the service

To see how this works, let's create an instance of a DocAttributeSet and then attempt to set both the OrientationRequested and ColorSupported attributes for that AttributeSet. The HashDocAttributeSet defines a no-argument constructor, so you can create an instance easily as follows:

 DocAttributeSet attrs = new HashDocAttributeSet(); 

Now that you've created the AttributeSet, you can call its add() method and pass to it instances of Attribute implementations. If you examine the documentation for the OrientationRequested class, you'll see it includes references to a number of static OrientationRequest instances, with each one corresponding to a document orientation, such as portrait or landscape. To specify the orientation you want, all you need to do is pass a reference to the appropriate static instance to the add() method as follows:

 DocAttributeSet attrs = new HashDocAttributeSet();
attrs.add(OrientationRequested.PORTRAIT); 

The ColorSupported class is slightly different but equally simple to use, and it defines two static instances: one that indicates that color printing is supported and another that indicates it's not supported. You can attempt to add a ColorSupported attribute to the DocAttributeSet with code like this:

 

DocAttributeSet attrs = new HashDocAttributeSet();

attrs.add(OrientationRequested.PORTRAIT);

attrs.add(ColorSupported.SUPPORTED);

As mentioned earlier, it's not appropriate to specify whether to support color printing because this isn't something an application is allowed to control. In other words, the ColorSupported attribute isn't valid within the context of a set of document attributes, and, as a result, attempting to run the previous code will cause a ClassCastException to be thrown when it attempts to add the ColorSupported attribute.

To understand how this works, remember that each AttributeSet subinterface (in this case, DocAttributeSet) has a corresponding Attribute subinterface (DocAttribute) and an implementation class (HashDocAttributeSet). When an attempt is made to add an attribute, the implementation class tries to cast the Attribute parameter to the corresponding subinterface type, which, in turn, ensures that only attributes appropriate for that context can be added successfully.

In this case, the add() method of HashDocAttributeSet is first called with an instance of OrientationRequested, and it successfully casts that object to a DocAttribute, because, as Figure 2 shows, OrientationRequested implements that interface. In contrast, however, passing an instance of ColorSupported fails because ColorSupported doesn't implement DocAttribute.

Figure 2. The class hierarchy of a portion of the javax.print.attribute package. Click on thumbnail to view full-sized image.

As this example illustrates, the four different groups of interfaces and classes shown in Table 4 ensure that only the appropriate attributes are used within the appropriate context. Notice that a great deal of overlap occurs between roles and the various attributes, so many of the attributes are associated with more than one role. For example, many of the attributes implement both PrintJobAttribute and PrintRequestAttribute because many of the attributes that are maintained and provided to you by a print job correspond to attributes you can specify when you request that printing be initiated. You can, for instance, both specify the job name by adding it to a PrintRequestAttributeSet and retrieve the name of the job during printing by retrieving it from a PrintJobAttributeSet. As a result, the JobName attribute class implements both PrintRequestAttribute and PrintJobAttribute.

AttributeSet and HashAttributeSet

You've now seen why the four groups of subclasses exist, but what about the base AttributeSet interface and the HashAttributeSet superclass? AttributeSet/HashAttributeSet is used in situations where you can't assume that only attributes associated with a single role will need to be stored in a collection. Remember that earlier I mentioned that the lookupPrintServices() method allows you to specify an AttributeSet parameter that will limit which print services are returned. On the surface it might appear that it'd be better to require that an instance of PrintServiceAttributeSet be specified, but many of the attributes you might want to specify don't implement PrintServiceAttribute.

Let's assume you want the lookupPrintServices() method to retrieve only services that support both color printing and landscape printing. Those attributes correspond to the ColorSupported and OrientationRequested attributes, respectively, but notice that those two attribute classes don't share a common role: ColorSupported is a PrintServiceAttribute, and OrientationRequested is associated with all three of the other roles (Doc, PrintRequest, and PrintJob), as shown in Figure 2. What this means is that there's no single role-specific AttributeSet interface/class that can contain both a ColorSupported attribute and a Sides attribute.

The way to create an AttributeSet that contains both an OrientationRequested and a ColorSupported instance is to simply use an instance of the generic HashAttributeSet. Unlike its subclasses, it doesn't limit you to adding attributes associated with a particular role, so you can successfully execute the following code:

 AttributeSet attrs = new HashAttributeSet();
attrs.add(ColorSupported.SUPPORTED);
attrs.add(OrientationRequested.LANDSCAPE);
PrintService[] services = PrintServiceLookup.lookupPrintServices(null, attrs); 

Printer selection via user interface

Up to this point, I've assumed that the printer to be used would be selected programmatically by the application. In practice, however, it's more common to simply display a dialog and allow the user to select which printer to use when printing the output. Fortunately, Java makes it easy to do just that by using the static printDialog() method in the ServiceUI class defined within the javax.print package.

Aside from the location of the dialog to be displayed, the only parameter values that must be specified on the call to printDialog() are these:

  • An array of PrintService instances from which the user can choose.
  • The default PrintService.
  • An instance of PrintRequestAttributeSet. This is used to populate the dialog that's displayed, and it returns any changes that were made by the user before the dialog was dismissed.

To illustrate how this works, you can use the following simple code segment to display a print dialog:

 PrintService[] services = PrintServiceLookup.lookupPrintServices(null, null);
PrintService svc = PrintServiceLookup.lookupDefaultPrintService();
PrintRequestAttributeSet attrs = new HashPrintRequestAttributeSet();
PrintService selection = ServiceUI.printDialog(
   null, 100, 100, services, svc, null, attrs); 

When run, the code produces a dialog like the one shown in Figure 3.

Figure 3. The printer dialog

As this code illustrates, the value returned from the printDialog() method is an instance of PrintService that identifies which printer the user selected or null if the user canceled the printer dialog. In addition, the PrintRequestAttributeSet is updated to reflect any changes made by the user through the dialog, such as the number of copies to be printed.

By using the printDialog() method, you can allow users to select which printer their output will be sent to, providing the kind of functionality that users have come to expect from professional applications.

Creating a print job

This is the simplest step involved in printing, because once you've obtained a reference to a PrintService, all you need to do is call its createPrintJob() method like so:

 PrintService service;
.
.
.
DocPrintJob job = service.createPrintJob(); 

As indicated in the code, the value returned from createPrintJob() is an instance of DocPrintJob, an object that allows you to control and monitor the status of the printing operation. To initiate printing, you'll call the DocPrintJob object's print() method, but, before you do so, you'll need to define the document to be printed and optionally a PrintRequestAttributeSet. You've already seen how to construct and populate an AttributeSet, so I won't review that step; instead, you'll see how you go about defining the document to be printed.

Defining the document to print

The next step in printing is to define the document that's to be printed, which is done by creating an instance of an implementation of the Doc interface defined in the javax.print package. Each instance of Doc has two mandatory attributes and an optional one:

  • An Object that represents the data to be printed
  • An instance of DocFlavor that describes the type of data to print
  • An optional DocAttributeSet containing attributes to use when printing the document

Reviewing the documentation for the Doc interface reveals that the javax.print package includes an implementation of the interface named SimpleDoc, which has a constructor that takes three arguments that match the three attributes described previously. To see how to construct an instance of SimpleDoc, let's assume you want to print two copies of a gif image that's stored at http://www.apress.com/ApressCorporate/supplement/1/421/bcm.gif.

All that's needed to construct a SimpleDoc instance that describes the document to be printed is to create a URL that points to the image, obtain a reference to the appropriate DocFlavor, and pass those two objects to the SimpleDoc constructor as follows:

 URL url = new URL(
   "http://www.apress.com/ApressCorporate/supplement/1/421/bcm.gif");
DocFlavor flavor = DocFlavor.URL.GIF;
SimpleDoc doc = new SimpleDoc(url, flavor, null); 

Initiating printing

The final step involved in printing is to call the DocPrintJob's print() method, passing it the Doc object that describes the data to be printed and optionally an instance of PrintRequestAttributeSet. For the sake of simplicity, I'll assume the default printer supports the flavor and attributes you need, in which case you could use the following code to print two copies of the gif file referenced in the previous example:

 PrintService service = PrintServiceLookup.lookupDefaultPrintService();
DocPrintJob job = service.createPrintJob();
URL url = new URL(
   "http://www.apress.com/ApressCorporate/supplement/1/421/bcm.gif ");
DocFlavor flavor = DocFlavor.URL.GIF;
Doc doc = new SimpleDoc(url, flavor, null);
PrintRequestAttributeSet attrs = new HashPrintRequestAttributeSet();
attrs.add(new Copies(2));
job.print(doc, attrs); 

Note that, in some cases, printing is performed asynchronously, in which case, the call to print() may return before printing has actually completed.

Brett Spell is a senior-level developer/programmer for Frito-Lay and authored the well-regarded original edition of Pro Java Programming.

Learn more about this topic

Join the discussion
Be the first to comment on this article. Our Commenting Policies