Repository: rollbar/rollbar-java
Pull request #3 (2016-03-31 12:46:03)

Multiple module
Refactor Rollbar into multiple modules.


Patch:
diff --git a/.gitignore b/.gitignore
index fd48813..de792ca 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,3 @@
 *.iml
 .idea
-/target
\ No newline at end of file
+**/target
\ No newline at end of file
diff --git a/README.md b/README.md
index 9594dde..ee763dc 100644
--- a/README.md
+++ b/README.md
@@ -1,187 +1,42 @@
-# Rollbar
+## Overview
 
-Rollbar is an official Java notifier for Rollbar. It is a work in progress. It represents a payload to be sent to
-Rollbar and ensures that all (applicable) portions of the payload spec'ed in https://rollbar.com/docs/api/items_post/
-can be sent correctly.
+`java-rollbar` is a set of maven modules that allows reporting issues to
+Rollbar in anything that can use Java.
 
-## Installation
+It's still under development, and many of the design decisions may still
+be altered. If you have an opinion voice it in the issues!
 
-Installation is through Maven:
+The library is split into small modules to enable re-use as much as
+possible. If you want to change a single piece of how it works it should
+be relatively straightforward.
 
-```xml
-<dependency>
-    <groupId>com.rollbar</groupId>
-    <artifactId>rollbar</artifactId>
-</dependency>
-```
+ * `rollbar-utilties` contains code shared by the other modules.
+ * `rollbar-testing` contains shared test code.
+ * `rollbar-sender` implements sending occurrences to Rollbar. No external
+   dependencies make this lightweight, but a good candidate for an
+   upgrade.
+ * `rollbar-payload` implements a Payload object that can be serialized to
+   JSON. It does so with no external dependencies.
+ * `rollbar` brings together all the pieces from above to make it easy to
+   install and start recording errors.
 
-## Usage
+ ## Installing
 
-Construct a `Rollbar` and call `handleUncaughtErrors` on it:
+ All these can be installed as Maven projects. Simply add the
+ dependency to your pom file:
 
-```java
-public class MainClass {
-    public static final Rollbar rollbar = new Rollbar("ACCESS_TOKEN", "production");
-    public int main(String[] args) {
-        rollbar.handleUncaughtErrors();
-        OtherClass.runProgram();
-        return 0;
-    }
-}
-```
+ ```xml
+ <dependencies>
+   <dependency>
+      <groupId>com.rollbar</groupId>
+       <artifactId>rollbar</artifactId>
+       <version>0.5.0</version>
+   </dependency>
+</dependencies>
+ ```
 
-If you're running a Web application or using a framework that handles errors in a special way you'll need to get an
-instance of a `Rollbar` using whatever technique is appropriate for your framework. For Spring, for instance, a properly
-configured Rollbar bean along with the following class will do the trick:
+You can, of course, build it yourself and depend on the .jar manually.
 
