Skip to content

PrinterJob.getPrinterJob(): use the java.awt.printerjob property - #67

Open
duncanmcqueen wants to merge 1 commit into
ingelabs:masterfrom
duncanmcqueen:fix-printerjob-property
Open

duncanmcqueen wants to merge 1 commit into
ingelabs:masterfrom
duncanmcqueen:fix-printerjob-property

Conversation

@duncanmcqueen

@duncanmcqueen duncanmcqueen commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Split from #58.

Problem and fix

PrinterJob.getPrinterJob() always returns gnu.java.awt.print.JavaPrinterJob.

In JDK 8, getPrinterJob() makes an instance of the class that the system property java.awt.printerjob names. With this change, Classpath does the same. The user sets the property, for example next to -Dawt.toolkit=....

Classpath loads the class through the system class loader, in a doPrivileged block, as Toolkit.getDefaultToolkit() loads awt.toolkit. The system class loader asks the boot class loader first, so a class on the boot class path also works. Thus a toolkit that is not in the boot class path can supply its own printer job. (JDK 8 uses the boot class loader.)

If the class cannot be loaded or made, this change prints a warning and returns JavaPrinterJob. It also does this for a LinkageError, for example NoClassDefFoundError when a dependency of the class is missing. (JDK 8 throws AWTError.) Without the property, the behavior does not change.

Note: OpenJDK 17 and later ignore the property.

How to reproduce

The test sets the property to a class on the class path, to a class that does not exist, and to a class whose initializer throws NoClassDefFoundError (BrokenPrinterJob, below). It does not need -Xbootclasspath.

javac PrinterJobProperty.java BrokenPrinterJob.java
java PrinterJobProperty
PrinterJobProperty.java
import java.awt.print.*;

/**
 * PrinterJob.getPrinterJob() must make an instance of the class that the
 * java.awt.printerjob system property names, as the JDK does. The class
 * can be on the class path. If the class cannot be used, it must print
 * a warning and use the default. BrokenPrinterJob fails with
 * NoClassDefFoundError when it is initialized.
 */
public class PrinterJobProperty
{
  static boolean failed;

  public static class TestPrinterJob extends PrinterJob
  {
    public void setPrintable(Printable p) {}
    public void setPrintable(Printable p, PageFormat f) {}
    public void setPageable(Pageable p) {}
    public boolean printDialog() { return false; }
    public PageFormat pageDialog(PageFormat f) { return f; }
    public PageFormat defaultPage(PageFormat f) { return f; }
    public PageFormat validatePage(PageFormat f) { return f; }
    public void print() {}
    public void setCopies(int n) {}
    public int getCopies() { return 1; }
    public String getUserName() { return "test"; }
    public void setJobName(String n) {}
    public String getJobName() { return "test"; }
    public void cancel() {}
    public boolean isCancelled() { return false; }
  }

  static void test(String property, String expected)
  {
    System.setProperty("java.awt.printerjob", property);
    String got = PrinterJob.getPrinterJob().getClass().getName();
    boolean ok = got.equals(expected);
    System.out.println((ok ? "PASS" : "FAIL") + ": java.awt.printerjob="
                       + property + " gave " + got + ", expected " + expected);
    failed |= !ok;
  }

  public static void main(String[] args)
  {
    String fallback = args.length > 0 ? args[0]
                                      : "gnu.java.awt.print.JavaPrinterJob";
    test(TestPrinterJob.class.getName(), TestPrinterJob.class.getName());
    test("NoSuchPrinterJob", fallback);
    test("BrokenPrinterJob", fallback);
    System.exit(failed ? 1 : 0);
  }
}
BrokenPrinterJob.java
/** A PrinterJob class that cannot be used: its initializer fails as if a dependency is missing. */
public class BrokenPrinterJob extends PrinterJobProperty.TestPrinterJob
{
  static
  {
    fail();
  }

  static void fail()
  {
    throw new NoClassDefFoundError("MissingDependency");
  }
}

Results

