Loading...

August 31, 2016

Check Which Candidates Can Be Updated With SDKMAN!

The Software Development Kit Manager (SDKMAN!) is an awesome and very useful tool. We can use it to install and manage candidates like Groovy, Grails, Griffon and Gradle. If we want to know if a new version of an installed candidate is available we use the outdated command. SKDMAN! returns a list of candidates with newer versions and also displays the version we have and is available. If we specify the candidate we can see if for that specific candidate a newer version is available.

For example we can get the following results:

$ sdk outdated
Outdated:
ant (1.9.6 < 1.9.7)
kotlin (1.0.0, 1.0.1, 1.0.2 < 1.0.3)
$ # Or we use the shortcut o
$ sdk o
Outdated:
ant (1.9.6 < 1.9.7)
kotlin (1.0.0, 1.0.1, 1.0.2 < 1.0.3)
$ # Check a specific candidate
$ sdk outdated ant
Outdated:
ant (1.9.6 < 1.9.7)
$

Written with SDKMAN! 5.0.0+51

July 6, 2016

Ratpacked: Create a Partial Response

Suppose we want to support partial JSON responses in our Ratpack application. The user must send a request parameter with a list of fields that need to be part of the response. In our code we must use the value of the request parameter and output only the given properties of an object. We implement this logic using a custom renderer in Ratpack. Inside the renderer we can get access to the request parameters of the original request.

In our example Ratpack application we have a Course class, which is a simple class withs some properties:

// File: src/main/groovy/mrhaki/ratpack/Course.groovy
package mrhaki.ratpack

import groovy.transform.Immutable

@Immutable
class Course {
    String name
    String teacher
    Integer maxOccupation
}

Next we create a custom renderer for our Course class. We extend the RendererSupport class and override the render method:

// File: src/main/groovy/mrhaki/ratpack/CourseRenderer.groovy
package mrhaki.ratpack

import ratpack.handling.Context
import ratpack.render.RendererSupport

import static ratpack.jackson.Jackson.json

class CourseRenderer extends RendererSupport<Course> {
    
    @Override
    void render(final Context context, final Course course) throws Exception {
        // Get request parameter fields with a comma separated list
        // of field names to include in the output.
        final String paramFields = context.request.queryParams.get('fields')
        
        if (paramFields) {
            // Transform comma separated property names to a Set.
            final Set<String> coursePropertyNames = 
                    paramFields.tokenize(',').toSet()
            
            // Create Map with only Course properties that need to
            // be included.
            final Map partialCourse = 
                    filterProperties(course, coursePropertyNames)
            
            // Render Map.
            context.render(json(partialCourse))
        } else {
            // No fields request parameter so we can return
            // the original Course object.
            context.render(json(course))
        }
    }

    /**
     * Find all properties in the object that are in the collection
     * of property names.
     * 
     * @param object Object with properties to filter
     * @param propertyNames Names of properties to find
     * @return Map with properties
     */
    private Map filterProperties(
            final Object object, 
            final Set<String> propertyNames) {

        object.properties.findAll { property -> 
            property.key in propertyNames 
        }
    }
}

Finally we need to add the CourseRenderer to the Ratpack registry. Ratpack will find the renderer when we want to render a Course object. This happens automatically, we don't have to do anything ourselves. The following Ratpack application configuration adds our CourseRenderer with the bind method. We also add a endpoint to show the contents of a sample Course object.

// File: src/ratpack/ratpack.groovy
import mrhaki.ratpack.Course
import mrhaki.ratpack.CourseRenderer
import ratpack.registry.Registry

import static ratpack.groovy.Groovy.ratpack

ratpack {
    bindings {
        // Add to registry, so Ratpack can use
        // it to render a Course object.
        bind CourseRenderer
    }
    handlers {
        all {
            final Course course = 
                    new Course(
                            name: 'Ratpack rules 101',
                            teacher: 'mrhaki',
                            maxOccupation: 450)
            next(Registry.single(course))
        }
        get('course') { Course course ->
            render(course)
        }
    }
}

Let's try several requests using the fields request parameter and without the fields request parameter using HTTPie as client:

$ http -b http://localhost:5050/course
{
    "maxOccupation": 450,
    "name": "Ratpack rules 101",
    "teacher": "mrhaki"
}

$ http -b http://localhost:5050/course fields==name,teacher
{
    "name": "Ratpack rules 101",
    "teacher": "mrhaki"
}

Written with Ratpack 1.3.3.

