diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml
new file mode 100644
index 0000000000000000000000000000000000000000..1e63e0e3b7ad2ff6e03191bbdb8588b292107dfe
--- /dev/null
+++ b/config/checkstyle/checkstyle.xml
@@ -0,0 +1,19 @@
+<!DOCTYPE module PUBLIC
+          "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
+          "https://checkstyle.org/dtds/configuration_1_3.dtd">
+<module name="Checker">
+  <property name="tabWidth" value="4"/>
+  <property name="charset" value="UTF-8"/>
+  <module name="LineLength">
+    <property name="fileExtensions" value="java"/>
+    <property name="max" value="100"/>
+    <property name="ignorePattern" value="^package.*|^import.*|a href|href|http://|https://|ftp://"/>
+  </module>
+  <module name="TreeWalker">
+    <module name="RegexpSinglelineJava">
+      <property name="format" value="^\t* +\t*\S"/>
+      <property name="message" value="Line has leading space characters; indentation should be performed with tabs only."/>
+      <property name="ignoreComments" value="true"/>
+    </module>
+  </module>
+</module>
diff --git a/onionwrapper-android/build.gradle b/onionwrapper-android/build.gradle
index d1dd271c13dc8629990583f70e95ab5347207dfd..8b531a825de6c248cda747ad2fc3a354ab73c3e4 100644
--- a/onionwrapper-android/build.gradle
+++ b/onionwrapper-android/build.gradle
@@ -1,6 +1,7 @@
 plugins {
     id 'com.android.library'
     id 'com.vanniktech.maven.publish' version '0.18.0'
+    id 'checkstyle'
 }
 
 android {
@@ -27,6 +28,10 @@ android {
     }
 }
 
