/ ClassNotFoundException

using a nested model for your custom gradle task or plugin

You probably know about using Annotation Types for your custom Gradle plugin input values:

class MyTask extends DefaultTask {  
  @Input  
  String aSimpleProperty  
  @InputFile  
  File anInputFile  
  @Input  
  @Optional  
  String[] optionalProperty  
  @OutputFile  
  File anOutputFile

  @TaskAction  
  def performTask() {  
    // …  
  }  
}

Using complex task input

When using a more complex input model, you can try something like this:

class TaskConfiguration {  
  String aSimpleProperty  
  File anInputFile  
  String[] optionalProperty  
}

class MyTask extends DefaultTask {  
  @Input  
  TaskConfiguration myConfiguration  
  @OutputFile  
  File anOutputFile

  @TaskAction  
  def performTask() {  
    // …  
  }  
}

After running that task Gradle will try to serialize your TaskConfiguration, but will fail due to the missing Serializable interface declaration. Gradle needs to serialize your input so that it can detemine in following builds whether any input has changed. You get error messages like:

org.gradle.api.UncheckedIOException: Could not add entry ‚:projectname:taskname‘ to cache taskArtifacts.bin (/home/user/projectname/.gradle/1.8/taskArtifacts/taskArtifacts.bin).  
 at org.gradle.cache.internal.btree.BTreePersistentIndexedCache.put(BTreePersistentIndexedCache.java:156)  
 at org.gradle.cache.internal.MultiProcessSafePersistentIndexedCache$2.run(MultiProcessSafePersistentIndexedCache.java:53)  
 Caused by: java.io.NotSerializableException: de.projectname.TaskConfiguration  
 at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1180)

Making the task input serializable…

Adding the java.io.Serializable interface to your TaskConfiguration won’t be enough, because Gradle also needs an equals() implementation to compare the serialized model with the current one. That’s quite much compared to the properties in your configuration model. Especially when using the daemon you can get error messages like:

org.gradle.api.UncheckedIOException: Could not read entry ‚:projectname:taskname‘ from cache taskArtifacts.bin (/home/user/projectname/.gradle/1.8/taskArtifacts/taskArtifacts.bin).  
 at org.gradle.cache.internal.btree.BTreePersistentIndexedCache.get(BTreePersistentIndexedCache.java:129)  
 Caused by: java.lang.ClassNotFoundException: de.projectname.TaskConfiguration  
 at java.net.URLClassLoader$1.run(URLClassLoader.java:366)

… the Gradle way

Looking at the Gradle documentation you’ll step over the @Nested annotation. The @Nested annotation allows you to configure each property of your task input like this:

class TaskConfiguration {  
  @Input  
  String aSimpleProperty  
  @InputFile  
  File anInputFile  
  @Input  
  @Optional  
  String[] optionalProperty  
}

class MyTask extends DefaultTask {  
  @Nested  
  TaskConfiguration myConfiguration  
  @OutputFile  
  File anOutputFile

  @TaskAction  
  def performTask() {  
    // …  
  }  
}

You might as well use @Nested to create a more complex model:

class NestedConfiguration {  
  @Input  
  String yetAnotherProperty  
}

class TaskConfiguration {  
  @Input  
  String aSimpleProperty  
  @InputFile  
  File anInputFile  
  @Input  
  @Optional  
  String[] optionalProperty  
  @Nested  
  NestedConfiguration nestedConfig  
}

class MyTask extends DefaultTask {  
  @Nested  
  TaskConfiguration myConfiguration  
  @OutputFile  
  File anOutputFile

  @TaskAction  
  def performTask() {  
    // …  
  }  
}

That way you don’t need any Serializable or equals() boilerplate code and Gradle can still check your task configuration for changes. You can find an example at the gradle-debian-plugin repository.