How to Build a Silent Printing Proxy for Windows Using Java and Spring Boot

This article explains the challenges of printing in a hybrid store‑warehouse environment, outlines the limitations of JavaScript‑based printing, and details a custom Java‑powered printing proxy that uses Spring Boot, printable APIs, executable packaging, and offset files to achieve silent, multi‑scenario printing on Windows.

Zhuanzhuan Tech
Zhuanzhuan Tech
Zhuanzhuan Tech
How to Build a Silent Printing Proxy for Windows Using Java and Spring Boot

1. Problems Faced by the Store‑Warehouse

The store combines retail and warehouse functions, requiring printing of receipts, tags, stickers, and accessories, often with multiple printers on a single PC. Operators need a simple, automated workflow that selects the correct printer and minimizes manual steps.

2. Limitations of Calling the Printer from JavaScript

2.1 JavaScript Constraints

Calling window.print() prints the whole page, not just the barcode or receipt data. Using an iframe still triggers the Windows print preview dialog, which is unnecessary for the store’s workflow.

2.2 Need for Silent Printing

Two common approaches exist: modifying Chrome flags (unstable across versions) or using a proxy program that receives print requests from the browser and prints silently. The latter is more reliable for the store’s specific scenarios.

3. Building a Custom Printing Proxy

3.1 Java Calls Windows Printer

Java can invoke the Windows print service via the javax.print API. The code below shows how to locate a printer by name and obtain a PrintService instance.

private PrintService findPrintService(String name) {
    PrintService targetPrintService = null;
    PrintService[] allPrintServices = PrintServiceLookup.lookupPrintServices(null, null);
    if (allPrintServices != null) {
        for (PrintService printService : allPrintServices) {
            if (printService.getName().equals(name)) {
                targetPrintService = printService;
                break;
            }
        }
    }
    if (targetPrintService == null) {
        // log missing printer
        return null;
    }
    return targetPrintService;
}

This method allows dynamic selection of printers when several are connected.

3.2 Generating Print Data

Three main strategies are used:

Template‑based printing (e.g., JasperSoft) for complex, stable layouts.

Using java.awt.print.Book to draw text and graphics directly for simple receipts.

Converting the content to an image and printing the image, which is flexible for barcode‑heavy outputs.

Example of drawing text with Book:

Book book = new Book();
book.append((graphics, pageFormat, pageIndex) -> {
    graphics.setFont(new Font("Default", Font.BOLD, 12));
    graphics.drawString("十二号字测试行", 0, 30);
    // ... more drawing commands ...
    return PAGE_EXISTS;
}, pf);

Printing an image involves reading the image, adjusting the printable area, and sending the job:

BufferedImage image = ImageIO.read(inputStream);
PrinterJob job = PrinterJob.getPrinterJob();
job.setPrintService(targetPrintService);
PageFormat pageFormat = getPageFormat(image.getHeight(), image.getWidth());
job.setPrintable(new ImagePrintable(image, scale), pageFormat);
job.print(new HashPrintRequestAttributeSet());

3.3 Multi‑Scenario Web‑Based Proxy

A lightweight Spring Boot web server exposes a single /print endpoint. Front‑end JavaScript posts the image and a type identifier; the server routes the request to the appropriate IPrintService implementation.

@RequestMapping("/print")
@ResponseBody
public WebResponse<Void> print(@RequestParam("type") String type,
                               @RequestParam("file") MultipartFile file) {
    // delegate to specific print service based on type
}

Print services are injected as a map keyed by the type parameter.

@Resource
private Map<String, IPrintService> printServiceMap;
打印服务类实现IPrintService接口,各自处理对应场景的打印参数
打印服务类实现IPrintService接口,各自处理对应场景的打印参数

3.4 Packaging as a Stand‑Alone Executable

The Java application is bundled into a single JAR using the Maven Assembly plugin, then wrapped into an EXE with Launch4j. This eliminates the need for a separate Java runtime on the store PCs.

<!-- Maven Assembly plugin configuration (simplified) -->
<plugin>
    <artifactId>maven-assembly-plugin</artifactId>
    <executions>
        <execution>
            <id>assembly</id>
            <phase>package</phase>
            <goals><goal>single</goal></goals>
            <configuration>
                <descriptors>
                    <descriptor>src/main/assembly/assembly.xml</descriptor>
                </descriptors>
            </configuration>
        </execution>
    </executions>
</plugin>
<!-- Launch4j plugin configuration (simplified) -->
<plugin>
    <groupId>com.akathist.maven.plugins.launch4j</groupId>
    <artifactId>launch4j-maven-plugin</artifactId>
    <executions>
        <execution>
            <id>l4j-clui</id>
            <phase>package</phase>
            <goals><goal>launch4j</goal></goals>
            <configuration>
                <headerType>console</headerType>
                <jar>${project.build.directory}/${project.build.finalName}.jar</jar>
                <outfile>${project.build.directory}/printWebserver_1.0.exe</outfile>
                <icon>src/main/resources/image/zzPrinter.ico</icon>
                <singleInstance>
                    <mutexName>warestorePrintWebserver</mutexName>
                </singleInstance>
                <classPath>
                    <mainClass>org.springframework.boot.loader.JarLauncher</mainClass>
                    <addDependencies>true</addDependencies>
                    <jarLocation>lib/</jarLocation>
                </classPath>
                <jre>
                    <minVersion>1.8.0</minVersion>
                    <initialHeapSize>1024</initialHeapSize>
                    <maxHeapSize>2048</maxHeapSize>
                </jre>
                <versionInfo>
                    <fileVersion>1.0.0.0</fileVersion>
                    <txtFileVersion>1.0.0.0</txtFileVersion>
                    <fileDescription>zhuanzhuan warestore universal printing program</fileDescription>
                    <productName>printWebserver</productName>
                    <originalFilename>printWebserver.exe</originalFilename>
                </versionInfo>
            </configuration>
        </execution>
    </executions>
</plugin>
编译生成的exe文件
编译生成的exe文件

Running the EXE starts the web service, allowing the front‑end to trigger silent printing via HTTP.

代理打印程序运行画面
代理打印程序运行画面

4. General Solution for Print Offset

Print media often shift due to pre‑printed patterns or roll changes. To provide a uniform adjustment, a plain‑text offset file is placed on the desktop (e.g., "打印机偏移参数.txt") containing X and Y offsets separated by a comma.

偏移文件,简单直观的控制偏移量
偏移文件,简单直观的控制偏移量

At startup the application reads this file and stores the offsets in a static entity. When creating the printable area, the offsets are added:

paper.setImageableArea(14.2D + localOffsetEntity.getxOffset(),
                       121.9D + localOffsetEntity.getyOffset(),
                       areaWidth, areaWidth * aspectRatio);

5. Summary

The store’s front‑end is web‑based, so a web service proxy simplifies integration.

Reducing manual steps and preventing unexpected dialogs improves operator efficiency.

Providing a simple, configurable solution (offset file, executable packaging) ensures stability across diverse hardware scenarios.

Spring BootwindowsPrint Serviceexecutable packagingPrint Offsetsilent printing
Zhuanzhuan Tech
Written by

Zhuanzhuan Tech

A platform for Zhuanzhuan R&D and industry peers to learn and exchange technology, regularly sharing frontline experience and cutting‑edge topics. We welcome practical discussions and sharing; contact waterystone with any questions.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.