A fast, dependency-free Java library for generating QR Codes. It implements the ISO/IEC 18004 pipeline from scratch — Reed-Solomon error correction, matrix construction, mask selection and rendering — with zero runtime dependencies.
Project page: fineiasantonio.github.io/FastQRGenerator
- No runtime dependencies — pure Java on top of the JDK only.
- Full version range — QR versions 1 through 40.
- All error-correction levels — L, M, Q, H.
- Numeric mode — pure digit payloads are auto-detected and packed at ~2.4x byte-mode density, often fitting a smaller symbol.
- Automatic mask selection following the ISO 18004 penalty rules.
- Styled rendering — custom colors, border thickness and rounded modules.
- Multiple output formats — PNG, JPG, JPEG, BMP, SVG, plus ANSI terminal printing.
- Java 8 or newer.
<dependency>
<groupId>io.github.fineiasantonio</groupId>
<artifactId>fastqrgenerator</artifactId>
<version>1.0.0-beta</version>
</dependency>implementation 'io.github.fineiasantonio:fastqrgenerator:1.0.0-beta'import com.qrlib.QRCode;
import com.qrlib.QRCodeGenerator;
import com.qrlib.QRCodeGeneratorBuilder;
import com.qrlib.config.ImageExtensions;
import java.io.FileOutputStream;
// No version or ECC level needed: the smallest fitting version is chosen
// automatically, with error-correction level M by default.
QRCodeGenerator generator = new QRCodeGeneratorBuilder().build();
QRCode qr = generator.generate("https://github.com/FineiasAntonio/FastQRGenerator");
try (FileOutputStream out = new FileOutputStream("qr.png")) {
qr.writeImage(out, ImageExtensions.PNG);
}By default the smallest version that fits the payload is selected
automatically, so you usually don't need to set one. To pin an explicit version
(V1–V40)…
new QRCodeGeneratorBuilder().version(QRCodeVersion.V10).build();…or a named QRCodeSize preset:
| Preset | Version |
|---|---|
TINY |
V1 |
SMALL |
V2 |
MEDIUM |
V5 |
LARGE |
V10 |
HUGE |
V20 |
MAX |
V40 |
new QRCodeGeneratorBuilder().size(QRCodeSize.MEDIUM).build();When you pin a version, it must hold the payload — otherwise
generate(...)throwsIllegalArgumentException. Higher versions and higher error-correction levels reduce the data capacity.
new QRCodeGeneratorBuilder()
.version(QRCodeVersion.V5)
.eccLevel(ECCLevel.H) // L, M (default), Q, H
.build();writeImage(...) writes the rendered image to any OutputStream; toImageBytes(...)
returns it as a byte[]:
// write straight to a stream (file, HTTP response, ...)
try (FileOutputStream out = new FileOutputStream("qr.png")) {
qr.writeImage(out, ImageExtensions.PNG);
}
// default: PNG, module size 10px, default style
byte[] png = qr.toImageBytes();
// pick a format
byte[] jpg = qr.toImageBytes(ImageExtensions.JPG);
// pick a format and module size (pixels per module)
byte[] scaled = qr.toImageBytes(ImageExtensions.PNG, 8);QRCodeStyleDefinitions style = QRCodeStyleDefinitions.builder()
.moduleColor("#1A1A2E") // #RRGGBB or shorthand #RGB
.backgroundColor("#FFF")
.borderThickness(4) // quiet-zone width, in modules
.cornerRadius(0.4) // 0 (square) to 0.5 (fully round), as a fraction of the module
.build();
qr.toImageBytes(ImageExtensions.PNG, 10, style);Notes:
- Colors accept
#RRGGBBor shorthand#RGBhex and are validated when set. borderColor(...)tints the quiet zone; when not set it follows the background color.cornerRadius(...)implies rounded modules;roundedCorners(true)uses the maximum radius. Rounded modules are drawn antialiased, and connected runs of modules merge into a single smooth shape rounded only at its ends.- Keep enough contrast between module and background colors — low-contrast symbols may not scan reliably.
Overlay an image on the center of the symbol. It is drawn over a small
background-colored pad and scaled — preserving its aspect ratio — to the
configured fraction of the symbol width (default 0.2, maximum 0.3):
BufferedImage logo = ImageIO.read(new File("logo.png"));
QRCodeStyleDefinitions style = QRCodeStyleDefinitions.builder()
.centerImage(logo)
.centerImageRatio(0.2)
.centerImagePadShape(CenterImagePadShape.ROUNDED) // SQUARE (default), ROUNDED or CIRCLE
.build();
qr.toImageBytes(ImageExtensions.PNG, 10, style);The pad behind the image can be SQUARE (default), ROUNDED (rounded
corners) or CIRCLE — with CIRCLE the image itself is also cropped to a
circle, scaled to cover it fully.
The covered modules are lost to the reader and must be recovered by error correction, so pair a center image with a high error-correction level:
new QRCodeGeneratorBuilder().eccLevel(ECCLevel.H).build();As a rule of thumb: the default ratio (0.2) scans reliably at level M and
above, the maximum ratio (0.3) requires Q or H, and level L should not
be combined with a center image at all.
getAsSVG(...) renders the symbol as a standalone SVG document string — plain text
built directly from the matrix, with no java.awt rasterization involved, so it also
works on runtimes without AWT (e.g. Android):
String svg = qr.getAsSVG(); // default style, 10px per module
// with a custom pixel size and style (same styling options as raster output)
String styled = qr.getAsSVG(8, style);
Files.write(Paths.get("qr.svg"), svg.getBytes(StandardCharsets.UTF_8));The viewBox is laid out in module units, so the symbol scales losslessly to any
size — the module size only sets the document's default width/height. SVG is the
natural choice for the web: browsers render it directly, files are compact, and it
stays sharp at every zoom level.
The one exception to the no-AWT guarantee: a style carrying a center image embeds it
as a base64 PNG data URI, which requires javax.imageio.
qr.print(); // renders the symbol with ANSI background blocks on System.out
qr.print(System.err); // or on any PrintStream
String ansi = qr.getAsTerminalString(); // the same output as a StringIf you only need the module data (no AWT), read it directly:
int size = qr.getSize(); // side length, in modules
boolean dark = qr.isDark(row, col); // read a single module
byte[][] matrix = qr.getMatrixData().getMatrix(); // snapshot copy, 1 = dark, 0 = lightgetMatrixData() returns a snapshot: modifying it (or the array from getMatrix())
never affects the generated symbol.
The samples/ directory contains generated symbols from version 2 up
to version 40.
./mvnw clean test # compile and run the test suite
./mvnw clean package # build the jarThis library is in beta. Notable constraints:
- Text is encoded in byte mode (UTF-8), which accepts any payload; pure digit payloads are automatically packed in numeric mode. There is no alphanumeric/kanji mode optimization yet, so uppercase-only payloads are not encoded at maximum density.
- Image rendering depends on
java.awt, which is unavailable on some runtimes (e.g. Android).
Licensed under the Apache License 2.0.
