Search

Dark theme | Light theme

November 16, 2023

Spring Sweets: Spring Boot 3 With Gradle In IntelliJ

Spring Boot 3 requires at least Java 17, but that also means the Java version used by Gradle must also be at least 17. Otherwise we will get the following error message when we build our Spring Boot project in IntelliJ using Gradle:

A problem occurred configuring root project 'springboot'.
> Could not resolve all files for configuration ':classpath'.
   > Could not resolve org.springframework.boot:spring-boot-gradle-plugin:3.1.5.
     Required by:
         project : > org.springframework.boot:org.springframework.boot.gradle.plugin:3.1.5
      > No matching variant of org.springframework.boot:spring-boot-gradle-plugin:3.1.5 was found. The consumer was configured to find a library for use during runtime, compatible with Java 11, packaged as a jar, and its dependencies declared externally, as well as attribute 'org.gradle.plugin.api-version' with value '8.4' but:
          - Variant 'apiElements' capability org.springframework.boot:spring-boot-gradle-plugin:3.1.5 declares a library, packaged as a jar, and its dependencies declared externally:
              - Incompatible because this component declares a component for use during compile-time, compatible with Java 17 and the consumer needed a component for use during runtime, compatible with Java 11
              - Other compatible attribute:
                  - Doesn't say anything about org.gradle.plugin.api-version (required '8.4')
          - Variant 'javadocElements' capability org.springframework.boot:spring-boot-gradle-plugin:3.1.5 declares a component for use during runtime, and its dependencies declared externally:
              - Incompatible because this component declares documentation and the consumer needed a library
              - Other compatible attributes:
                  - Doesn't say anything about its target Java version (required compatibility with Java 11)
                  - Doesn't say anything about its elements (required them packaged as a jar)
                  - Doesn't say anything about org.gradle.plugin.api-version (required '8.4')
          - Variant 'mavenOptionalApiElements' capability org.springframework.boot:spring-boot-gradle-plugin-maven-optional:3.1.5 declares a library, packaged as a jar, and its dependencies declared externally:
              - Incompatible because this component declares a component for use during compile-time, compatible with Java 17 and the consumer needed a component for use during runtime, compatible with Java 11
              - Other compatible attribute:
                  - Doesn't say anything about org.gradle.plugin.api-version (required '8.4')
          - Variant 'mavenOptionalRuntimeElements' capability org.springframework.boot:spring-boot-gradle-plugin-maven-optional:3.1.5 declares a library for use during runtime, packaged as a jar, and its dependencies declared externally:
              - Incompatible because this component declares a component, compatible with Java 17 and the consumer needed a component, compatible with Java 11
              - Other compatible attribute:
                  - Doesn't say anything about org.gradle.plugin.api-version (required '8.4')
          - Variant 'runtimeElements' capability org.springframework.boot:spring-boot-gradle-plugin:3.1.5 declares a library for use during runtime, packaged as a jar, and its dependencies declared externally:
              - Incompatible because this component declares a component, compatible with Java 17 and the consumer needed a component, compatible with Java 11
              - Other compatible attribute:
                  - Doesn't say anything about org.gradle.plugin.api-version (required '8.4')
          - Variant 'sourcesElements' capability org.springframework.boot:spring-boot-gradle-plugin:3.1.5 declares a component for use during runtime, and its dependencies declared externally:
              - Incompatible because this component declares documentation and the consumer needed a library
              - Other compatible attributes:
                  - Doesn't say anything about its target Java version (required compatibility with Java 11)
                  - Doesn't say anything about its elements (required them packaged as a jar)
                  - Doesn't say anything about org.gradle.plugin.api-version (required '8.4')

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
> Get more help at https://help.gradle.org.

The issue is that the Spring Boot Gradle plugin 3.1.5 requires Java 17, but our project is using Java 11. We can fix this by explicitly setting the Java version that Gradle uses in IntelliJ. Go to Settings > Build, Execution, Deployment > Build Tools > Gradle and change the JVM used for Gradle to a JDK version of at least version 17.

Written with Spring Boot 3.1.5 and IntelliJ 2023.2.4.

November 5, 2023

IntelliJ HTTP Client: Re-using Javascript In Pre-Request And Response Handlers

When we use the IntelliJ HTTP Client we can write Javascript for the pre-request and response handlers. The Javascript code must be in between {% …​ %} delimeters. If we want to re-use Javascript functions in the pre-request or response handlers we can store them in an external Javascript file. Then we use the import statement to import either the whole file or specify explicitly the code we want to import. This way we can reuse code for different pre-request and response handlers.

In the following example we import the createUsername function from the external scripts/username.js file and use it in the pre-request handler. In the response handler we import the external file scripts/validate-200-response.js and the code from the file is executed when the response is available.

// File: scripts/username.js
function createUsername() {
    const usernames = ["mrhaki", "hubert"];
    const randomIndex = (Math.floor(Math.random() * 11) % 2);

    // Return mrhaki or hubert as username.
    return usernames[randomIndex];
}

export {createUsername};
// File: scripts/validate-200-response.js
client.test("Response is OK", function () {
    client.assert(response.status === 200);
});
### POST JSON payload to /anything
< {%
    // Refer to createUsername function from external Javascript file.
    import {createUsername} from './scripts/username';

    // Use function createUsername.
    request.variables.set("username", createUsername());
%}

POST https://ijhttp-examples.jetbrains.com/anything

{
  "username": "{{username}}"
}

> {%
    // Validate 200 response from external file. 
    import './scripts/validate-200-response';

    client.test("Response has username set", function () {
        client.log("Username -> " + response.body.json.username);
        client.assert(["mrhaki", "hubert"].includes(response.body.json.username));
    });
 %}

Written with IntelliJ 2023.2.4.

IntelliJ HTTP Client: Using External Files As JSON Payload

The built-in IntelliJ HTTP Client is very useful for testing HTTP requests and responses. We can use it to test for example a REST API that works with JSON data. If an endpoint expects a JSON payload we can specify the payload in our HTTP Client request file. But if we have a lot of endpoints and large payload the request file can get big and messy. Instead of having the payload in the request file directly we can specify an external JSON file with the payload and use it for a request body. We must use the < operator and give the name of the file with our JSON payload. The IntelliJ HTTP Client will read the contents of that file and use it as the request body. The payload may also contain (dynamic) variables and those variables will be replaced with correct values when the request is executed.

In the following example we have a POST request with a JSON payload from an external file. The payload in the file contains variables. We use an endpoint from the test API at https://ijhttp-examples.jetbrains.com that expects a JSON payload. The response will contain the payload with request payload where all variables are replaced in the json property.

### POST to /anything with payload from external file
POST https://ijhttp-examples.jetbrains.com/anything
Content-Type: application/json

< ./sample-payload.json

# Content of sample-playload.json:
#{
#  "id": "{{$random.uuid}}",
#  "username": "{{username}}",
#}

# Content of http-client.env.json with value for variable username:
#{
#  "test": {
#    "username": "mrhaki"
#  }
#}

> {%
    client.test("Response is OK", function () {
        client.assert(response.status === 200);
    });
    client.test("Response has username set", function () {
        client.assert(response.body.json.username === "mrhaki");
    });
    client.test("Response has id with value", function () {
        const regexExp = /^[0-9a-f]{8}\b-[0-9a-f]{4}\b-[0-9a-f]{4}\b-[0-9a-f]{4}\b-[0-9a-f]{12}$/gi;
        client.assert(regexExp.test(response.body.json.id));
    });
%}

Written with IntelliJ IDEA 2023.2.4.

October 23, 2023

jq Joy: Using String Interpolation

jq is a powerful tool to work with JSON from the command-line. The tool has a lot of functions that makes our live easier. With jq we can use expressions in strings that will be evaluated and inserted into the string value. This is called string interpolation. The expression is enclosed by parentheses and the first parenthesis is prefixed with a backslash: \(<expression>). The expression can be any valid jq expression and the result of the expression will be inserted into the string.

In the following example we use string interpolation to print a greeting message and we use a simple property reference, but also a bit more complicated expressions:

$ jq --raw-output --null-input '{"prefix": "Hi", "name": "mrhaki", "visitors": 41} | "\(.prefix),\nwelcome \(.name | ascii_upcase)\nYou are visitor \(.visitors + 1)!"'
Hi,
welcome MRHAKI
You are visitor 42!

Written with jq 1.7.

October 9, 2023

Groovy Goodness: Using NullCheck Annotation To Prevent NullPointerException

In Groovy we can apply the @NullCheck annotation to a class, constructor or method. The annotation is an AST (Abstract Syntax Tree) transformation and will insert code that checks for null values in methods or constructors. If a null value is passed to an annotated method or constructor, it will throw an IllegalArgumentException. Without the annotation we could have a NullPointerException if we try to invoke a method on the value we pass as argument. The annotation has an optional property includeGenerated which by default is false. If we set it to true then the null checks are also applied to generated methods and constructors. This is very useful if we apply other AST transformations to our class that generates additional code.

In the following example we use the @NullCheck annotation for a method and at class level:

import groovy.transform.NullCheck

@NullCheck
String upper(String value) {
    "Upper:" + value.toUpperCase()
}

assert upper("groovy") == "Upper:GROOVY"

try {
    upper(null)
} catch (IllegalArgumentException e) {
    assert e.message == "value cannot be null"
}
import groovy.transform.NullCheck

// Apply null check for all constructors and methods.
@NullCheck
class Language {
    private String name

    Language(String name) {
       this.name = name;
    }

    String upper(String prefix) {
        return prefix + name.toUpperCase();
    }
}