July 2, 2016

Spring Sweets: Using Groovy Configuration As PropertySource

We have many ways to provide configuration properties to a Spring (Boot) application. We can add our own custom configuration properties format. For example we can use Groovy's ConfigObject object to set configuration properties. We need to read a configuration file using ConfigSlurper and make it available as a property source for Spring. We need to implement two classes and add configuration file to support a Groovy configuration file in a Spring application.

First we need to write a class that extends the PropertySource in the package org.springframework.core.env. This class has methods to get property values based on a given key. There are already some subclasses for specific property sources. There is for example also a MapPropertySource class. We will extend that class for our implementation, because we can pass our flattened ConfigObject and rely on all existing functionality of the MapPropertySource class:

// File: src/main/java/mrhaki/spring/configobject/ConfigObjectPropertySource.java
package mrhaki.spring.configobject;

import groovy.util.ConfigObject;
import org.springframework.core.env.MapPropertySource;

/**
 * Property source that supports {@link ConfigObject}. The {@link ConfigObject}
 * is flattened and all functionality is delegated to the
 * {@link MapPropertySource}.
 */
public class ConfigObjectPropertySource extends MapPropertySource {
    
    public ConfigObjectPropertySource(
            final String name, 
            final ConfigObject config) {
        
        super(name, config.flatten());
    }
    
}

Next we must implement a class that loads the configuration file. The configuration file must be parsed and we must our ConfigObjectPropertySource so we can use it in our Spring context. We need to implement the PropertySourceLoader interface, where we can define the file extensions the loader must be applied for. Also we override the load method to load the actual file. In our example we also add some extra binding variables we can use when we define our Groovy configuration file.

// File: src/main/java/mrhaki/spring/configobject/ConfigObjectPropertySourceLoader.java
package mrhaki.spring.configobject;

import groovy.util.ConfigObject;
import groovy.util.ConfigSlurper;
import org.springframework.boot.env.PropertySourceLoader;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource;
import org.springframework.util.ClassUtils;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class ConfigObjectPropertySourceLoader implements PropertySourceLoader {

    /**
     * Allowed Groovy file extensions for configuration files.
     *
     * @return List of extensions for Groovy configuration files.
     */
    @Override
    public String[] getFileExtensions() {
        return new String[]{"groovy", "gvy", "gy", "gsh"};
    }

    /**
     * Load Groovy configuration file with {@link ConfigSlurper}.
     */
    @Override
    public PropertySource<?> load(
            final String name,
            final Resource resource,
            final String profile) throws IOException {

        if (isConfigSlurperAvailable()) {
            final ConfigSlurper configSlurper = profile != null ?
                    new ConfigSlurper(profile) :
                    new ConfigSlurper();

            // Add some extra information that is accessible
            // in the Groovy configuration file.
            configSlurper.setBinding(createBinding(profile));
            final ConfigObject config = configSlurper.parse(resource.getURL());

            // Return ConfigObjectPropertySource if configuration
            // has key/value pairs.
            return config.isEmpty() ?
                    null :
                    new ConfigObjectPropertySource(name, config);
        }

        return null;
    }

    private boolean isConfigSlurperAvailable() {
        return ClassUtils.isPresent("groovy.util.ConfigSlurper", null);
    }

    private Map<String, Object> createBinding(final String profile) {
        final Map<String, Object> bindings = new HashMap<>();
        bindings.put("userHome", System.getProperty("user.home"));
        bindings.put("appDir", System.getProperty("user.dir"));
        bindings.put("springProfile", profile);
        return bindings;
    }

}

Finally we need to add our ConfigObjectPropertySourceLoader to a Spring configuration file, so it used in our Spring application. We need to create the file spring.factories in the META-INF directory with the following contents:

# File: src/main/resources/META-INF/spring.factories
org.springframework.boot.env.PropertySourceLoader=\
  mrhaki.spring.configobject.ConfigObjectPropertySourceLoader

Now when we create a Spring application with all our classes and configuration file in the classpath we can provide an application.groovy file in the root of the classpath:

// File: src/main/resources/application.groovy
app {
    // Using binding property appDir,
    // which is added to the ConfigSlurper 
    // in ConfigObjectPropertySourceLoader.
    startDir = appDir

    // Configuration hierarchy.
    message {
        text = "Text from Groovy configuration!"
    }
}

