How to send raw data to printer with Java

aCarella picture aCarella · Aug 3, 2017 · Viewed 7.6k times · Source

I am trying to create a simple program that sends a string to a printer to print. This is what my program looks like:

import javax.print.Doc;
import javax.print.DocFlavor;
import javax.print.DocPrintJob;
import javax.print.PrintException;
import javax.print.PrintService;
import javax.print.PrintServiceLookup;
import javax.print.SimpleDoc;

public class PrinterTest {
  public static void main (String [] args) throws PrintException {
    DocPrintJob job = null;
    PrintService[] printServices = 
    PrintServiceLookup.lookupPrintServices(null, null);
    System.out.println("Number of print services: " + printServices.length);
    for (PrintService printer : printServices) {
        System.out.println("Printer: " + printer.getName());
        if (printer.getName().contains("ZM400")) {
            String hello = "Hello";
            DocFlavor flavor = DocFlavor.STRING.TEXT_PLAIN;
            Doc doc = new SimpleDoc(hello, flavor, null);
            job = printer.createPrintJob();
            job.print(doc, null);
        }
    }
  }
}

I export this as a jar file and run it on command line (Windows) using:

java -jar PrinterTest.jar

The program runs, and starts looping through all of the installed printers on the computer. But when it gets to the printer I am looking for, I then get the following error:

Exception in thread "main" sun.print.PrintJobFlavorException: invalid flavor
  at sun.print.Win32PrintJob.print(Unknown Source)
  at PrinterTest.main(PrinterTest.java:21)

Not really sure what I'm doing wrong here, as the printer that I am searching for does in fact show up.

-Using the following link for reference: http://docs.oracle.com/javase/7/docs/technotes/guides/jps/spec/jpsOverview.fm4.html

-Tried changing DocFlavor flavor = DocFlavor.STRING.TEXT_PLAIN to DocFlavor flavor = DocFlavor.INPUT_STREAM.AUTOSENSE, but I get the error IllegalArgumentException: data is not of declared type.

-Tried changing Doc doc = new SimpleDoc(hello, flavor, null) to Doc doc = new SimpleDoc(hello, null, null), but it seems like you need to add a flavor there.

-Tried changing the printer, as the original printer I was trying to call was a labeling printer, but that did not make a difference.

Any idea what I'm doing wrong here? What can I do to fix this code and make it print?

UPDATE

I got this to (somewhat) work. This is what I have so far:

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import javax.print.Doc;
import javax.print.DocFlavor;
import javax.print.DocPrintJob;
import javax.print.PrintException;
import javax.print.PrintService;
import javax.print.PrintServiceLookup;
import javax.print.SimpleDoc;

public class PrinterTest {
  public static void main (String [] args) throws PrintException, IOException {
    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
    System.out.print("Enter the name of the printer: ");
    String printerName = bufferedReader.readLine();
    System.out.print("Enter a short message of what you would like to print here: ");
    String printerMessage = "PRINTER MESSAGE: " + bufferedReader.readLine();
    boolean printerCheck = false;
    DocPrintJob job = null;
    PrintService[] printServices = PrintServiceLookup.lookupPrintServices(null, null);
    System.out.println("Number of print services: " + printServices.length);
    for (PrintService printer : printServices) {
        System.out.println("Printer: " + printer.getName());
        if (printer.getName().contains(printerName)) {
            InputStream inputStream = new ByteArrayInputStream(printerMessage.getBytes());
            DocFlavor flavor = DocFlavor.INPUT_STREAM.AUTOSENSE;
            Doc doc = new SimpleDoc(inputStream, flavor, null);
            job = printer.createPrintJob();
            job.print(doc, null);
            printerCheck = true;
        }
    }
    if (printerCheck == false) {
        System.out.println("The printer you were searching for could not be found.");
    }
  }
}

What I did was put the string into an input stream, and changed DocFlavor.STRING.TEXT_PLAIN to DocFlavor.INPUT_STREAM.AUTOSENSE.

My only hiccup now is that nothing actually prints unless something else is sent to the printer. Leaving this here for now for reference.

Answer

davidxxx picture davidxxx · Aug 3, 2017

The exception messages are rather helpful and should help you to find a solution.

1) First, this flavor is not supported by your printer :

 DocFlavor.STRING.TEXT_PLAIN;

This exception says it :

Exception in thread "main" sun.print.PrintJobFlavorException: invalid flavor

If you look in the source code of this constant value, you can see that it is declared as :

public static final STRING TEXT_PLAIN =
    new STRING ("text/plain; charset=utf-16");

So, the first thing you should do is checking which flavors are supported by your printer.
To render it, change the actual code :

if (printer.getName().contains("ZM400")) {
    String hello = "Hello";
    DocFlavor flavor = DocFlavor.STRING.TEXT_PLAIN;
    Doc doc = new SimpleDoc(hello, flavor, null);
    job = printer.createPrintJob();
    job.print(doc, null);
}

by :

if (printer.getName().contains("ZM400")) {
    Arrays.stream(printer.getSupportedDocFlavors()).forEach(f->System.out.println(f.getMediaType() + ":" + f.getMimeType() + ":" + f.getRepresentationClassName()));
    String hello = "Hello";
    DocFlavor flavor = DocFlavor.STRING.TEXT_PLAIN;
    Doc doc = new SimpleDoc(hello, flavor, null);
    job = printer.createPrintJob();
    job.print(doc, null);
}

With my printer, it produces the following output :

image:image/gif:[B image:image/gif:java.io.InputStream

image:image/gif:java.net.URL image:image/jpeg:[B

image:image/jpeg:java.io.InputStream image:image/jpeg:java.net.URL

image:image/png:[B image:image/png:java.io.InputStream

image:image/png:java.net.URL

application:application/x-java-jvm-local-objectref:java.awt.print.Pageable

application:application/x-java-jvm-local-objectref:java.awt.print.Printable

application:application/octet-stream:[B

application:application/octet-stream:java.net.URL

application:application/octet-stream:java.io.InputStream

You may notice the my printer doesn't support the "text/plain; charset=utf-16" flavor either.

2) By changing the flavor to DocFlavor.INPUT_STREAM.AUTOSENSE, you don't have any longer the exception related to missing support for the DocFlavor.
So it means that DocFlavor.INPUT_STREAM.AUTOSENSE is supported by your printer.
But you encounter another problem as the data to print doesn't match to declared type associated to DocFlavor.INPUT_STREAM.AUTOSENSE :

IllegalArgumentException: data is not of declared type.

In DocFlavor, the INPUT_STREAM.AUTOSENSE constant is declared in this way :

    public static final INPUT_STREAM AUTOSENSE =
        new INPUT_STREAM ("application/octet-stream");

This corresponds to the last supported flavor in the previous output :

application:application/octet-stream:java.io.InputStream

And the problem is there :

String hello = "Hello";     
...
Doc doc = new SimpleDoc(hello, flavor, null);

you don't have pass a InputStream but a String.

You can for example provide an InputStream in this way:

String hello = "Hello";     
DocFlavor flavor = DocFlavor.INPUT_STREAM.AUTOSENSE;
Doc doc = new SimpleDoc(new ByteArrayInputStream(hello.getBytes()),
                         flavor, null);

Or you could also use this flavor :

application:application/octet-stream:[B

as you have not an InputStream but a String as input :

Here is the flavor constant to use :

DocFlavor.BYTE_ARRAY.AUTOSENSE

You could also use it in this way :

String hello = "Hello";     
DocFlavor flavor = DocFlavor.BYTE_ARRAY.AUTOSENSE;
Doc doc = new SimpleDoc(hello.getBytes(), flavor, null);