Haiku R1/beta6 x86_64 (hrev60228), JamVM with the Haiku port (#25 in ingelabs/jamvm), and ingelabs/classpath built on Haiku. I compiled each version of the changed classes with javac from OpenJDK 8 and put them first on the boot class path (-Xbootclasspath/p). "Before" is master. "Previous" is the first version of this pull request (boot class loader, no warning). "After" is this version.

Before:
FAIL: java.awt.printerjob=PrinterJobProperty$TestPrinterJob gave gnu.java.awt.print.JavaPrinterJob, expected PrinterJobProperty$TestPrinterJob
PASS: java.awt.printerjob=NoSuchPrinterJob gave gnu.java.awt.print.JavaPrinterJob, expected gnu.java.awt.print.JavaPrinterJob
PASS: java.awt.printerjob=BrokenPrinterJob gave gnu.java.awt.print.JavaPrinterJob, expected gnu.java.awt.print.JavaPrinterJob

Previous:
FAIL: java.awt.printerjob=PrinterJobProperty$TestPrinterJob gave gnu.java.awt.print.JavaPrinterJob, expected PrinterJobProperty$TestPrinterJob
PASS: java.awt.printerjob=NoSuchPrinterJob gave gnu.java.awt.print.JavaPrinterJob, expected gnu.java.awt.print.JavaPrinterJob
PASS: java.awt.printerjob=BrokenPrinterJob gave gnu.java.awt.print.JavaPrinterJob, expected gnu.java.awt.print.JavaPrinterJob

After:
PASS: java.awt.printerjob=PrinterJobProperty$TestPrinterJob gave PrinterJobProperty$TestPrinterJob, expected PrinterJobProperty$TestPrinterJob
PrinterJob: cannot use java.awt.printerjob=NoSuchPrinterJob (java.lang.ClassNotFoundException: NoSuchPrinterJob), using the default
PASS: java.awt.printerjob=NoSuchPrinterJob gave gnu.java.awt.print.JavaPrinterJob, expected gnu.java.awt.print.JavaPrinterJob
PrinterJob: cannot use java.awt.printerjob=BrokenPrinterJob (java.lang.NoClassDefFoundError: MissingDependency), using the default
PASS: java.awt.printerjob=BrokenPrinterJob gave gnu.java.awt.print.JavaPrinterJob, expected gnu.java.awt.print.JavaPrinterJob

With the test classes also on the boot class path (-Xbootclasspath/a), the previous version stopped with java.lang.NoClassDefFoundError: MissingDependency at the third test. This version gives the same output as above.

Platforms

Not specific to Haiku. It is a general extension point that all toolkits can use. The Haiku toolkit (#61) supplies printing through the Haiku print system with -Djava.awt.printerjob=gnu.java.awt.peer.haiku.HaikuPrinterJob.

AI assistance

An AI model (Claude Code, by Anthropic) wrote this change, the test and this text. The repository owner directed the work and checked the results. Please review it as you review other contributions.

🤖 Generated with Claude Code

@guillerodriguez

Copy link
Copy Markdown
Contributor

I have some concerns here.

The fact that the class is loaded using the boot class loader means that this would not be useful for any out-of-tree AWT toolkit, unless the toolkit is put in the boot class path. Classpath makes it especially easy to have out-of-tree toolkits (unlike the JDK), so this seems like an arbitrary limitation.

I know the JDK does the same, but I don't think this is a good model. In the JDK, java.awt.printerjob was implemented as an internal knob (the JDK sets it itself for each platform) and never intended as an extension point; in fact recent JDKs (17+) no longer use it.

Also, setting the property in the toolkit itself is fragile, since applications could create a PrinterJob before the toolkit has a chance to run. So they would get the custom PrinterJob or the standard JavaPrinterJob depending on what runs first. For example, an application that prints from the CLI would not see a property that is only set by the toolkit.

I think the approach is fine as long as:

  1. The class is loaded through the system class loader (in a doPrivileged block), the same way Toolkit.getDefaultToolkit() loads awt.toolkit. The system class loader asks the boot class loader first, so anything that works now still works.
  2. LinkageError is caught as well as Exception (e.g. NoClassDefFoundError when a dependency is missing), so we really do fall back to JavaPrinterJob, and we print a warning when the class can’t be used. Otherwise a typo in the property just silently gives you JavaPrinterJob.
  3. The property is set by the user (e.g. -Djava.awt.printerjob=... next to -Dawt.toolkit=...), not by the toolkit. Although that's not part of this PR anyway.

With those changes the test should also pass without -Xbootclasspath.

duncanmcqueen added a commit to duncanmcqueen/bekaffe that referenced this pull request Oct 22, 2026
Upstream review of ingelabs/classpath#67: the user sets
java.awt.printerjob, not the toolkit. Give
-Djava.awt.printerjob=gnu.java.awt.peer.haiku.HaikuPrinterJob in
run.sh and run-jamvm.sh, and say so in the README. The current
patches still set it in HaikuToolkit, so this changes nothing until
the patches are regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
As in the JDK, the system property java.awt.printerjob names the
PrinterJob class. The user sets it, for example next to awt.toolkit.
Load the class through the system class loader, as
Toolkit.getDefaultToolkit() loads awt.toolkit, so that a class on the
class path also works. Without the property, use JavaPrinterJob as
before. If the class cannot be loaded or made (also for a
LinkageError, for example NoClassDefFoundError), print a warning and
use JavaPrinterJob.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: duncanmcqueen <dwmcqueen@gmail.com>
@duncanmcqueen
duncanmcqueen force-pushed the fix-printerjob-property branch from ecadc92 to 0170d42 Compare October 22, 2026 13:25
@duncanmcqueen

Copy link
Copy Markdown
Contributor Author

Thank you. I agree with all three points, and I changed the commit:

  1. getPrinterJob() now loads the class through the system class loader, in a doPrivileged block, as Toolkit.getDefaultToolkit() loads awt.toolkit.
  2. It also catches LinkageError. If the class cannot be used, it prints a warning and returns JavaPrinterJob. For example:
    PrinterJob: cannot use java.awt.printerjob=BrokenPrinterJob (java.lang.NoClassDefFoundError: MissingDependency), using the default
  3. The Haiku toolkit in Add an AWT toolkit and a sound mixer for Haiku #61 does not set the property now. The user sets -Djava.awt.printerjob=gnu.java.awt.peer.haiku.HaikuPrinterJob. I also removed the comment about the Haiku toolkit from PrinterJob.

The test now runs without -Xbootclasspath. I added a class that does not exist and a class that throws NoClassDefFoundError. On Haiku with JamVM, all three cases pass. With the first version, the class on the class path was not found. With the test also on the boot class path, the first version stopped with NoClassDefFoundError. I updated the description.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants