Build a Grails application with Leaflet for map integration
Putting an interactive map inside a Grails web project might sound like a luxury, but for many Australian development teams it is fast becoming table stakes. Logistics operators tracking parcels between Sydney and Perth, real estate portals listing rentals from Cairns to Hobart, tourism startups charting walking trails around the Grampians, and council services mapping bike paths along the Yarra all need geographic context. Groovy's concise syntax and Grails' opinionated scaffolding make it a sensible pick for these projects, and pairing the framework with a lightweight JavaScript mapping library keeps the front-end responsive even on regional NBN connections where upload bandwidth can be tight.
This walk-through focuses on wiring up Leaflet through a Grails plugin so the tiles, markers, and popups load as part of the standard asset pipeline. By the end you will have a working view centred on the Australian continent, a domain-backed source of points of interest, and a packaged artefact ready for the cloud. The code samples assume Grails 5 running on JDK 11 or newer, with the Groovy language handled in its usual dynamic way.
Preparing the Grails project skeleton
The starting point is a fresh application created with the standard Grails profile. Open a terminal on your workstation and run grails create-app aussie-mapper followed by cd aussie-mapper to drop into the freshly generated folder. Inside, the familiar grails-app directory will hold your controllers, views, services, and domain classes, while the build.gradle file controls plugin dependencies. Australian teams often work across multiple time zones in a single company, so set the default time zone inside application.yml to Australia/Sydney for east coast deployments or Australia/Perth if most of your users are west of the Nullarbor. This stops date-bound map data, such as event markers or time-windowed delivery zones, from drifting an hour when a Brisbane colleague edits a record while a Melbourne teammate is still at lunch.
Once the skeleton is in place, run grails run-app to confirm the welcome page renders. If the development server boots without complaint and the default layout shows up in your browser, the project is healthy and ready for plugin work. Many Australian developers pair this step with a quick git init, a tuned .gitignore for Grails, and an initial commit before going further, which is a habit worth keeping for any serious project. From here, the application.yml file becomes the home for map-related defaults so they can be tweaked without rebuilding the front-end.
Installing the Leaflet plugin and wiring assets
Leaflet itself is a self-contained JavaScript library, so a dedicated Grails plugin typically exists to bundle its CSS and JS into the asset pipeline rather than reaching for a CDN at runtime. The community plugin grails-leaflet exposes a tag library and configuration bean so GSP templates can render map containers with a single tag. Add the plugin to your build.gradle dependencies block, refresh dependencies, and the tag library will register itself automatically. Australian production workloads usually prefer self-hosted assets over third-party CDNs to avoid cross-border latency and to keep the project within the ACSC Essential Eight guidance on supplier dependency.
After the plugin is installed, drop a small configuration block into application.yml so the default tile provider, initial centre, and zoom level can be edited without touching GSP. Centring the map on roughly latitude -25.2744, longitude 133.7751 (the geographic centre of the continent) and zooming to level 4 gives a sensible first view of Australia from a desktop browser. Mobile users on the train between Central and Wynyard in Melbourne will see a slightly cropped but still useful frame thanks to Leaflet's responsive container handling. If you later switch tile providers, only the YAML key needs to change, which makes promoting the build between staging and production a clean operation.
Rendering your first map view
With the plugin loaded, the next step is creating a GSP page that renders the map div and includes the required JavaScript. Inside grails-app/views/map/index.gsp, drop a <g:leafletMap> tag pointing at the configured centre. The plugin typically injects a div with a stable id, plus the bundled Leaflet script, so you do not have to manage <script> tags manually. Save the view, restart the dev server, and navigate to /map/index to confirm a tile-rendered map of Australia appears in the browser window.
Once the base tiles load, the fun begins. You can overlay a marker for each state capital with a popup listing the local time, current weather, or any other piece of contextual data your application cares about. Capital city coordinates are easy to remember: Canberra sits near -35.2809, 149.1300, while Darwin is at -12.4634, 130.8456. Spreading markers across such a wide geographic spread is a nice way to demonstrate that Leaflet handles the entire continent gracefully. If your crew is in Adelaide and a colleague in Perth opens the same URL, both will see the same map state, which is the point of moving the configuration into YAML rather than baking it into the view layer.
Adding markers, popups, and layer groups
Plain maps are not very useful for business workflows, so the next layer is dynamic markers backed by real records. The Leaflet API exposes L.marker, L.popup, and L.layerGroup, all of which work happily inside a <g:javascript> block placed at the bottom of your GSP. A typical pattern is to iterate over a list supplied by the controller, build a marker for each entry, and attach a popup that surfaces human-readable details. Because Leaflet renders markers as DOM nodes rather than canvas tiles, the map stays accessible to screen readers, which matters for any Australian government or health-sector deployment that has to meet WCAG obligations.
Layer groups become handy once you have more than a handful of categories. A common use case is a tourism portal that distinguishes national parks, lookouts, and campgrounds; bundling each set into its own L.layerGroup and toggling them through L.control.layers gives end users a clean switchboard. If your application is supporting a road-trip planner that runs from Cairns down the coast to Melbourne, you can attach each day's stops to its own group and let the user filter by day. This is the kind of small UX win that distinguishes a polished product from a bare-bones demo, and it costs only a handful of extra lines of JavaScript.
Serving points of interest from a controller and service
Markers rendered from static JSON lose their appeal quickly, so the production-ready pattern is to back them with a Grails domain class and serve them through a JSON endpoint. Define a PointOfInterest domain class with fields such as name, latitude, longitude, category, and description, then run grails generate-all to scaffold the CRUD artefacts. Add a controller action, say /map/poi.json, that returns a list of records serialised through the standard Grails JSON converter. The front-end then fetches this payload with a small fetch call and hands each record to L.marker, which keeps the view data-driven rather than hand-coded.
Putting a thin service between the controller and the domain class is worthwhile once business rules appear. Imagine filtering points of interest within a 50-kilometre radius of the user's click on the Leaflet canvas; the geometry math belongs in a service so it can be unit-tested with Spock and reused across multiple actions. Australian developers who maintain open-data portals often lean on this pattern to expose datasets published by data.gov.au without baking assumptions into the view layer. The same service can later power a mobile client or a partner integration without rewriting the controller, which keeps the roadmap honest.
Packaging, security, and cloud deployment
When the map view is solid in development, the next step is producing a deployable artefact. Run grails war to build a standard WAR file, or grails assemble for an executable JAR if you prefer a self-contained service. Australian teams often deploy into the AWS Sydney region (ap-southeast-2) to keep latency low for east-coast users, with a secondary standby in Melbourne for resilience. If the application is serving tile overlays that originate from a corporate GIS server, remember that NBN upload speeds outside the capitals can be modest, so caching tiles at the edge or pre-bundling popular zoom levels into the WAR is a practical performance win.
Security deserves a short pause before going live. The Leaflet plugin ships only client-side assets, but the JSON endpoint that exposes points of interest must be locked down like any other Grails controller, ideally with Spring Security rules and rate limiting. Aligning the deployment with the ACSC Essential Eight, particularly around application control and multi-factor authentication for administrators, will keep the project comfortable for any Australian government or critical-infrastructure client. Once those bases are covered, the build can be promoted through a CI pipeline that compiles, tests, packages, and finally uploads the artefact to your chosen platform.
If you would like to see the full source for a working Grails map application, including the domain class, controller, GSP view, and Gradle dependency snippet, the code examples section on Grails Example collects these snippets alongside other plugin walk-throughs. Drop the files into a fresh project, run grails run-app, and you will have a tile-rendered map of Australia with live markers within a few minutes, ready to be adapted for your own industry vertical.