-```java
-@ControllerAdvice
-class RollbarExceptionHandler {
-    private final Rollbar rollbar;
-
-    public RollbarExceptionHandler(Rollbar rollbar) {
-        this.rollbar = rollbar;
-    }
-
-    @ExceptionHandler(value = Exception.class)
-    public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
-        if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null)
-            throw e;
-
-        rollbar.log(e);
-
-        throw e;
-    }
-}
-```
-
-You might configure your bean with something like this:
-
-```java
-package com.yourcompany.product;
-
-import org.springframework.beans.factory.FactoryBean;
-import com.rollbar.Rollbar;
-
-public class RollbarBean implements FactoryBean<Rollbar> {
-	private Rollbar rollbar = new Rollbar("YOUR_ACCESS_TOKEN_HERE", "production");
-
-	@Override
-	public Rollbar getObject() throws Exception {
-		return rollbar;
-	}
-
-	@Override
-	public Class<? extends Rollbar> getObjectType() {
-		return rollbar.getClass();
-	}
-
-	@Override
-	public boolean isSingleton() {
-		return false;
-	}
-}
-```
-
-You can, of course, always choose to configure this with XML.
-
-## Custom Usage
-
-Rollbar also provides a set of classes for building and sending error reports to Rollbar.
-
-The simplest way to use this is with a `try/catch` like so:
-
-```java
-public class MyClass {
-    public static final String SERVER_POST_ACCESS_TOKEN = getAccessToken();
-    public static final String ENVIRONMENT = currentEnvironment();
-
-    /*...*/
-    public void doSomething() {
-        try {
-            this.monitoredMethod();
-        } catch (Throwable t) {
-            Payload p = Payload.fromError(SERVER_POST_ACCESS_TOKEN, ENVIRONMENT, t, null);
-            try {
-                // Here you can filter or transform the payload as needed before sending it
-                p.send();
-            } catch (ConnectionFailedExeption e) {
-                Logger.getLogger(MyClass.class.getName()).severe(p.toJson());
-            }
-            // You can obviously choose to do something *other* than re-throw the exception
-            throw t;
-        }
-    }
-}
-```
-
-Uncaught errors look different depending on the framework you're using. Play, for instance, uses an `HttpErrorHandler`.
-For general use you'll want to do something like this:
-
-```java
-public class Program {
-    public static final String SERVER_POST_ACCESS_TOKEN = getAccessToken();
-    public static final String ENVIRONMENT = currentEnvironment();
-
-    /*...*/
-    public void main(String[] argv) {
-        Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
-            public void uncaughtException(Thread t, Throwable e) {
-                Payload p = Payload.fromError(SERVER_POST_ACCESS_TOKEN, ENVIRONMENT, t, null);
-                try {
-                    // Here you can filter or transform the payload as needed before sending it
-                    p.send();
-                } catch (ConnectionFailedExeption e) {
-                    Logger.getLogger(MyClass.class.getName()).severe(p.toJson());
-                }
-                throw t;
-            }
-        });
-    }
-}
-```
-
-Note that the above snippet monitors a **single** thread. If your framework starts a thread per request, or uses
-different threads for different parts of your UI you'll need to take that into consideration.
-
-## Tips for Optimal Usage
-
- * If you can construct the `Payload`, it compiles, and does not throw an exception in the process, then it's valid to
-   send to Rollbar and will be displayed correctly in the interface.
-
- * Everything in the `payload` package is immutable, exposing a fluent interface for making small changes to the Payload.
-
-   In practice this means the 'setters' are very expensive (because they create a whole new object every time). Since
-   reporting exceptions should be the "exceptional" case, this should not matter in practice. (Please report any serious
-   performance issues!). If you are only going to alter one or two fields then using these setters is a great time
-   saver, and you should feel free to use them. If you are fully customizing a portion of your payload with lots of
-   custom data, however, you should use the constructor that exposes all the fields available to the class.
-
- * The fluent interface uses `property()` as the getter, and `property(TypeName val)` as the setter, rather than the
-   typical Java `getProperty()` and `setProperty()` convention. This should help remind the user that the setter isn't
-   doing a simple set operation, and results in an attractive (to the Author) fluent interface:
-
-   ```java
-   Server s = new Server()
-       .host("www.rollbar.com")
-       .branch("master")
-       .codeVersion("b01ff9e")
-       .put("TAttUQoLtUaE", 42);
-   ```
-
- * Every class in `payload` has two constructors: one that contains the required fields, and one that contains all the
-   fields that the class offers. Prefer the latter ones to using the fluent setters with the smaller constructor.
- * The `Extensible` class represents the various portions of the payload that can have arbitrary additional data sent.
-
-   This class contains two additional methods: `get(String key)` and `put(String key, Object value)`. `get` simply
-   retrieves the value at that key. It can be used for built-in and custom keys. `put` checks the key against the
-   built-in properties and will throw an `IllegalArgumentException` if it is one of the known values. This allows the
-   built-in property setters to validate the properties when they are set.
- * The `send` method, replace `Sender` if you need something asynchronous you will not be able to use `Payload.send`.
-   Build a similar static method to what you find in `PayloadSender` and use that instead! This will also allow you to
-   use a library (like Apache or Google's HttpClient library).
- * If you integrate your library into a library for which there is no sub-library on Maven, consider creating a package
-   so others can benefit from your expertise!
 ## Contributing
 
 This library was written by someone who knows C# much better than Java. Feel free to issue stylistic PRs, or offer
@@ -191,4 +46,4 @@ suggestions on how we can improve this library.
 2. Create your feature branch (```git checkout -b my-new-feature```).
 3. Commit your changes (```git commit -am 'Added some feature'```)
 4. Push to the branch (```git push origin my-new-feature```)
-5. Create new Pull Request
+5. Create new Pull Request
\ No newline at end of file
diff --git a/parent-pom.xml b/parent-pom.xml
new file mode 100644
index 0000000..ef05d71
--- /dev/null
+++ b/parent-pom.xml
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>com.rollbar</groupId>
+    <artifactId>rollbar-parent</artifactId>
+    <version>${currentVersion}</version>
+
+    <packaging>pom</packaging>
+
+    <name>Parent POM for Rollbar Modules</name>
+    <description>For Rollbar modules to inherit from</description>
+
+    <properties>
+        <currentVersion>0.5.0</currentVersion>
+    </properties>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <configuration>
+                    <archive>
+                        <manifest>
+                            <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
+                            <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
+                        </manifest>
+                    </archive>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-source-plugin</artifactId>
+                <version>2.2.1</version>
+                <executions>
+                    <execution>
+                        <id>attach-sources</id>
+                        <goals>
+                            <goal>jar-no-fork</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-javadoc-plugin</artifactId>
+                <version>2.9.1</version>
+                <executions>
+                    <execution>
+                        <id>attach-javadocs</id>
+                        <goals>
+                            <goal>jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-gpg-plugin</artifactId>
+                <version>1.5</version>
+                <executions>
+                    <execution>
+                        <id>sign-artifacts</id>
+                        <phase>verify</phase>
+                        <goals>
+                            <goal>sign</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.sonatype.plugins</groupId>
+                <artifactId>nexus-staging-maven-plugin</artifactId>
+                <version>1.6.3</version>
+                <extensions>true</extensions>
+                <configuration>
+                    <serverId>ossrh</serverId>
+                    <nexusUrl>https://oss.sonatype.org/</nexusUrl>
+                    <autoReleaseAfterClose>true</autoReleaseAfterClose>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.10</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.google.code.gson</groupId>
+            <artifactId>gson</artifactId>
+            <version>2.3.1</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest-core</artifactId>
+            <scope>test</scope>
+            <version>1.1</version>
+        </dependency>
+    </dependencies>
+
+    <licenses>
+        <license>
+            <name>MIT License</name>
+            <url>http://www.opensource.org/licenses/mit-license.php</url>
+        </license>
+    </licenses>
+
+    <developers>
+        <developer>
+            <name>Christopher Pfohl</name>
+            <email>chris@rollbar.com</email>
+            <organization>Rollbar</organization>
+            <organizationUrl>http://www.rollbar.com</organizationUrl>
+        </developer>
+    </developers>
+
+    <scm>
+        <connection>scm:git:git@github.com:rollbar/rollbar-java.git</connection>
+        <developerConnection>scm:git:git@github.com:rollbar/rollbar-java.git</developerConnection>
+        <url>github.com/rollbar/rollbar-java</url>
+    </scm>
+
+    <distributionManagement>
+        <snapshotRepository>
+            <id>ossrh</id>
+            <url>https://oss.sonatype.org/content/repositories/snapshots</url>
+        </snapshotRepository>
+    </distributionManagement>
+</project>
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 8973d26..ac8096e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,119 +1,21 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project xmlns="http://maven.apache.org/POM/4.0.0"
-         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-    <modelVersion>4.0.0</modelVersion>
-    <name>Rollbar</name>
-    <description>A set of classes that allow reporting errors to Rollbar.</description>
-    <url>http://www.rollbar.com</url>
+	 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <groupId>com.rollbar</groupId>
+  <artifactId>java-rollbar</artifactId>
+  <version>n/a</version>
+  <packaging>pom</packaging>
+  <modelVersion>4.0.0</modelVersion>
 
-    <groupId>com.rollbar</groupId>
-    <artifactId>rollbar</artifactId>
-    <version>0.4.0</version>
-    <dependencies>
-        <dependency>
-            <groupId>junit</groupId>
-            <artifactId>junit</artifactId>
-            <version>4.10</version>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>com.google.code.gson</groupId>
-            <artifactId>gson</artifactId>
-            <version>2.3.1</version>
-            <scope>test</scope>
-        </dependency>
-    </dependencies>
-    <licenses>
-        <license>
-            <name>MIT License</name>
-            <url>http://www.opensource.org/licenses/mit-license.php</url>
-        </license>
-    </licenses>
-    <developers>
-        <developer>
-            <name>Christopher Pfohl</name>
-            <email>chris@rollbar.com</email>
-            <organization>Rollbar</organization>
-            <organizationUrl>http://www.rollbar.com</organizationUrl>
-        </developer>
-    </developers>
-    <scm>
-        <connection>scm:git:git@github.com:rollbar/rollbar-java.git</connection>
-        <developerConnection>scm:git:git@github.com:rollbar/rollbar-java.git</developerConnection>
-        <url>github.com/rollbar/rollbar-java</url>
-    </scm>
-    <distributionManagement>
-        <snapshotRepository>
-            <id>ossrh</id>
-            <url>https://oss.sonatype.org/content/repositories/snapshots</url>
-        </snapshotRepository>
-    </distributionManagement>
-    <build>
-        <plugins>
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-jar-plugin</artifactId>
-                <configuration>
-                    <archive>
-                        <manifest>
-                            <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
-                            <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
-                        </manifest>
-                    </archive>
-                </configuration>
-            </plugin>
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-source-plugin</artifactId>
-                <version>2.2.1</version>
-                <executions>
-                    <execution>
-                        <id>attach-sources</id>
-                        <goals>
-                            <goal>jar-no-fork</goal>
-                        </goals>
-                    </execution>
-                </executions>
-            </plugin>
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-javadoc-plugin</artifactId>
-                <version>2.9.1</version>
-                <executions>
-                    <execution>
-                        <id>attach-javadocs</id>
-                        <goals>
-                            <goal>jar</goal>
-                        </goals>
-                    </execution>
-                </executions>
-            </plugin>
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-gpg-plugin</artifactId>
-                <version>1.5</version>
-                <executions>
-                    <execution>
-                        <id>sign-artifacts</id>
-                        <phase>verify</phase>
-                        <goals>
-                            <goal>sign</goal>
-                        </goals>
-                    </execution>
-                </executions>
-            </plugin>
-            <plugin>
-                <groupId>org.sonatype.plugins</groupId>
-                <artifactId>nexus-staging-maven-plugin</artifactId>
-                <version>1.6.3</version>
-                <extensions>true</extensions>
-                <configuration>
-                    <serverId>ossrh</serverId>
-                    <nexusUrl>https://oss.sonatype.org/</nexusUrl>
-                    <autoReleaseAfterClose>true</autoReleaseAfterClose>
-                </configuration>
-            </plugin>
-        </plugins>
-    </build>
+  <name>Rollbar for the JVM</name>
+  <description>For connecting your applications built on the JVM to Rollbar for Error Reporting</description>
+
+  <modules>
+    <module>rollbar-payload</module>
+    <module>rollbar-sender</module>
+    <module>rollbar-utilities</module>
+    <module>rollbar-testing</module>
+    <module>rollbar</module>
+  </modules>
 </project>
diff --git a/rollbar-payload/README.md b/rollbar-payload/README.md
new file mode 100644
index 0000000..8c37998
--- /dev/null
+++ b/rollbar-payload/README.md
@@ -0,0 +1,5 @@
+## Overview
+
+`rollbar-payload` defines a set of classes that represent a valid
+payload you can post to Rollbar. It also includes the code for
+serializing
\ No newline at end of file
diff --git a/rollbar-payload/pom.xml b/rollbar-payload/pom.xml
new file mode 100644
index 0000000..356ba3b
--- /dev/null
+++ b/rollbar-payload/pom.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>com.rollbar</groupId>
+        <artifactId>rollbar-parent</artifactId>
+        <version>${currentVersion}</version>
+        <relativePath>../parent-pom.xml</relativePath>
+    </parent>
+
+    <name>rollbar-payload</name>
+    <description>A set of classes that allow reporting errors to Rollbar.</description>
+    <url>http://www.rollbar.com</url>
+
+    <groupId>com.rollbar</groupId>
+    <artifactId>rollbar-payload</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.rollbar</groupId>
+            <artifactId>rollbar-utilities</artifactId>
+            <version>${currentVersion}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.rollbar</groupId>
+            <artifactId>rollbar-testing</artifactId>
+            <version>${currentVersion}</version>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/src/main/java/com/rollbar/payload/Payload.java b/rollbar-payload/src/main/java/com/rollbar/payload/Payload.java
similarity index 80%
rename from src/main/java/com/rollbar/payload/Payload.java
rename to rollbar-payload/src/main/java/com/rollbar/payload/Payload.java
index 17eb997..3aaa56c 100644
--- a/src/main/java/com/rollbar/payload/Payload.java
+++ b/rollbar-payload/src/main/java/com/rollbar/payload/Payload.java
@@ -1,14 +1,13 @@
 package com.rollbar.payload;
 
-import com.rollbar.http.*;
 import com.rollbar.payload.data.Data;
 import com.rollbar.payload.data.Level;
 import com.rollbar.payload.data.Notifier;
 import com.rollbar.payload.data.body.Body;
-import com.rollbar.payload.utilities.ArgumentNullException;
-import com.rollbar.payload.utilities.RollbarSerializer;
-import com.rollbar.payload.utilities.Validate;
-import com.rollbar.payload.utilities.JsonSerializable;
+import com.rollbar.utilities.ArgumentNullException;
+import com.rollbar.utilities.RollbarSerializer;
+import com.rollbar.utilities.Validate;
+import com.rollbar.utilities.JsonSerializable;
 
 import java.util.Date;
 import java.util.LinkedHashMap;
@@ -19,18 +18,6 @@ import java.util.Map;
  * successful when serialized and POSTed to the correct endpoint.
  */
 public final class Payload implements JsonSerializable {
-    private static Sender sender = new PayloadSender();
-
-    /**
-     * Call to set the sender used by all Payloads when `send` is called.
-     * Note: This should be thread safe. That is easiest when the sender keeps *no* instance data.
-     * @param sender not nullable, the sender to use instead of the default
-     */
-    public static void setSender(Sender sender) {
-        Validate.isNotNull(sender, "sender");
-        Payload.sender = sender;
-    }
-
     /**
      * A shortcut factory for creating a payload
      * @param accessToken not nullable, the server_post access token to send this payload to
@@ -130,22 +117,6 @@ public final class Payload implements JsonSerializable {
         return new RollbarSerializer().serialize(this);
     }
 
-    /**
-     * Send this payload to Rollbar by the default Sender and Serializer
-     * @return the response from Rollbar
-     */
-    public RollbarResponse send() {
-        return sender.send(this);
-    }
-
-    /**
-     * Send this payload to Rollbar. Handle the response with the handler.
-     * @param handler the handler for
-     */
-    public void send(RollbarResponseHandler handler) {
-        sender.send(this, handler);
-    }
-
     public Map<String, Object> asJson() {
         Map<String, Object> obj = new LinkedHashMap<String, Object>();
         obj.put("access_token", accessToken());
diff --git a/src/main/java/com/rollbar/payload/data/Data.java b/rollbar-payload/src/main/java/com/rollbar/payload/data/Data.java
similarity index 98%
rename from src/main/java/com/rollbar/payload/data/Data.java
rename to rollbar-payload/src/main/java/com/rollbar/payload/data/Data.java
index 610d8ac..a955e20 100644
--- a/src/main/java/com/rollbar/payload/data/Data.java
+++ b/rollbar-payload/src/main/java/com/rollbar/payload/data/Data.java
@@ -1,10 +1,10 @@
 package com.rollbar.payload.data;
 
 import com.rollbar.payload.data.body.Body;
-import com.rollbar.payload.utilities.ArgumentNullException;
-import com.rollbar.payload.utilities.InvalidLengthException;
-import com.rollbar.payload.utilities.JsonSerializable;
-import com.rollbar.payload.utilities.Validate;
+import com.rollbar.utilities.ArgumentNullException;
+import com.rollbar.utilities.InvalidLengthException;
+import com.rollbar.utilities.JsonSerializable;
+import com.rollbar.utilities.Validate;
 
 import java.util.Date;
 import java.util.LinkedHashMap;
diff --git a/src/main/java/com/rollbar/payload/data/Level.java b/rollbar-payload/src/main/java/com/rollbar/payload/data/Level.java
similarity index 95%
rename from src/main/java/com/rollbar/payload/data/Level.java
rename to rollbar-payload/src/main/java/com/rollbar/payload/data/Level.java
index b3969f6..2afa06d 100644
--- a/src/main/java/com/rollbar/payload/data/Level.java
+++ b/rollbar-payload/src/main/java/com/rollbar/payload/data/Level.java
@@ -1,6 +1,6 @@
 package com.rollbar.payload.data;
 
-import com.rollbar.payload.utilities.JsonSerializable;
+import com.rollbar.utilities.JsonSerializable;
 
 /**
  * The Level of a Rollbar Report.
diff --git a/src/main/java/com/rollbar/payload/data/Notifier.java b/rollbar-payload/src/main/java/com/rollbar/payload/data/Notifier.java
similarity index 97%
rename from src/main/java/com/rollbar/payload/data/Notifier.java
rename to rollbar-payload/src/main/java/com/rollbar/payload/data/Notifier.java
index fa09a0a..a944ed0 100644
--- a/src/main/java/com/rollbar/payload/data/Notifier.java
+++ b/rollbar-payload/src/main/java/com/rollbar/payload/data/Notifier.java
@@ -1,6 +1,6 @@
 package com.rollbar.payload.data;
 
-import com.rollbar.payload.utilities.JsonSerializable;
+import com.rollbar.utilities.JsonSerializable;
 
 import java.util.LinkedHashMap;
 import java.util.Map;
diff --git a/src/main/java/com/rollbar/payload/data/Person.java b/rollbar-payload/src/main/java/com/rollbar/payload/data/Person.java
similarity index 93%
rename from src/main/java/com/rollbar/payload/data/Person.java
rename to rollbar-payload/src/main/java/com/rollbar/payload/data/Person.java
index 21c0de4..3ae4c6d 100644
--- a/src/main/java/com/rollbar/payload/data/Person.java
+++ b/rollbar-payload/src/main/java/com/rollbar/payload/data/Person.java
@@ -1,9 +1,9 @@
 package com.rollbar.payload.data;
 
-import com.rollbar.payload.utilities.ArgumentNullException;
-import com.rollbar.payload.utilities.InvalidLengthException;
-import com.rollbar.payload.utilities.JsonSerializable;
-import com.rollbar.payload.utilities.Validate;
+import com.rollbar.utilities.ArgumentNullException;
+import com.rollbar.utilities.InvalidLengthException;
+import com.rollbar.utilities.JsonSerializable;
+import com.rollbar.utilities.Validate;
 
 import java.util.LinkedHashMap;
 import java.util.Map;
diff --git a/src/main/java/com/rollbar/payload/data/Request.java b/rollbar-payload/src/main/java/com/rollbar/payload/data/Request.java
similarity index 99%
rename from src/main/java/com/rollbar/payload/data/Request.java
rename to rollbar-payload/src/main/java/com/rollbar/payload/data/Request.java
index 8897b72..e31535b 100644
--- a/src/main/java/com/rollbar/payload/data/Request.java
+++ b/rollbar-payload/src/main/java/com/rollbar/payload/data/Request.java
@@ -1,6 +1,6 @@
 package com.rollbar.payload.data;
 
-import com.rollbar.payload.utilities.Extensible;
+import com.rollbar.utilities.Extensible;
 
 import java.net.InetAddress;
 import java.util.*;
diff --git a/src/main/java/com/rollbar/payload/data/Server.java b/rollbar-payload/src/main/java/com/rollbar/payload/data/Server.java
similarity index 98%
rename from src/main/java/com/rollbar/payload/data/Server.java
rename to rollbar-payload/src/main/java/com/rollbar/payload/data/Server.java
index b77d4cf..51b29b7 100644
--- a/src/main/java/com/rollbar/payload/data/Server.java
+++ b/rollbar-payload/src/main/java/com/rollbar/payload/data/Server.java
@@ -1,6 +1,6 @@
 package com.rollbar.payload.data;
 
-import com.rollbar.payload.utilities.Extensible;
+import com.rollbar.utilities.Extensible;
 
 import java.util.Collections;
 import java.util.HashSet;
diff --git a/src/main/java/com/rollbar/payload/data/body/Body.java b/rollbar-payload/src/main/java/com/rollbar/payload/data/body/Body.java
similarity index 96%
rename from src/main/java/com/rollbar/payload/data/body/Body.java
rename to rollbar-payload/src/main/java/com/rollbar/payload/data/body/Body.java
index 256efc2..f41ce09 100644
--- a/src/main/java/com/rollbar/payload/data/body/Body.java
+++ b/rollbar-payload/src/main/java/com/rollbar/payload/data/body/Body.java
@@ -1,9 +1,9 @@
 package com.rollbar.payload.data.body;
 
-import com.rollbar.payload.utilities.ArgumentNullException;
-import com.rollbar.payload.utilities.JsonSerializable;
-import com.rollbar.payload.utilities.StringUtils;
-import com.rollbar.payload.utilities.Validate;
+import com.rollbar.utilities.ArgumentNullException;
+import com.rollbar.utilities.JsonSerializable;
+import com.rollbar.utilities.StringUtils;
+import com.rollbar.utilities.Validate;
 
 import java.util.LinkedHashMap;
 import java.util.Map;
diff --git a/src/main/java/com/rollbar/payload/data/body/BodyContents.java b/rollbar-payload/src/main/java/com/rollbar/payload/data/body/BodyContents.java
similarity index 100%
rename from src/main/java/com/rollbar/payload/data/body/BodyContents.java
rename to rollbar-payload/src/main/java/com/rollbar/payload/data/body/BodyContents.java
diff --git a/src/main/java/com/rollbar/payload/data/body/CodeContext.java b/rollbar-payload/src/main/java/com/rollbar/payload/data/body/CodeContext.java
similarity index 97%
rename from src/main/java/com/rollbar/payload/data/body/CodeContext.java
rename to rollbar-payload/src/main/java/com/rollbar/payload/data/body/CodeContext.java
index 7220898..1af2c1f 100644
--- a/src/main/java/com/rollbar/payload/data/body/CodeContext.java
+++ b/rollbar-payload/src/main/java/com/rollbar/payload/data/body/CodeContext.java
@@ -1,6 +1,6 @@
 package com.rollbar.payload.data.body;
 
-import com.rollbar.payload.utilities.JsonSerializable;
+import com.rollbar.utilities.JsonSerializable;
 
 import java.util.LinkedHashMap;
 import java.util.Map;
diff --git a/src/main/java/com/rollbar/payload/data/body/CrashReport.java b/rollbar-payload/src/main/java/com/rollbar/payload/data/body/CrashReport.java
similarity index 88%
rename from src/main/java/com/rollbar/payload/data/body/CrashReport.java
rename to rollbar-payload/src/main/java/com/rollbar/payload/data/body/CrashReport.java
index 1a7f474..6e8c5d0 100644
--- a/src/main/java/com/rollbar/payload/data/body/CrashReport.java
+++ b/rollbar-payload/src/main/java/com/rollbar/payload/data/body/CrashReport.java
@@ -1,8 +1,8 @@
 package com.rollbar.payload.data.body;
 
-import com.rollbar.payload.utilities.ArgumentNullException;
-import com.rollbar.payload.utilities.JsonSerializable;
-import com.rollbar.payload.utilities.Validate;
+import com.rollbar.utilities.ArgumentNullException;
+import com.rollbar.utilities.JsonSerializable;
+import com.rollbar.utilities.Validate;
 
 import java.util.LinkedHashMap;
 import java.util.Map;
diff --git a/src/main/java/com/rollbar/payload/data/body/ExceptionInfo.java b/rollbar-payload/src/main/java/com/rollbar/payload/data/body/ExceptionInfo.java
similarity index 96%
rename from src/main/java/com/rollbar/payload/data/body/ExceptionInfo.java
rename to rollbar-payload/src/main/java/com/rollbar/payload/data/body/ExceptionInfo.java
index 1ec3f08..fc279f1 100644
--- a/src/main/java/com/rollbar/payload/data/body/ExceptionInfo.java
+++ b/rollbar-payload/src/main/java/com/rollbar/payload/data/body/ExceptionInfo.java
@@ -1,8 +1,8 @@
 package com.rollbar.payload.data.body;
 
-import com.rollbar.payload.utilities.ArgumentNullException;
-import com.rollbar.payload.utilities.JsonSerializable;
-import com.rollbar.payload.utilities.Validate;
+import com.rollbar.utilities.ArgumentNullException;
+import com.rollbar.utilities.JsonSerializable;
+import com.rollbar.utilities.Validate;
 
 import java.util.LinkedHashMap;
 import java.util.Map;
diff --git a/src/main/java/com/rollbar/payload/data/body/Frame.java b/rollbar-payload/src/main/java/com/rollbar/payload/data/body/Frame.java
similarity index 98%
rename from src/main/java/com/rollbar/payload/data/body/Frame.java
rename to rollbar-payload/src/main/java/com/rollbar/payload/data/body/Frame.java
index 0b835be..3747adc 100644
--- a/src/main/java/com/rollbar/payload/data/body/Frame.java
+++ b/rollbar-payload/src/main/java/com/rollbar/payload/data/body/Frame.java
@@ -1,8 +1,8 @@
 package com.rollbar.payload.data.body;
 
-import com.rollbar.payload.utilities.ArgumentNullException;
-import com.rollbar.payload.utilities.JsonSerializable;
-import com.rollbar.payload.utilities.Validate;
+import com.rollbar.utilities.ArgumentNullException;
+import com.rollbar.utilities.JsonSerializable;
+import com.rollbar.utilities.Validate;
 
 import java.util.ArrayList;
 import java.util.Collections;
diff --git a/src/main/java/com/rollbar/payload/data/body/Message.java b/rollbar-payload/src/main/java/com/rollbar/payload/data/body/Message.java
similarity index 92%
rename from src/main/java/com/rollbar/payload/data/body/Message.java
rename to rollbar-payload/src/main/java/com/rollbar/payload/data/body/Message.java
index 3c6d2ef..d349997 100644
--- a/src/main/java/com/rollbar/payload/data/body/Message.java
+++ b/rollbar-payload/src/main/java/com/rollbar/payload/data/body/Message.java
@@ -1,8 +1,8 @@
 package com.rollbar.payload.data.body;
 
-import com.rollbar.payload.utilities.ArgumentNullException;
-import com.rollbar.payload.utilities.Extensible;
-import com.rollbar.payload.utilities.Validate;
+import com.rollbar.utilities.ArgumentNullException;
+import com.rollbar.utilities.Extensible;
+import com.rollbar.utilities.Validate;
 
 import java.util.HashSet;
 import java.util.Map;
diff --git a/src/main/java/com/rollbar/payload/data/body/Trace.java b/rollbar-payload/src/main/java/com/rollbar/payload/data/body/Trace.java
similarity index 94%
rename from src/main/java/com/rollbar/payload/data/body/Trace.java
rename to rollbar-payload/src/main/java/com/rollbar/payload/data/body/Trace.java
index 79be143..befb994 100644
--- a/src/main/java/com/rollbar/payload/data/body/Trace.java
+++ b/rollbar-payload/src/main/java/com/rollbar/payload/data/body/Trace.java
@@ -1,8 +1,8 @@
 package com.rollbar.payload.data.body;
 
-import com.rollbar.payload.utilities.ArgumentNullException;
-import com.rollbar.payload.utilities.JsonSerializable;
-import com.rollbar.payload.utilities.Validate;
+import com.rollbar.utilities.ArgumentNullException;
+import com.rollbar.utilities.JsonSerializable;
+import com.rollbar.utilities.Validate;
 
 import java.util.LinkedHashMap;
 import java.util.Map;
diff --git a/src/main/java/com/rollbar/payload/data/body/TraceChain.java b/rollbar-payload/src/main/java/com/rollbar/payload/data/body/TraceChain.java
similarity index 90%
rename from src/main/java/com/rollbar/payload/data/body/TraceChain.java
rename to rollbar-payload/src/main/java/com/rollbar/payload/data/body/TraceChain.java
index e9a5064..142fa72 100644
--- a/src/main/java/com/rollbar/payload/data/body/TraceChain.java
+++ b/rollbar-payload/src/main/java/com/rollbar/payload/data/body/TraceChain.java
@@ -1,13 +1,11 @@
 package com.rollbar.payload.data.body;
 
-import com.rollbar.payload.utilities.ArgumentNullException;
-import com.rollbar.payload.utilities.InvalidLengthException;
-import com.rollbar.payload.utilities.JsonSerializable;
-import com.rollbar.payload.utilities.Validate;
+import com.rollbar.utilities.ArgumentNullException;
+import com.rollbar.utilities.InvalidLengthException;
+import com.rollbar.utilities.JsonSerializable;
+import com.rollbar.utilities.Validate;
 
 import java.util.ArrayList;
-import java.util.LinkedHashMap;
-import java.util.Map;
 
 /**
  * Represents a chain of errors (typically from Exceptions with {@link Exception#getCause()} returning some value)
diff --git a/rollbar-sender/pom.xml b/rollbar-sender/pom.xml
new file mode 100644
index 0000000..2b1c8ee
--- /dev/null
+++ b/rollbar-sender/pom.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>rollbar-parent</artifactId>
+        <groupId>com.rollbar</groupId>
+        <version>${currentVersion}</version>
+        <relativePath>../parent-pom.xml</relativePath>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>rollbar-sender</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.rollbar</groupId>
+            <artifactId>rollbar-payload</artifactId>
+            <version>${currentVersion}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.rollbar</groupId>
+            <artifactId>rollbar-utilities</artifactId>
+            <version>${currentVersion}</version>
+        </dependency>
+    </dependencies>
+</project>
\ No newline at end of file
diff --git a/src/main/java/com/rollbar/http/ConnectionFailedException.java b/rollbar-sender/src/main/java/com/rollbar/sender/ConnectionFailedException.java
similarity index 95%
rename from src/main/java/com/rollbar/http/ConnectionFailedException.java
rename to rollbar-sender/src/main/java/com/rollbar/sender/ConnectionFailedException.java
index 970ceef..6db502f 100644
--- a/src/main/java/com/rollbar/http/ConnectionFailedException.java
+++ b/rollbar-sender/src/main/java/com/rollbar/sender/ConnectionFailedException.java
@@ -1,4 +1,4 @@
-package com.rollbar.http;
+package com.rollbar.sender;
 
 import java.net.URL;
 
diff --git a/src/main/java/com/rollbar/http/InvalidResponseCodeException.java b/rollbar-sender/src/main/java/com/rollbar/sender/InvalidResponseCodeException.java
similarity index 95%
rename from src/main/java/com/rollbar/http/InvalidResponseCodeException.java
rename to rollbar-sender/src/main/java/com/rollbar/sender/InvalidResponseCodeException.java
index 88a2765..bd6c8a5 100644
--- a/src/main/java/com/rollbar/http/InvalidResponseCodeException.java
+++ b/rollbar-sender/src/main/java/com/rollbar/sender/InvalidResponseCodeException.java
@@ -1,4 +1,4 @@
-package com.rollbar.http;
+package com.rollbar.sender;
 
 /**
  * Represents an unexpected return value from the Rollbar API.
diff --git a/src/main/java/com/rollbar/http/PayloadSender.java b/rollbar-sender/src/main/java/com/rollbar/sender/PayloadSender.java
similarity index 98%
rename from src/main/java/com/rollbar/http/PayloadSender.java
rename to rollbar-sender/src/main/java/com/rollbar/sender/PayloadSender.java
index faab20e..0336caf 100644
--- a/src/main/java/com/rollbar/http/PayloadSender.java
+++ b/rollbar-sender/src/main/java/com/rollbar/sender/PayloadSender.java
@@ -1,8 +1,8 @@
-package com.rollbar.http;
+package com.rollbar.sender;
 
 import com.rollbar.payload.Payload;
-import com.rollbar.payload.utilities.ArgumentNullException;
-import com.rollbar.payload.utilities.Validate;
+import com.rollbar.utilities.ArgumentNullException;
+import com.rollbar.utilities.Validate;
 
 import java.io.*;
 import java.net.*;
diff --git a/src/main/java/com/rollbar/http/RollbarResponse.java b/rollbar-sender/src/main/java/com/rollbar/sender/RollbarResponse.java
similarity index 98%
rename from src/main/java/com/rollbar/http/RollbarResponse.java
rename to rollbar-sender/src/main/java/com/rollbar/sender/RollbarResponse.java
index b61eeec..095188e 100644
--- a/src/main/java/com/rollbar/http/RollbarResponse.java
+++ b/rollbar-sender/src/main/java/com/rollbar/sender/RollbarResponse.java
@@ -1,4 +1,4 @@
-package com.rollbar.http;
+package com.rollbar.sender;
 
 /**
  * The RollbarResponse is the response received from Rollbar after sending a report.
diff --git a/src/main/java/com/rollbar/http/RollbarResponseCode.java b/rollbar-sender/src/main/java/com/rollbar/sender/RollbarResponseCode.java
similarity index 98%
rename from src/main/java/com/rollbar/http/RollbarResponseCode.java
rename to rollbar-sender/src/main/java/com/rollbar/sender/RollbarResponseCode.java
index 45661e5..dedfd6a 100644
--- a/src/main/java/com/rollbar/http/RollbarResponseCode.java
+++ b/rollbar-sender/src/main/java/com/rollbar/sender/RollbarResponseCode.java
@@ -1,4 +1,4 @@
-package com.rollbar.http;
+package com.rollbar.sender;
 
 /**
  * Represents expected response codes from POSTing an item to Rollbar
diff --git a/src/main/java/com/rollbar/http/RollbarResponseHandler.java b/rollbar-sender/src/main/java/com/rollbar/sender/RollbarResponseHandler.java
similarity index 77%
rename from src/main/java/com/rollbar/http/RollbarResponseHandler.java
rename to rollbar-sender/src/main/java/com/rollbar/sender/RollbarResponseHandler.java
index ee25095..852a4b8 100644
--- a/src/main/java/com/rollbar/http/RollbarResponseHandler.java
+++ b/rollbar-sender/src/main/java/com/rollbar/sender/RollbarResponseHandler.java
@@ -1,4 +1,4 @@
-package com.rollbar.http;
+package com.rollbar.sender;
 
 public interface RollbarResponseHandler {
     void handleResponse(RollbarResponse response);
diff --git a/src/main/java/com/rollbar/http/Sender.java b/rollbar-sender/src/main/java/com/rollbar/sender/Sender.java
similarity index 95%
rename from src/main/java/com/rollbar/http/Sender.java
rename to rollbar-sender/src/main/java/com/rollbar/sender/Sender.java
index cd03607..f374550 100644
--- a/src/main/java/com/rollbar/http/Sender.java
+++ b/rollbar-sender/src/main/java/com/rollbar/sender/Sender.java
@@ -1,4 +1,4 @@
-package com.rollbar.http;
+package com.rollbar.sender;
 
 import com.rollbar.payload.Payload;
 
diff --git a/rollbar-utilities/README.md b/rollbar-utilities/README.md
new file mode 100644
index 0000000..540f356
--- /dev/null
+++ b/rollbar-utilities/README.md
@@ -0,0 +1,5 @@
+## Overview
+
+`rollbar-utilities` contains code that's shared by the other rollbar
+modules. These are things like Exceptions, serialization, validation,
+and string manipulation code.
\ No newline at end of file
diff --git a/rollbar-utilities/pom.xml b/rollbar-utilities/pom.xml
new file mode 100644
index 0000000..9dc45b7
--- /dev/null
+++ b/rollbar-utilities/pom.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>com.rollbar</groupId>
+        <artifactId>rollbar-parent</artifactId>
+        <version>${currentVersion}</version>
+        <relativePath>../parent-pom.xml</relativePath>
+    </parent>
+
+    <artifactId>rollbar-utilities</artifactId>
+</project>
\ No newline at end of file
diff --git a/src/main/java/com/rollbar/payload/utilities/ArgumentNullException.java b/rollbar-utilities/src/main/java/com/rollbar/utilities/ArgumentNullException.java
similarity index 90%
rename from src/main/java/com/rollbar/payload/utilities/ArgumentNullException.java
rename to rollbar-utilities/src/main/java/com/rollbar/utilities/ArgumentNullException.java
index bb45a91..3f59c0f 100644
--- a/src/main/java/com/rollbar/payload/utilities/ArgumentNullException.java
+++ b/rollbar-utilities/src/main/java/com/rollbar/utilities/ArgumentNullException.java
@@ -1,4 +1,4 @@
-package com.rollbar.payload.utilities;
+package com.rollbar.utilities;
 
 /**
  * Indicates a null argument was passed when it shouldn't have been.
diff --git a/src/main/java/com/rollbar/payload/utilities/Extensible.java b/rollbar-utilities/src/main/java/com/rollbar/utilities/Extensible.java
similarity index 99%
rename from src/main/java/com/rollbar/payload/utilities/Extensible.java
rename to rollbar-utilities/src/main/java/com/rollbar/utilities/Extensible.java
index 38667d7..8559490 100644
--- a/src/main/java/com/rollbar/payload/utilities/Extensible.java
+++ b/rollbar-utilities/src/main/java/com/rollbar/utilities/Extensible.java
@@ -1,4 +1,4 @@
-package com.rollbar.payload.utilities;
+package com.rollbar.utilities;
 
 import java.util.*;
 
diff --git a/src/main/java/com/rollbar/payload/utilities/InvalidLengthException.java b/rollbar-utilities/src/main/java/com/rollbar/utilities/InvalidLengthException.java
similarity index 96%
rename from src/main/java/com/rollbar/payload/utilities/InvalidLengthException.java
rename to rollbar-utilities/src/main/java/com/rollbar/utilities/InvalidLengthException.java
index c4bfb7f..5a57ea0 100644
--- a/src/main/java/com/rollbar/payload/utilities/InvalidLengthException.java
+++ b/rollbar-utilities/src/main/java/com/rollbar/utilities/InvalidLengthException.java
@@ -1,4 +1,4 @@
-package com.rollbar.payload.utilities;
+package com.rollbar.utilities;
 
 /**
  * An IllegalArgumentException indicating an argument that's too long or too short.
diff --git a/src/main/java/com/rollbar/payload/utilities/JsonSerializable.java b/rollbar-utilities/src/main/java/com/rollbar/utilities/JsonSerializable.java
similarity index 74%
rename from src/main/java/com/rollbar/payload/utilities/JsonSerializable.java
rename to rollbar-utilities/src/main/java/com/rollbar/utilities/JsonSerializable.java
index e3eebd7..74c37b9 100644
--- a/src/main/java/com/rollbar/payload/utilities/JsonSerializable.java
+++ b/rollbar-utilities/src/main/java/com/rollbar/utilities/JsonSerializable.java
@@ -1,4 +1,4 @@
-package com.rollbar.payload.utilities;
+package com.rollbar.utilities;
 
 /**
  * An object that can be serialized to JSON
diff --git a/rollbar-utilities/src/main/java/com/rollbar/utilities/JsonSerializer.java b/rollbar-utilities/src/main/java/com/rollbar/utilities/JsonSerializer.java
new file mode 100644
index 0000000..fc89f9b
--- /dev/null
+++ b/rollbar-utilities/src/main/java/com/rollbar/utilities/JsonSerializer.java
@@ -0,0 +1,13 @@
+package com.rollbar.utilities;
+
+/**
+ * The interface that encapsulates turning a {@link JsonSerializable} into a JSON payload string.
+ */
+public interface JsonSerializer {
+    /**
+     * Turn the {@link JsonSerializable} into a json string
+     * @param payload the {@link JsonSerializable} to serialize
+     * @return the json payload.
+     */
+    String serialize(JsonSerializable payload);
+}
diff --git a/src/main/java/com/rollbar/payload/utilities/RollbarSerializer.java b/rollbar-utilities/src/main/java/com/rollbar/utilities/RollbarSerializer.java
similarity index 93%
rename from src/main/java/com/rollbar/payload/utilities/RollbarSerializer.java
rename to rollbar-utilities/src/main/java/com/rollbar/utilities/RollbarSerializer.java
index bbb4cf1..88678ca 100644
--- a/src/main/java/com/rollbar/payload/utilities/RollbarSerializer.java
+++ b/rollbar-utilities/src/main/java/com/rollbar/utilities/RollbarSerializer.java
@@ -1,13 +1,11 @@
-package com.rollbar.payload.utilities;
-
-import com.rollbar.payload.Payload;
+package com.rollbar.utilities;
 
 import java.util.*;
 
 /**
  * A Payload Serializer
  */
-public class RollbarSerializer implements PayloadSerializer {
+public class RollbarSerializer implements JsonSerializer {
     private final boolean prettyPrint;
 
     /**
@@ -26,12 +24,12 @@ public class RollbarSerializer implements PayloadSerializer {
     }
 
     /**
-     * @param payload the {@link Payload} to serialize
+     * @param payload the {@link JsonSerializable} to serialize
      * @return the json representation of the payload
      */
-    public String serialize(Payload payload) {
+    public String serialize(JsonSerializable payload) {
         StringBuilder builder = new StringBuilder();
-        serializeObject(payload.asJson(), builder, 0);
+        serializeValue(builder, payload, 0);
         return builder.toString();
     }
 
diff --git a/src/main/java/com/rollbar/payload/utilities/StringUtils.java b/rollbar-utilities/src/main/java/com/rollbar/utilities/StringUtils.java
similarity index 96%
rename from src/main/java/com/rollbar/payload/utilities/StringUtils.java
rename to rollbar-utilities/src/main/java/com/rollbar/utilities/StringUtils.java
index 5f655a9..cd7d519 100644
--- a/src/main/java/com/rollbar/payload/utilities/StringUtils.java
+++ b/rollbar-utilities/src/main/java/com/rollbar/utilities/StringUtils.java
@@ -1,4 +1,4 @@
-package com.rollbar.payload.utilities;
+package com.rollbar.utilities;
 
 import java.util.Arrays;
 
diff --git a/src/main/java/com/rollbar/payload/utilities/Validate.java b/rollbar-utilities/src/main/java/com/rollbar/utilities/Validate.java
similarity index 98%
rename from src/main/java/com/rollbar/payload/utilities/Validate.java
rename to rollbar-utilities/src/main/java/com/rollbar/utilities/Validate.java
index dbe2bc3..5e8ccc5 100644
--- a/src/main/java/com/rollbar/payload/utilities/Validate.java
+++ b/rollbar-utilities/src/main/java/com/rollbar/utilities/Validate.java
@@ -1,4 +1,4 @@
-package com.rollbar.payload.utilities;
+package com.rollbar.utilities;
 
 /**
  * Validates arguments. Throws runtime exceptions when the validation fails.
diff --git a/rollbar/README.md b/rollbar/README.md
new file mode 100644
index 0000000..503b167
--- /dev/null
+++ b/rollbar/README.md
@@ -0,0 +1,185 @@
+# Rollbar
+
+Rollbar is an official Java notifier for Rollbar. It is a work in progress. It represents a payload to be sent to
+Rollbar and ensures that all (applicable) portions of the payload spec'ed in https://rollbar.com/docs/api/items_post/
+can be sent correctly.
+
+## Installation
+
+Installation is through Maven:
+
+```xml
+<dependency>
+    <groupId>com.rollbar</groupId>
+    <artifactId>rollbar</artifactId>
+</dependency>
+```
+
+## Usage
+
+Construct a `Rollbar` and call `handleUncaughtErrors` on it:
+
+```java
+public class MainClass {
+    public static final Rollbar rollbar = new Rollbar("ACCESS_TOKEN", "production");
+    public int main(String[] args) {
+        rollbar.handleUncaughtErrors();
+        OtherClass.runProgram();
+        return 0;
+    }
+}
+```
+
+If you're running a Web application or using a framework that handles errors in a special way you'll need to get an
+instance of a `Rollbar` using whatever technique is appropriate for your framework. For Spring, for instance, a properly
+configured Rollbar bean along with the following class will do the trick:
+
+```java
+@ControllerAdvice
+class RollbarExceptionHandler {
+    private final Rollbar rollbar;
+
+    public RollbarExceptionHandler(Rollbar rollbar) {
+        this.rollbar = rollbar;
+    }
+
+    @ExceptionHandler(value = Exception.class)
+    public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
+        if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null)
+            throw e;
+
+        rollbar.log(e);
+
+        throw e;
+    }
+}
+```
+
+You might configure your bean with something like this:
+
+```java
+package com.yourcompany.product;
+
+import org.springframework.beans.factory.FactoryBean;
+import com.rollbar.Rollbar;
+
+public class RollbarBean implements FactoryBean<Rollbar> {
+	private Rollbar rollbar = new Rollbar("YOUR_ACCESS_TOKEN_HERE", "production");
+
+	@Override
+	public Rollbar getObject() throws Exception {
+		return rollbar;
+	}
+
+	@Override
+	public Class<? extends Rollbar> getObjectType() {
+		return rollbar.getClass();
+	}
+
+	@Override
+	public boolean isSingleton() {
+		return false;
+	}
+}
+```
+
+You can, of course, always choose to configure this with XML.
+
+## Custom Usage
+
+Rollbar also provides a set of classes for building and sending error reports to Rollbar.
+
+The simplest way to use this is with a `try/catch` like so:
+
+```java
+public class MyClass {
+    public static final String SERVER_POST_ACCESS_TOKEN = getAccessToken();
+    public static final String ENVIRONMENT = currentEnvironment();
+
+    /*...*/
+    public void doSomething() {
+        try {
+            this.monitoredMethod();
+        } catch (Throwable t) {
+            Payload p = Payload.fromError(SERVER_POST_ACCESS_TOKEN, ENVIRONMENT, t, null);
+            try {
+                // Here you can filter or transform the payload as needed before sending it
+                p.send();
+            } catch (ConnectionFailedExeption e) {
+                Logger.getLogger(MyClass.class.getName()).severe(p.toJson());
+            }
+            // You can obviously choose to do something *other* than re-throw the exception
+            throw t;
+        }
+    }
+}
+```
+
+Uncaught errors look different depending on the framework you're using. Play, for instance, uses an `HttpErrorHandler`.
+For general use you'll want to do something like this:
+
+```java
+public class Program {
+    public static final String SERVER_POST_ACCESS_TOKEN = getAccessToken();
+    public static final String ENVIRONMENT = currentEnvironment();
+
+    /*...*/
+    public void main(String[] argv) {
+        Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
+            public void uncaughtException(Thread t, Throwable e) {
+                Payload p = Payload.fromError(SERVER_POST_ACCESS_TOKEN, ENVIRONMENT, t, null);
+                try {
+                    // Here you can filter or transform the payload as needed before sending it
+                    p.send();
+                } catch (ConnectionFailedExeption e) {
+                    Logger.getLogger(MyClass.class.getName()).severe(p.toJson());
+                }
+                throw t;
+            }
+        });
+    }
+}
+```
+
+Note that the above snippet monitors a **single** thread. If your framework starts a thread per request, or uses
+different threads for different parts of your UI you'll need to take that into consideration.
+
+## Tips for Optimal Usage
+
+ * If you can construct the `Payload`, it compiles, and does not throw an exception in the process, then it's valid to
+   send to Rollbar and will be displayed correctly in the interface.
+
+ * Everything in the `payload` package is immutable, exposing a fluent interface for making small changes to the Payload.
+
+   In practice this means the 'setters' are very expensive (because they create a whole new object every time). Since
+   reporting exceptions should be the "exceptional" case, this should not matter in practice. (Please report any serious
+   performance issues!). If you are only going to alter one or two fields then using these setters is a great time
+   saver, and you should feel free to use them. If you are fully customizing a portion of your payload with lots of
+   custom data, however, you should use the constructor that exposes all the fields available to the class.
+
+ * The fluent interface uses `property()` as the getter, and `property(TypeName val)` as the setter, rather than the
+   typical Java `getProperty()` and `setProperty()` convention. This should help remind the user that the setter isn't
+   doing a simple set operation, and results in an attractive (to the Author) fluent interface:
+
+   ```java
+   Server s = new Server()
+       .host("www.rollbar.com")
+       .branch("master")
+       .codeVersion("b01ff9e")
+       .put("TAttUQoLtUaE", 42);
+   ```
+
+ * Every class in `payload` has two constructors: one that contains the required fields, and one that contains all the
+   fields that the class offers. Prefer the latter ones to using the fluent setters with the smaller constructor.
+ * The `Extensible` class represents the various portions of the payload that can have arbitrary additional data sent.
+
+   This class contains two additional methods: `get(String key)` and `put(String key, Object value)`. `get` simply
+   retrieves the value at that key. It can be used for built-in and custom keys. `put` checks the key against the
+   built-in properties and will throw an `IllegalArgumentException` if it is one of the known values. This allows the
+   built-in property setters to validate the properties when they are set.
+ * The `send` method, replace `Sender` if you need something asynchronous you will not be able to use `Payload.send`.
+   Build a similar static method to what you find in `PayloadSender` and use that instead! This will also allow you to
+   use a library (like Apache or Google's HttpClient library).
+ * If you integrate your library into a library for which there is no sub-library on Maven, consider creating a package
+   so others can benefit from your expertise!
+
diff --git a/rollbar/pom.xml b/rollbar/pom.xml
new file mode 100644
index 0000000..9d57162
--- /dev/null
+++ b/rollbar/pom.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>rollbar-parent</artifactId>
+        <groupId>com.rollbar</groupId>
+        <version>${currentVersion}</version>
+        <relativePath>../parent-pom.xml</relativePath>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>rollbar</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.rollbar</groupId>
+            <artifactId>rollbar-payload</artifactId>
+            <version>${currentVersion}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.rollbar</groupId>
+            <artifactId>rollbar-sender</artifactId>
+            <version>${currentVersion}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.rollbar</groupId>
+            <artifactId>rollbar-utilities</artifactId>
+            <version>${currentVersion}</version>
+        </dependency>
+    </dependencies>
+</project>
\ No newline at end of file
diff --git a/src/main/java/com/rollbar/PayloadFilter.java b/rollbar/src/main/java/com/rollbar/PayloadFilter.java
similarity index 100%
rename from src/main/java/com/rollbar/PayloadFilter.java
rename to rollbar/src/main/java/com/rollbar/PayloadFilter.java
diff --git a/src/main/java/com/rollbar/PayloadTransform.java b/rollbar/src/main/java/com/rollbar/PayloadTransform.java
similarity index 100%
rename from src/main/java/com/rollbar/PayloadTransform.java
rename to rollbar/src/main/java/com/rollbar/PayloadTransform.java
diff --git a/src/main/java/com/rollbar/Rollbar.java b/rollbar/src/main/java/com/rollbar/Rollbar.java
similarity index 99%
rename from src/main/java/com/rollbar/Rollbar.java
rename to rollbar/src/main/java/com/rollbar/Rollbar.java
index 3ef2311..b203313 100644
--- a/src/main/java/com/rollbar/Rollbar.java
+++ b/rollbar/src/main/java/com/rollbar/Rollbar.java
@@ -1,11 +1,13 @@
 package com.rollbar;
 
-import com.rollbar.http.*;
 import com.rollbar.payload.Payload;
 import com.rollbar.payload.data.*;
 import com.rollbar.payload.data.body.Body;
-import com.rollbar.payload.utilities.ArgumentNullException;
-import com.rollbar.payload.utilities.Validate;
+import com.rollbar.sender.PayloadSender;
+import com.rollbar.sender.RollbarResponseHandler;
+import com.rollbar.sender.Sender;
+import com.rollbar.utilities.ArgumentNullException;
+import com.rollbar.utilities.Validate;
 
 import java.util.Date;
 import java.util.LinkedHashMap;
diff --git a/src/main/java/com/rollbar/payload/utilities/PayloadSerializer.java b/src/main/java/com/rollbar/payload/utilities/PayloadSerializer.java
deleted file mode 100644
index 45a501c..0000000
--- a/src/main/java/com/rollbar/payload/utilities/PayloadSerializer.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.rollbar.payload.utilities;
-
-import com.rollbar.payload.Payload;
-
-/**
- * The interface that encapsulates turning a {@link Payload} into a JSON payload string.
- */
-public interface PayloadSerializer {
-    /**
-     * Turn the {@link Payload} into a json string
-     * @param payload the {@link Payload} to serialize
-     * @return the json payload.
-     */
-    String serialize(Payload payload);
-}


Test patch:
diff --git a/src/test/java/com/rollbar/payload/PayloadTest.java b/rollbar-payload/src/test/java/com/rollbar/payload/PayloadTest.java
similarity index 100%
rename from src/test/java/com/rollbar/payload/PayloadTest.java
rename to rollbar-payload/src/test/java/com/rollbar/payload/PayloadTest.java
diff --git a/src/test/java/com/rollbar/http/RollbarSerializerTest.java b/rollbar-payload/src/test/java/com/rollbar/payload/RollbarSerializerTest.java
similarity index 92%
rename from src/test/java/com/rollbar/http/RollbarSerializerTest.java
rename to rollbar-payload/src/test/java/com/rollbar/payload/RollbarSerializerTest.java
index 7beeebb..39a6bb1 100644
--- a/src/test/java/com/rollbar/http/RollbarSerializerTest.java
+++ b/rollbar-payload/src/test/java/com/rollbar/payload/RollbarSerializerTest.java
@@ -1,16 +1,17 @@
-package com.rollbar.http;
+package com.rollbar.payload;
 
 import com.google.gson.JsonArray;
 import com.google.gson.JsonObject;
 import com.google.gson.JsonParser;
-import com.rollbar.payload.Payload;
+
 import com.rollbar.payload.data.Data;
 import com.rollbar.payload.data.Notifier;
 import com.rollbar.payload.data.body.Body;
 import com.rollbar.payload.data.body.Message;
-import com.rollbar.payload.utilities.ArgumentNullException;
-import com.rollbar.payload.utilities.InvalidLengthException;
-import com.rollbar.payload.utilities.RollbarSerializer;
+import com.rollbar.utilities.ArgumentNullException;
+import com.rollbar.utilities.InvalidLengthException;
+
+import com.rollbar.utilities.RollbarSerializer;
 import org.junit.Test;
 
 import java.util.LinkedHashMap;
@@ -57,8 +58,8 @@ public class RollbarSerializerTest {
             final JsonObject lastFrame = frames.get(frames.size() - 1).getAsJsonObject();
             final JsonObject secondToLastFrame = frames.get(frames.size() - 2).getAsJsonObject();
 
-            assertEquals("com.rollbar.http.RollbarSerializerTest.java", lastFrame.get("filename").getAsString());
-            assertEquals("com.rollbar.http.RollbarSerializerTest.java", secondToLastFrame.get("filename").getAsString());
+            assertEquals("com.rollbar.payload.RollbarSerializerTest.java", lastFrame.get("filename").getAsString());
+            assertEquals("com.rollbar.payload.RollbarSerializerTest.java", secondToLastFrame.get("filename").getAsString());
             assertEquals("throwException", lastFrame.get("method").getAsString());
             assertEquals("getError", secondToLastFrame.get("method").getAsString());
         } catch (ArgumentNullException e) {
diff --git a/src/test/java/com/rollbar/payload/data/DataTest.java b/rollbar-payload/src/test/java/com/rollbar/payload/data/DataTest.java
similarity index 98%
rename from src/test/java/com/rollbar/payload/data/DataTest.java
rename to rollbar-payload/src/test/java/com/rollbar/payload/data/DataTest.java
index 7cfdf89..79ff5d7 100644
--- a/src/test/java/com/rollbar/payload/data/DataTest.java
+++ b/rollbar-payload/src/test/java/com/rollbar/payload/data/DataTest.java
@@ -1,10 +1,10 @@
 package com.rollbar.payload.data;
 
-import com.rollbar.GetAndSet;
-import com.rollbar.TestThat;
+import com.rollbar.testing.GetAndSet;
+import com.rollbar.testing.TestThat;
 import com.rollbar.payload.data.body.Body;
-import com.rollbar.payload.utilities.ArgumentNullException;
-import com.rollbar.payload.utilities.InvalidLengthException;
+import com.rollbar.utilities.ArgumentNullException;
+import com.rollbar.utilities.InvalidLengthException;
 import org.junit.Before;
 import org.junit.Test;
 
diff --git a/src/test/java/com/rollbar/payload/data/NotifierTest.java b/rollbar-payload/src/test/java/com/rollbar/payload/data/NotifierTest.java
similarity index 91%
rename from src/test/java/com/rollbar/payload/data/NotifierTest.java
rename to rollbar-payload/src/test/java/com/rollbar/payload/data/NotifierTest.java
index ab02796..90a9607 100644
--- a/src/test/java/com/rollbar/payload/data/NotifierTest.java
+++ b/rollbar-payload/src/test/java/com/rollbar/payload/data/NotifierTest.java
@@ -1,12 +1,10 @@
 package com.rollbar.payload.data;
 
-import com.rollbar.GetAndSet;
-import com.rollbar.TestThat;
+import com.rollbar.testing.GetAndSet;
+import com.rollbar.testing.TestThat;
 import org.junit.Before;
 import org.junit.Test;
 
-import static org.junit.Assert.*;
-
 /**
  * Created by chris on 11/20/15.
  */
diff --git a/src/test/java/com/rollbar/payload/data/PersonTest.java b/rollbar-payload/src/test/java/com/rollbar/payload/data/PersonTest.java
similarity index 91%
rename from src/test/java/com/rollbar/payload/data/PersonTest.java
rename to rollbar-payload/src/test/java/com/rollbar/payload/data/PersonTest.java
index 45b46af..4bb50e4 100644
--- a/src/test/java/com/rollbar/payload/data/PersonTest.java
+++ b/rollbar-payload/src/test/java/com/rollbar/payload/data/PersonTest.java
@@ -1,10 +1,9 @@
 package com.rollbar.payload.data;
 
-import com.rollbar.GetAndSet;
-import com.rollbar.TestThat;
-import com.rollbar.payload.utilities.ArgumentNullException;
-import com.rollbar.payload.utilities.InvalidLengthException;
-import com.sun.org.apache.xpath.internal.Arg;
+import com.rollbar.testing.GetAndSet;
+import com.rollbar.testing.TestThat;
+import com.rollbar.utilities.ArgumentNullException;
+import com.rollbar.utilities.InvalidLengthException;
 import org.junit.Before;
 import org.junit.Test;
 
diff --git a/src/test/java/com/rollbar/payload/data/RequestTest.java b/rollbar-payload/src/test/java/com/rollbar/payload/data/RequestTest.java
similarity index 99%
rename from src/test/java/com/rollbar/payload/data/RequestTest.java
rename to rollbar-payload/src/test/java/com/rollbar/payload/data/RequestTest.java
index 5660a99..5c4ce19 100644
--- a/src/test/java/com/rollbar/payload/data/RequestTest.java
+++ b/rollbar-payload/src/test/java/com/rollbar/payload/data/RequestTest.java
@@ -1,7 +1,7 @@
 package com.rollbar.payload.data;
 
-import com.rollbar.GetAndSet;
-import com.rollbar.TestThat;
+import com.rollbar.testing.*;
+
 import org.junit.Before;
 import org.junit.Test;
 
diff --git a/src/test/java/com/rollbar/payload/data/ServerTest.java b/rollbar-payload/src/test/java/com/rollbar/payload/data/ServerTest.java
similarity index 97%
rename from src/test/java/com/rollbar/payload/data/ServerTest.java
rename to rollbar-payload/src/test/java/com/rollbar/payload/data/ServerTest.java
index 15feb7e..e27b826 100644
--- a/src/test/java/com/rollbar/payload/data/ServerTest.java
+++ b/rollbar-payload/src/test/java/com/rollbar/payload/data/ServerTest.java
@@ -1,13 +1,11 @@
 package com.rollbar.payload.data;
 
-import com.rollbar.GetAndSet;
-import com.rollbar.TestThat;
+import com.rollbar.testing.GetAndSet;
+import com.rollbar.testing.TestThat;
 import org.junit.Before;
 import org.junit.Test;
 
 import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashSet;
 
 import static org.junit.Assert.*;
 
diff --git a/src/test/java/com/rollbar/payload/data/body/BodyTest.java b/rollbar-payload/src/test/java/com/rollbar/payload/data/body/BodyTest.java
similarity index 100%
rename from src/test/java/com/rollbar/payload/data/body/BodyTest.java
rename to rollbar-payload/src/test/java/com/rollbar/payload/data/body/BodyTest.java
diff --git a/src/test/java/com/rollbar/payload/data/body/CodeContextTest.java b/rollbar-payload/src/test/java/com/rollbar/payload/data/body/CodeContextTest.java
similarity index 92%
rename from src/test/java/com/rollbar/payload/data/body/CodeContextTest.java
rename to rollbar-payload/src/test/java/com/rollbar/payload/data/body/CodeContextTest.java
index d7af240..16c77ad 100644
--- a/src/test/java/com/rollbar/payload/data/body/CodeContextTest.java
+++ b/rollbar-payload/src/test/java/com/rollbar/payload/data/body/CodeContextTest.java
@@ -1,11 +1,9 @@
 package com.rollbar.payload.data.body;
 
-import com.rollbar.GetAndSet;
-import com.rollbar.TestThat;
+import com.rollbar.testing.GetAndSet;
+import com.rollbar.testing.TestThat;
 import org.junit.Test;
 
-import static org.junit.Assert.*;
-
 /**
  * Created by chris on 11/25/15.
  */
diff --git a/src/test/java/com/rollbar/payload/data/body/CrashReportTest.java b/rollbar-payload/src/test/java/com/rollbar/payload/data/body/CrashReportTest.java
similarity index 87%
rename from src/test/java/com/rollbar/payload/data/body/CrashReportTest.java
rename to rollbar-payload/src/test/java/com/rollbar/payload/data/body/CrashReportTest.java
index 3788afc..fd94163 100644
--- a/src/test/java/com/rollbar/payload/data/body/CrashReportTest.java
+++ b/rollbar-payload/src/test/java/com/rollbar/payload/data/body/CrashReportTest.java
@@ -1,8 +1,8 @@
 package com.rollbar.payload.data.body;
 
-import com.rollbar.GetAndSet;
-import com.rollbar.TestThat;
-import com.rollbar.payload.utilities.ArgumentNullException;
+import com.rollbar.testing.GetAndSet;
+import com.rollbar.testing.TestThat;
+import com.rollbar.utilities.ArgumentNullException;
 import org.junit.Before;
 import org.junit.Test;
 
diff --git a/src/test/java/com/rollbar/payload/data/body/ExceptionInfoTest.java b/rollbar-payload/src/test/java/com/rollbar/payload/data/body/ExceptionInfoTest.java
similarity index 93%
rename from src/test/java/com/rollbar/payload/data/body/ExceptionInfoTest.java
rename to rollbar-payload/src/test/java/com/rollbar/payload/data/body/ExceptionInfoTest.java
index 74e51dc..f74293f 100644
--- a/src/test/java/com/rollbar/payload/data/body/ExceptionInfoTest.java
+++ b/rollbar-payload/src/test/java/com/rollbar/payload/data/body/ExceptionInfoTest.java
@@ -1,8 +1,8 @@
 package com.rollbar.payload.data.body;
 
-import com.rollbar.GetAndSet;
-import com.rollbar.TestThat;
-import com.rollbar.payload.utilities.ArgumentNullException;
+import com.rollbar.testing.GetAndSet;
+import com.rollbar.testing.TestThat;
+import com.rollbar.utilities.ArgumentNullException;
 import org.junit.Before;
 import org.junit.Test;
 
diff --git a/src/test/java/com/rollbar/payload/data/body/FrameTest.java b/rollbar-payload/src/test/java/com/rollbar/payload/data/body/FrameTest.java
similarity index 96%
rename from src/test/java/com/rollbar/payload/data/body/FrameTest.java
rename to rollbar-payload/src/test/java/com/rollbar/payload/data/body/FrameTest.java
index 22af8dd..2b891d0 100644
--- a/src/test/java/com/rollbar/payload/data/body/FrameTest.java
+++ b/rollbar-payload/src/test/java/com/rollbar/payload/data/body/FrameTest.java
@@ -1,11 +1,10 @@
 package com.rollbar.payload.data.body;
 
-import com.rollbar.GetAndSet;
-import com.rollbar.TestThat;
-import com.rollbar.payload.utilities.ArgumentNullException;
+import com.rollbar.testing.GetAndSet;
+import com.rollbar.testing.TestThat;
+import com.rollbar.utilities.ArgumentNullException;
 import org.junit.Before;
 import org.junit.Test;
-import org.omg.CORBA.OBJECT_NOT_EXIST;
 
 import java.util.LinkedHashMap;
 import java.util.Map;
diff --git a/src/test/java/com/rollbar/payload/data/body/MessageTest.java b/rollbar-payload/src/test/java/com/rollbar/payload/data/body/MessageTest.java
similarity index 100%
rename from src/test/java/com/rollbar/payload/data/body/MessageTest.java
rename to rollbar-payload/src/test/java/com/rollbar/payload/data/body/MessageTest.java
diff --git a/src/test/java/com/rollbar/payload/data/body/TraceChainTest.java b/rollbar-payload/src/test/java/com/rollbar/payload/data/body/TraceChainTest.java
similarity index 83%
rename from src/test/java/com/rollbar/payload/data/body/TraceChainTest.java
rename to rollbar-payload/src/test/java/com/rollbar/payload/data/body/TraceChainTest.java
index f3f7900..2294be9 100644
--- a/src/test/java/com/rollbar/payload/data/body/TraceChainTest.java
+++ b/rollbar-payload/src/test/java/com/rollbar/payload/data/body/TraceChainTest.java
@@ -1,13 +1,14 @@
 package com.rollbar.payload.data.body;
 
-import com.rollbar.GetAndSet;
-import com.rollbar.TestThat;
-import com.rollbar.http.ConnectionFailedException;
-import com.rollbar.payload.utilities.ArgumentNullException;
-import com.rollbar.payload.utilities.InvalidLengthException;
+import com.rollbar.testing.*;
+
+import com.rollbar.utilities.ArgumentNullException;
+import com.rollbar.utilities.InvalidLengthException;
+
 import org.junit.Before;
 import org.junit.Test;
 
+import java.net.ConnectException;
 import java.net.URL;
 
 import static org.junit.Assert.*;
@@ -27,7 +28,7 @@ public class TraceChainTest {
     @Test
     public void testTraces() throws Exception {
         Trace one = Trace.fromThrowable(new ArgumentNullException("null_argument"));
-        Trace two = Trace.fromThrowable(new ConnectionFailedException(new URL("http://www.google.com"), "oops", null));
+        Trace two = Trace.fromThrowable(new ConnectException("oops"));
         TestThat.getAndSetWorks(t, new Trace[] { one }, new Trace[] { two }, new GetAndSet<TraceChain, Trace[]>() {
             public Trace[] get(TraceChain traceChain) {
                 return traceChain.traces();
diff --git a/src/test/java/com/rollbar/payload/data/body/TraceTest.java b/rollbar-payload/src/test/java/com/rollbar/payload/data/body/TraceTest.java
similarity index 94%
rename from src/test/java/com/rollbar/payload/data/body/TraceTest.java
rename to rollbar-payload/src/test/java/com/rollbar/payload/data/body/TraceTest.java
index 76b49cc..50fe54d 100644
--- a/src/test/java/com/rollbar/payload/data/body/TraceTest.java
+++ b/rollbar-payload/src/test/java/com/rollbar/payload/data/body/TraceTest.java
@@ -1,8 +1,8 @@
 package com.rollbar.payload.data.body;
 
-import com.rollbar.GetAndSet;
-import com.rollbar.TestThat;
-import com.rollbar.payload.utilities.ArgumentNullException;
+import com.rollbar.testing.GetAndSet;
+import com.rollbar.testing.TestThat;
+import com.rollbar.utilities.ArgumentNullException;
 import org.junit.Before;
 import org.junit.Test;
 
diff --git a/src/test/java/com/rollbar/http/InvalidResponseCodeExceptionTest.java b/rollbar-sender/src/test/java/com/rollbar/sender/InvalidResponseCodeExceptionTest.java
similarity index 92%
rename from src/test/java/com/rollbar/http/InvalidResponseCodeExceptionTest.java
rename to rollbar-sender/src/test/java/com/rollbar/sender/InvalidResponseCodeExceptionTest.java
index d7f0f1c..8e412ac 100644
--- a/src/test/java/com/rollbar/http/InvalidResponseCodeExceptionTest.java
+++ b/rollbar-sender/src/test/java/com/rollbar/sender/InvalidResponseCodeExceptionTest.java
@@ -1,4 +1,4 @@
-package com.rollbar.http;
+package com.rollbar.sender;
 
 import org.junit.Test;
 
diff --git a/src/test/java/com/rollbar/http/PayloadSenderTest.java b/rollbar-sender/src/test/java/com/rollbar/sender/PayloadSenderTest.java
similarity index 93%
rename from src/test/java/com/rollbar/http/PayloadSenderTest.java
rename to rollbar-sender/src/test/java/com/rollbar/sender/PayloadSenderTest.java
index 00ade87..34ae9eb 100644
--- a/src/test/java/com/rollbar/http/PayloadSenderTest.java
+++ b/rollbar-sender/src/test/java/com/rollbar/sender/PayloadSenderTest.java
@@ -1,8 +1,9 @@
-package com.rollbar.http;
+package com.rollbar.sender;
 
 import com.rollbar.payload.Payload;
 import com.rollbar.payload.data.Notifier;
-import com.rollbar.payload.utilities.ArgumentNullException;
+import com.rollbar.utilities.ArgumentNullException;
+
 import org.junit.Test;
 
 import static org.junit.Assert.*;
diff --git a/rollbar-testing/README.md b/rollbar-testing/README.md
new file mode 100644
index 0000000..d9b9a70
--- /dev/null
+++ b/rollbar-testing/README.md
@@ -0,0 +1,5 @@
+## Overview
+
+`rollbar-testing` contains shared test-code. If you find yourself
+needing the same test helpers across modules you should include them
+here.
\ No newline at end of file
diff --git a/rollbar-testing/pom.xml b/rollbar-testing/pom.xml
new file mode 100644
index 0000000..f1df2fc
--- /dev/null
+++ b/rollbar-testing/pom.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>com.rollbar</groupId>
+        <artifactId>rollbar-parent</artifactId>
+        <version>${currentVersion}</version>
+        <relativePath>../parent-pom.xml</relativePath>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>rollbar-testing</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.rollbar</groupId>
+            <artifactId>rollbar-utilities</artifactId>
+            <version>${currentVersion}</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <artifactId>maven-jar-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>test-jar</id>
+                        <phase>test-compile</phase>
+                        <goals>
+                            <goal>test-jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
\ No newline at end of file
diff --git a/src/test/java/com/rollbar/GetAndSet.java b/rollbar-testing/src/test/java/com/rollbar/testing/GetAndSet.java
similarity index 80%
rename from src/test/java/com/rollbar/GetAndSet.java
rename to rollbar-testing/src/test/java/com/rollbar/testing/GetAndSet.java
index d843194..763a7dc 100644
--- a/src/test/java/com/rollbar/GetAndSet.java
+++ b/rollbar-testing/src/test/java/com/rollbar/testing/GetAndSet.java
@@ -1,4 +1,4 @@
-package com.rollbar;
+package com.rollbar.testing;
 
 /**
  * Created by chris on 11/20/15.
diff --git a/src/test/java/com/rollbar/TestThat.java b/rollbar-testing/src/test/java/com/rollbar/testing/TestThat.java
similarity index 98%
rename from src/test/java/com/rollbar/TestThat.java
rename to rollbar-testing/src/test/java/com/rollbar/testing/TestThat.java
index 57a1f38..614ac18 100644
--- a/src/test/java/com/rollbar/TestThat.java
+++ b/rollbar-testing/src/test/java/com/rollbar/testing/TestThat.java
@@ -1,12 +1,12 @@
-package com.rollbar;
-
-import com.rollbar.payload.utilities.Extensible;
-import com.rollbar.payload.utilities.StringUtils;
+package com.rollbar.testing;
 
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
 import java.util.*;
 
+import com.rollbar.utilities.StringUtils;
+import com.rollbar.utilities.Extensible;
+
 import static org.junit.Assert.*;
 
 /**
diff --git a/src/test/java/com/rollbar/payload/utilities/ValidateTest.java b/rollbar-utilities/src/test/java/com/rollbar/utilities/ValidateTest.java
similarity index 72%
rename from src/test/java/com/rollbar/payload/utilities/ValidateTest.java
rename to rollbar-utilities/src/test/java/com/rollbar/utilities/ValidateTest.java
index d0ce92e..f327067 100644
--- a/src/test/java/com/rollbar/payload/utilities/ValidateTest.java
+++ b/rollbar-utilities/src/test/java/com/rollbar/utilities/ValidateTest.java
@@ -1,10 +1,8 @@
-package com.rollbar.payload.utilities;
+package com.rollbar.utilities;
 
-import org.hamcrest.Matcher;
 import org.junit.Test;
 
-import static org.junit.Assert.*;
-import static org.hamcrest.CoreMatchers.*;
+import org.junit.Assert;
 
 /**
  * Created by chris on 11/20/15.
@@ -31,7 +29,7 @@ public class ValidateTest {
             Validate.isNotNullOrWhitespace("  \t\n", "argName");
         } catch (ArgumentNullException e) {
             String msg = e.getMessage();
-            assertTrue(String.format("'%s' does not contain 'argName'", msg), msg != null && msg.contains("argName"));
+            Assert.assertTrue(String.format("'%s' does not contain 'argName'", msg), msg != null && msg.contains("argName"));
         }
     }
 
@@ -46,8 +44,8 @@ public class ValidateTest {
             Validate.maxLength("Test", 2, "argName");
         } catch (InvalidLengthException e) {
             String msg = e.getMessage();
-            assertTrue(String.format("'%s' does not contain 'over 2'", msg), msg != null && msg.contains("over 2"));
-            assertTrue(String.format("'%s' does not contain 'argName'", msg), msg.contains("argName"));
+            Assert.assertTrue(String.format("'%s' does not contain 'over 2'", msg), msg != null && msg.contains("over 2"));
+            Assert.assertTrue(String.format("'%s' does not contain 'argName'", msg), msg.contains("argName"));
         }
     }
 
@@ -62,8 +60,8 @@ public class ValidateTest {
             Validate.minLength(new Character[0], 2, "argName");
         } catch (InvalidLengthException e) {
             String msg = e.getMessage();
-            assertTrue(String.format("'%s' does not contain 'under 2'", msg), msg != null && msg.contains("under 2"));
-            assertTrue(String.format("'%s' does not contain 'argName'", msg), msg.contains("argName"));
+            Assert.assertTrue(String.format("'%s' does not contain 'under 2'", msg), msg != null && msg.contains("under 2"));
+            Assert.assertTrue(String.format("'%s' does not contain 'argName'", msg), msg.contains("argName"));
         }
     }
 
@@ -78,7 +76,7 @@ public class ValidateTest {
             Validate.isNotNull(null, "argName");
         } catch (ArgumentNullException e) {
             String msg = e.getMessage();
-            assertTrue(String.format("'%s' does not contain 'argName'", msg), msg != null && msg.contains("argName"));
+            Assert.assertTrue(String.format("'%s' does not contain 'argName'", msg), msg != null && msg.contains("argName"));
         }
     }