Building a Grails application with a QR code generation plugin

Grails makes it surprisingly quick to assemble a web application that can mint QR codes on the fly, and the approach suits a lot of Australian businesses that still rely on scannable tickets, loyalty cards and asset tags. Whether you run a brewery in Melbourne, a festival promoter in Sydney, or a maintenance crew on a Pilbara mine site, the framework's plugin system removes the boilerplate so you can focus on the actual workflow. The result is a tidy Groovy application that talks to standard JVM libraries, persists data through GORM, and renders an image the moment a customer scans their phone.

This walkthrough takes you through creating a fresh Grails app, wiring in a community plugin for QR code generation, and exposing it through a small REST endpoint and a GSP view. Along the way, you'll see how to control error correction levels, output formats, and caching, while keeping an eye on the Australian Privacy Principles that apply when the codes link back to personal records.

Setting up the project and choosing a plugin

Start by installing Grails 6 on your development machine and confirming that the JVM, SDKMAN and your shell are aligned. From a terminal in Sydney or Brisbane, you can scaffold a new application with grails create-app qrhub, which produces a Gradle-based project that you can open in IntelliJ IDEA or VS Code. The default profile uses Spring Boot under the hood, so anything you know about Spring beans translates cleanly into the Grails context.

Once the skeleton is in place, browse the plugin portal for an actively maintained QR code generator. The most common pick is the grails-qrcode plugin, which wraps the ZXing library and exposes a qrcodeService bean. Add it to your build.gradle dependencies, refresh the project, and verify that the artefact resolves from Maven Central. If your team operates behind a corporate proxy in Perth or Adelaide, configure the Gradle settings file accordingly so the first build does not stall.

A quick sanity check is to run grails run-app and hit the default home page. From there, inject the service into a controller with a constructor or field annotation and call it from a temporary action. Seeing a PNG image render in the browser confirms the wiring before you build out the rest of the domain model.

Designing the domain model and persistence layer

With the plugin confirmed, decide what each code actually represents. A common pattern is a QrAsset domain class that holds a UUID, a payload string, a label, a creation timestamp and an optional expiry date. GORM handles the schema migration automatically when you start the app in development, which is helpful when iterating on ideas during a hackathon in a Surry Hills co-working space.

If your deployment target already runs MongoDB, the same domain class adapts without much fuss. You can read about using Grails with MongoDB to see how the mappings translate and what to watch out for with document storage. Australian teams occasionally prefer MongoDB for QR metadata because ticket payloads vary in shape, since some include seat numbers, others include dietary notes, and rigid tables become a chore to evolve.

For more transactional workloads, stick with the default H2 database during local development and switch the datasource to PostgreSQL for production. Grails reads application.yml for environment-specific settings, so you can keep your A$-priced managed database credentials out of source control. Remember to set up a DataSource configuration that matches the region of your cloud provider, whether that is Sydney's ap-southeast-2 zone or Melbourne's ap-southeast-4.

Generating codes from controllers and services

The real work happens in the service layer. Inject QrCodeService into your own service and write a method that accepts a payload, an error correction level and a target size in pixels. ZXing supports four correction levels, namely L, M, Q and H, and the choice affects how much damage a printed code can take before it stops scanning. A beer coaster in a Brisbane pub benefits from level Q, while a laminated festival wristband might survive level H.

Inside the method, call qrCodeService.generate(payload, size, correction) and return the byte array. You can then push the bytes through a controller action that sets the correct Content-Type header, such as image/png or image/svg+xml. SVG is worth considering when you want crisp codes on retina screens, because the file stays sharp at any zoom level. PNG works better for emails and PDFs that customers might forward around the country.

For GSP rendering, expose the action through a route like /code/$id, and reference it from a template using the createLink tag. The browser will display the image inline, and you can wrap it in a styled card that matches your brand. A loyalty programme for a Melbourne roastery, for instance, might pair the code with the customer's name and a "scan at the counter" prompt.

Output formats, downloads and caching

Once generation works, think about how users will actually obtain the codes. A download endpoint that returns the byte array with a Content-Disposition: attachment header lets staff print batches of tickets from a back-office screen. If you need vector output for a signage company, configure the plugin to emit SVG and let the printer scale it to whatever panel they are working with.

Caching matters once the workload grows. Because the same UUID always produces the same QR matrix, you can store the generated PNG in a directory served by the static resources plugin, or push it to an S3 bucket in the ap-southeast-2 region. Set a sensible Cache-Control header so browsers do not request the same code repeatedly during a busy Saturday night service at a Sydney bar.

Logging is another quiet win. A small audit table that records who generated which code, and when, helps when a customer disputes an entry at a Brisbane stadium gate. Grails' built-in logging adapts to the Australian Eastern Standard Time zone without much fuss, but you should explicitly set the JVM timezone in your container so timestamps remain consistent across microservices.

Security, privacy and operational concerns

QR codes are convenient, but they also invite mischief. Always sanitise the payload before encoding it, and reject any input that looks like a javascript: URL or a raw HTML payload. Treat the generation endpoint like any other user-facing API and apply rate limiting, especially if the app is publicly reachable from the open internet. A small Grails filter can throttle requests per IP without much overhead.

In Australia, the Privacy Act 1988 and the Australian Privacy Principles govern how you handle personal information. If a QR code resolves to a profile page that shows a customer's name, contact details or purchase history, you need a clear collection notice and a way to honour access and correction requests. For health-related codes such as vaccination certificates or patient intake forms, the My Health Records Act regime adds another layer, so consult your legal counsel before going live.

Finally, monitor the application in production with the same care you would apply to any Spring Boot service. Heap dumps, thread analysis and Grails' built-in metrics endpoint give you a good baseline. Rotate the signing keys used for any signed payloads, and keep the ZXing library current so you inherit the latest encoder improvements. With those habits in place, the plugin becomes a reliable building block rather than a fragile dependency.

Practical guidelines for production rollout

Ready to take the application further? You can extend the same scaffold into a streaming experience by following the video streaming tutorial, which pairs nicely with QR-based ticket validation. Subscribe to the Grails Example newsletter for monthly walkthroughs, code repositories and Australian-focused deployment tips delivered straight to your inbox.