Search code examples
javaaws-lambdafontsapache-poi

Java 11 does not have fonts?


I'm using Java 11 Corretto + Spring Boot with Apache POI but I'm facing issues when using on AWS Lambda. Works fine if I run as "normal" API but fails when running servless in a AWS lambda.

try (ByteArrayOutputStream out = new ByteArrayOutputStream(); SXSSFWorkbook workbook = new SXSSFWorkbook(1000)) {
    SXSSFSheet sheet = workbook.createSheet("test"); //error here!!
    } ...

Caused by: java.lang.Error: Probable fatal error: No physical fonts found. at java.desktop/sun.font.SunFontManager.lambda$getDefaultPhysicalFont$0(Unknown Source) at java.base/java.util.Optional.orElseThrow(Unknown Source) at java.desktop/sun.font.SunFontManager.getDefaultPhysicalFont(Unknown Source) at java.desktop/sun.font.CompositeFont.doDeferredInitialisation(Unknown Source) at java.desktop/sun.font.CompositeFont.getSlotFont(Unknown Source) at java.desktop/sun.font.CompositeGlyphMapper.initMapper(Unknown Source) at java.desktop/sun.font.CompositeGlyphMapper.(Unknown Source) at java.desktop/sun.font.CompositeFont.getMapper(Unknown Source) at java.desktop/sun.font.CompositeFont.canDisplay(Unknown Source) at java.desktop/java.awt.Font.canDisplayUpTo(Unknown Source) at java.desktop/java.awt.font.TextLayout.singleFont(Unknown Source) at java.desktop/java.awt.font.TextLayout.(Unknown Source) at org.apache.poi.ss.util.SheetUtil.getDefaultCharWidth(SheetUtil.java:285) at org.apache.poi.xssf.streaming.AutoSizeColumnTracker.(AutoSizeColumnTracker.java:117) at org.apache.poi.xssf.streaming.SXSSFSheet.(SXSSFSheet.java:84) at org.apache.poi.xssf.streaming.SXSSFWorkbook.createAndRegisterSXSSFSheet(SXSSFWorkbook.java:705) at org.apache.poi.xssf.streaming.SXSSFWorkbook.createSheet(SXSSFWorkbook.java:724)

I tried to create a fix to the fonts but I have no idea how to proceed

@PostConstruct
public void loadFonts() {
    URL configURL = getClass().getClassLoader().getResource("fontconfig.properties");
    String path = configURL != null ? configURL.getPath() : null;
    Properties props = System.getProperties();
    LOGGER.info("Loading font config file: {}", path);
    props.put("sun.awt.fontconfig", path);

    String[] fonts;
    try {
        GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames();
    } catch (Throwable ex) {
        LOGGER.warn("Reloading Fonts");
    }
    try {
        fonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames();

        for (String font : fonts) {
            LOGGER.info("Available Font: {}", font);
        }
    } catch (Throwable ex) {
        LOGGER.error("Could not load Fonts");
    }
}

Config file:

version=1
sequence.allfonts=default

When running on AWS Lambda it outputs some fonts to the logs so I don't understand why I get that error:

2022-05-31 16:00:14  INFO  Starting Lambda Container Handler
2022-05-31 16:00:18  INFO  Loading font config file: /var/task/fontconfig.properties
2022-05-31 16:00:18  INFO  Available Font: Dialog
2022-05-31 16:00:18  INFO  Available Font: DialogInput
2022-05-31 16:00:18  INFO  Available Font: Monospaced
2022-05-31 16:00:18  INFO  Available Font: SansSerif
2022-05-31 16:00:18  INFO  Available Font: Serif

Any idea how to add physical fonts and why can't the POI use the available fonts that output to the logs?

EDIT: I also tried to run as headless but I get the same error:

`props.setProperty("java.awt.headless", "true");`

Solution

  • Fonts bundling

    Any idea how to add physical fonts

    The Oracle JDK product from Oracle stopped bundling fonts as of Java 11.

    The OpenJDK project does not bundle fonts, at least not in recent versions.

    The Corretto JDK from Amazon product is based on OpenJDK codebase. Given your report, apparently they do not bundle fonts.

    You must choose to:

    • Rely on the fonts provided by the host OS.
    • Bundle fonts within your Java app. Be careful to study license terms. Some fonts allow such bundling, some don’t. Some require a fee, some don’t. Some require an acknowledgment, some don’t.
    • Obtain a JDK from a vendor that includes fonts.

    I know of at least one such vendor: Azul Systems bundles fonts with their Azul Platform Core commercial product.

    There may be other such vendors that I’m not aware of. Many vendors provide JDK binaries and installers. These vendors include Adoptium, SAP, BellSoft, Microsoft, Azul Systems, Amazon, Oracle, Red Hat/IBM, Pivotal, and more.

    Physical fonts versus logical fonts

    why can't the POI use the available fonts that output to the logs

    The fonts you listed in your logs are logical fonts, not physical fonts. The distinction is clearly documented in the Javadoc for the Font class.

    To use a logical font name, you must have some backing physical font. If no fonts exist within your app, within your JDK, or within your host OS, then the logical fonts cannot work.

    Fonts for AWS Lambda

    Regarding making fonts available in AWS Lambda, perhaps these two pages might help. (Thanks to jarmod for the links.)

    The basic idea is to create a /fonts folder within your package. Incude a fonts.conf file within, with XML configuration elements. Place your bundled fonts files alongside that config file.

    License terms

    Be aware that some fonts are commercial, with a license that requires a fee. When bundling/deploying fonts, be sure to verify that you are meeting the terms of their license.