NullPointer exception when generating code using swagger codegen with openapi 3.0
A NullPointer issue was encountered while generating code through swagger codegen.
I’m new to gradle and have updated build.gradle with the latest changes needed to generate code for a given API spec.
Please provide your valuable comments.
My build.gradle is shown below:
import io.swagger.codegen.v3.CodegenConfigLoader
import io.swagger.codegen.v3.DefaultGenerator
import io.swagger.codegen.v3.ClientOptInput
import io.swagger.codegen.v3.ClientOpts
import io.swagger.v3.parser.OpenAPIV3Parser
buildscript {
ext {
springBootVersion = '2.0.5.RELEASE'
jacocoWorkspaceDirectory = "/jacoco/"
}
repositories {
mavenLocal()
maven { url "http://repo.maven.apache.org/maven2" }
}
dependencies {
classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion"
classpath group: "io.spring.gradle", name: "dependency-management-plugin", version: "1.0.6.RELEASE"
classpath 'co.bambo.sonar:bambo-sonar-gradle-plugin:2.7.1.RELEASE'
classpath group: "org.unbroken-dome.gradle-plugins", name: "gradle-testsets-plugin", version: "1.4.2"
classpath('io.swagger.codegen.v3:swagger-codegen-maven-plugin:3.0.16')
classpath("io.swagger.core.v3:swagger-core:2.1.1")
}
}
apply plugin: 'java'
apply plugin: 'maven'
apply plugin: 'maven-publish'
apply plugin: "io.spring.dependency-management"
apply plugin: "org.springframework.boot"
apply plugin: 'bambo-sonar'
apply plugin: "jacoco"
apply plugin: "findbugs"
apply plugin: "checkstyle"
apply plugin: "pmd"
apply plugin: 'org.unbroken-dome.test-sets'
project.buildDir = 'target'
ext {
appName = 'school-management'
apiPackage = 'co.bambo.school.management.generated.api'
modelPackage = 'co.bambo.school.management.generated.model'
swaggerFile = "${rootDir}/src/main/resources/schoolmanagement.v1.yaml"
swaggerBuildDir = "${project.buildDir}/" + appName
}
NOTE :-> Following are to be included as and when required.
These variables are defined for the purpose of exclusion from the sonar scan. Packages mentioned will be exluded
from Sonar Scans.
def excludeschoolmanagementConstants = "src/main/java/co/bambo/school/management/constants/schoolmanagementConstants.java"
def excludeCaasConstants = "src/main/java/co/bambo/school/management/pcf/constants/CaasConstants.java"
sourceCompatibility = 1.8
targetCompatibility = 1.8
group = 'co.bambo.schoolmanagement'
version = '1.0.0-SNAPSHOT'
tasks.withType(JavaCompile) {
options.encoding = 'UTF-8'
}
repositories {
mavenLocal()
maven { url "http://repo.maven.apache.org/maven2" }
}
configurations {
testUtilCompile.extendsFrom testImplementation
testUtilRuntime.extendsFrom testRuntime
}
sourceSets {
testUtil {
java {
srcDir 'src/test-util/java'
}
}
}
testSets {
funcTest { dirName = "func-test" }
}
PLUGIN CONFIGs
springBoot {
mainClassName = 'co.bambo.school.management.schoolmanagementApplication'
buildInfo()
}
bootJar {
baseName = 'school-management'
destinationDir = project.buildDir
archiveName = 'school-management.jar'
}
FIXME - IN FUTURE WE NEED TO MAKE THIS to Zero.
checkstyle {
maxErrors = 70
maxWarnings = 70
toolVersion = 8.17
}
[checkstyleMain, checkstyleTest].each { task ->
task.logging.setLevel(LogLevel.LIFECYCLE)
FIXME - This is to be updated with False.
task.ignoreFailures = true
}
findbugs {
toolVersion = "3.0.1"
sourceSets = [sourceSets.main]
FIXME - Make this false.
ignoreFailures = true
effort = "max"
reportLevel = "medium"
excludeFilter = file("$rootDir/config/findbugs/exclude.xml")
}
pmd {
toolVersion = "5.6.1"
ignoreFailures = true
sourceSets = [sourceSets.main]
reportsDir = file("${project.buildDir}/reports/pmdTest")
ruleSets = [
'java-basic'
]
}
tasks.withType(FindBugs) {
reports {
xml.enabled = false
html.enabled = true
}
}
tasks.withType(Pmd) {
reports {
xml.enabled = false
html.enabled = true
}
}
sonarqube {
properties {
property "sonar.projectName", "school-management"
property "sonar.projectKey", "school-management"
property "sonar.jacoco.reportPath", "build/jacoco/test.exec"
property "sonar.junit.reportPaths", "build/test-results/test"
property "sonar.host.url", "https://fusion.bambo.int/sonar"
property "sonar.gateId", "307"
property "sonar.projectDescription", "school management microservice."
property "sonar.skip.qualityGate", "false"
property "sonar.exclusions", [excludeschoolmanagementConstants, excludeCaasConstants]
}
}
Actual task for generating the server
task generateServer {
doLast {
def openAPI = new OpenAPIV3Parser().read(rootProject.swaggerFile.toString(), null, null)
def clientOptInput = new ClientOptInput().openAPI(openAPI)
def codeGenConfig = CodegenConfigLoader.forName('spring')
codeGenConfig.setApiPackage(rootProject.ext.apiPackage) // Package to be used for the API interfaces
codeGenConfig.setModelPackage(rootProject.ext.modelPackage) // Package to be used for the API models
codeGenConfig.setInputSpec(rootProject.ext.swaggerFile.toString()) // The swagger API file
codeGenConfig.setOutputDir(rootProject.ext.swaggerBuildDir)
The output directory, user-service-contract/build/user-service-server/
codegenConfig.addSystemProperty("models", "");
codegenConfig.addSystemProperty("apis", "");
def clientOps = new ClientOpts()
clientOps.setProperties([
'dateLibrary' : 'java8', // Date library to use
'useTags' : 'true', // Use tags for the naming
'interfaceOnly': 'false'// Generating the Controller API interface and the models only
])
clientOptInput.setOpts(clientOps)
def generator = new DefaultGenerator().opts(clientOptInput)
generator.generate() // Executing the generation
}
}
compileJava {
dependsOn generateServer
}
build {
dependsOn generateServer
}
---- PLUGIN CONFIG ENDs
dependencyManagement {
imports {
mavenBom "org.springframework.boot:spring-boot-dependencies:$springBootVersion"
}
}
funcTest {
doFirst {
jacoco {
destinationFile = file("${buildDir}" + jacocoWorkspaceDirectory + "test.exec")
}
}
dependsOn cleanTest
dependsOn compileTestUtilJava
dependsOn cleanJacocoTestReport
dependsOn cleanJacocoTestCoverageVerification
finalizedBy jacocoTestReport
finalizedBy jacocoTestCoverageVerification
environment SPRING_PROFILES_ACTIVE: environment. SPRING_PROFILES_ACTIVE ?: "local"
}
dependencies {
Spring specific dependencies
implementation('org.springframework:spring-tx')
implementation('org.springframework:spring-core')
implementation('org.springframework:spring-context')
implementation('org.springframework:spring-beans')
implementation('org.springframework:spring-expression')
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-actuator'
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-aop'
implementation group: 'org.springframework.retry', name: 'spring-retry'
implementation group: 'org.springframework.boot', name: "spring-boot-starter-data-jpa"
Spring cloud
Upgraded springcloud-starter-vault from 2.0.0.RC1 to 2.0.1.RELEASE.
implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-vault-config', version: '2.0.1.RELEASE'
implementation group: 'io.pivotal.spring.cloud', name: 'spring-cloud-services-starter-config-client', version: '2.0.1.RELEASE'
Upgraded "spring-cloud-starter-bus-amqp" following from 2.0.0.RC1 to 2.0.1.RELEASE
implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-bus-amqp', version: '2.0.1.RELEASE'
SWagger dependencies
implementation group: 'io.swagger', name: 'swagger-parser', version: '1.0.23'
implementation group: 'io.springfox', name: 'springfox-swagger2', version: '2.9.2'
implementation group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.9.2'
implementation group: 'com.atlassian.oai', name: 'swagger-request-validator-core', version: '1.3.9'
Apache - Commons
implementation group: 'commons-io', name: 'commons-io', version: '2.6'
Oracle dependencies
implementation group: 'com.oracle', name: 'ojdbc7', version: '12.1.0'
2nd level cache dependency
implementation group: 'org.hibernate', name: 'hibernate-ehcache'
Logback
implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3'
implementation group: 'ch.qos.logback', name: 'logback-core', version: '1.2.3'
implementation group: 'org.apache.camel', name: 'camel-jsonpath', version: '2.22.0'
implementation group: 'com.google.gdata', name: 'core', version: '1.47.1'
implementation group: 'com.google.guava', name: 'guava', version: '20.0'
FIXME - Eventually phase - out this Mapper
implementation group: 'ma.glasnost.orika', name: 'orika-core', version: '1.5.4'
JSON
implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-xml', version: '2.8.10'
implementation group: 'com.googlecode.json-simple', name: 'json-simple', version: '1.1.1'
Mapstruct
implementation 'org.mapstruct:mapstruct:1.3.0.Final'
implementation 'org.mapstruct:mapstruct-processor:1.3.0.Final'
implementation group: 'ch.qos.logback.contrib', name: 'logback-json-classic', version: '0.1.5'
implementation group: 'ch.qos.logback.contrib', name: 'logback-jackson', version: '0.1.5'
testImplementation "junit:junit:4.12"
testImplementation group: "org.springframework.boot", name: "spring-boot-starter-test"
testImplementation group: 'com.github.tomakehurst', name: 'wiremock', version: '2.21.0'
testUtilCompile sourceSets.main.output
funcTestCompile sourceSets.testUtil.output
}
I had a NullPointer
problem in the def generator = new DefaultGenerator().opts(clientOptInput) line.
Update -1
I know this issue seems fairly straightforward for NullPointer exceptions in java. But trust me, I can’t figure out in build.gradle why it fails because it doesn’t even show the correct error message stating what’s missing. The message shown below is what I got. Even debugging of groovy scripts won’t help.
Execution failed for task ':generateServer'.
> java.lang.NullPointerException (no error message)
Update 2
I just saw in the DefaultGenerator .java on line 77 and found this line.
This is how I get NullPointerException this.config.additionalProperties().putAll(opts.getOpts().getProperties());
place
I’m not sure what’s missing in the parameters. I’m passing opts
and getOpts()
is populating the properties file as well.
Use Gradle version 4.9.
Solution
Well, I see, this is a stupid mistake both in understanding and in code.
I only learned about this bug after debugging and going through the swagger-codegen
code. I realized that generating code requires configuration, and it is generating code based on the specified configuration. I’m building a spring-based
CodeGenConfiguration, but I’m not passing it to clientOptInput. My understanding is wrong, I’m assuming it will load CodeGenConfig’s class (which it does) and that’s it. However, you also need to pass it to the clientOptInput
variable.
def codeGenConfig = CodegenConfigLoader.forName('spring')
clientOptInput.setConfig(codeGenConfig)
Because the code for DefaultGenerator.java
is as follows:
@Override
public Generator opts(ClientOptInput opts) {
this.opts = opts;
this.openAPI = opts.getOpenAPI();
this.config = opts.getConfig();
I need to set it up as a ClientOptInput
instance. Here’s why. Post 🙂 🙂 for people like me who make mistakes