implementation("io.micronaut.projectgen:micronaut-projectgen-core")
Table of Contents
Micronaut projectgen
API to generate JVM projects
Version: 0.0.1-SNAPSHOT
1 Introduction
Micronaut ProjectGen offers an API to generate JVM projects.
2 Release History
For this project, you can find a list of releases (with release notes) here:
3 Micronaut ProjectGen Core
The following dependency contains the core APIs to generate applications.
<dependency>
<groupId>io.micronaut.projectgen</groupId>
<artifactId>micronaut-projectgen-core</artifactId>
</dependency>
4 Micronaut ProjectGen Test
The following dependency contains utilities to test the generated application.
testImplementation("io.micronaut.projectgen:micronaut-projectgen-test")
<dependency>
<groupId>io.micronaut.projectgen</groupId>
<artifactId>micronaut-projectgen-test</artifactId>
<scope>test</scope>
</dependency>
5 Features
To generate a project, you create beans of type Feature.
Each feature has a unique identifier; you can offer a UI to allow users to select a feature.
6 Demo
To talk about the ProjectGen API, let’s discuss the following demo: a Hello World application with Maven and Gradle.
The project structure will be like this:
├── build.gradle.kts
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── mvnw
├── mvnw.bat
├── pom.xml
├── settings.gradle.kts
└── src
├── main
│ └── java
│ └── com
│ └── example
│ └── HelloWorld.java
└── test
└── java
└── com
└── example
└── HelloWorldTest.java
You want to generate a build.gradle.kts
with the following content:
plugins {
id("java")
id("application")
}
group = "io.micronaut.projectgen"
version = "1.0.0"
repositories {
mavenCentral()
}
dependencies {
testImplementation("org.junit.jupiter:junit-jupiter:5.10.2")
}
java {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
tasks.test {
useJUnitPlatform()
}
application {
mainClass.set("com.example.HelloWorld")
}
and a pom.xml
with the following content:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.micronaut.projectgen</groupId>
<artifactId>demo-project</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.1.2</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<manifest>
<mainClass>com.example.HelloWorld</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>
6.1 Options
To generate a project, you need an implementation of the interface Options.
For example:
package io.micronaut.projectgen.demo;
import io.micronaut.projectgen.core.buildtools.BuildTool;
import io.micronaut.projectgen.core.buildtools.gradle.GradleDsl;
import io.micronaut.projectgen.core.options.GenericOptionsBuilder;
import io.micronaut.projectgen.core.options.JdkVersion;
import io.micronaut.projectgen.core.options.Options;
import java.util.List;
public final class OptionsFactory {
private OptionsFactory() {
}
public static Options create() {
return GenericOptionsBuilder.builder()
.name("demo")
.packageName("com.example")
.group("io.micronaut.projectgen")
.artifact("demo-project")
.version("1.0.0")
.buildTools(List.of(BuildTool.MAVEN, BuildTool.GRADLE))
.gradleDsl(GradleDsl.KOTLIN)
.java(JdkVersion.JDK_21)
.build();
}
}
6.2 Add Dependency
The following feature adds a dependency:
package io.micronaut.projectgen.demo;
import io.micronaut.projectgen.core.buildtools.MavenCentral;
import io.micronaut.projectgen.core.buildtools.dependencies.Dependency;
import io.micronaut.projectgen.core.feature.Feature;
import io.micronaut.projectgen.core.generator.GeneratorContext;
import io.micronaut.projectgen.core.generator.ModuleContext;
import io.micronaut.projectgen.core.utils.OptionUtils;
import jakarta.inject.Singleton;
@Singleton
class JunitJupiter implements Feature {
private static final Dependency DEPENDENCY_JUNIT_JUPITER = Dependency.builder() (1)
.groupId("org.junit.jupiter")
.artifactId("junit-jupiter")
.version("5.10.2")
.test()
.build();
@Override
public String getName() {
return "junit-jupiter";
}
@Override
public String getDescription() {
return "Adds the JUnit Jupiter dependency to the project";
}
@Override
public void apply(GeneratorContext generatorContext) {
ModuleContext module = generatorContext.getRootModule();
if (OptionUtils.hasGradleBuildTool(generatorContext.getOptions())) {
module.repositories().add(new MavenCentral()); (1)
}
module.addDependency(DEPENDENCY_JUNIT_JUPITER); (2)
}
}
1 | A feature can add repository. |
2 | You can use fluid Dependency API to add dependencies to your project. |
6.3 Add Build Properties
The following feature adds build properties:
package io.micronaut.projectgen.demo;
import io.micronaut.projectgen.core.buildtools.BuildProperties;
import io.micronaut.projectgen.core.feature.Feature;
import io.micronaut.projectgen.core.generator.GeneratorContext;
import io.micronaut.projectgen.core.options.Options;
import io.micronaut.projectgen.core.utils.OptionUtils;
import jakarta.inject.Singleton;
@Singleton
class MavenCompilerProperties implements Feature {
@Override
public String getName() {
return "maven-compiler-properties";
}
@Override
public void apply(GeneratorContext generatorContext) {
Options options = generatorContext.getOptions();
if (OptionUtils.hasMavenBuildTool(options)) {
BuildProperties buildProperties = generatorContext.getRootModule()
.buildProperties();
String java = String.valueOf(options
.java()
.majorVersion());
buildProperties.put("maven.compiler.source", java);
buildProperties.put("maven.compiler.target", java);
}
}
}
6.4 Add Gradle Plugins
You can use ProjectGen GradlePlugin API to add Gradle Plugins to a project.
The following feature adds the Gradle Java Plugin.
package io.micronaut.projectgen.demo;
import io.micronaut.projectgen.core.buildtools.gradle.GradlePlugin;
import io.micronaut.projectgen.core.feature.Feature;
import io.micronaut.projectgen.core.generator.GeneratorContext;
import io.micronaut.projectgen.core.utils.OptionUtils;
import jakarta.inject.Singleton;
@Singleton
class GradleJavaPluginFeature implements Feature {
@Override
public String getName() {
return "gradle-plugin-java";
}
@Override
public void apply(GeneratorContext generatorContext) {
if (OptionUtils.hasGradleBuildTool(generatorContext.getOptions())) {
int javaVersion = generatorContext.getOptions()
.java()
.majorVersion();
generatorContext.getRootModule()
.addBuildPlugin(GradlePlugin.builder()
.id("java")
.extension(String.format("""
java {
sourceCompatibility = JavaVersion.VERSION_%1$d
targetCompatibility = JavaVersion.VERSION_%1$d
}
tasks.test {
useJUnitPlatform()
}""", javaVersion))
.build());
}
}
}
The following feature adds Gradle Application Plugin:
package io.micronaut.projectgen.demo;
import io.micronaut.projectgen.core.buildtools.gradle.GradlePlugin;
import io.micronaut.projectgen.core.feature.Feature;
import io.micronaut.projectgen.core.generator.GeneratorContext;
import io.micronaut.projectgen.core.utils.OptionUtils;
import jakarta.inject.Singleton;
@Singleton
class GradleApplicationPluginFeature implements Feature {
@Override
public String getName() {
return "gradle-plugin-application";
}
@Override
public void apply(GeneratorContext generatorContext) {
if (OptionUtils.hasGradleBuildTool(generatorContext.getOptions())) {
generatorContext.getRootModule()
.addBuildPlugin(GradlePlugin.builder()
.id("application")
.extension("""
application {
mainClass.set("%s.HelloWorld")
}""".formatted(generatorContext.getOptions().packageName()))
.build());
}
}
}
6.5 Add Maven Plugins
You can use ProjectGen MavenPlugin API to add Maven Plugins to a project.
The following feature adds the Maven Jar Plugin.
package io.micronaut.projectgen.demo;
import io.micronaut.projectgen.core.buildtools.maven.MavenPlugin;
import io.micronaut.projectgen.core.feature.Feature;
import io.micronaut.projectgen.core.generator.GeneratorContext;
import jakarta.inject.Singleton;
@Singleton
class MavenJarPluginFeature implements Feature {
@Override
public String getName() {
return "maven-jar-plugin";
}
@Override
public void apply(GeneratorContext generatorContext) {
generatorContext.getRootModule().addBuildPlugin(MavenPlugin.builder()
.groupId("org.apache.maven.plugins")
.artifactId("maven-jar-plugin")
.extension(String.format("""
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<manifest>
<mainClass>%s.HelloWorld</mainClass>
</manifest>
</archive>
</configuration>
</plugin>""", generatorContext.getOptions().packageName()))
.build());
}
}
The following feature adds Maven Surefire Plugin:
package io.micronaut.projectgen.demo;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.projectgen.core.buildtools.maven.MavenPlugin;
import io.micronaut.projectgen.core.feature.Feature;
import io.micronaut.projectgen.core.generator.GeneratorContext;
import io.micronaut.projectgen.core.utils.OptionUtils;
import jakarta.inject.Singleton;
@Singleton
class MavenSurefirePlugin implements Feature {
private static final @NonNull MavenPlugin MAVEN_PLUGIN_SUREFIRE = MavenPlugin.builder() (1)
.groupId("org.apache.maven.plugins")
.artifactId("maven-surefire-plugin")
.version("3.1.2")
.build();
@Override
public String getName() {
return "maven-surefire-plugin";
}
@Override
public void apply(GeneratorContext generatorContext) {
if (OptionUtils.hasMavenBuildTool(generatorContext.getOptions())) {
generatorContext.getRootModule().addBuildPlugin(MAVEN_PLUGIN_SUREFIRE);
}
}
}
6.6 Add Code
The following feature adds source code:
package io.micronaut.projectgen.demo;
import io.micronaut.projectgen.core.feature.Feature;
import io.micronaut.projectgen.core.generator.GeneratorContext;
import io.micronaut.projectgen.core.generator.ModuleContext;
import io.micronaut.projectgen.core.template.StringTemplate;
import jakarta.inject.Singleton;
@Singleton
class SampleCode implements Feature {
@Override
public String getName() {
return "sample-code";
}
@Override
public void apply(GeneratorContext generatorContext) {
ModuleContext module = generatorContext.getRootModule();
addHelloWorldJavaClass(module);
addHelloWorldTestJavaClass(module);
}
private void addHelloWorldJavaClass(ModuleContext module) {
String path = "src/main/java/com/example/HelloWorld.java";
module.addTemplate("HelloWorld.java", new io.micronaut.projectgen.core.template.StringTemplate(path, """
package com.example;
public class HelloWorld {
public static void main(String[] args) {
System.out.println(hello());
}
public static String hello() {
return "Hello, World!";
}
}
"""));
}
private void addHelloWorldTestJavaClass(ModuleContext module) {
String path = "src/test/java/com/example/HelloWorldTest.java";
module.addTemplate("HelloWorldTest.java", new StringTemplate(path, """
package com.example;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class HelloWorldTest {
@Test
void testHello() {
assertEquals("Hello, World!", HelloWorld.hello());
}
}"""));
}
}
You can add files to the generated project. The previous example shows a String, but you can use the RockerWritable API to render files with Rocker Templates.
6.7 Default Feature
You can create beans of type DefaultFeature to be
automatically applied if the method DefaultFeature::shouldApply
evaluates to true.
A common pattern is to define a DefaultFeature
to act as the entry point of the project generation.
A feature can apply other features as shown below:
package io.micronaut.projectgen.demo;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.projectgen.core.buildtools.BuildProperties;
import io.micronaut.projectgen.core.buildtools.MavenCentral;
import io.micronaut.projectgen.core.buildtools.dependencies.Dependency;
import io.micronaut.projectgen.core.buildtools.gradle.GradlePlugin;
import io.micronaut.projectgen.core.buildtools.maven.MavenPlugin;
import io.micronaut.projectgen.core.feature.DefaultFeature;
import io.micronaut.projectgen.core.feature.Feature;
import io.micronaut.projectgen.core.feature.FeatureContext;
import io.micronaut.projectgen.core.generator.GeneratorContext;
import io.micronaut.projectgen.core.generator.ModuleContext;
import io.micronaut.projectgen.core.options.Options;
import io.micronaut.projectgen.core.utils.OptionUtils;
import jakarta.inject.Singleton;
import io.micronaut.projectgen.core.template.StringTemplate;
import java.util.Set;
@Singleton
class Root implements DefaultFeature {
private final GradleJavaPluginFeature gradleJavaPluginFeature;
private final GradleApplicationPluginFeature gradleApplicationPluginFeature;
private final JunitJupiter junitJupiter;
private final MavenSurefirePlugin mavenSurefirePlugin;
private final MavenJarPluginFeature mavenJarPluginFeature;
private final SampleCode sampleCode;
private final MavenCompilerProperties mavenCompilerProperties;
Root(GradleJavaPluginFeature gradleJavaPluginFeature,
GradleApplicationPluginFeature gradleApplicationPluginFeature,
JunitJupiter junitJupiter,
MavenSurefirePlugin mavenSurefirePlugin,
MavenJarPluginFeature mavenJarPluginFeature,
SampleCode sampleCode, MavenCompilerProperties mavenCompilerProperties) {
this.gradleJavaPluginFeature = gradleJavaPluginFeature;
this.gradleApplicationPluginFeature = gradleApplicationPluginFeature;
this.junitJupiter = junitJupiter;
this.mavenSurefirePlugin = mavenSurefirePlugin;
this.mavenJarPluginFeature = mavenJarPluginFeature;
this.sampleCode = sampleCode;
this.mavenCompilerProperties = mavenCompilerProperties;
}
@Override
public void processSelectedFeatures(FeatureContext featureContext) {
featureContext.addFeature(gradleJavaPluginFeature);
featureContext.addFeature(gradleApplicationPluginFeature);
featureContext.addFeature(junitJupiter);
featureContext.addFeature(mavenSurefirePlugin);
featureContext.addFeature(mavenJarPluginFeature);
featureContext.addFeature(sampleCode);
featureContext.addFeature(mavenCompilerProperties);
}
@Override
public boolean shouldApply(Options options, Set<Feature> selectedFeatures) {
return true;
}
@Override
public String getName() {
return "entry-point";
}
@Override
public String getDescription() {
return "It generates a Hello World Maven and Gradle project";
}
@Override
public boolean isVisible() {
return false;
}
@Override
public void apply(GeneratorContext generatorContext) {
ModuleContext module = generatorContext.getRootModule();
Options options = generatorContext.getOptions();
populateModuleAttributes(module, options);
}
private void populateModuleAttributes(ModuleContext module, Options options) {
module.moduleAttributes()
.setCoordinate(Dependency.builder()
.groupId(options.group())
.artifactId(options.artifact())
.version(options.version())
.build());
}
}
6.8 Project Generation in a Command Line Application
Generate projects in a command line application is a common use case of project generation. The following example shows a Micronaut CLI application which generates a project:
package io.micronaut.projectgen.demo;
import io.micronaut.configuration.picocli.PicocliRunner;
import io.micronaut.projectgen.core.generator.ProjectGenerator;
import io.micronaut.projectgen.core.options.Options;
import jakarta.inject.Inject;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import java.io.File;
@Command(
name = "projectgen",
description = "Generates a project in the supplied folder",
mixinStandardHelpOptions = true
)
public class ProjectGenCommand implements Runnable {
@CommandLine.Option(
names = { "--output", "-o" },
required = true,
description = "The output folder where the project file will be generated")
private File outputDir;
@Inject
ProjectGenerator projectGenerator; (1)
public static void main(String[] args) {
PicocliRunner.run(ProjectGenCommand.class, args);
}
public void run() {
if (!outputDir.exists() || !outputDir.isDirectory()) {
System.err.println("Provided path is not an existing directory: " + outputDir);
} else {
Options options = OptionsFactory.create();
projectGenerator.writeTo(options, outputDir);
}
}
}
1 | To generate a project into a folder, you can use the ProjectGenerator API. |
6.9 Test Project Generation
You can test the project generation easily:
package io.micronaut.projectgen.demo;
import io.micronaut.projectgen.core.buildtools.BuildTool;
import io.micronaut.projectgen.core.io.PreviewGenerator;
import io.micronaut.projectgen.core.options.Options;
import io.micronaut.projectgen.test.BuildTestVerifier;
import io.micronaut.projectgen.test.GradleBuildTestVerifier;
import io.micronaut.projectgen.test.MavenBuildTestVerifier;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import org.junit.jupiter.api.Test;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@MicronautTest(startApplication = false)
class ProjectGenTest {
@Test
void testProjectGeneration(PreviewGenerator previewGenerator) throws Exception {
Options options = OptionsFactory.create();
Map<String, String> project = previewGenerator.generate(options);
assertTrue(project.containsKey("mvnw"));
assertTrue(project.containsKey("mvnw.bat"));
assertTrue(project.containsKey(".mvn/wrapper/maven-wrapper.jar"));
assertTrue(project.containsKey(".mvn/wrapper/maven-wrapper.properties"));
assertTrue(project.containsKey("pom.xml"));
assertTrue(project.containsKey("src/main/java/com/example/HelloWorld.java"));
String pom = project.get("pom.xml");
BuildTestVerifier verifier = new MavenBuildTestVerifier(pom, options.language());
assertEquals("21", verifier.getProperty("maven.compiler.source"));
assertEquals("21", verifier.getProperty("maven.compiler.target"));
assertTrue(verifier.hasBuildPlugin("org.apache.maven.plugins", "maven-jar-plugin"));
String buildGradleKts = project.get("build.gradle.kts");
verifier = new GradleBuildTestVerifier(buildGradleKts, BuildTool.GRADLE, options.language(), options.testFramework());
assertTrue(verifier.hasBuildPlugin("java"));
assertTrue(verifier.hasBuildPlugin("application"));
assertTrue(project.containsKey("settings.gradle.kts"));
String settings = project.get("settings.gradle.kts");
assertEquals("""
rootProject.name="demo"
""", settings);
assertTrue(project.containsKey("gradlew"));
assertTrue(project.containsKey("gradlew.bat"));
assertTrue(project.containsKey("gradle/wrapper/gradle-wrapper.jar"));
assertTrue(project.containsKey("gradle/wrapper/gradle-wrapper.properties"));
}
}
7 Micronaut Project Generation
To generate a Micronaut application, add the following dependencies:
implementation("io.micronaut.projectgen:micronaut-projectgen-micronaut")
<dependency>
<groupId>io.micronaut.projectgen</groupId>
<artifactId>micronaut-projectgen-micronaut</artifactId>
</dependency>
To generate a Micronaut application, inject a bean of type MicronautProjectGenerator
8 OpenRewrite Integration
To use OpenRewrite integration your features should implement OpenRewriteFeature
To run a list of OpenRewrite recipes in a Maven build which adds the OpenRewrite Maven Plugin add the following dependency:
implementation("io.micronaut.projectgen:micronaut-projectgen-runner-maven")
<dependency>
<groupId>io.micronaut.projectgen</groupId>
<artifactId>micronaut-projectgen-runner-maven</artifactId>
</dependency>
To run a list of OpenRewrite recipes in a Gradle build which adds the OpenRewrite Gradle Plugin add the following dependency:
implementation("io.micronaut.projectgen:micronaut-projectgen-runner-gradle")
<dependency>
<groupId>io.micronaut.projectgen</groupId>
<artifactId>micronaut-projectgen-runner-gradle</artifactId>
</dependency>
9 Repository
You can find the source code of this project in this repository: