Codename One build (build)

The build goal is used to send builds to the Codename One build server. It also supports a few local build targets, such as ios-source, which generates an Xcode project, and android-source which generates an Android Gradle project.

This goal is bound to the package phase of the Codename One application project archetype (cn1app-archetype), so you don’t need to run this directly.

Example

Listing 32. Submitting a Mac Desktop build directly using this goal:
mvn cn1:build -Dcodename1.platform=javase -Dcodename1.buildTarget=mac-os-x-desktop

Properties

codename1.platform

Specifies the platform to build for. Values include Java SE, android, ios, JavaScript, and win.

codename1.buildTarget

The build target. Different platforms support different build sets of build targets.

Build TargetPlatformDescription

mac-os-x-desktop

Java SE

Sends a Mac desktop build to the Codename One build server.

windows-desktop

Java SE

Sends a Windows desktop build to the Codename One build server.

android-device

android

Sends an Android build to the Codename One build server.

android-source

android

Generates an Android Gradle project locally that can be opened and built in Android Studio.

ios-device

ios

Sends an iOS build to the Codename One build server.

ios-source

ios

Generates an iOS Xcode project locally that can be opened and built in Xcode.

JavaScript

JavaScript

Sends a JavaScript build to the Codename One build server.

automated

Set to true to submit build as an automated build. When using this flag, the goal will wait for the build server to complete the build, then download it and save it in the target directory (of the associated module) using standard maven artifact file naming conventions. This allows you to set up automated CI workflows more.

Default value is false.

Requires a Codename One enterprise subscription.

open

If set to true, this will automatically open the generated Gradle or Xcode project in Android studio or Xcode. Only applicable to the ios-source and android-source buildTargets.

Default is false

Generate legacy cn1lib (cn1lib)

This goal generates a legacy.cn1lib file. It’s bound to the package phase of cn1lib-archetype projects so you will never need to run this goal directly.

Output location

This will output the cn1lib file inside the common/target directory of the root multimodule maven project.

Compliance check (compliance-check)

Checks API usage in the project’s "common" module to ensure that it doesn’t use any classes or methods that aren’t supported by Codename One.

This goal is bound to the process-classes phase of both Codename One application project archetype (cn1app-archetype) projects and Codename One library project archetype (cn1lib-archetype) projects, so you shouldn’t ever need to run this goal directly.

Compile CSS (css)

Compiles the project’s CSS files, generating a theme.res file which is placed in the build/classes directory.

This goal is bound to the process-classes phase of Codename One application project archetype (cn1app-archetype) projects, so you shouldn’t need to run this goal directly.

Generate app project (generate-app-project)

Generates a Maven project using the Codename One application project archetype (cn1app-archetype) as a basis, and applying an optional project template. This goal can also be used to migrate legacy Codename One Ant-based Application projects into maven.

Usage example

This goal shouldn’t be run inside an existing Maven project directory. It will output a project into a directory named after the artifactId parameter.

Because there is no existing project, you will need to provide the full maven path to the goal:

mvn com.codenameone:codenameone-maven-plugin:$CN1VERSION:generate-app-project \
  -DsourceProject=/path/to/my/ProjectTemplate \
  -DgroupId=com.example \
  -DartifactId=myapp \
  -Dcn1Version=$CN1VERSION
