The library cue-api-java provides a way to use CUE from Java programs. A common use for CUE is validating data against a schema, and this tutorial shows you how to use the the checkSchema() method to check a Value against a schema. The method throws CueError if the CUE value is not an instance of the schema.

Prerequisites

  • You need the low-level library libcue to be compiled and available on your computer, which is demonstrated in the guide “Building libcue as a shared library”.
  • You must have the Java library cue-api-java compiled and available on your computer. The guide “Building cue-api-java as a JAR file” shows you how to compile and install it. This tutorial needs you to install the same version as that guide.
  • Your computer needs to have the Java Development Kit (JDK) version 22 (or later) installed. If you need help choosing a distribution of Java, the site whichjdk.com is a useful guide. Note that many operating systems offer a “Long Term Support” version of Java, but this often means JDK version 21, which isn’t recent enough. Make sure that you have version 22 or later!

Set up your environment

1

Tell the operating system (and Java) where to find the library libcue on your computer:

TERMINAL
$ export LD_LIBRARY_PATH=/usr/local/lib/

If you have installed libcue into a different directory than /usr/local/lib/ then adapt the command to refer to that directory instead.

2

Tell Java where to find the library cue-api-java on your computer:

TERMINAL
$ export CLASSPATH='/usr/local/share/java/*'

If you have installed cue-api-java into a different directory than /usr/local/share/java/ then adapt the command to refer to that directory instead.

3

Cross-check the value of this important variable:

TERMINAL
$ echo "$CLASSPATH"
/usr/local/share/java/*

Java’s requirements mean that we need the value of the CLASSPATH variable to end with an asterisk: make sure you see the trailing *.

If this value ends with a filename instead of an asterisk (for example: /usr/local/share/java/CUE.jar) then you need to fix this. Repeat the previous step while making sure that you surround the value of the variable with quotes (') when you export it.

4

Check that this tutorial’s prerequisites are present:

TERMINAL
$ javac --version
javac 22.0.2
$ ls $LD_LIBRARY_PATH | grep libcue.so || echo 'fail!'
libcue.so
$ ls $CLASSPATH | grep /CUE.*jar$ || echo 'fail!'
/usr/local/share/java/CUE.jar

If any of these commands fail then your computer doesn’t have the related prerequisite installed as expected and this is a problem that you need to fix before continuing with this tutorial.

5

Create a directory to hold some files, and change into it:

TERMINAL
$ mkdir -p cue-java-api-tutorials
$ cd cue-java-api-tutorials

Create a Java program

6

Place this Java program in the file CheckSchema.java:

cue-java-api-tutorials/CheckSchema.java
import org.cuelang.cue.*;

public class CheckSchema {
    public static void main(String[] args) throws Exception {
        var ctx = new CueContext();

        assertDoesNotThrow(() -> {
            ctx.toValue(true).checkSchema(ctx.compile("true"));
            ctx.toValue(true).checkSchema(ctx.compile("bool"));
        });
        assertThrows(CueError.class, () ->
            ctx.toValue(true).checkSchema(ctx.compile("int"))
        );

        assertDoesNotThrow(() -> {
            ctx.toValue(1).checkSchema(ctx.compile("1"));
            ctx.toValue(1).checkSchema(ctx.compile("<128"));
            ctx.toValue(1).checkSchema(ctx.compile("int"));
        });
        assertThrows(CueError.class, () ->
            ctx.toValue(1).checkSchema(ctx.compile(">128"))
        );
        assertThrows(CueError.class, () ->
            ctx.toValue(1).checkSchema(ctx.compile("string"))
        );

        assertDoesNotThrow(() -> {
            ctx.compile("a: b: 1").checkSchema(ctx.compile("a: b: 1"));
            ctx.compile("a: b: 1").checkSchema(ctx.compile("a: b: int"));
            ctx.compile("a: b: 1").checkSchema(ctx.compile("a: b!: int"));
            ctx.compile("a: { b: 1, c: 1 }").checkSchema(ctx.compile("a: b: int"));
            ctx.compile("a: { b: int, c: 1 }").checkSchema(ctx.compile("a: b: int"));
        });

        assertThrows(CueError.class, () ->
            ctx.compile("a: b: 1").checkSchema(ctx.compile("string"))
        );
        assertThrows(CueError.class, () ->
            ctx.compile("a: b: 1").checkSchema(ctx.compile("a: b: 2"))
        );
        assertThrows(CueError.class, () ->
            ctx.compile("a: b: 1").checkSchema(ctx.compile("a: { b: int, c: int }"))
        );
    }

    @FunctionalInterface
    interface ThrowingRunnable {
        void run() throws Exception;
    }

    // Asserts that the given runnable does not throw any exception.
    static void assertDoesNotThrow(ThrowingRunnable runnable) {
        try {
            runnable.run();
        } catch (Exception e) {
            throw new AssertionError(
                "Expected no exception to be thrown, but got: " + e
            );
        }
    }

    // Asserts that the given runnable throws the expected exception.
    static <T extends Throwable> void assertThrows(
        Class<T> expectedException, ThrowingRunnable runnable) {
        try {
            runnable.run();
            throw new AssertionError(
                "Expected exception: " +
                expectedException.getName() +
                " to be thrown, but nothing was thrown."
            );
        } catch (Throwable actualException) {
            if (!expectedException.isInstance(actualException)) {
                throw new AssertionError(
                    "Expected exception: " +
                    expectedException.getName() +
                    " but got: " +
                    actualException
                );
            }
        }
    }
}

Compile the program

7

Compile the Java program:

TERMINAL
$ javac CheckSchema.java

The Java compiler automatically uses the value of the CLASSPATH environment variable to locate the JAR file containing cue-api-java.

Run the program

8

Run the Java program:

TERMINAL
$ java --enable-native-access=ALL-UNNAMED -cp .:$CLASSPATH CheckSchema

This program doesn’t produce any output, demonstrating that all its positive and negative assertions succeed, as expected.

The Java runtime must be told about a slightly different classpath from the compiler, through the -cp flag, because it needs to locate both the cue-api-java JAR and your compiled code. The --enable-native-access flag avoids a runtime warning that the Foreign Function & Memory API is being used by cue-api-java.

Conclusion

Great job! You’ve managed to run some Java that demonstrates CUE being used to validate data against schemas.

See Related content, below, for tutorials and guides that explain more about using CUE in Java.