+checkstyle {
+    configFile = new File('../config/checkstyle/checkstyle.xml')
+}
+
 dependencies {
     api project(':onionwrapper-core')
     api 'org.briarproject:dont-kill-me-lib:0.2.7'
diff --git a/onionwrapper-android/src/main/java/org/briarproject/onionwrapper/AndroidLocationUtils.java b/onionwrapper-android/src/main/java/org/briarproject/onionwrapper/AndroidLocationUtils.java
index 23e8b190469563acd7d0506c95e9f77c52c1b344..3d70a89f1f6be6c7b7e03827a699450c4a91b211 100644
--- a/onionwrapper-android/src/main/java/org/briarproject/onionwrapper/AndroidLocationUtils.java
+++ b/onionwrapper-android/src/main/java/org/briarproject/onionwrapper/AndroidLocationUtils.java
@@ -1,7 +1,5 @@
 package org.briarproject.onionwrapper;
 
-import static android.content.Context.TELEPHONY_SERVICE;
-
 import android.annotation.SuppressLint;
 import android.app.Application;
 import android.content.Context;
@@ -15,57 +13,60 @@ import java.util.logging.Logger;
 
 import javax.inject.Inject;
 
+import static android.content.Context.TELEPHONY_SERVICE;
+
 @NotNullByDefault
 class AndroidLocationUtils implements LocationUtils {
 
-    private static final Logger LOG =
-            Logger.getLogger(AndroidLocationUtils.class.getName());
+	private static final Logger LOG =
+			Logger.getLogger(AndroidLocationUtils.class.getName());
 
-    private final Context appContext;
+	private final Context appContext;
 
-    @Inject
-    AndroidLocationUtils(Application app) {
-        appContext = app.getApplicationContext();
-    }
+	@Inject
+	AndroidLocationUtils(Application app) {
+		appContext = app.getApplicationContext();
+	}
 
-    /**
-     * This guesses the current country from the first of these sources that
-     * succeeds (also in order of likelihood of being correct):
-     *
-     * <ul>
-     * <li>Phone network. This works even when no SIM card is inserted, or a
-     *     foreign SIM card is inserted.</li>
-     * <li>SIM card. This is only an heuristic and assumes the user is not
-     *     roaming.</li>
-     * <li>User locale. This is an even worse heuristic.</li>
-     * </ul>
-     *
-     * Note: this is very similar to <a href="https://android.googlesource.com/platform/frameworks/base/+/cd92588%5E/location/java/android/location/CountryDetector.java">
-     * this API</a> except it seems that Google doesn't want us to use it for
-     * some reason - both that class and {@code Context.COUNTRY_CODE} are
-     * annotated {@code @hide}.
-     */
-    @Override
-    @SuppressLint("DefaultLocale")
-    public String getCurrentCountry() {
-        String countryCode = getCountryFromPhoneNetwork();
-        if (!TextUtils.isEmpty(countryCode)) return countryCode.toUpperCase();
-        LOG.info("Falling back to SIM card country");
-        countryCode = getCountryFromSimCard();
-        if (!TextUtils.isEmpty(countryCode)) return countryCode.toUpperCase();
-        LOG.info("Falling back to user-defined locale");
-        return Locale.getDefault().getCountry();
-    }
+	/**
+	 * This guesses the current country from the first of these sources that
+	 * succeeds (also in order of likelihood of being correct):
+	 *
+	 * <ul>
+	 * <li>Phone network. This works even when no SIM card is inserted, or a
+	 *     foreign SIM card is inserted.</li>
+	 * <li>SIM card. This is only an heuristic and assumes the user is not
+	 *     roaming.</li>
+	 * <li>User locale. This is an even worse heuristic.</li>
+	 * </ul>
+	 * <p>
+	 * Note: this is very similar to
+	 * <a href="https://android.googlesource.com/platform/frameworks/base/+/cd92588%5E/location/java/android/location/CountryDetector.java">
+	 * this API</a> except it seems that Google doesn't want us to use it for
+	 * some reason - both that class and {@code Context.COUNTRY_CODE} are
+	 * annotated {@code @hide}.
+	 */
+	@Override
+	@SuppressLint("DefaultLocale")
+	public String getCurrentCountry() {
+		String countryCode = getCountryFromPhoneNetwork();
+		if (!TextUtils.isEmpty(countryCode)) return countryCode.toUpperCase();
+		LOG.info("Falling back to SIM card country");
+		countryCode = getCountryFromSimCard();
+		if (!TextUtils.isEmpty(countryCode)) return countryCode.toUpperCase();
+		LOG.info("Falling back to user-defined locale");
+		return Locale.getDefault().getCountry();
+	}
 
-    private String getCountryFromPhoneNetwork() {
-        Object o = appContext.getSystemService(TELEPHONY_SERVICE);
-        TelephonyManager tm = (TelephonyManager) o;
-        return tm == null ? "" : tm.getNetworkCountryIso();
-    }
+	private String getCountryFromPhoneNetwork() {
+		Object o = appContext.getSystemService(TELEPHONY_SERVICE);
+		TelephonyManager tm = (TelephonyManager) o;
+		return tm == null ? "" : tm.getNetworkCountryIso();
+	}
 
-    private String getCountryFromSimCard() {
-        Object o = appContext.getSystemService(TELEPHONY_SERVICE);
-        TelephonyManager tm = (TelephonyManager) o;
-        return tm == null ? "" : tm.getSimCountryIso();
-    }
+	private String getCountryFromSimCard() {
+		Object o = appContext.getSystemService(TELEPHONY_SERVICE);
+		TelephonyManager tm = (TelephonyManager) o;
+		return tm == null ? "" : tm.getSimCountryIso();
+	}
 }
diff --git a/onionwrapper-android/src/main/java/org/briarproject/onionwrapper/AndroidLocationUtilsFactory.java b/onionwrapper-android/src/main/java/org/briarproject/onionwrapper/AndroidLocationUtilsFactory.java
index 0708ffa218a5e0ebf9f30c74cd36784f2134af25..b576a3cc99ce9097a452f0091b7e2afa41674acc 100644
--- a/onionwrapper-android/src/main/java/org/briarproject/onionwrapper/AndroidLocationUtilsFactory.java
+++ b/onionwrapper-android/src/main/java/org/briarproject/onionwrapper/AndroidLocationUtilsFactory.java
@@ -7,8 +7,8 @@ import org.briarproject.nullsafety.NotNullByDefault;
 @NotNullByDefault
 public class AndroidLocationUtilsFactory {
 
-    public static LocationUtils createAndroidLocationUtils(Application app) {
-        return new AndroidLocationUtils(app);
-    }
+	public static LocationUtils createAndroidLocationUtils(Application app) {
+		return new AndroidLocationUtils(app);
+	}
 
 }
diff --git a/onionwrapper-android/src/main/java/org/briarproject/onionwrapper/AndroidTorWrapper.java b/onionwrapper-android/src/main/java/org/briarproject/onionwrapper/AndroidTorWrapper.java
index 9ddf657d08d3acdf6a3d4ce262ac706be2e438de..26a6b73058f30351d131071ab2cd6d449815ccb2 100644
--- a/onionwrapper-android/src/main/java/org/briarproject/onionwrapper/AndroidTorWrapper.java
+++ b/onionwrapper-android/src/main/java/org/briarproject/onionwrapper/AndroidTorWrapper.java
@@ -133,7 +133,7 @@ public class AndroidTorWrapper extends AbstractTorWrapper {
 	}
 
 	@Override
-    public File getObfs4ExecutableFile() {
+	public File getObfs4ExecutableFile() {
 		return obfs4Lib.exists() ? obfs4Lib : super.getObfs4ExecutableFile();
 	}
 
diff --git a/onionwrapper-core/build.gradle b/onionwrapper-core/build.gradle
index 7167ad514fb99003cad0f6a697f1f870cc7b3ede..2886fe4ffc41cbc85a19454a6f36b029c25e6c91 100644
--- a/onionwrapper-core/build.gradle
+++ b/onionwrapper-core/build.gradle
@@ -1,6 +1,7 @@
 plugins {
     id 'java-library'
     id 'com.vanniktech.maven.publish' version '0.18.0'
+    id 'checkstyle'
 }
 
 java {
@@ -8,6 +9,10 @@ java {
     targetCompatibility = JavaVersion.VERSION_1_8
 }
 
+checkstyle {
+    configFile = new File('../config/checkstyle/checkstyle.xml')
+}
+
 dependencies {
     api 'org.briarproject:null-safety:0.1'
     api 'com.google.code.findbugs:jsr305:3.0.2'
diff --git a/onionwrapper-core/src/main/java/org/briarproject/onionwrapper/AbstractTorWrapper.java b/onionwrapper-core/src/main/java/org/briarproject/onionwrapper/AbstractTorWrapper.java
index 75c9acffe411054b001d94b45847fd4c4b801768..d857f020d890163d45282d26ba29678d8029ca0f 100644
--- a/onionwrapper-core/src/main/java/org/briarproject/onionwrapper/AbstractTorWrapper.java
+++ b/onionwrapper-core/src/main/java/org/briarproject/onionwrapper/AbstractTorWrapper.java
@@ -107,7 +107,7 @@ abstract class AbstractTorWrapper implements EventHandler, TorWrapper {
 		return new File(torDirectory, "tor");
 	}
 
-    @Override
+	@Override
 	public File getObfs4ExecutableFile() {
 		return new File(torDirectory, "obfs4proxy");
 	}
diff --git a/onionwrapper-core/src/main/java/org/briarproject/onionwrapper/CircumventionProviderFactory.java b/onionwrapper-core/src/main/java/org/briarproject/onionwrapper/CircumventionProviderFactory.java
index e568e43236976093ad5a4fc8cf3647b6febd611f..9ebc340853b9f1c9643ab380632ab149a896d789 100644
--- a/onionwrapper-core/src/main/java/org/briarproject/onionwrapper/CircumventionProviderFactory.java
+++ b/onionwrapper-core/src/main/java/org/briarproject/onionwrapper/CircumventionProviderFactory.java
@@ -5,8 +5,8 @@ import org.briarproject.nullsafety.NotNullByDefault;
 @NotNullByDefault
 public class CircumventionProviderFactory {
 
-    public static CircumventionProvider createCircumventionProvider() {
-        return new CircumventionProviderImpl();
-    }
+	public static CircumventionProvider createCircumventionProvider() {
+		return new CircumventionProviderImpl();
+	}
 
 }
diff --git a/onionwrapper-core/src/main/java/org/briarproject/onionwrapper/LocationUtils.java b/onionwrapper-core/src/main/java/org/briarproject/onionwrapper/LocationUtils.java
index 637f515b85e8584382d69e34932400222cf83a88..34f9e2f93b242a57aa4f34b81ff3badc221ff84e 100644
--- a/onionwrapper-core/src/main/java/org/briarproject/onionwrapper/LocationUtils.java
+++ b/onionwrapper-core/src/main/java/org/briarproject/onionwrapper/LocationUtils.java
@@ -7,28 +7,28 @@ import java.util.Locale;
 @NotNullByDefault
 public interface LocationUtils {
 
-    /**
-     * Get the country the device is currently located in, or "" if it cannot
-     * be determined.
-     * <p>
-     * The country codes are formatted upper-case and as per <a href="
-     * https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2">ISO 3166-1 alpha 2</a>.
-     */
-    String getCurrentCountry();
+	/**
+	 * Get the country the device is currently located in, or "" if it cannot
+	 * be determined.
+	 * <p>
+	 * The country codes are formatted upper-case and as per <a href="
+	 * https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2">ISO 3166-1 alpha 2</a>.
+	 */
+	String getCurrentCountry();
 
-    /**
-     * Returns the name of the country for display in the UI
-     * or the isoCode if none could be found.
-     *
-     * @param isoCode The result from {@link #getCurrentCountry()}.
-     */
-    static String getCountryDisplayName(String isoCode) {
-        for (Locale locale : Locale.getAvailableLocales()) {
-            if (locale.getCountry().equalsIgnoreCase(isoCode)) {
-                return locale.getDisplayCountry();
-            }
-        }
-        // Name is unknown
-        return isoCode;
-    }
+	/**
+	 * Returns the name of the country for display in the UI
+	 * or the isoCode if none could be found.
+	 *
+	 * @param isoCode The result from {@link #getCurrentCountry()}.
+	 */
+	static String getCountryDisplayName(String isoCode) {
+		for (Locale locale : Locale.getAvailableLocales()) {
+			if (locale.getCountry().equalsIgnoreCase(isoCode)) {
+				return locale.getDisplayCountry();
+			}
+		}
+		// Name is unknown
+		return isoCode;
+	}
 }
diff --git a/onionwrapper-core/src/main/java/org/briarproject/onionwrapper/TorWrapper.java b/onionwrapper-core/src/main/java/org/briarproject/onionwrapper/TorWrapper.java
index 21d05106b955359335bbee002b54888ccc046b18..ed04b3c869850b629a84a0ec0304909e3862cce1 100644
--- a/onionwrapper-core/src/main/java/org/briarproject/onionwrapper/TorWrapper.java
+++ b/onionwrapper-core/src/main/java/org/briarproject/onionwrapper/TorWrapper.java
@@ -18,7 +18,7 @@ public interface TorWrapper {
 
 	/**
 	 * Starts the Tor process, but does not yet connect to the Tor Network.
-     * Call {@link #enableNetwork(boolean)} for this.
+	 * Call {@link #enableNetwork(boolean)} for this.
 	 * <p>
 	 * This method must only be called once. To restart the Tor process, stop
 	 * this wrapper instance and then create a new instance.
@@ -100,10 +100,10 @@ public interface TorWrapper {
 	 */
 	void enableIpv6(boolean ipv6Only) throws IOException;
 
-    /**
-     * Returns the Obfs4 executable as a File for use with Moat.
-     */
-    File getObfs4ExecutableFile();
+	/**
+	 * Returns the Obfs4 executable as a File for use with Moat.
+	 */
+	File getObfs4ExecutableFile();
 
 	/**
 	 * The state of the Tor wrapper.
diff --git a/onionwrapper-java/build.gradle b/onionwrapper-java/build.gradle
index 1f043fad1bf6a1e43845709873154897820efcb8..913f000b25e6e8ecdefdbca470d2a10acf790042 100644
--- a/onionwrapper-java/build.gradle
+++ b/onionwrapper-java/build.gradle
@@ -5,6 +5,7 @@ import static org.briarproject.onionwrapper.OsUtils.currentOS
 plugins {
     id 'java-library'
     id 'com.vanniktech.maven.publish' version '0.18.0'
+    id 'checkstyle'
 }
 
 java {
@@ -12,6 +13,10 @@ java {
     targetCompatibility = JavaVersion.VERSION_1_8
 }
 
+checkstyle {
+    configFile = new File('../config/checkstyle/checkstyle.xml')
+}
+
 dependencies {
     api project(':onionwrapper-core')
     def jna_version = '4.5.2'
diff --git a/onionwrapper-java/src/main/java/org/briarproject/onionwrapper/JavaLocationUtils.java b/onionwrapper-java/src/main/java/org/briarproject/onionwrapper/JavaLocationUtils.java
index 033928a21e29e702335c42360146b5d968ac6dab..2ee24cd43d083164bf6622fef4e41eac33b41b90 100644
--- a/onionwrapper-java/src/main/java/org/briarproject/onionwrapper/JavaLocationUtils.java
+++ b/onionwrapper-java/src/main/java/org/briarproject/onionwrapper/JavaLocationUtils.java
@@ -10,17 +10,17 @@ import javax.inject.Inject;
 @NotNullByDefault
 class JavaLocationUtils implements LocationUtils {
 
-    private static final Logger LOG =
-            Logger.getLogger(JavaLocationUtils.class.getName());
-
-    @Inject
-    JavaLocationUtils() {
-    }
-
-    @Override
-    public String getCurrentCountry() {
-        LOG.info("Using user-defined locale");
-        return Locale.getDefault().getCountry();
-    }
+	private static final Logger LOG =
+			Logger.getLogger(JavaLocationUtils.class.getName());
+
+	@Inject
+	JavaLocationUtils() {
+	}
+
+	@Override
+	public String getCurrentCountry() {
+		LOG.info("Using user-defined locale");
+		return Locale.getDefault().getCountry();
+	}
 
 }
diff --git a/onionwrapper-java/src/main/java/org/briarproject/onionwrapper/JavaLocationUtilsFactory.java b/onionwrapper-java/src/main/java/org/briarproject/onionwrapper/JavaLocationUtilsFactory.java
index 34b5f16a182b10af61b526d84883f1dee1ae1808..8fa237109abd253fd460524408a7efbda42f19f7 100644
--- a/onionwrapper-java/src/main/java/org/briarproject/onionwrapper/JavaLocationUtilsFactory.java
+++ b/onionwrapper-java/src/main/java/org/briarproject/onionwrapper/JavaLocationUtilsFactory.java
@@ -5,8 +5,8 @@ import org.briarproject.nullsafety.NotNullByDefault;
 @NotNullByDefault
 public class JavaLocationUtilsFactory {
 
-    public static LocationUtils createJavaLocationUtils() {
-        return new JavaLocationUtils();
-    }
+	public static LocationUtils createJavaLocationUtils() {
+		return new JavaLocationUtils();
+	}
 
 }