environments {
    // When starting application with 
    // -Dspring.profiles.active=blog this
    // environment configuration is used.
    blog {
        app {
            message {
                text = "Groovy Goodness"
            }
        }
    }
}

Written with Spring Boot 1.3.5.RELEASE

June 27, 2016

Grails Goodness: Pass JSON Configuration Via Command Line

We can use the environment variable SPRING_APPLICATION_JSON with a JSON value as configuration source for our Grails 3 application. The JSON value is parsed and merged with the configuration. Instead of the environment variable we can also use the Java system property spring.application.json.

Let's create a simple controller that reads the configuration property app.message:

// File: grails-app/controllers/mrhaki/grails/config/SampleController.groovy
package mrhaki.grails.config

import org.springframework.beans.factory.annotation.Value

class MessageController {
    
    @Value('${app.message}')
    String message

    def index() { 
        render message
    }
}

Next we start Grails and set the environment variable SPRING_APPLICATION_JSON with a value for app.message:

$ SPRING_APPLICATION_JSON='{"app":{"message":"Grails 3 is Spring Boot on steroids"}}' grails run-app
| Running application...
Grails application running at http://localhost:8080 in environment: development

When we request the sample endpoint we see the value of app.message:

$ http -b :8080/message
Grails 3 is Spring Boot on steroids
$

If we want to use the Java system property spring.application.json with the Grails command we must first configure the bootRun task so all system properties are passed along:

// File: build.gradle
...
bootRun {
    systemProperties System.properties
}
...

With the following command we pass the configuration as inline JSON:

$ grails -Dspring.application.json='{"app":{"message":"Grails 3 is Spring Boot on steroids"}}' run-app
| Running application...
Grails application running at http://localhost:8080 in environment: development

Written with Grails 3.1.8.

Ratpacked: Using Groovy Configuration Scripts As Configuration Source

Ratpack has a lot of options to add configuration data to our application. We can use for example YAML and JSON files, properties, environment variables and Java system properties. Groovy has the ConfigSlurper class to parse Groovy script with configuration data. It even supports an environments block to set configuration value for a specific environment. If we want to support Groovy scripts as configuration definition we write a class that implements the ratpack.config.ConfigSource interface.

We create a new class ConfigSlurperConfigSource and implement the ConfigSource interface. We must implement the loadConfigData method in which we read the Groovy configuration and transform it to a ObjectNode so Ratpack can use it:

// File: src/main/groovy/mrhaki/ratpack/config/ConfigSlurperConfigSource.groovy
package mrhaki.ratpack.config

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.node.ArrayNode
import com.fasterxml.jackson.databind.node.ObjectNode
import groovy.transform.CompileDynamic
import groovy.transform.CompileStatic
import ratpack.config.ConfigSource
import ratpack.file.FileSystemBinding

import java.nio.file.Path

@CompileStatic
class ConfigSlurperConfigSource implements ConfigSource {
    
    private final String configScript
    
    private final URL scriptUrl
    
    private final String environment
    
    ConfigSlurperConfigSource(final String configScript) {
        this(configScript, '')
    }

    ConfigSlurperConfigSource(final String configScript, final String environment) {
        this.configScript = configScript
        this.environment = environment
    }

    ConfigSlurperConfigSource(final Path configPath) {
        this(configPath, '')
    }

    ConfigSlurperConfigSource(final Path configPath, final String environment) {
        this(configPath.toUri(), environment)
    }

    ConfigSlurperConfigSource(final URI configUri) {
        this(configUri, '')
    }

    ConfigSlurperConfigSource(final URI configUri, final String environment) {
        this(configUri.toURL(), environment)
    }

    ConfigSlurperConfigSource(final URL configUrl) {
        this(configUrl, '')
    }

    ConfigSlurperConfigSource(final URL configUrl, final String environment) {
        this.scriptUrl = configUrl
        this.environment = environment
    }

    @Override
    ObjectNode loadConfigData(
            final ObjectMapper objectMapper, 
            final FileSystemBinding fileSystemBinding) throws Exception {

        // Create ConfigSlurper for given environment.
        final ConfigSlurper configSlurper = new ConfigSlurper(environment)

        // Read configuration.
        final ConfigObject configObject = 
                configScript ? 
                        configSlurper.parse(configScript) : 
                        configSlurper.parse(scriptUrl)
        
        // Transform configuration to node tree
        final ObjectNode rootNode = objectMapper.createObjectNode()
        populate(rootNode, configObject)
        return rootNode
    }

