How Do I Configure Java RESTful Engine for Printing?
This article describes how to configure the Java RESTful Engine to send output directly to a printer.
Manual Windows Installation
If you performed a manual installation of the Java RESTful Engine with Tomcat on Windows from here, this should work out of the box and no additional configuration is necessary!
Docker Installation
If you set up the Java RESTful Engine through Docker, there are a few steps you need to take to allow the Java RESTful Engine to send jobs to your printers.
Prerequisites (Windows Shared Printers Only)
If you are connecting to a printer shared from a Windows machine, you must complete these steps on the Windows host before configuring Docker. If you are not using a Windows Shared Printer, you can skip this section.
1. Create a local Windows user account for printer authentication
Azure AD and Microsoft accounts do not support the SMB authentication used by CUPS. You must create a dedicated local account.
- Go to Settings > Accounts > Other users > Add other user
- Select I don't have this person's sign-in info, then Add a user without a Microsoft account
- Set a username and password (e.g.
printuser)
2. Share the printer
- Go to Settings > Bluetooth & devices > Printers & scanners > [Your Printer] > Printer properties > Sharing tab
- Check Share this printer
- On the Security tab, grant Print permission to the local account you just created
3. Find the exact share name
The SMB share name may differ from the printer's display name. Find it by running the following in a Windows Command Prompt:
net share
Note the share name listed next to your printer — you will need it when adding the printer to CUPS.
Step 1: Exec Into the Container
docker exec -it <container_name> bash
Step 2: Install Required Packages
apt update && apt install -y \
cups cups-client cups-bsd cups-filters \
smbclient \
xvfb libxtst6 libxi6 libxrender1 libxt6 libxext6 libx11-6
| Packages | Purpose |
|---|---|
cups cups-client cups-bsd cups-filters | CUPS printing system — required for Java to discover printers |
smbclient | SMB backend for CUPS — required for Windows shared printers |
xvfb | Virtual framebuffer — provides a fake display since containers have no monitor |
libxtst6 libxi6 libxrender1 libxt6 libxext6 libx11-6 | X11 native libraries — required by Java AWT in non-headless mode |
Step 3: Start CUPS and Add Printers
Start the CUPS service:
cupsd
Then add your printer. The command varies depending on printer type.
IPP network printer:
lpadmin -p <PrinterName> -E -v ipp://PRINTER_IP:631/ipp/print -m everywhere
If you get a Host is down error on port 631, your printer may use port 80 instead. Try:
lpadmin -p <PrinterName> -E -v ipp://PRINTER_IP/ipp/print -m everywhere
Or over HTTPS (port 443):
lpadmin -p <PrinterName> -E -v ipps://PRINTER_IP/ipp/print -m everywhere
The exact IPP path (/ipp/print, /ipp, etc.) can be found in your printer's web interface at http://PRINTER_IP.
Raw TCP/IP printer:
lpadmin -p <PrinterName> -E -v socket://PRINTER_IP:9100 -m raw
Windows shared printer (SMB):
lpadmin -p <PrinterName> -E -v 'smb://<localuser>:<password>@host.docker.internal/<WindowsShareName>' -m raw
| Situation | Printer Type | lpadmin URI format |
|---|---|---|
| Printer is plugged into a Windows PC and shared | Windows Shared (SMB) | smb://<user>:<password>@<host>/<ShareName> |
Printer has its own IP and a web interface at http://PRINTER_IP | IPP Network Printer | ipp://PRINTER_IP:631/ipp/print |
| Printer has its own IP, no web interface, or is a label/industrial printer | Raw TCP/IP | socket://PRINTER_IP:9100 |
You connect on Windows via \\PC\PrinterName | Windows Shared (SMB) | smb://<user>:<password>@<host>/<ShareName> |
You can also test connectivity from inside the container to confirm:
- SMB:
smbclient -L <hostname> -U <user> - IPP:
curl http://PRINTER_IP:631/ - Raw TCP/IP:
nc -zv PRINTER_IP 9100
Replace <PrinterName> with the name you will use in the MainPrinter field of your POST request.
<WindowsShareName> is the share name from net share, which may differ from the printer's display name. You can also list all available shares from inside the container with:
smbclient -L host.docker.internal -U <localuser>
If your password contains special characters such as !, $, or &, wrap the entire -v value in single quotes to prevent shell interpretation.
Verify the printer was added successfully:
lpstat -p -d
Step 4: Configure Tomcat for Non-Headless Mode
Java's print API requires access to a display. You must disable headless mode and set the display variable so Tomcat picks them up at startup:
cat > /usr/local/tomcat/bin/setenv.sh << 'EOF'
export JAVA_OPTS="$JAVA_OPTS -Djava.awt.headless=false"
export DISPLAY=:0
EOF
chmod +x /usr/local/tomcat/bin/setenv.sh
Step 5: Commit the Container and Restart With the Correct Startup Order
Since Xvfb must be running before Tomcat, you need to commit the container (to preserve all installed packages and configuration) and recreate it with the correct startup command.
Exit the container first:
exit
Then from the host:
docker stop <container_name>
docker commit <container_name> <image_name>:printing
docker rm <container_name>
docker run -d \
--name <container_name> \
-p 8080:8080 \
<image_name>:printing \
bash -c "cupsd && Xvfb :0 -screen 0 1024x768x24 & sleep 1 && catalina.sh run"
This ensures CUPS and Xvfb are always started before Tomcat each time the container runs.
CUPS and Xvfb must be running before Tomcat starts. Java permanently caches the display state on first use — if Tomcat starts without a display available, print requests will fail for the lifetime of that container process even if Xvfb is started afterwards. Follow the steps below carefully to ensure the correct startup order.
<image_name> can be anything you would like the image name to be. You can also omit the "printing" tag if you'd like.
Because the startup command is now baked into the docker run command, CUPS and Xvfb will start automatically on every container restart — no manual intervention required.
Step 6: Test
Send a POST request with OutputFormat set to prn and MainPrinter set to the printer name used in the lpadmin -p command:
{
"ConnectionString": "{TEMPLATE_URL}",
"OutputFormat": "prn",
"Format": "pdf",
"MainPrinter": "{PRINTER_NAME}"
}
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
No local or network printers found | java.awt.headless=true (default) prevents Java from querying CUPS | Ensure setenv.sh has -Djava.awt.headless=false and restart the container |
Can't connect to X11 window server | Xvfb was not running before Tomcat started | Full container restart — use the startup command in Step 5 |
Could not initialize class GraphicsEnvironment$LocalGE | An earlier request failed before Xvfb was ready; the JVM cached the failure | Full container restart required |
libXtst.so.6: cannot open shared object file | Missing X11 libraries | Install libxtst6 libxi6 libxrender1 libxt6 libxext6 libx11-6 |
Bad device-uri scheme "smb" | SMB backend not installed | Install smbclient |
NT_STATUS_LOGON_FAILURE | Azure AD or Microsoft account used for SMB auth | Create a local Windows account instead |
NT_STATUS_BAD_NETWORK_NAME | Wrong share name in the URI | Run smbclient -L host.docker.internal -U <user> to find the correct share name |
| Special characters in password fail | Bash interprets !, $, etc. as shell syntax | Wrap the -v URI value in single quotes |
Address already in use when starting Tomcat | Tomcat is already running | Do not run catalina.sh run manually — use the commit/restart approach in Step 5 |