Substitute the latest Codename One plugin version, listed as <release> in the repository metadata, for the $CN1VERSION in the above example. Because this goal runs outside any project, Maven can only find that version if you have configured the Codename One repository in your Maven settings; see the Configuring the Codename One repository section of Creating a new project.
This command is formatted for the bash prompt (for example, Linux or Mac). It will work on Windows also if you use bash. If you’re on Windows and are using PowerShell or the regular command prompt, then you’ll need to modify the command slightly. In particular, the entire command would need to be on a single line. (Remove the '\' at the end of each line, and merge lines together, with space between the command-line flags)

In the above example, assuming all went well, it would output your project into a directory named myapp.

Parameters

cn1Version

The Codename One version that you want to use for the project. This will be manifested as the cn1.version and cn1.plugin.version properties in the common/pom.xml file of the generated project. If omitted it will default to the cn1Version that’s hard-coded in the cn1app-archetype artifact.

sourceProject

The path to an optional project template to use. This may be either a legacy Ant project, or a Maven project that follows the structure of Codename One application project archetype (cn1app-archetype).

artifactId

The artifactId to use for the new project.

groupId

The groupId to use for the new project.

version

The version to use for the new project. Optional.

Default value is "1.0-SNAPSHOT"

packageName

The package name to use for the new project.

This is necessary if the sourceProject property is a Maven project. If the sourceProject is a legacy Ant project, then this property is ignored.

mainName

The main class name to use for the new project.

This is necessary if the sourceProject property is a Maven project. If the sourceProject is a legacy Ant project, then this property is ignored.

Migrating legacy Ant projects into Maven

When providing an Ant-based Codename One application project as the sourceProject parameter, this goal will generate an equivalent Maven project to the Ant project, with the same settings and sources.

See Migrating an existing project for examples using this goal to migrate an existing project into Maven.

Project templates

When using a Codename One application project archetype (cn1app-archetype) maven project as the sourceProject parameter, the project will be treated project template, and perform some basic processing of the source files as necessary convert the template into a real project. This includes replacing all occurrences of ${mainName} and ${packageName} in project sources with the value of the mainName and packageName parameters provided on the command-line.

Any occurrences of mainName and packagePath in file or directory names will be swapped with the values of mainName and packagePath (which is automatically derived from packageName by substituting '.' with file separators).

The generate-app-project.rpf file

The (optional) secret sauce that differentiates a regular Maven project from a Maven project template is the existence of a generate-app-project.rpf file in the root project directory. This file is in rich property file format, and allows you to define a minimal amount of configuration details that the generate-app-project goal needs to convert the template into a real project.

This file may contain the following properties:

template.mainName

The name of the main class that’s used in this project. This property isn’t required if the project is already using the mainName placeholder in the file name that contains the main class, and the ${mainName} placeholder in any source code referring to the main class.

Often times it’s easier to specify this property here rather than injecting placeholders into the template source base, because that way the template itself can be used as a valid project.

template.packageName

The package name for the app. This property isn’t required if the project is already using the packagePath placeholder in directories containing your main package files, and the ${packageName} placeholder in any source code that refers to the main package.

Often times it’s easier to specify this property here rather than injecting placeholders into the template source base, because that way the template itself can be used as a valid project.

template.type

Either maven or ant, depending on the project type.

dependencies

An XML snippet containing any more Maven dependencies that should be added to the project. This is handy of the project template relies on other cn1libs that are on Maven central.

Sample generate-app-project.rpf file

template.mainName=MyApp
template.packageName=com.example
template.type=maven

[dependencies]
====
<dependency>
  <groupId>com.codenameone</groupId>
  <artifactId>googlemaps-lib</artifactId>
  <version>1.0.1</version>
  <type>pom</type>
</dependency>
====

Sample: The bare-bones Kotlin app project

As a fuller example of a project template, see the generate-app-project.rpf file in the bare-bones Kotlin app template.

This is the template that’s used in Codename One initializr for the Bare-bones Kotlin project.

Clone project (clone)

Clones the current project as a new project with a different groupId, artifactId, packageName, and mainName. Resulting project will be made available in the common/target/generated-sources/cn1-cloned-projects directory.

Usage example

mvn cn1:clone \
  -DgroupId=com.example.newgroup \
  -DartifactId=newapp \
This command is formatted for the bash prompt (for example, Linux or Mac). It will work on Windows also if you use bash. If you’re on Windows and are using PowerShell or the regular command prompt, then you’ll need to modify the command slightly. In particular, the entire command would need to be on a single line. (Remove the '\' at the end of each line, and merge lines together, with space between the command-line flags)

In the above example, assuming all went well, it would output your project into a directory named myapp.

Parameters

artifactId

The artifactId to use for the cloned project.

groupId

The groupId to use for the cloned project.

Create GUI Form (create-gui-form)

The create-gui-form goal will generate a GUI form that can be edited in the GUI builder.

Basic Usage Example

mvn cn1:create-gui-form -DclassName=com.example.MyForm

The above example will generate a GUIBuilder form with the provided class name. It effectively generates two files:

  1. common/src/main/guibuilder/com/example/MyForm.gui

  2. common/src/main/java/com/example/MyForm.java

The paths above assume that this goal was run from the root multimodule maven project. If it were run inside the "common" submodule, then the files would be generated in the "src/main/…​" directory of that module. (It would try to create yet another "common" submodule). See Project Structure.

You can then open the GUI builder to edit this form using the cn1:guibuilder goal:

mvn cn1:guibuilder -DclassName=com.example.MyForm

Parameters

className

(Required) The class name of the form that you wish to generate. E.g com.example.MyForm.

guiType

The kind of GUI component to generate. Supports Form, Dialog, and Container. Default value is "Form"

autoLayout

Whether to use autolayout. This is boolean (true/false), and the default value is true.

Generate cn1lib project (generate-cn1lib-project)

Generates a Maven cn1lib project (using the cn1lib-archetype) given an ANT cn1lib project as a template.

This is to assist in migrating Ant projects to Maven projects. This won’t make any changes to the source Ant project. It generates a new project using the Codename One library project archetype (cn1lib-archetype) and copies all files and configuration from the source project, into the new project.

Usage example

This goal isn’t run in an existing maven project directory, so you need to include the absolute Maven coordinates for the goal.

Suppose you have an Ant Codename One library project at /path/to/MyLegacyAntLibraryProject and you want to convert it to a Maven project.

You can run the generate-cn1lib-project goal as follows:

Listing 33. Maven command to migrate an Ant project to Maven:
mvn com.codenameone:codenameone-maven-plugin:$CN1VERSION:generate-cn1lib-project \
  -DsourceProject=/path/to/MyLegacyAntLibraryProject \
  -DgroupId=com.example \
  -DartifactId=my-maven-lib \
  -Dversion=1.0-SNAPSHOT \
  -U
Make sure you substitute the latest codenameone-maven-plugin version, listed as <release> in the repository metadata, for the $CN1VERSION in this command. Because this goal runs outside any project, Maven can only find that version if you have configured the Codename One repository in your Maven settings; see the Configuring the Codename One repository section of Creating a new project.

Some notes about this command as shown above:

  1. This command is formatted for Unix/Mac on multiple lines, using the \ character to escape the new-lines. On Windows the command will need to be all one line, and you should omit those \ escape characters.

  2. The -U flag tells Maven to update its catalogs to ensure that it can find the $CN1VERSION that you specify.

If all goes well, you should find a new maven project generated in the "your-maven-lib" directory (named after the artifactId that you specified).

To test that the project was generated successfully, try opening the resulting project in your IDE or run its "install" goal on the command-line.

For example:

cd my-maven-lib
mvn install

After running the "install" command, you should be able to add your library as a dependency to a Codename One application project archetype (cn1app-archetype) project using the following dependency:

<dependency>
  <groupId>com.example</groupId>
  <artifactId>my-maven-lib-lib</artifactId>
  <version>1.0-SNAPSHOT</version>
  <type>pom</type>
</dependency>
Notice that the artifactId has an extra "-lib" appended. That is, it’s <artifactId>my-maven-lib-lib</artifactId> and not <artifactId>my-maven-lib</artifactId>. This is because the artifactId that you specify in the generate-cn1lib-project goal is used for the "root" module of the multimodule maven project. The actual "lib" project that you can use as a Maven dependency is the "lib" submodule, which uses the specified artifactId with a "-lib" suffix.

See Codename One libraries for more information about the resulting maven library project.

Properties

sourceProject

The path to the legacy Ant project that you want to convert to a Maven project.

groupId

The maven groupId to use for the resulting project.

artifactId

The maven artifactId to use for the resulting project.

version

The maven version to use for the resulting project. Default "1.0-SNAPSHOT"

Generate desktop app wrapper (generate-desktop-app-wrapper)

Generates the bootstrapping wrapper class for the Java SE desktop app. This is used by the Java SE module of Codename One application project archetype (cn1app-archetype) projects.

You shouldn’t need to call this goal directly.

Generate GUI sources (generate-gui-sources)

Generates Java sources from the Codename One GUI builder files. This goal is bound to the process-sources phase of Codename One application project archetype (cn1app-archetype) projects, so you should never need to run this goal explicitly.

Generate native interfaces (generate-native-interfaces)

Generates stub implementations for all native interfaces defined in the project. This won’t overwrite any existing implementations that may exist.

You should run this goal explicitly after you create a native interface in your class.

See the Codename One Developer guide section on native interfaces for more information on creating native interfaces.

Usage example

Suppose you’ve created a native interface as the Java interface com.mycompany.myapp.MyNative, as described in the example in native interfaces.

After creating this (and possibly other) native interfaces in your project, run the generate-native-interfaces Maven goal as follows:

mvn cn1:generate-native-interfaces

By default this generates Java/Objective-C stubs. You can optionally include Swift and Kotlin stubs (both off by default):

mvn cn1:generate-native-interfaces \
  -Dcn1.generateNativeInterfaces.swift=true \
  -Dcn1.generateNativeInterfaces.kotlin=true

This will generate the following files (if they don’t exist yet).

Java SE

Java SE/src/main/java/com/mycompany/myapp/MyNativeImpl.java

ios
  1. ios/src/main/objectivec/com_mycompany_myapp_MyNativeImpl.h

  2. ios/src/main/objectivec/com_mycompany_myapp_MyNativeImpl.m

android

android/src/main/java/com/mycompany/myapp/MyNativeImpl.java

android (optional Kotlin)

android/src/main/java/com/mycompany/myapp/MyNativeImpl.kt

JavaScript

JavaScript/src/main/JavaScript/com_mycompany_myapp_MyNativeImpl.js

ios (optional Swift)

ios/src/main/objectivec/com_mycompany_myapp_MyNativeImpl.swift

Open and edit these files to implement your native interface methods as desired.

Generate OpenAPI client (generate-openapi)

Generates a typed Codename One client from an OpenAPI 3.x JSON specification. Writes one @Mapped record (Java 17+) or class (Java 8 target) per components.schemas entry and one @RestClient-annotated interface per OpenAPI tag. The generated files land in common/src/main/java so the project owns the contract; the matching networking implementation is emitted into common/target/generated-sources by the build-time annotation processor so the project source stays clean.

The mojo is paired with the existing process-annotations pipeline: identical schemas across operations collapse to one record/class, and operationIds become interface methods that resolve at runtime via a com.codename1.io.rest.RestClients registry populated by the generated bootstrap class (the same splice pattern as @Mapped mappers).

Usage example

mvn -pl common cn1:generate-openapi \
    -Dcn1.openapi.spec=petstore.json \
    -Dcn1.openapi.basePackage=com.example.petstore

Configuration:

PropertyDescription

-Dcn1.openapi.spec=PATH

Local file path or URL of the OpenAPI 3.x JSON document, for example petstore.json or https://petstore3.swagger.io/api/v3/openapi.json. YAML isn’t supported — convert with yq upstream.

-Dcn1.openapi.basePackage=PKG

Java package the generated sources are written under. Records / classes go under <basePackage>.model; the @RestClient interfaces go under <basePackage>.

-Dcn1.openapi.outputDirectory=DIR (optional)

Defaults to ${project.basedir}/src/main/java.

-Dcn1.openapi.overwrite=false (optional)

Defaults to true. Set to false to preserve user edits to existing files (only missing files are written).

Generated output

The listings in this section are the goal’s own output, generated from a Swagger Petstore specification cut down to the pet, store and user tags. That spec is in the repository at docs/demos/common/src/main/snippets/developer-guide/appendix-goal-generate-openapi-petstore.json, so you can run the goal against it and compare. The committed copies carry a license header and the tag:: markers this guide includes them by; strip those two additions and the rest is exactly what the goal wrote.

One @RestClient interface per tag and one model per schema land under common/src/main/java:

com/example/petstore/
  PetApi.java          // @RestClient interface, methods addPet, updatePet,
                       // findPetsByStatus, getPetById, deletePet
  StoreApi.java        // @RestClient interface, methods placeOrder,
                       // getOrderById, deleteOrder
  UserApi.java         // @RestClient interface, methods createUser,
                       // getUserByName
com/example/petstore/model/
  Pet.java             // @Mapped record (Java 17+) or class (Java 8)
  Order.java
  User.java
  Category.java

Tag.java is missing from that list on purpose. The Petstore declares Tag and Category with the same two properties, and identical shapes collapse to one record, so Pet.tags() comes back typed as a list of Category. Give the two schemas different shapes if you want them to stay separate classes.

Each @RestClient interface method is annotated with the HTTP verb and path; parameters are annotated @Path / @Query / @Header / @Body so the processor knows how to assemble the Rest call. The emitted method shape:

@RestClient
public interface PetApi {

    @POST("/pet")
    void addPet(@Body com.example.petstore.model.Pet body, @Header("Authorization") String bearerToken, OnComplete<Response<com.example.petstore.model.Pet>> callback);

    @PUT("/pet")
    void updatePet(@Body com.example.petstore.model.Pet body, @Header("Authorization") String bearerToken, OnComplete<Response<com.example.petstore.model.Pet>> callback);

    @GET("/pet/findByStatus")
    void findPetsByStatus(@Query("status") String status, @Header("Authorization") String bearerToken, OnComplete<Response<java.util.List<com.example.petstore.model.Pet>>> callback);

    @GET("/pet/{petId}")
    void getPetById(@Path("petId") Long petId, @Header("Authorization") String bearerToken, OnComplete<Response<com.example.petstore.model.Pet>> callback);

    @DELETE("/pet/{petId}")
    void deletePet(@Path("petId") Long petId, @Header("Authorization") String bearerToken, OnComplete<Response<String>> callback);

    static PetApi of(String baseUrl) {
        return RestClients.create(PetApi.class, baseUrl);
    }
}

The model type referenced by the generated API is emitted into the generated model package:

@Mapped
public record Pet(@JsonProperty("id") Long id, @JsonProperty("name") String name, @JsonProperty("category") com.example.petstore.model.Category category, @JsonProperty("photoUrls") java.util.List<String> photoUrls, @JsonProperty("tags") java.util.List<com.example.petstore.model.Category> tags, @JsonProperty("status") String status) {}

Model types are emitted as fully qualified names (the API interface lives in <basePackage> and models under <basePackage>.model) so the generator never needs to track imports or worry about collisions between an API class name and a same-named model.

Call sites use the static factory:

void loadPet(String bearerToken) {
    PetApi api = PetApi.of("https://petstore3.swagger.io/api/v3");

    api.getPetById(10L, bearerToken, response -> {
        // Two things have to be settled before the payload is a Pet.
        // The generated impl passes null when the request never
        // completed, and on an error status it forwards the raw
        // Response<String> under this type -- so on that path
        // getResponseData() is the error body, not a Pet, and
        // touching it as one is a cast that does not throw on iOS.
        if (response == null) {
            ToastBar.showErrorMessage("The request did not complete");
        } else if (response.getResponseCode() < 200 || response.getResponseCode() > 299) {
            ToastBar.showErrorMessage(response.getResponseErrorMessage());
        } else if (response.getResponseData() == null) {
            ToastBar.showErrorMessage("The server returned no pet");
        } else {
            ToastBar.showInfoMessage(response.getResponseData().name());
        }
    });
}
The call site here is written against the Java 17 output, where Pet is a record. On a Java 8 target the generator emits a class with public fields instead, so pet.name() becomes pet.name.

The <Tag>ApiImpl class that actually performs the HTTP call lives in target/generated-sources — the project source never references it directly. The build server probes the project zip for the generated cn1app.RestClientBootstrap and splices the registry wiring in, mirroring the existing cn1app.MapperBootstrap pattern.

Scope

  • HTTP verbs: GET, POST, PUT, DELETE, PATCH.

  • Parameter locations: path, query, header, cookie. Multiple cookie parameters on the same operation are joined into a single Cookie: a=1; b=2 request header.

  • Request bodies: application/json — serialized via Mappers.toJson(body) before being attached.

  • Response schemas: $ref resolution, primitives (string / number / integer / boolean), arrays, object schemas. oneOf / anyOf / allOf collapse to Object — callers cast.

  • String enum schemas are emitted as Java enums and bound by the JSON mapper through each constant’s name(), so a model property typed by an enum $ref gets the generated enum type. This requires every enum value to be a valid (non-reserved) Java identifier; an enum whose values are not — for example "two-day" — degrades to String rather than generating an enum that couldn’t round-trip. Integer/number enums keep their numeric type.

  • Schema unification: two components.schemas entries with identical property shapes collapse to a single record/class to avoid an explosion of duplicates.

  • Authentication: bearer token is exposed as a @Header("Authorization") String bearerToken parameter on every operation. API-key auth declared in the spec is emitted as @Header or @Query like any other parameter; basic auth and OAuth bearer tokens travel through the same bearerToken slot.

Generate gRPC client (generate-grpc)

Generates a typed Codename One gRPC-Web client from a proto3 .proto specification. Writes one @ProtoMessage record (Java 17+) or class (Java 8 target) per message, one @ProtoEnum enum per enum, and one @GrpcClient-annotated interface per service. The generated files land in common/src/main/java so the project owns the contract; the matching protobuf codec and gRPC-Web call site are emitted into common/target/generated-sources by the build-time annotation processors so the project source stays clean.

The mojo is paired with the existing process-annotations pipeline: @ProtoMessage triggers per-class binary protobuf codec generation and a cn1app.ProtoBootstrap registration entry; @GrpcClient triggers per-interface <Service>GrpcImpl generation chained through com.codename1.io.grpc.GrpcWeb.invokeUnary(…​) and a cn1app.GrpcClientBootstrap that wires everything to the com.codename1.io.grpc.GrpcClients registry (same splice pattern as @Mapped mappers).

Usage example

mvn -pl common cn1:generate-grpc \
    -Dcn1.grpc.proto=helloworld.proto \
    -Dcn1.grpc.basePackage=com.example.hello

Configuration:

PropertyDescription

-Dcn1.grpc.proto=PATH

Local path to the .proto file (proto3 syntax).

-Dcn1.grpc.basePackage=PKG

Java package the generated sources are written under. Messages, enums, and the @GrpcClient interface all land directly in <basePackage> (no nested model package — proto namespaces are flat by design).

-Dcn1.grpc.outputDirectory=DIR (optional)

Defaults to ${project.basedir}/src/main/java.

-Dcn1.grpc.overwrite=false (optional)

Defaults to true. Set to false to preserve user edits to existing files (only missing files are written).

Generated output

For a helloworld.proto declaring message HelloRequest, message HelloReply, enum Mood, and service Greeter { rpc SayHello(HelloRequest) returns (HelloReply); }, the goal emits under common/src/main/java:

com/example/hello/
  HelloRequest.java     // @ProtoMessage record / class
  HelloReply.java
  Mood.java             // @ProtoEnum enum with `number` accessor
  GreeterGrpc.java      // @GrpcClient interface

Each @ProtoMessage carries one @ProtoField(tag = N) per field; scalar fields use the default varint wire kind, sint32 / sint64 use wireType = ProtoField.WireKind.SINT, and fixed32 / fixed64 / sfixed32 / sfixed64 use wireType = ProtoField.WireKind.FIXED. Renamed fields (e.g. snake_casecamelCase) carry the original proto name via the optional name attribute so introspection tooling can recover it.

The @GrpcClient interface looks like:

@GrpcClient("helloworld.Greeter")
public interface GreeterGrpc {

    @Rpc("SayHello")
    void sayHello(com.example.hello.HelloRequest request, @Header("Authorization") String bearerToken, OnComplete<GrpcResponse<com.example.hello.HelloReply>> callback);

    static GreeterGrpc of(String baseUrl) {
        return GrpcClients.create(GreeterGrpc.class, baseUrl);
    }
}

Call sites use the static factory:

void sayHello(String bearerToken) {
    GreeterGrpc greeter = GreeterGrpc.of("https://grpc.example.com");
    HelloRequest request = new HelloRequest("Ada", Arrays.asList("ada", "countess"), Mood.HAPPY);

    greeter.sayHello(request, bearerToken, response -> {
        if (response.isOk() && response.getResponseData() != null) {
            ToastBar.showInfoMessage(response.getResponseData().message());
        } else if (response.isOk()) {
            ToastBar.showErrorMessage("The server returned an empty reply");
        } else {
            // getResponseCode() is the gRPC status, not the HTTP one --
            // a gRPC-Web call can carry a failure under HTTP 200.
            ToastBar.showErrorMessage("gRPC status " + response.getResponseCode()
                    + ": " + response.getResponseErrorMessage());
        }
    });
}
The call site above is written against the Java 17 output, where the messages are records. On a Java 8 target the generator emits classes with a public no-arg constructor and public fields instead, so construction becomes new HelloRequest(); r.name = …​ and a read is reply.message rather than reply.message().

The <Service>GrpcImpl class that actually performs the gRPC-Web POST lives in target/generated-sources — the project source never references it directly. The build server probes the project zip for cn1app.ProtoBootstrap and cn1app.GrpcClientBootstrap and splices the registry wiring in, mirroring the existing cn1app.MapperBootstrap pattern.

Wire protocol

The runtime speaks gRPC-Web binary (application/grpc-web+proto) over HTTP/1.1, which is the standard mobile / browser variant of gRPC supported by Envoy, the official grpcweb Go proxy, and the gRPC-Web filter in modern gRPC server implementations. Plain HTTP/2 gRPC requires trailers that ConnectionRequest doesn’t expose; gRPC-Web carries grpc-status in a synthetic trailer frame in the response body instead.

A successful call:

  1. Encodes the request message via the build-time-generated ProtoCodec for the request type.

  2. Wraps the encoded bytes in a 5-byte frame header (flag + length) and posts to <baseUrl>/<service-fqn>/<method> with Content-Type: application/grpc-web+proto and X-Grpc-Web: 1.

  3. Reads the response body, iterates frames, accumulates data payload, and parses the trailer frame for grpc-status / grpc-message.

  4. Decodes the accumulated payload via the response codec and invokes the supplied OnComplete<GrpcResponse<T>>.

Scope

  • Unary RPCs (single request, single response). Streaming RPCs (stream keyword on request or response) are rejected at generation time — gRPC-Web client streaming requires HTTP/2.

  • proto3 syntax. proto2 required is rejected; optional is accepted but treated as a nullable field.

  • Scalar types: int32, int64, uint32, uint64, sint32, sint64, fixed32, fixed64, sfixed32, sfixed64, float, double, bool, string, bytes.

  • enum declarations (nested or top-level) emit @ProtoEnum enums with a public final int number field and a static T forNumber(int n) lookup.

  • Nested message declarations are emitted as top-level siblings in the same package so the generated codec layout stays flat.

  • repeated fields land as java.util.List<T>. Scalar lists are encoded packed on the wire (proto3 default); the reader accepts both packed and unpacked forms.

  • oneof collapses to a group of nullable fields on the parent message — the mutual-exclusion guarantee is lost but round-trips work. Use application code to enforce the invariant if needed.

  • Not yet supported: map<K, V>, well-known types (Timestamp, Empty, etc.), file import, streaming RPCs. These produce a clear error from the parser pointing at the offending line.

  • Authentication: a bearer token is exposed uniformly as a @Header("Authorization") String bearerToken parameter on every RPC method, mirroring the OpenAPI bearer-token slot.

Generate GraphQL client (generate-graphql)

Generates a typed Codename One GraphQL client from a GraphQL SDL schema, optionally driven by a set of operation documents. Writes one @Mapped record (Java 17+) or class (Java 8 target) per response shape and input type, a Java enum per GraphQL enum, and one @GraphQLClient-annotated interface carrying one method per operation. The generated files land in common/src/main/java so the project owns the contract; the matching client implementation and JSON mappers are emitted into common/target/generated-sources by the build-time annotation processors so the project source stays clean.

The mojo is paired with the existing process-annotations pipeline: the generated @Mapped types trigger per-class JSON mapper generation and a cn1app.MapperBootstrap entry, while @GraphQLClient triggers per-interface <Name>Impl generation chained through com.codename1.io.graphql.GraphQL.execute(…​) (queries and mutations) or com.codename1.io.graphql.GraphQL.subscribe(…​) (subscriptions), plus a cn1app.GraphQLClientBootstrap that wires everything to the com.codename1.io.graphql.GraphQLClients registry (same splice pattern as @Mapped mappers and @GrpcClient clients).

Usage example

mvn -pl common cn1:generate-graphql \
    -Dcn1.graphql.schema=schema.graphqls \
    -Dcn1.graphql.operations=operations.graphql \
    -Dcn1.graphql.basePackage=com.example.starwars

Configuration:

PropertyDescription

-Dcn1.graphql.schema=PATH

Local path to the GraphQL SDL schema (.graphqls / .graphql). Required: it supplies the type graph used to resolve field types.

-Dcn1.graphql.basePackage=PKG

Java package the generated sources are written under. Models, enums, input types, and the @GraphQLClient interface all land directly in <basePackage>.

-Dcn1.graphql.operations=PATH (optional)

Path to a .graphql operation-document file, or a directory of them. When supplied the generator runs in the precise operations mode (one method and one response-type tree per named operation). When omitted it falls back to the schema-only quick-start mode.

-Dcn1.graphql.clientName=NAME (optional)

Simple name of the generated @GraphQLClient interface. Defaults to GraphQLApi.

-Dcn1.graphql.endpoint=URL (optional)

Default endpoint URL recorded on the generated annotation. The effective endpoint is always the argument passed to of(String endpoint).

-Dcn1.graphql.maxDepth=N (optional)

Maximum selection-set depth used by the schema-only quick-start mode. Defaults to 2.

-Dcn1.graphql.outputDirectory=DIR (optional)

Defaults to ${project.basedir}/src/main/java.

-Dcn1.graphql.overwrite=false (optional)

Defaults to true. Set to false to preserve user edits to existing files (only missing files are written).

Generation modes

Unlike OpenAPI paths or gRPC service methods, a GraphQL schema doesn’t enumerate the operations a client wants — the client chooses which fields to select. The goal therefore supports two modes:

  • Operations mode (recommended): supply cn1.graphql.operations. For each named query / mutation / subscription the generator emits one interface method plus a precise @Mapped response-type tree that mirrors exactly that operation’s selection set (nested selections become nested generated types). Referenced fragment definitions are merged into the request document automatically.

  • Schema-only quick-start mode: omit cn1.graphql.operations. For each field of the root Query / Mutation / Subscription type the generator emits one method whose selection set is automatically expanded to cn1.graphql.maxDepth levels, stopping at recursive types. This is a convenience for getting started; it may over- or under-fetch, so prefer operations mode for production code.

Generated output

For a Star Wars schema and an operations.graphql declaring query HeroName, mutation AddReview, and subscription OnReview, the goal emits under common/src/main/java:

com/example/starwars/
  Episode.java               // GraphQL enum -> Java enum
  ReviewInput.java           // @Mapped input type
  HeroNameData.java          // @Mapped query response root
  HeroNameData_Hero.java     // nested selection type
  AddReviewData.java         // @Mapped mutation response root
  OnReviewData.java          // @Mapped subscription response root
  StarWarsApi.java           // @GraphQLClient interface

GraphQL enums map to a generated Java enum in response types, input types, and method variables alike — the JSON mapper binds enums by their name(). Built-in scalars map to their boxed Java type (IntInteger, FloatDouble, BooleanBoolean, String / IDString); custom scalars fall back to String.

The @GraphQLClient interface looks like:

@GraphQLClient("https://example.com/graphql")
public interface StarWarsApi {

    @Query(value = "query HeroName($episode: Episode) { hero(episode: $episode) { ...HeroFields friends { name } } } fragment HeroFields on Character { name }", operationName = "HeroName")
    void heroName(@Var("episode") Episode episode, @Header("Authorization") String bearerToken, OnComplete<GraphQLResponse<HeroNameData>> callback);

    @Mutation(value = "mutation AddReview($ep: Episode!, $review: ReviewInput!) { createReview(episode: $ep, review: $review) { stars commentary } }", operationName = "AddReview")
    void addReview(@Var("ep") Episode ep, @Var("review") ReviewInput review, @Header("Authorization") String bearerToken, OnComplete<GraphQLResponse<AddReviewData>> callback);

    @Subscription(value = "subscription OnReview($ep: Episode!) { reviewAdded(episode: $ep) { stars } }", operationName = "OnReview")
    GraphQLSubscription onReview(@Var("ep") Episode ep, @Header("Authorization") String bearerToken, GraphQLSubscription.Handler<OnReviewData> handler);

    static StarWarsApi of(String endpoint) {
        return GraphQLClients.create(StarWarsApi.class, endpoint);
    }
}

Call sites use the static factory. Queries and mutations report through an OnComplete<GraphQLResponse<T>>:

void heroName(String bearerToken) {
    StarWarsApi api = StarWarsApi.of("https://swapi.example.com/graphql");

    api.heroName(Episode.EMPIRE, bearerToken, response -> {
        // isOk() classifies the GraphQL payload, not the trip: a call that
        // never reached the server, and a 2xx whose body would not parse,
        // both arrive with an empty errors array and null data, so isOk()
        // answers true for them. getResponseErrorMessage() is the one
        // thing that is null only on a clean success -- it carries the
        // first GraphQL error, the transport failure, or the parse
        // failure -- and the HTTP code catches an error status whose body
        // happened to decode.
        boolean reachedTheServer = response.getResponseCode() >= 200
                && response.getResponseCode() <= 299;
        if (!reachedTheServer || response.getResponseErrorMessage() != null) {
            ToastBar.showErrorMessage(response.getResponseErrorMessage());
        } else if (response.getData() == null || response.getData().hero() == null) {
            // And an error-free answer still need not have found anything:
            // the schema declares hero as Character, not Character!.
            ToastBar.showInfoMessage("No hero for that episode");
        } else {
            ToastBar.showInfoMessage(response.getData().hero().name());
        }
    });
}
The call sites here are written against the Java 17 output, where the response types are records. On a Java 8 target the generator emits classes with public fields instead, so response.getData().hero().name() becomes response.getData().hero.name.

A subscription returns a GraphQLSubscription handle whose cancel() ends the stream:

GraphQLSubscription watchReviews(String bearerToken) {
    StarWarsApi api = StarWarsApi.of("https://swapi.example.com/graphql");

    return api.onReview(Episode.JEDI, bearerToken,
            new GraphQLSubscription.Handler<OnReviewData>() {
        @Override
        public void onNext(GraphQLResponse<OnReviewData> response) {
            // onError is for the end of the stream. A per-field failure
            // arrives here instead, as a next payload whose errors array
            // is non-empty and whose data may be partial or absent.
            if (response.hasErrors()) {
                ToastBar.showErrorMessage(response.getResponseErrorMessage());
            }
            OnReviewData data = response.getData();
            if (data != null && data.reviewAdded() != null) {
                ToastBar.showInfoMessage(data.reviewAdded().stars() + " stars");
            }
        }

        @Override
        public void onError(GraphQLResponse<OnReviewData> response) {
            ToastBar.showErrorMessage(response.getResponseErrorMessage());
        }

        @Override
        public void onComplete() {
        }
    });
    // Hold on to the handle and call cancel() to end the stream --
    // leaving the form is the usual place to do it.
}

The <Name>Impl class that performs the request lives in target/generated-sources — the project source never references it directly. The build server probes the project zip for cn1app.GraphQLClientBootstrap and splices the registry wiring in, mirroring the existing cn1app.MapperBootstrap pattern.

Wire protocol

Queries and mutations are sent as an HTTP POST to the endpoint with a JSON body {"query":…​,"operationName":…​,"variables":{…​}} and a Content-Type: application/json header. The response envelope ({"data":…​,"errors":[…​]}) is parsed into a typed GraphQLResponse<T>; because a GraphQL response can carry both data and errors, GraphQLResponse exposes getData() and getErrors() independently and isOk() reports whether the errors array was empty.

Subscriptions run over a WebSocket using the graphql-transport-ws protocol (offered via the Sec-WebSocket-Protocol handshake header): the client sends connection_init, waits for connection_ack, sends a subscribe message carrying the operation, and maps each next payload’s data to T before delivering it to the handler. The WebSocket endpoint defaults to the query endpoint with its scheme rewritten to ws / wss; pass a ws:// / wss:// URL to of(…​) to override it. All handler callbacks are dispatched on the EDT.

Scope

  • Queries, mutations, and subscriptions.

  • GraphQL enums map to generated Java enums in every position (response fields, input fields, and method variables); unknown enum names decode to null rather than throwing.

  • Input object types map to generated @Mapped classes; variables of scalar, enum, input-object, and list types are supported.

  • Named and inline fragments are resolved against the schema and merged into the generated response types and the request document.

  • Built-in scalars (Int, Float, Boolean, String, ID) map to their boxed Java types. Custom scalars fall back to String.

  • union / interface selections are emitted with their common fields in the quick-start mode; use explicit operation documents with inline fragments for full polymorphic typing.

  • Authentication: a bearer token is exposed uniformly as a @Header("Authorization") String bearerToken parameter on every method, mirroring the OpenAPI and gRPC bearer-token slots.

GUIBuilder goal (guibuilder)

The guibuilder goal opens the Codename One GUI builder to edit a specified GUIBuilder form.

Usage

mvn cn1:guibuilder -DclassName=com.example.MyForm

This will open the GUI builder to edit the form whose class is com.example.MyForm.

Parameters

className

The fully qualified name to the form class that you wish to edit. This must have been earlier generated using Create GUI Form (create-gui-form).

Generate archetype (generate-archetype)

This goal isn’t used. It was originally developed to simplifying the maintenance of multiple application archetypes, but this approach was later abandoned in favor of application project templates.

The generate-archetype mojo will generate a new archetype maven project based on an existing archetype project. It’s designed to ease the maintenance of several similar archetypes which differ in some dependencies or default project source code. It takes as input a template file which references the base archetype project and specifies what to customize.

Example Usage

mvn cn1:generate-archetype -Dtemplate=/path/to/mytemplate.java

Parameters

template

Required. The path to a template file that should be used to generate the archetype project.

outputDir

Optional. The output directory where the archetype project should be written to. The project will be created at outputDir/artifactId, where the artifactId is as specified in the [archetype] section of the template. By default this will be the current working directory.

This goal doesn’t require a project to run. You can run it directly using the full goal coordinates:

mvn com.codenameone:codenameone-maven-plugin:7.0-SNAPSHOT:generate-archetype \
-Dtemplate=/path/to/mytemplate.java

Template syntax

Here is a sample template file:

Listing 34. A sample template, which is a.Java file:
#set( $symbol_pound = '#' )
#set( $symbol_dollar = '$' )
#set( $symbol_escape = '\' )
package ${package};
/*
[archetype] (1)
----
extends=../cn1app-archetype (2)
groupId=com.codenameone.archetypes
artifactId=helloworld2-archetype
version=7.0-SNAPSHOT
----

[dependencies] (3)
----
<dependency>
<groupId>com.codenameone.libs</groupId>
<artifactId>filechooser-lib</artifactId>
<version>1.0-SNAPSHOT</version>
<type>pom</type>
</dependency>
----
 */
import static com.codename1.ui.CN.*;
import com.codename1.ui.*;
import com.codename1.ui.layouts.*;
import com.codename1.io.*;
import com.codename1.ui.plaf.*;
import com.codename1.ui.util.Resources;

/**
 * This file was generated by <a href="https://www.codenameone.com/">Codename One</a> for the purpose
 * of building native mobile applications using Java.
 */
public class ${mainName} {

 private Form current;
 private Resources theme;

 public void init(Object context) {
 // use two network threads instead of one
 updateNetworkThreadCount(2);

 theme = UIManager.initFirstTheme("/theme");

 // Enable Toolbar on all Forms by default
 Toolbar.setGlobalToolbar(true);

 // Pro feature
 Log.bindCrashProtection(true);

 addNetworkErrorListener(err -> {
 // prevent the event from propagating
 err.consume();
 if(err.getError()!= null) {
 Log.e(err.getError());
 }
 Log.sendLogAsync();
 Dialog.show("Connection Error", "There was a networking error in the connection to " + err.getConnectionRequest().getUrl(), "OK", null);
 });
 }


 public void start() {
 if(current!= null){
 current.show();
 return;
 }
 //WebSocket sock;
 Form hi = new Form("Hi World", BoxLayout.y());
 hi.add(new Label("Hello World"));

 hi.show();
 }

 public void stop() {
 current = getCurrentForm();
 if(current instanceof Dialog) {
 ((Dialog)current).dispose();
 current = getCurrentForm();
 }
 }

 public void destroy() {
 }

}
  1. The [archetype] section specifies details about the archetype project to be generated. For example, the groupId, artifactId, etc.

  2. The extends property in the [archetype] section is required, and points to the location of the archetype project that this template is based on. Path is relative to the location of the template file.

  3. The [dependencies] section includes content that should be injected into the <dependencies> section of the pom.xml file. Note that this isn’t the pom file for the archetype project itself. It’s the pom.xml file for the project that the archetype project is to generate.

Notice that the above template is velocity template for a Java file. It will be used as the source code for the main class in the resulting project. It’s a velocity template because maven’s archetype:generate goal will process it to replace properties such as package and the main class name.

Template sections

archetype

This is a required section and specifies both the location of the base archetype project from which this project is to be derived, and the coordinates of the output archetype project, such as groupId, archetypeId, and version.

Example

[archetype]
----
extends=../cn1app-archetype
groupId=com.codenameone.archetypes
artifactId=helloworld2-archetype
version=7.0-SNAPSHOT
----

Properties

extends

Required. The path to the archetype project that this is based on. This should usually be the cn1app-archetype project as it includes the placeholders that this generator relies on for injecting content into the pom.xml file and its project structure was used as a basis for developing this mojo.

groupId

The groupID of the resulting archetype project. You can or use the id property to specify groupId, artifactId and version in a single string.

artifactId

The artifactId of the resulting archetype project. You can or use the id property to specify groupId, artifactId and version in a single string.

version

The version of the resulting archetype project. You can or use the id property to specify groupId, artifactId and version in a single string.

id

A colon-separated string in the format groupId:artifactId:version that can be used as an alternative to groupId, artifactId, and version.

parentGroupId

If the output archetype project should be part of a multi-module project, then this will specify the parent groupId for the <parent> tag in the pom.xml file.

parentArtifactId

If the output archetype project should be part of a multi-module project, then this will specify the parent archetypeId for the <parent> tag in the pom.xml file.

parentVersion

If the output archetype project should be part of a multi-module project, then this will specify the parent archetypeId for the <parent> tag in the pom.xml file.

parent

A colon-delimited string in the format parentGroupId:parentArtifactId:parentVersion that can be used as an alternative to the parentGroupId, parentArtifactId, and parentVersion properties.

dependencies

Specify more dependencies that should be injected into the <dependencies> section of the pom.xml file for the common module of the maven project that the archetype will generate. The content of this section will be injected into the src/main/resources/archetype-resources/common/pom.xml of the archetype project.

Example

[dependencies]
---
<dependency>
    <groupId>com.codenameone.libs</groupId>
    <artifactId>filechooser-lib</artifactId>
    <version>1.0-SNAPSHOT</version>
    <type>pom</type>
</dependency>
---
css

CSS content that should be injected into the theme.css file of the project. This CSS will be injected into the src/main/resources/archetype-resources/common/src/main/css/theme.css file of the archetype project.

Example

[css]
---
#Constants {
    includeNativeBool: true;
}
Button {
    color:green;
    border:1px solid green;
    border-radius: 2mm;
    margin: 5mm;
}
---
properties

Properties that should be appended to the codenameone_settings.properties file. These will be added to the src/main/resources/archetype_resources/common/codenameone_settings.properties file of the archetype project.

Example

[properties]
---
codename1.arg.win.desktop-vm=zuluFx8-32bit
codename1.arg.win.desktopExtractDll=true
codename1.arg.win.launchOnStart=true
codename1.arg.win.runAfterInstall=true
---
files

Files contains a list of files that should be created in the archetype project. All file paths are relative to the "common" project root directory. Each path should have a corresponding section with the heading [file:path/to/file].

Example

[files]
----
src/main/guibuilder/__mainName__MainForm.gui
src/main/java/__mainName__MainForm.java
----

[file:src/main/guibuilder/__mainName__MainForm.gui]
----
<?xml version="1.0" encoding="UTF-8"?>

<component type="Form" layout="LayeredLayout" layeredLayoutPreferredWidthMM="0.0" layeredLayoutPreferredHeightMM="0.0"  autolayout="true" title="${mainName}MainForm" scrollableY="true" name="${mainName}MainForm">
<component type="Button" text="Click Me" name="Button">
<layoutConstraint insets="auto auto auto auto" referenceComponents="-1 -1 -1 -1" referencePositions="0.0 0.0 0.0 0.0" />
</component>
</component>
----

[file:src/main/java/__mainName__MainForm.java]
----
package ${package};
public class ${mainName}MainForm extends com.codename1.ui.Form {
public ${mainName}MainForm() {
this(com.codename1.ui.util.Resources.getGlobalResources());
}

    public ${mainName}MainForm(com.codename1.ui.util.Resources resourceObjectInstance) {
        initGuiBuilderComponents(resourceObjectInstance);
    }

//-- DON'T EDIT BELOW THIS LINE!!!
    protected com.codename1.ui.Button gui_Button = new com.codename1.ui.Button();


// <editor-fold defaultstate="collapsed" desc="Generated Code">
    private void initGuiBuilderComponents(com.codename1.ui.util.Resources resourceObjectInstance) {
        setLayout(new com.codename1.ui.layouts.LayeredLayout());
        setInlineStylesTheme(resourceObjectInstance);
        setScrollableY(true);
                setInlineStylesTheme(resourceObjectInstance);
        setTitle("MyForm");
        setName("MyForm");
        gui_Button.setText("Click Me");
                gui_Button.setInlineStylesTheme(resourceObjectInstance);
        gui_Button.setName("Button");
        addComponent(gui_Button);
        ((com.codename1.ui.layouts.LayeredLayout)gui_Button.getParent().getLayout()).setInsets(gui_Button, "auto auto auto auto").setReferenceComponents(gui_Button, "-1 -1 -1 -1").setReferencePositions(gui_Button, "0.0 0.0 0.0 0.0");
    }// </editor-fold>

//-- DON'T EDIT ABOVE THIS LINE!!!
}
----

Install Codename One (install-codenameone)

Installs/Updates Codename One into the user’s home directory. This goal is automatically run if a Maven build is attempted and it detects that Codename One isn’t yet installed (in the $HOME/.codenameone) directory.

Install legacy Cn1lib (install-cn1lib)

Installs a legacy cn1lib file as a dependency in this application project.

See Adding project dependencies for fuller coverage of project dependencies.

Using this goal explicitly is a last resort. The best solution for installing add-ons into your project is Managing add-ons in Codename One Settings.

Usage example

mvn cn1:install-cn1lib -Dfile=/path/to/MyLegacyLib.cn1lib

This will generate a Maven pom project for this lib inside the "cn1libs" directory, and it will add a dependency inside the common/pom.xml file.

The generated project isn’t a cn1lib-archetype project, and it isn’t meant to change. It’s merely a Maven wrapper around the cn1lib file that provides compatibility with Maven’s dependency mechanism.

Removing the Cn1lib later

If you need to remove the cn1lib later (that is, revert an installation), you can remove the <dependency> tag that was added to the common/pom.xml file for the cn1lib.

You can also remove the directory that was created inside the cn1libs folder for this cn1lib - but this step isn’t necessary.

Parameters

file

Path to the cn1lib file you want to install.

groupId

The groupId to use for the generated pom project. Optional. If not specified, it will use the same groupId as the root project.

artifactId

The artifactId to use for the generated pom project. Optional. If not provided, it will generate an artifactId derived from the project’s artifactId.

version

The version to use for the generated pom project. Optional. If not provided, it will use the project version.

updatePom

A boolean flag indicating whether it should automatically update the pom.xml file with the dependency.

Default is true

overwrite

A boolean flag indicating whether it should overwrite an existing project of the same name. Default false.

Default is false

Open Designer (designer)

Opens the legacy Codename One designer app to edit the project "theme.res" file.

If your project is using CSS it may not have a theme.res file, thus this goal will fail in this case.

Usage example

mvn cn1:designer

This will open the Codename One designer.

Prepare simulator classpath (prepare-simulator-classpath)

Sets up some properties and environment variables required for the Codename One simulator to run properly.

This goal is bound to the initialize phase of the Java SE module of the Codename One application project archetype (cn1app-archetype) and shouldn’t be executed directly.

Open settings (settings)

Downloads and opens the standalone Codename One Settings editor bound to the current Maven project. The editor manages basic project properties, build hints, and extensions.

mvn cn1:settings

Run tests (test)

Runs Codename One unit tests.

This goal is bound to the test phase of Codename One application project archetype (cn1app-archetype) projects, so that running it directly isn’t necessary. If you build a cn1app-archetype project using:

mvn install

or

mvn package

Then this goal will be executed automatically.

Prevent tests from running using the skipTests flag. For example, mvn install -DskipTests

Update Codename One (update)

Updates Codename One. This updates tools that live inside $HOME/.codenameone, such as the GUI builder, and tries to update the cn1.version and cn1.plugin.version properties in the project’s pom.xml. The standalone Settings editor is resolved from Maven using cn1.plugin.version when you run mvn cn1:settings.

Usage example

mvn cn1:update

Parameters

newVersion

(Optional) The version to update to. This should be a published Codename One version. Will accept a value of "LATEST" to cause it to resolve to the latest version in the Codename One Maven repository. LATEST never downgrades a project: if the newest version offered is older than the one the project already uses, the goal reports it and leaves cn1.version and cn1.plugin.version untouched.

If this parameter is omitted, then it will be implicitly set to LATEST, but it won’t update the cn1.version or cn1.plugin.version properties if they’re set to a SNAPSHOT version.

See Updating Codename One for more information about updating Codename One.