    @CompileDynamic
    private populate(final ObjectNode node, final ConfigObject config) {
        // Loop through configuration.
        // ConfigObject also implements Map interface,
        // so we can loop through key/value pairs.
        config.each { key, value ->
            // Value is another configuration,
            // this means the nested configuration
            // block.
            if (value instanceof Map) {
                populate(node.putObject(key), value)
            } else {
                // If value is a List we convert it to
                // an array node.
                if (value instanceof List) {
                    final ArrayNode listNode = node.putArray(key)
                    value.each { listValue ->
                        listNode.add(listValue)
                    }
                } else {
                    // Put key/value pair in node.
                    node.put(key, value)
                }
            }
        }
    }
}

We have several options to pass the Groovy configuration to the ConfigSlurperConfigSource class. We can use a String, URI, URL or Path reference. Let's create a file with some configuration data.

// File: src/ratpack/application.groovy
app {
    serverPort = 9000
}

environments {
    development {
        app {
            serverName = 'local'
        }
    }
    production {
        app {
            serverName = 'cloud'
            serverPort = 80
        }
    }
}

Next we create a Ratpack application using the Groovy DSL. In the serverConfig section we use our new ConfigSlurperConfigSource:

// File: src/ratpack/ratpack.groovy
import com.google.common.io.Resources
import com.mrhaki.config.ConfigSlurperConfigSource

import static groovy.json.JsonOutput.prettyPrint
import static groovy.json.JsonOutput.toJson
import static ratpack.groovy.Groovy.ratpack

//final Logger log = LoggerFactory.getLogger('ratpack')

ratpack {

    serverConfig {
        // Use Groovy configuration.
        add new ConfigSlurperConfigSource('''\
            app {
                message = 'Ratpack swings!'
            }''')

        // Use external Groovy configuration script file.
        add new ConfigSlurperConfigSource(
                Resources.getResource('application.groovy'), 'development')

        require '/app', SimpleConfig
    }

    handlers {
        get('configprops') { SimpleConfig config ->
            render(prettyPrint(toJson(config)))
        }
    }

}

// Simple configuration.
class SimpleConfig {
    String message
    String serverName
    Integer serverPort
}

Let's check the output of the configprops endpoint:

$ http -b localhost:5050/configprops
{
    "message": "Ratpack swings!",
    "serverName": "local",
    "serverPort": 9000
}

Now we set the environment to production in our Ratpack application:

// File: src/ratpack/ratpack.groovy
...

ratpack {

    serverConfig {
        ...

        // Use external Groovy configuration script file.
        add new ConfigSlurperConfigSource(
                Resources.getResource('application.groovy'), 'production')

       ...
    }

    ...
}

If we check configprops again we see different configuration values:

$ http -b localhost:5050/configprops
{
    "message": "Ratpack swings!",
    "serverName": "cloud",
    "serverPort": 80
}

Written with Ratpack 1.3.3.

June 24, 2016

Ratpacked: Handling Exceptions When Reading Configuration Sources

To define configuration sources for our Ratpack application we have several options. We can set default properties, look at environment variables or Java system properties, load JSON or YAML formatted configuration files or implement our own configuration source. When something goes wrong using one of these methods we want to be able to handle that situation. For example if an optional configuration file is not found, we want to inform the user, but the application must still start. The default exception handling will throw the exception and the application is stopped. We want to customise this so we have more flexibility on how to handle exceptions.

We provide the configuration source in the serverConfig configuration block of our Ratpack application. We must add the onError method and provide an error handler implementation before we load any configuration source. This error handler will be passed to each configuration source and is execute when an exception occurs when the configuration source is invoked. The error handler implements the Action interface with the type Throwable. In our implementation we can for example check for the type of Throwable and show a correct status message to the user.

In the following example application we rely on external configuration source files that are optional. If the file is present it must be loaded, otherwise a message must be shown to indicate the file is missing, but the application still starts:

// File: src/ratpack/ratpack.groovy
import org.slf4j.Logger
import org.slf4j.LoggerFactory

import java.nio.file.NoSuchFileException
import java.nio.file.Paths

import static ratpack.groovy.Groovy.ratpack

final Logger log = LoggerFactory.getLogger('ratpack.server')