def groovy = new Language("groovy")

assert groovy.upper("Upper:") == "Upper:GROOVY"

// Method arguments are checked.
try {
    groovy.upper(null)
} catch (IllegalArgumentException e) {
    assert e.message == "prefix cannot be null"
}

// Constructor argument is also checked.
try {
    def lang = new Language(null)
} catch (IllegalArgumentException e) {
    assert e.message == "name cannot be null"
}

In the following example we set the includeGenerated property to true to also generate null checks for generated code like the constructor generated by @TupleConstructor:

import groovy.transform.NullCheck
import groovy.transform.TupleConstructor

@NullCheck(includeGenerated = true)
@TupleConstructor
class Language {
    final String name

    String upper(String prefix) {
        return prefix + name.toUpperCase();
    }
}

// Constructor is generated by @TupleConstructor and
// @NullCheck is applied to the generated constructor.
try {
    def lang = new Language(null)
} catch (IllegalArgumentException e) {
    assert e.message == "name cannot be null"
}

Written with Groovy 4.0.13

jq Joy: Using Default Values With The Alternative Operator

jq is a powerful tool to work with JSON from the command-line. The tool has a lot of functions and operators that makes our live easier. One of the operators we can use is the alternative operator // which allows us to specify default values. If the value on the left side of the operator // is empty, null or false then the value on the right side is returned, otherwise the value itself is returned. The operator can be used multiple times if we want to have multiple fallbacks for a value we are checking.

In the following examples we use the // operator in different scenarios:

$ jq --null-input  '[42, "jq joy", null, false] | [.[] | . // "default"]'
[
  42,
  "jq joy",
  "default",
  "default"
]
$ jq --null-input 'empty // "value"'
"value"

We can chain the // operator to specify multiple fallback options:

$ jq --null-input '{"name": "mrhaki"} | {"alias": (.username // .user // "not available") }'
{
  "alias": "not available"
}
$ jq --null-input '{"user": "mrhaki"} | {"alias": (.username // .user // "not available") }'
{
  "alias": "mrhaki"
}

Written with jq 1.7.

October 5, 2023

jq Joy: Reverse An Array

jq is a powerful tool to work with JSON from the command-line. The tool has a lot of functions that makes our live easier. To simply reverse the order of elements in an array we can use the reverse function. The values in the input array are reversed and returned in the output array.

In the following example we use the reverse function for an array:

$ jq --null-input --compact-output '[1, 2, 3, 4, 5] | reverse'
[5,4,3,2,1]

Written with jq 1.7.

October 4, 2023

jq Joy: Checking String Ends Or Starts With Given String

jq is a powerful tool to work with JSON from the command-line. The tool has a lot of functions that makes our live easier. We can check if a string starts or ends with a given string using the startswith and endswith functions. We pass the string value we want to see a string value starts with or ends with as the argument to the functions. The function returns true if the string starts or ends with the given string and false otherwise.

In the following examples we use both functions to check some string values in an array:

$ jq --null-input '["mrhaki", "hubert", "MrHaki"] | [.[] | startswith("mr") or startswith("Mr")]'
[
    true,
    false,
    true
]
$ jq --null-input '["jq!", "JSON"] | [.[] | endswith("!")]'
[
  true,
  false
]

Written with jq 1.7.

October 3, 2023

jq Joy: Sum Of Elements In An Array Or Object

jq is a powerful tool to work with JSON from the command-line. The tool has a lot of functions that makes our live easier. One of the functions is add which adds all elements in an array or values in an object. The function has no arguments. The elements in an array are added together if they are numbers and concatenated if they are strings. If the input is an object then the values are added together. When the input is an empty array or object then null is returned.

In the following examples we see different usages of the add function:

$ jq --null-input '[1, 2, 3, 4,] | add'
10
$ jq --null-input '["a", "b", "c", "d"] | add'
"abcd"
$ jq --null-input '{ "burger": 3.23, "drinks": 2.89 } | add'
6.12
$ jq --null-input '[] | add'
null

Written with jq 1.7.

jq Joy: Flatten Arrays

jq is a powerful tool to work with JSON from the command-line. The tool has a lot of functions that makes our live easier. We can use the flatten function to flatten nested arrays into a single array. If we use the function without an argument the arrays are flattened recursively, resulting in a flat array with all elements. But we can also set the depth we want to flatten the array by passing an integer argument to the flatten function.

In the following example we use the flatten function without arguments to recursively flatten the array:

$ jq --null-input '[1, [2, 3], [[4]], 5] | flatten'
[
  1,
  2,
  3,
  4,
  5
]

We can pass an argument to the flatten function to indicate the depth to flatten the collection. In the following example we want to flatten only one level deep:

$ jq --null-input '[1, [2, 3], [[4]], 5] | flatten(1)'
[
  1,
  2,
  3,
  [
    4
  ],
  5
]

Written with jq 1.7.