How to Detect and Replace Excel Watermarks with Apache POI in Java

This guide explains how to reliably identify and remove existing watermarks from .xlsx files and then add new, precisely positioned semi‑transparent watermarks using Java's Apache POI library, complete with Maven setup, code examples, and performance tips.

Cognitive Technology Team
Cognitive Technology Team
Cognitive Technology Team
How to Detect and Replace Excel Watermarks with Apache POI in Java

1. What is an Excel watermark?

Excel does not have a native watermark feature; watermarks are simulated by inserting semi‑transparent images into the drawing layer, using text boxes, setting background images, or leveraging headers/footers. The most flexible method is to add a PNG image to the drawing layer.

Control position, size, transparency

Support any text or graphic

Visible both on screen and when printed (unless hidden)

2. Technical choice – Apache POI

Apache POI is the de‑facto Java library for Microsoft Office formats. For .xlsx files the XSSF module (based on the OpenXML standard) is used.

3. Detailed code implementation

3.1 Maven dependencies

<dependencies>
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi</artifactId>
        <version>4.1.2</version>
    </dependency>
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>4.1.2</version>
    </dependency>
    <!-- Optional: enhance image handling compatibility -->
    <dependency>
        <groupId>com.twelvemonkeys.imageio</groupId>
        <artifactId>imageio-core</artifactId>
        <version>3.9.4</version>
    </dependency>
</dependencies>

3.2 Watermark image generator

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

public class WaterMarkHandler {
    public static ByteArrayOutputStream createWaterMark(String content) throws IOException {
        int width = 500;
        int height = 300;
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        String fontType = "微软雅黑";
        int fontStyle = Font.BOLD;
        int fontSize = 20;
        Font font = new Font(fontType, fontStyle, fontSize);
        Graphics2D g2d = image.createGraphics();
        image = g2d.getDeviceConfiguration().createCompatibleImage(width, height, Transparency.TRANSLUCENT);
        g2d.dispose();
        g2d = image.createGraphics();
        g2d.setColor(new Color(0, 0, 0, 20)); // black with alpha
        g2d.setStroke(new BasicStroke(1));
        g2d.setFont(font);
        g2d.rotate(-0.5, (double) image.getWidth() / 2, (double) image.getHeight() / 2);
        FontRenderContext context = g2d.getFontRenderContext();
        Rectangle2D bounds = font.getStringBounds(content, context);
        double x = (width - bounds.getWidth()) / 2;
        double y = (height - bounds.getHeight()) / 2;
        double ascent = -bounds.getY();
        double baseY = y + ascent;
        g2d.drawString(content, (int) x, (int) baseY);
        g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
        g2d.dispose();
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        ImageIO.write(image, "png", os);
        return os;
    }
}

3.3 Core logic – remove old watermark and add new one

import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ooxml.POIXMLDocumentPart;
import org.apache.poi.openxml4j.opc.PackagePartName;
import org.apache.poi.openxml4j.opc.PackageRelationship;
import org.apache.poi.openxml4j.opc.TargetMode;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

public final class ExcelWatermarkUtil {
    /**
     * Add a watermark to an Excel file (in‑memory).
     * @param excelData original Excel file bytes
     * @param watermarkText watermark text, e.g., "CONFIDENTIAL"
     * @return Excel bytes with watermark applied
     */
    public static byte[] addWatermarkToExcel(byte[] excelData, String watermarkText) throws IOException {
        ByteArrayOutputStream waterMark = WaterMarkHandler.createWaterMark(watermarkText);
        try (ByteArrayInputStream bis = new ByteArrayInputStream(excelData);
             ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
            XSSFWorkbook workbook = new XSSFWorkbook(bis);
            // Remove existing pictures (simple approach)
            for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
                XSSFSheet sheet = workbook.getSheetAt(i);
                try { sheet.getCTWorksheet().unsetPicture(); } catch (Exception e) { e.printStackTrace(); }
            }
            int pictureIdx = workbook.addPicture(waterMark.toByteArray(), Workbook.PICTURE_TYPE_PNG);
            POIXMLDocumentPart poixmlDocumentPart = workbook.getAllPictures().get(pictureIdx);
            for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
                XSSFSheet sheet = workbook.getSheetAt(i);
                PackagePartName ppn = poixmlDocumentPart.getPackagePart().getPartName();
                String relType = XSSFRelation.IMAGES.getRelation();
                PackageRelationship pr = sheet.getPackagePart().addRelationship(ppn, TargetMode.INTERNAL, relType, null);
                sheet.getCTWorksheet().addNewPicture().setId(pr.getId());
            }
            workbook.write(bos);
            return bos.toByteArray();
        }
    }
}

4. Usage example

public void addWatermarkToExcel() throws IOException {
    byte[] fileBytes = Files.readAllBytes(Paths.get("/Users/me/logs/test.xlsx"));
    byte[] addWatermarkToExcel = ExcelWatermarkUtil.addWatermarkToExcel(fileBytes, "王叔叔 12122121212 2025-12-05 09:17:35");
    Files.write(Paths.get("/Users/me/logs/test111.xlsx"), addWatermarkToExcel);
}

5. Key issues and optimisation tips

5.1 Accurately identify watermark images

Add metadata when inserting watermark, e.g., name the picture "WATERMARK_CONFIDENTIAL" and later check picture.getPictureData().getFileName().

Filter by position/size – watermarks are usually centered, large, and low‑opacity.

Use custom properties via POI’s CTPicture API for advanced tagging.

5.2 Adapt watermark position to different resolutions

Calculate the sheet’s last row and column and set the anchor to cover the whole sheet dynamically.

int lastRow = sheet.getLastRowNum();
int lastCol = sheet.getRow(0).getLastCellNum();
anchor.setCol1(0); anchor.setRow1(0);
anchor.setCol2(lastCol); anchor.setRow2(lastRow);

5.3 Performance optimisation

Cache the generated watermark image when the same text is reused.

For read‑only scenarios, use ReadOnlySharedStringsTable to speed up large file processing.

6. Conclusion

The article demonstrates a complete “remove old watermark → add new watermark” workflow for .xlsx files using Java and Apache POI. Although Excel lacks native watermark support, inserting a semi‑transparent image into the drawing layer provides an efficient and flexible solution.

Best practice: standardise watermark naming, position and size at the system‑design stage to simplify later detection and replacement.
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavawatermarkExcelApache POIFile Processing
Cognitive Technology Team
Written by

Cognitive Technology Team

Cognitive Technology Team regularly delivers the latest IT news, original content, programming tutorials and experience sharing, with daily perks awaiting you.

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.