diff --git a/.travis.yml b/.travis.yml
index 1e3c285847ba1de435c0c20fa4a6b6945f3a6c5b..37b747e05f5e34432e465430e994f5404f735c52 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -22,6 +22,13 @@ cache:
     - $HOME/.gradle/caches/
     - $HOME/.gradle/wrapper/
 
+script:
+  - ./gradlew check
+  - ./gradlew codeCoverageReport
+
+after_success:
+  - bash <(curl -s https://codecov.io/bash)
+
 notifications:
   irc:
     channels:
diff --git a/build.gradle b/build.gradle
index 180888ec3d2616c42fa0a14419f226b5bdf3fc6f..af398cf737d275e33b7c90ccf5bed8655bede6ea 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,6 +1,9 @@
 // Exclude apps/ dir itself, but include its subdirs
-configure(subprojects - project(':apps')) {
+def javaProjects = subprojects - project(':apps')
+
+configure(javaProjects) {
     apply plugin: 'java'
+    apply plugin: 'jacoco'
     apply plugin: 'eclipse'
     apply plugin: 'idea'
 
@@ -29,4 +32,25 @@ configure(subprojects - project(':apps')) {
     }
 }
 
+task codeCoverageReport(type: JacocoReport) {
+    dependsOn(javaProjects.test)
+
+    jacocoClasspath = project(':core').configurations.jacocoAnt
+    additionalSourceDirs = files(javaProjects.sourceSets.main.allSource.srcDirs)
+    sourceDirectories = files(javaProjects.sourceSets.main.allSource.srcDirs)
+    classDirectories = files(javaProjects.sourceSets.main.output)
+    executionData = files(javaProjects.jacocoTestReport.executionData)
+
+    doFirst {
+        executionData = files(executionData.findAll { it.exists() })
+    }
+
+    reports {
+        xml.enabled true
+        xml.destination "${buildDir}/reports/jacoco/report.xml"
+        html.enabled true
+        html.destination "${buildDir}/reports/jacoco/html"
+    }
+}
+
 //apply from: file('gradle/update.gradle')