Normally when using swagger, you generate a swagger.yaml file for your API. But what if you already have a swagger.yaml file and you want to generate the API interface and models, like you would also do with a webservice using a WSDL file? To achieve this, swagger has a great tool: swagger-codegen. The tool has a CLI and a maven plugin, but no gradle plugin. So how do we use it with gradle?

Here is a list of things we need to do to get it to work:

  • Create a gradle task that generates the API interface every time we build (which should be generated to src/generated/java to keep everything separated)

  • Make sure ‘gradle clean’ also cleans the generated files

  • Support gradle incremental builds

  • Making sure it compiles: the generated classes should be available in src/main/java, and execute the generate task before build/compile

  • BONUS: IntelliJ should generate the files automatically when you import the project or sync the project.

A big thanks to my colleague Willem Cheizoo from JDriven for helping me create this list and pointing me in the right direction.

Creating a generate task

To be able to use the codegen in our gradle task, we need to add the dependency to the buildscript in our build.gradle file.

buildscript {
  ...
  dependencies {
    ...
    classpath('io.swagger:swagger-codegen:2.2.2')
  }
}

Now we can create a gradle task that generates our API using the swagger-codegen tool.

import io.swagger.codegen.config.CodegenConfigurator
import io.swagger.codegen.DefaultGenerator

def swaggerSourceFile = 'src/main/resources/petstore-minimal.yaml'
def swaggerTargetFolder = 'src/generated/java'

task generateApi {
  doLast {
    def config = new CodegenConfigurator()
    config.setInputSpec("file:///$projectDir/$swaggerSourceFile")
    config.setOutputDir("$projectDir")
    config.setLang('spring')
    config.setAdditionalProperties([
        'interfaceOnly' : 'true',
        'apiPackage'    : 'com.dturan.api',
        'modelPackage'  : 'com.dturan.model',
        'sourceFolder'  : swaggerTargetFolder
    ])
    new DefaultGenerator().opts(config.toClientOptInput()).generate()
  }
}

So there are a few things going on here. The inputSpec is our swagger file describing our API. This can also be a json file, or a link to a hosted swagger file.

The output directory is the same as our project directory. This is because swagger-codegen generates a whole project including a pom.xml. To prevent it from generating a pom.xml we can add a .swagger-codegen-ignore file, which works just like a .gitignore file. As for language I went for a Spring Boot Java implementation: ‘spring’, but you can use whatever you need here.

By default, the codegen also generates rest controllers for Spring which you have to modify. But we want it to generate the interfaces only. After that we set the package names for the API, and the package names for the Models. And because we don’t want it to mix with our src/main/java classes, we set the target folder as src/generated/java.

The codegen supports many more options and languages, so you can really customize it the way you want or need. See the swagger-codegen github page for a full documentation.

Now let’s test our task:

first test gradle api

With as result:

swagger gradle package structure

We can see it works and generates the files, so that takes care of the first item in our list.

Supporting gradle clean

Because we want to make sure that there is nothing left from the previous build, we need to make sure that gradle clean removes our generated classes. To achieve that, simply add the following to the build.gradle file:

clean.doFirst {
  delete("${projectDir}/$swaggerTargetFolder")
}

Supporting gradle incremental builds

Incremental builds in gradle makes sure that gradle doesn’t waste time executing a task it has done before. In our case, if the swagger.yaml file hasn’t changed, and the correct output is still there, why execute the task again? This is also one of the reasons why gradle is a lot faster than Maven, it doesn’t recompile files that didn’t change. To support this, we only have to let gradle know what our input and output is, by adding it to the top of our task:

task generateApi {
  inputs.file("$projectDir/$swaggerSourceFile")
  outputs.dir("$projectDir/$swaggerTargetFolder")
  doLast{
    ...
  }
}

Now let’s run it twice to see if it works:

second test gradle api

The second time shows that our incremental build support is working because there is UP-TO-DATE behind it. That means that the input and output hasn’t changed, so gradle skips this task. In this case we didn’t save a lot of time because our API is small. But if you have a large API it can save you a few seconds.

Making sure it compiles

Now we need to make sure that everything compiles, and that we can use the interface in our own code.

  • First we add a new configuration, so we can add the dependencies needed for the API interface. In our case the generated code uses the swagger dependencies and a Spring dependency.

  • Next we need to add the generated classes as a source set to the Gradle Java plugin. And we need to modify the main and test source sets to include the compiled classes from our new source set.

  • Lastly, we need to make sure everything runs in the correct order. ‘compileGeneratedJava’ and ‘generatedClasses’ are tasks that are automatically generated by the Gradle Java plugin. So before the compilation of the generated classes can happen, we need to actually generate them. And before the compilation of the main source set (the task ‘compileJava’ or ‘classes’) is run, we need to make sure the generated source set is already compiled.

configurations {
  generatedCompile
}

dependencies {
  ...
  generatedCompile 'org.springframework.boot:spring-boot-starter-data-rest'
  generatedCompile 'io.springfox:springfox-swagger2:2.5.0'
  generatedCompile 'io.springfox:springfox-swagger-ui:2.5.0'
}

sourceSets {
  generated {
    compileClasspath = configurations.generatedCompile
  }
  main {
    compileClasspath += generated.output
    runtimeClasspath += generated.output
  }
  test {
    compileClasspath += generated.output
    runtimeClasspath += generated.output
  }
}

compileGeneratedJava.dependsOn generateApi
classes.dependsOn generatedClasses
compileJava.dependsOn compileGeneratedJava

Now we can test everything using ‘gradle clean build’:

third test gradle api

Great, it works. And if you are using Spring Boot, like me, you need to add the following as well to include the classes in the bootRun and fatJar:

bootRun {
  classpath += sourceSets.generated.output
}

jar {
  from sourceSets.generated.output
}

I also created a working example project with Spring Boot which you can use to play around with. You can find it on github.

Bonus: IntelliJ integration

To make sure it runs when you import the project to IntelliJ, we can modify the build.gradle a little bit:

apply plugin: 'idea'
...
ideaModule.dependsOn generateApi

And that’s it! You can also configure it in IntelliJ that it runs with every sync, but unfortunately we can’t do that using the build.gradle file. Simply right click the task in the IntelliJ gradle tool:

run before sync
shadow-left