GS1 DataMatrix codes in Java

TLDR; GS1 Datamatrix codes can be tricky. Use OkapiBarcode for easy handling.

Anyone who has had to deal with GS1 barcodes has most likely made an acquaintance with this document, or perhaps even this one if working more specifically with DataMatrix. If you found your way here then you most likely already know what DataMatrix codes look like, and you should also know that they consist of one to several fields, where a field essentially is a key value pair; the key consisting of 2-4 digits (called Application Identifier, or AI for short), and the value being a fixed or dynamic number of digits, depending on the Application Identifier.

Furthermore, you’ve likely also figured out that in order for the scanner/receiving system to know where fields of dynamic length end, the GS1 standards organisation has decided that a special character known as FNC1 (Function 1 Symbol Character) should be used. This is a non-printable character which should be replaced by the ASCII control character <GS> (Group Separator, ASCII character 29) when encoded; except of course in the first position, when it should be [d2, which in turn is encoded differently depending on the system. All in all, it’s really rather confusing, so let’s see how some ready-made solutions can help us.

Firstly, there are two primary ways of generating barcodes:

  1. Using barcode fonts
  2. Creating a bitmap

The benefit of barcode fonts is that they scale without a problem, so you can generate a barcode for anything from a stamp to a poster without any changes. The drawback, depending on how price sensitive you are, is that (to my knowledge) there are no free options, and they generally cost in the order of hundreds or thousands of US dollars. Libraries will generally also be of varying quality, but as long as you have the funds then this generally tends to be a good option. Search the web a bit and you’ll find several alternatives.

However, assuming that you’re not printing building-sized banners, then option number 2 should basically be able to cover most use cases. Let us therefore have a closer look at what’s available (this post is limited to looking at Javabased options). All options are licensed under the Apache 2 license (meaning you can use it freely; even commercially).

  • Barcode4J
  • ZXing
  • OkapiBarcode

If we start with Barcode4J then we quickly notice that its last release was version 2.0.1 in December of 2010. So let’s just go ahead and remove that from our list without further ado.

This leaves us with two potential candidates. ZXing certainly has the bigger community, but as it turns out, does not actually support GS1 encoding due to irregularities with the <GS> character mentioned above (or at least it didn’t at the time of writing).

Okapi Barcode on the other hand is built more as a standalone java application rather than a library, but is able to handle everything flawlessly – hooray! The only thing you need to keep in mind is that the Application Identifiers need to be enclosed in square brackets, for example like this: [21]12345678987654[3901]00000000590 (just in case you’re curious, AI 21 represents a serial number, and AI 3901 means “Amount payable – single monetary area”, i.e. the price of the product, where the 1 represents the number of decimals). This ensures that the <GS> character is properly encoded by Okapi.

The following code illustrates an example where we generate a DataMatrix and return it as a Base64 encoded String, including returning an image containing the text “Invalid Barcode” in case the input was incorrectly formatted:

package some.package.or.other;

import uk.org.okapibarcode.backend.*;
import uk.org.okapibarcode.output.Java2DRenderer;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Base64;
import java.util.logging.Level;

public class GetBarcode {
    
    /*
     * These values adjust the size of the DataMatrix bitmap.
     * Border size sets the size of the surrounding white border (0 means none).
     * Magnification is used to enlarge the initial DataMatrix X times, as this will automatically have the smallest possible 
     * size that the data will fit inside. In this particular use case they were usually around 24x24, so we multiply by 10 to make it 240x240.
     * For crisp barcodes in for example a PDF, we want to generate bitmaps at least twice the size of the expected output size.
     */ 
    private static final int MAGNIFICATION = 10;
    private static final int BORDER_SIZE = 0 * MAGNIFICATION;
    
    /*
     * Input has to be correctly formatted, meaning GS1 Application Identifiers are enclosed in square brackets.
     */
    public static final String getBarcode(String input){
        try{
            // Set up the DataMatrix object
            DataMatrix dataMatrix = new DataMatrix();
            // We need a GS1 DataMatrix barcode.
            dataMatrix.setDataType(Symbol.DataType.GS1); 
            // 0 means size will be set automatically according to amount of data (smallest possible).
            dataMatrix.setPreferredSize(0); 
            // Don't want no funky rectangle shapes, if we can avoid it.
            dataMatrix.forceSquare(true); 
            dataMatrix.setContent(dataToEncode);

            return getBase64FromByteArrayOutputStream(getMagnifiedBarcode(dataMatrix, MAGNIFICATION));
        } catch(OkapiException oe){
            java.util.logging.Logger.getLogger(GetBarcode.class.getName()).log(Level.SEVERE, null, oe);
            return getInvalidBarcodeImage(100);
        }
    }
    
    private static BufferedImage getMagnifiedBarcode(Symbol symbol, int magnification){
        // Make DataMatrix object into bitmap
        BufferedImage image = new BufferedImage((symbol.getWidth() * magnification) + (2 * BORDER_SIZE),
                                                (symbol.getHeight() * magnification) + (2 * BORDER_SIZE), 
                                                BufferedImage.TYPE_INT_RGB);
        Graphics2D g2d = image.createGraphics();
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setColor(Color.WHITE);
        g2d.fillRect(0, 0, (symbol.getWidth() * magnification) + (2 * BORDER_SIZE),
                    (symbol.getHeight() * magnification) + (2 * BORDER_SIZE));
        Java2DRenderer renderer = new Java2DRenderer(g2d, magnification, BORDER_SIZE, Color.WHITE, Color.BLACK);
        renderer.render(symbol);
        
        return image;
    }
    
    private static String getBase64FromByteArrayOutputStream(BufferedImage image){
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            // Write the image into a stream
            ImageIO.write(image, "png", baos);
            // So that we can Base64 encode it directly and don't have to write it to disk
            Base64.Encoder encoder = Base64.getEncoder(); 
            return encoder.encodeToString(baos.toByteArray());
        }catch (IOException ioe) {
            java.util.logging.Logger.getLogger(GetBarcode.class.getName()).log(Level.SEVERE, null, ioe);
            return "Something went wrong, please check the logs for closer details.";
        }
    }
    
    /**
     * Blatantly copied from StackOverflow: http://stackoverflow.com/questions/18800717/convert-text-content-to-image?answertab=votes#tab-top
     * @return Base64 encoded String representing an image containing the text "Invalid Barcode" (used in case of incorrectly formatted input data)
     */
    private static String getInvalidBarcodeImage(int textSize){
        String errorMessage = "Invalid barcode";

        /*
           Because font metrics is based on a graphics context, we need to create
           a small, temporary image so we can ascertain the width and height
           of the final image
         */
        BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = img.createGraphics();
        Font font = new Font("Arial", Font.PLAIN, textSize);
        g2d.setFont(font);
        FontMetrics fm = g2d.getFontMetrics();
        int width = fm.stringWidth(errorMessage);
        int height = fm.getHeight();
        g2d.dispose();

        img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        g2d = img.createGraphics();
        g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
        g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
        g2d.setFont(font);
        fm = g2d.getFontMetrics();
        g2d.setColor(Color.BLACK);
        g2d.drawString(errorMessage, 0, fm.getAscent());
        g2d.dispose();
        return getBase64FromByteArrayOutputStream(img);
    }
}

Needless to say, the same method can be used to generate non-GS1-DataMatrix codes by simply setting the DataType to UTF8

dataMatrix.setDataType(Symbol.DataType.UTF8);

and then throwing whatever URL or text at it that you feel like (I haven’t actually tested this myself, but I’m presuming it should work).

And as a bonus, here’s the code for generating an Interleaved 2 of 5 barcode as well:

private static String getInterleaved2of5Barcode(String input, int barHeight, int moduleWidth, double moduleWidthRatio, int magnification, 
                                                int invalidBarcodeTextSize, boolean humanReadable){
    try{
        Code2Of5 c25inter = new Code2Of5();
        c25inter.setInterleavedMode();
        if(humanReadable) {
            c25inter.setHumanReadableLocation(HumanReadableLocation.BOTTOM);
        } else{
            c25inter.setHumanReadableLocation(HumanReadableLocation.NONE);
        }
        //Default is 40 in OkapiBarcode
        c25inter.setBarHeight(barHeight);
        //Default is 1 in OkapiBarcode.
        c25inter.setModuleWidth(moduleWidth); 
        //Default is 3 in OkapiBarcode
        c25inter.setModuleWidthRatio(moduleWidthRatio);
        c25inter.setContent(modifiedInput);
        return getBase64FromBarcode(c25inter, magnification);
    } catch(OkapiException oe){
        java.util.logging.Logger.getLogger(GetBarcode.class.getName()).log(Level.SEVERE, null, oe);
        return getInvalidBarcodeImage(invalidBarcodeTextSize);
    }
}

All in all, a potentially complicated issue becomes surprisingly easy with the right tools. No need to read through the various GS1 specification documents. Good luck!

This Post Has 8 Comments

  1. Thanks for summarizing your findings for other’s benefit. I was looking for a GS1Parser, which would accept a string of characters and extract the Application identifiers and their values out of it. If you have come across a solution for this, please let me know.

    1. Hi Sandhya,

      Glad it could be of some use. I haven’t used any GS1 parser myself since I was only converting non-GS1 to GS1 before making a barcode out it. I was never on the receiving end that had to decode it. However, maybe this could be of help? https://github.com/PeterBrockfeld/BarcodeParser

  2. Hi,

    thank you for you notes.
    i’ve got a question regarding the ratio. i’m not understanding the management of square.
    the force square means that avoiding this i can create a rectangular data matrix that use a different data path or just that the image is in reality squared and without that it would be stretched in a different ratio?

    1. Hi Maruizio,

      Glad the post was to some help. I’m uncertain what you mean by “different data path”, could you explain a bit more?

      To be honest though, my knowledge regarding this stretches only as far as what I experienced while developing this. What I saw then is that there is a certain breakpoint (related to the amount of data) at which the library would rather output a rectangle than a square, and by setting forceSquare(true) I was able to make it output squares for a bigger amount of data, but still only up to a certain point. Not setting it to true – at least as far as I experienced it – simply pushes that boundary a bit, so that you might get rectangles with a smaller amount of data than otherwise. The exact details of the algorithm in the library that decides on square or rectangle are unknown to me, I’m afraid.

  3. Hi Karl,
    Thanks for the wonderful post. This definitely helps a lot of people.
    I tried the example and I am able to create the 2D Barcode but it is not scan able.

    Just wanted to know what should I change in this piece of code so that it is scan able?

    1. Hi Roy,

      Thank you for the comment. Hard to say what you might need to change without knowing more about the actual setup you’re using, including the kind of scanner. Straight off the bat I would guess that maybe your scanner is expecting data encoded in a particular way that doesn’t correspond with what’s being outputted by the code above? Feel free to send me an email if you want to get into more detail, and I’ll see what I can do (no promises).

  4. Hi Karl,

    Thank you so much for the post! I was struggling to find any documentation on OkapiBarcode and your code snippet was immensely helpful – especially the formatting of the AI in the content string (the fact that you use brackets). I was wondering about your comment re: “Okapi Barcode on the other hand is built more as a standalone java application rather than a library” – I see that your example is a standalone application, however, I had no issues using it within the context of a web application as a Maven dependency. Is there any issues you’re aware of that I could run into (eg thread safety issues, etc)?

    1. Hi Staci,

      Thank you for your comment. I’m glad the blog post was of use!

      The simple answer is that no, I don’t know of/think you will have any issues that you might run into as a result of running it in the context of a web application. The main difference as far as I’m concerned is that the standalone java application includes a UI and related code that bloats your own application unless you’ve extracted only the necessary bits, but thread safety and general functionality should not be a problem.

Leave a Reply

Close Menu