ratpack {
    serverConfig {
        // Use custom error handler, when
        // exceptions happen during loading
        // of configuration sources.
        onError { throwable ->
            if (throwable in NoSuchFileException) {
                final String file = throwable.file
                log.info "Cannot load optional configuration file '{}'", file
            } else {
                throw throwable
            }
        }
        
        yaml('application.yml')

        // Optional configuration files
        // to override values in 
        // 'application.yml'. This could
        // potentially give an exception if
        // the files don't exist.
        yaml(Paths.get('conf/application.yml'))
        json(Paths.get('conf/application.json'))
        
        args(args)
        sysProps()
        env()
        
        ...
    }

    ...
}

Next we start the application with the absence of the optional configuration files conf/application.yml and conf/application.json:

$ gradle run
...
:run

12:28:38.887 [main]            INFO  ratpack.server.RatpackServer - Starting server...
12:28:39.871 [main]            INFO  ratpack.server - Cannot load optional configuration file 'conf/application.yml'
12:28:39.873 [main]            INFO  ratpack.server - Cannot load optional configuration file 'conf/application.json'
12:28:40.006 [main]            INFO  ratpack.server.RatpackServer - Building registry...
12:28:40.494 [main]            INFO  ratpack.server.RatpackServer - Ratpack started (development) for http://localhost:5050

Notice that application is started and in the logging we have nice status messages that tell us the files are not found.

Written with Ratpack 1.3.3.

June 23, 2016

Groovy Goodness: Customise Log AST Annotations

Adding logging support to a class in Groovy is easy. We can choose to add SLF4J, Log4j, Log4j2, Apache Commons or Java Util Logging to our class. The default implementation of the Abstract Syntax Tree (AST) transformation is to add a log field of the correct type. As category name the complete class name (including the package) is used. We can change the name of the field with the value attribute. To alter the category name we use the attribute category.

In the following example snippet we change the log field name to LOGGER and set a custom category name:

@Grapes(
    @Grab(group='ch.qos.logback', module='logback-classic', version='1.1.7')
)
import groovy.util.logging.Slf4j

// Change name of the field to LOGGER and
// the category to 'mrhaki.blog.samples'.
@Slf4j(value = 'LOGGER', category = 'mrhaki.blog.samples')
class SampleLogging {

    String sample(final String message) {
        LOGGER.info 'Running sample({}) method', message
        
        "Groovy is $message!"
    }
    
}

def s = new SampleLogging()
println "System.out says: ${s.sample('gr8')}"

When we execute the script we get the following output:

16:33:56.972 [Thread-7] INFO mrhaki.blog.samples - Running sample(gr8) method
System.out says: Groovy is gr8!

Notice the category is mrhaki.blog.samples and we use the field LOGGER in our code.

Written with Groovy 2.4.7.

June 22, 2016

Groovy Goodness: Turn A Map Or List As String To Map Or List

In a previous post we learned how to use the toListString or toMapString methods. With these methods we create a String representation of a List or Map object. With a bit of Groovy code we can take such a String object and turn it into a List or Map again.

In the following code snippet we turn the String value [abc, 123, Groovy rocks!] to a List with three items:

// Original List with three items.
def original = ['abc', 123, 'Groovy rocks!']

// Create a String representation:
// [abc, 123, Groovy rocks!]
def listAsString = original.toListString()

// Take the String value between
// the [ and ] brackets, then
// split on , to create a List
// with values.
def list = listAsString[1..-2].split(', ')

assert list.size() == 3
assert list[0] == 'abc'
assert list[1] == '123' // String value
assert list[2] == 'Groovy rocks!'

We can do something similar for a String value representing a map structure:

// Original Map structure.
def original = [name: 'mrhaki', age: 42]

// Turn map into String representation:
// [name:mrhaki, age:42]
def mapAsString = original.toMapString()

def map = 
    // Take the String value between
    // the [ and ] brackets.
    mapAsString[1..-2]
        // Split on , to get a List.
        .split(', ')
        // Each list item is transformed
        // to a Map entry with key/value.
        .collectEntries { entry -> 
            def pair = entry.split(':')
            [(pair.first()): pair.last()]
        }
        

assert map.size() == 2
assert map.name == 'mrhaki'
assert map.age == '42'

Written with Groovy 2.4.7.

June 21, 2016

Groovy Goodness: Represent Map As String

Groovy adds to Map objects the toMapString method. With this method we can have a String representation of our Map. We can specify an argument for the maximum width of the generated String. Groovy will make sure at least the key/value pairs are added as a pair, before adding three dots (...) if the maximum size is exceeded.

def course = [
    name: 'Groovy 101',
    teacher: 'mrhaki',
    location: 'The Netherlands']
    
assert course.toMapString(15) == '[name:Groovy 101, ...]'
assert course.toMapString(25) == '[name:Groovy 101, teacher:mrhaki, ...]'

As mentioned in a previous post we can use the toListString method to represent a List as a String:

def names = ['mrhaki', 'hubert']

assert names.toListString(5) == '[mrhaki, ...]'

Written with Groovy 2.4.7.

June 20, 2016

Grails Goodness: Add Banner To Grails 3.1 Application

In a previous post we learned how to add a banner to a Grails 3.0 application. We used the Spring Boot support in Grails to show a banner on startup. The solution we used doesn't work for a Grails 3.1 application. We need to implement a different solution to show a banner on startup.

First of all we create a new class that implements the org.springframework.boot.Banner interface. We implement the single method printBanner and logic to display a banner, including colors:

// File: src/main/groovy/mrhaki/grails/GrailsBanner.groovy
package mrhaki.grails

import org.springframework.boot.Banner
import grails.util.Environment
import org.springframework.boot.ansi.AnsiColor
import org.springframework.boot.ansi.AnsiOutput
import org.springframework.boot.ansi.AnsiStyle

import static grails.util.Metadata.current as metaInfo

/**
 * Class that implements Spring Boot Banner
 * interface to show information on application startup.
 */
class GrailsBanner implements Banner {

    /**
     * ASCCI art Grails 3.1 logo built on
     * http://patorjk.com/software/taag/#p=display&f=Graffiti&t=Type%20Something%20
     */
    private static final String BANNER = $/
  _________________    _____  .___.____       _________ ________      ____ 
 /  _____|______   \  /  _  \ |   |    |     /   _____/ \_____  \    /_   |
/   \  ___|       _/ /  /_\  \|   |    |     \_____  \    _(__  <     |   |
\    \_\  \    |   \/    |    \   |    |___  /        \  /       \    |   |
 \______  /____|_  /\____|__  /___|_______ \/_______  / /______  / /\ |___|
        \/       \/         \/            \/        \/         \/  \/      
    /$

    @Override
    void printBanner(
            org.springframework.core.env.Environment environment,
            Class<?> sourceClass,
            PrintStream out) {

        // Print ASCII art banner with color yellow.
        out.println AnsiOutput.toString(AnsiColor.BRIGHT_YELLOW, BANNER)

        // Display extran infomratio about the application.
        row 'App version', metaInfo.getApplicationVersion(), out
        row 'App name', metaInfo.getApplicationName(), out
        row 'Grails version', metaInfo.getGrailsVersion(), out
        row 'Groovy version', GroovySystem.version, out
        row 'JVM version', System.getProperty('java.version'), out
        row 'Reloading active', Environment.reloadingAgentEnabled, out
        row 'Environment', Environment.current.name, out
        
        out.println()
    }

    private void row(final String description, final value, final PrintStream out) {
        out.print AnsiOutput.toString(AnsiColor.DEFAULT, ':: ')
        out.print AnsiOutput.toString(AnsiColor.GREEN, description.padRight(16))
        out.print AnsiOutput.toString(AnsiColor.DEFAULT, ' :: ')
        out.println AnsiOutput.toString(AnsiColor.BRIGHT_CYAN, AnsiStyle.FAINT, value)
    }

}

Next we must override the GrailsApp class. We override the printBanner method, which has no implementation in the GrailsApp class. In our printBanner method we use GrailsBanner:

// File: src/main/groovy/mrhaki/grails/BannerGrailsApp.groovy
package mrhaki.grails

import grails.boot.GrailsApp
import groovy.transform.InheritConstructors
import org.springframework.core.env.Environment

@InheritConstructors
class BannerGrailsApp extends GrailsApp {
    
    @Override
    protected void printBanner(final Environment environment) {
        // Create GrailsBanner instance.
        final GrailsBanner banner = new GrailsBanner()

        banner.printBanner(environment, Application, System.out)
    }
    
}

Finally in the Application class we use BannerGrailsApp instead of the default GrailsApp object:

// File: grails-app/init/mrhaki/grails/Application.groovy
package mrhaki.grails

import grails.boot.config.GrailsAutoConfiguration

class Application extends GrailsAutoConfiguration {
    static void main(String[] args) {
        final BannerGrailsApp app = new BannerGrailsApp(Application)
        app.run(args)
    }
}

When we start our Grails application on a console with color support we see the following banner:

Written with Grails 3.1.8.