Theory In Practice

HalBuilder 4.x Released

HalBuilder 4.x

Finally, after way too long a period of time - I’ve finally released the 4.x series of the HalBuilder library, with several long awaited, and long requested features:

  • Access to the original, underlying content.

  • Array and Complex object support reading JSON

  • Support for recursive collection support for the InterfaceRenderer.

  • Support for exposing links/resources to the InterfaceRenderer.


  • halbuilder-api — 4.0.1

  • halbuilder-core — 4.0.3

  • halbuilder-json — 4.0.2

  • halbuilder-xml — 4.0.1

  • halbuilder-guava — 4.0.1

  • halbuilder-standard — 4.0.1


Resource Content

The return type from reading remote representations is now the ContentRepresentation class, which adds a simple String getContent() method to access the underlying representation content:

ContentRepresentation rep = factory.readRepresentation(HAL_JSON, inputStream);
String content = rep.getContent()

If the original representation contained subresource representations, and you call getContent on one of those subresources, then you’ll only get the content of that particular resource, and not the full stream.

Arrays and Complex Objects

Given the following resource representation, comtaining two forms of complex objects:

    "_links" : {
      "self" : { "href" : "" },
    "child" : { "age" : 12, "expired" : false, "id" : 111111, "name" : "Example Resource 1","optional" : true  },
    "children" : [
        { "age" : 12, "expired" : false, "id" : 111111, "name" : "Example Resource 1","optional" : true  },
        { "age" : 10, "expired" : false, "id" : 222222, "name" : "Example Resource 2","optional" : true  }

We can access the complex object either via getContent above, and using any JSON parser at our disposal, or you can simply access the property ( however, under Java we will need to cast it to a Map):

ContentRepresentation rep = factory.readRepresentation(HAL_JSON, inputStream);

Map child = (Map) rep.getValue("child");
String name = child.get(name);

List<Map> children = (List<Map>) rep.getValue("children");
Integer age = Lists.idx(list, 0).get("age");

Improvements to Interface Rendering

The interface renderer has had support added to allow for Map and List type properties as well:

public static interface Family {
  public Map child();
  public List<Map> children();

Map childMap =  rep.toClass(Family.class).child();
Integer age = childMap.get("age";)

List<Map> childList = rep.toClass(Family.class).children();
Integer age = idx(childList, 0).get("age");

Interface rendering can now be taken further with recursive interfaces:

  public static interface Family2 {
      public Child child();
      public List<Child> children();

  public static interface Child {
      public Integer age();
      public Integer name();

Child child =  rep.toClass(Family2.class).child();
Integer age = child.age();

List<Child> childList = rep.toClass(Family2.class).children();
Integer age = idx(childList, 0).age();

Interface rendering can also now expose a representations links and underlying sub-represenations by declaring two special methods:

public static interface Family3 {
  List<Link> getLinks();

  Map<String, Collection<ReadableRepresentation>> getEmbedded();

Chatting in Haskell

The other week we held a CodeLounge centered on the Go langauge from Google. The project we tasked ourselves with writing for the day was a simple replicated/distributed network chat system.

The version Richard and I came up is available on Github and I decided over lunch to give my hand at writing a Haskell based version to compare.

We decided on using a simple text based socket protocol to distribute messages around the network: one message per line with the first word being a UUID to identify the message being sent.

data Message = Message UUID String
  deriving (Show, Eq)

Here we declare our system message type - now we can start our project. In the Go version we used channels, so why not the same here? A quick googling leads me to Control.Concurrent.Chan and the basic shape ends up looking very much like the original Go version:

  1. Create a channel to hold the messages

  2. Start a thread which continuously reads from STDIN for text, constructing a message and submitting it to the channel for processing.

  3. On the main thread, continously read from the channel displaying the message back on the screen.

main = do
    channel  <- newChan
    forkIO (getMessagesFromStdIn channel)
    displayMessagesFrom channel
    forever a = a >> forever a
    getMessagesFromStdIn channel = forever $ do
      line <- getLine
      uuid <- nextRandom
      writeChan channel $ Message uuid line
    displayMessagesFrom channel = do
      msgs <- getChanContents channel
      mapM_ (\msg -> putStrLn $ show msg ) msgs

The code here is pretty straight forward, except for two pieces of “magic”.

The first here being the forever helper function which simply recursivly binds a IO a operation to itself, essentially an endless loop ( but that’s ok because we’re running this in a background thread ).

The second is the call to mapM_ which seems rather strange, but isn’t really:

mapM_ :: (Foldable t, Monad m) => (a -> m b) -> t a -> m ()

Map each element of a structure to a monadic action, evaluate these actions from left to right, and ignore the results.

Basically, it’s a standard map operation that gets lifted into an IO () monad which is what our main function requires.

What I find interesting here, is that the getChanContents function returns a lazy, blocking list of values from the channel.

Now that we have our basic chat system, lets open ourselves up to other participants with a socket connection. In a similar fashion to our Go version, we want something running on another thread that takes incoming connections and reading Message values to push on the channel.

This is where I think the Go is a bit clearner and concise, we start by updating our main function with:

sock <- getListeningSocket
forkIO (listenForSocketConnections sock channel)
    getListeningSocket = do
      sock <- socket AF_INET Stream 0
      bindSocket sock (SockAddrInet 7077 iNADDR_ANY)
      listen sock 2
      return sock
    listenForSocketConnections sock channel = forever $ do
      (conn, _) <- accept sock
      hdl <- socketToHandle conn ReadWriteMode
      hSetBuffering hdl NoBuffering
      forkIO (getMessagesFromSocket hdl channel)
    getMessagesFromSocket hdl channel = forever $ do
        line <- hGetLine hdl
        uuid <- nextRandom
        writeChan channel $ Message uuid line

getListeningSocket simply gets, binds, and listens to a socket, then passes that to another thread for handling - unlike the Go variant this call is spread over a few lines - there may be a more consice way but this is all I could find - and not so much of an issue.

listenForSocketConnections repeats forever, accepting sockets and forking yet another thread to handle the messages received. At all points along the way we’re passing in the channel which is finally used again in getMessagesFromSocket.

Taking this one step further we can also echo back out to all connected clients any message we receive by repeating something similar to our displayMessagesFrom function, rewriting getMessagesFromSocket to duplicate the channel, and fork yet another thread to displayMessagesFrom that channel, onto the handler:

    getMessagesFromSocket hdl channel = do
      echoChan <- dupChan channel
      forkIO (displayMessagesFrom echoChan hdl)
      forever $ do
        line <- hGetLine hdl
        uuid <- nextRandom
        writeChan channel $ Message uuid line
    displayMessagesFrom channel hdl = do
      msgs <- getChanContents channel
      mapM_ (\msg -> hPutStrLn hdl $ show msg ) msgs

Unlike the Go implemenation when I constructed a mutable Map to track the open sockets to echo back out, here we simple call dupChan to create a duplicate channel, which receives a copy of all messages sent to the original and use that. There may be a way of doing something similar in Go but I’ve not seen it mentioned.

The final code I came up with is on Github as a simple gist.

It’s interesting to me to compare the differences (or rather, the similarities) in the concurrency models here - both end up being clean and easy to follow.


For awhile I’ve been using Thomas Jung’s QuickCheck port to Java in various tests but I’ve never really exploited it to the level I probably should, in part because the visibility over which pieces of generated data failed was not as clear as it could have been - usually due to the fast fail nature of the tests.

At the same time I’m also a huge fan of TestNG’s @DataProvider support for writing tests that get their data given to them - and reported as individual test failures.

Now it’s fairly trivial to integrate the two, and write a simple conversation from a given QuickCheck Generator and expose that as a TestNG data provider method, but sometimes even a small amount of boiler-plate is enough to drive someone like myself crazy - so let’s automate that sucker with todays simple library - QuickCheckNG:



The QuickCheckNG library is a combination of a single annotation, an associated annotation processor and another small utility class to generate TestNG @DataProvider methods for a set of QuickCheck Generator methods.

Let’s say we have a method that validates a set of length contraints on some input:

public static void lengthCheck(String input) {
  if (input.length() < MIN_LENGTH) {
    throw new IllegalArgumentException("Too short.");
  } else if (input.length() >MAX_LENGTH) {
    throw new IllegalArgumentException("Too long.");

We can create a set of property generators to match positive/negative test cases for this:

package com.theoryinpractise.quickcheckng;

import com.theoryinpractise.quickcheckng.DataProviders;

import static;

public class LengthGenerators {

  public static Generator<String> validLengths() {
    return strings(LengthCheck.VALID_CHARACTERS,

  public static Generator<String> invalidLengths() {
    final Generator<String> underSized = strings(LengthCheck.VALID_CHARACTERS,

    final Generator<String> overSized = strings(LengthCheck.VALID_CHARACTERS,
                                                LengthCheck.MAX_LENGTH + 1,
                                                LengthCheck.MAX_LENGTH + 100);

    return new Generator<String>() {
      boolean useFirst = false;
      public String next() {
        useFirst = !useFirst;
        return useFirst ? :;

The first generator - validLengths is rather simple and simply returns a standard string generator, with a given min/max length, the second generator - invalidLengths is only a little more involved, taken a pair of string generators with min/max values either side of our valid range, then returning a new generator which alternates between values from each.

The magic that QuickCheckNG adds here is in the @DataProviders annotation at the class level - this tells our annotation processor to generate a new class, with a TestNG @DataProvider method for each public static Generator<T> method found in this class.

Given these two things - we can now write a pair of tests mixing them:

@Test(dataProviderClass = LengthGeneratorsDataProviders.class, dataProvider = "validLengths")
public void testPasswordLengths(String input) {
  try {
  } catch (IllegalArgumentException e) {
    fail("Valid length should not fail: " + input + " - " + e.getMessage());

@Test(dataProviderClass = LengthGeneratorsDataProviders.class, dataProvider = "invalidLengths")
public void testInvalidPasswordLengths(String password) {
  try {
    fail("Invalid length should fail: " + input);
  } catch (IllegalArgumentException e) {
    // Ignore

Given the generators, TestNG now runs these two tests against a barage of around 500 or so random inputs that match the properties we’ve described.

So what’s in the generated class?

package com.theoryinpractise.quickcheckng;

import java.util.Iterator;
import org.testng.annotations.DataProvider;
import static com.theoryinpractise.quickcheckng.testng.GeneratorProvider.toObjectArrayIterator;

public final class LengthGeneratorsDataProviders {

   * A TestNG @DataProvider for the validInputs quickcheck generator
  public static final Iterator<Object[]> validInputs() {
    return toObjectArrayIterator(LengthGenerators.validInputs());

   * A TestNG @DataProvider for the invalidInputs quickcheck generator
  public static final Iterator<Object[]> invalidInputs() {
    return toObjectArrayIterator(LengthGenerators.invalidInputs());

As previously mentioned, it’s an extremely simple wrapping class, that delegates to the toObjectArrayIterator helper function which takes all the values from a generator into a HashSet containing a single Object[] then returned as an Iterator for TestNG to apply to your test methods.

Intra-Repository Review Builds with Gerrit and Apache Maven

As a follow up to an earlier post on using the Gerrit Code Review tool and automated topic submission, I thought I’d write a short post on how I’ve now solved the problem of intra-project dependent review builds with Apache Maven.

Along with using Gerrit, we also use the Jenkins Gerrit integration to provide automated build/verification of reviews. Traditionally, we’d configured the Jenkins review jobs to run mvn clean verify so that review builds wouldn’t polute the local maven repository - unfortunately the downside of this was a lack of propagation of -SNAPSHOT artifacts for other, related review jobs to use. This leads to false-negative build errors and lengthy arguments over whether or not every commit should pass.

After thinking about this for awhile and trying various alternative approaches I’ve finally come up with a novel solution - dynamically defined local repositories.

Whenever Gerrit triggers a Jenkins Job it sets, among others the GERRIT_BRANCH and GERRIT_TOPIC environment variables, after realizing that Mavens property expansion also works in the ~/.m2/settings.xml file, I updated the build servers configuratoin to include:

<?xml version="1.0"?>

Which after a few jobs have fun yields a directoty structure like:

drwxr-xr-x 91 jenkis jenkis   4096 Feb  5 02:41 default
drwxr-xr-x  4 jenkis jenkis   4096 Feb  2 23:57 develop-
drwxr-xr-x 43 jenkis jenkis   4096 Feb  2 23:58 develop-${env.GERRIT_TOPIC}
drwxr-xr-x 48 jenkis jenkis   4096 Feb  4 06:40 develop-jdk8
lrwxrwxrwx  1 jenkins jenkins    7 Jan 31 02:55 ${env.GERRIT_BRANCH}-${env.GERRIT_TOPIC} -> default  

Unfortunately it doesn’t appear that Maven’s property expansion allows for default values which generates the lovely ${env.GERRIT_TOPIC} directory entries - not very attractive, but also not really an issue.

The upside of this however is that Maven is now building each branch/topic pair in isolated directories, across disparate Gerrit projects - with this in place you can set jenkins to simply mvn clean install the build like normal - and now, you’re Gerrit review builds will be legimately green when they should be, and red when they legitimately have build errors.

clojure-maven-plugin 1.3.19

clojure-maven-plugin 1.3.19 was released earlier in the week - this is just a minor update which adds a new setting:


Similar to the <temporaryOutputDirectory/> setting which will compile your main sources to a temporary directory,
this new setting will do the same for your test sources.

Free Refills with coffee-maven-plugin 1.4.10

A new release of the coffee-maven-plugin has been pushed out to Maven Central adding support for CoffeeScript 1.7.1 ( see the change log ) whilst skipping the earlier 1.7.0 release.

Along with the update, the plugin now supports configuring additional source directories to look for join-sets in, extra coffee can be had no configuring a list of refills:


This will allow you another means of organising your source layouts.

Lamdafication of HalBuilder - An experiment in API evolution

Lamdafication of HalBuilder - An experiment in API evolution.

Since Java 8 is almost upon us, I thought I’d make use of the Christmas break to give a moment to investigate updating an existing API to make use of some of the newer features, and since I’ve recently been doing some minor work on HalBuilder - why not look at what could be done there?

The intention in this exercise is not an exhaustive look at Java 8 in detail, but merely to take one element of our existing API, and enhance it with some lovely lambda love and see what falls out.

Note: To anyone coming from Groovy, Scala, Kotlin or almost any other language out there today - this is childs play, but play along and be nice :)

One of the driving things behind HAL, and RESTful APIs in general is that of links between resources and services, so the discovery and usage of such links is often a common thing you do.

The existing HalBuilder API provides two functions on the ReadableRepresentation interface: getLinksByRel and the singular getLinkByRel which asserts that only one value exists, and returns just that value rather than a collection of matching links.

Using these API calls, a consumer of the HalBuilder API is lead down a path of local variables, collection iteration, and tightly coupled pieces of inline logic and other constructs that often prohibit reuse, or merely blur the business intent of the method at hand - maybe some lambda love can bring something useful to the table?

We can start to clean this up by providing two additional methods to the interface:

<T> Stream<T> mapLinksByRel(String rel, Function<Link, T> fn );
<T> T mapLinkByRel(String rel, Function<Link, T> fn );

The first thing to notice here is the usage of the Function class ( Google Guava anyone? ). Oracles decision to not introduce a new function type syntax in favour of the reusing the existing Java idiom of single method interfaces initially feels somewhat cumbersome after using Scala, but also brings about a level of simplicity and familiarity that is quite refreshing.

These two methods are intended to operate practically identical to the find* methods above, except that they take, and apply the given Function<T,R> argument before returning with the result of the function. The implementation of these two functions is also rather trivial and actually reuse our existing methods:

public <T> Stream<T> mapLinksByRel(final String rel, Function<Link, T> fn) {
    return getLinksByRel(rel).stream().map(fn);
public <T> T mapLinkByRel(final String rel, Function<Link, T> fn) {
    return mapLinksByRel(rel, fn).findFirst().orElse(null);

In the first function, we simply call our existing getLinksByRel method, turn it into a Stream and mapping the provided fn over each element in the stream, whilst the second function takes the first element off the (mapped) stream ( returning a new Optional instance ) with which we then return the actual value, or return null if the sequence is empty.

Returning null here is something I’d probably not normally recommend, but with the HalBuilder 2.x API I decided to not bleed out Guava’s own Optional class to make usage from other languages more easier, so this is just a hold over from that.

So… we have our new methods defined - lets use them…

We’ll start off with a simple HAL representation to work with:

<resource href=“/userlist”>
  <link rel=“create-user” href=“/create-user{?username}” templated=“true”/>
Representation representation = …

We have a simple resource for a user listing, which happens to contain a link to a user creation endpoint resource - we’ve been told that we can POST to the resolved template URI to create new users; we’ve also been told that resolution of the endpoint takes a username value - somewhere in the URI.

Let’s start with simple function that merely expands that template as a URI instance:

final URI uri = representation.mapLinkByRel("create-user", link -> {
  try {
    return new URI(UriTemplate.fromTemplate(link.getHref())
      .expand(ImmutableMap.of("username", "example”)));
  } catch (URISyntaxException | MalformedUriTemplateException | VariableExpansionException e) {
    throw new RuntimeException(e);

Here we see our first usage of the new Java 8 lambda syntax, along with Java 7’s mutli catch. Not too unpleasant to look at - we see a nice measure of type inference in the lambda argument, but since our code block isn’t a simple expression we need to wrap everything inside braces.

That hard-coded “example” username however really bites…

We could easily extract that to a variable, but let’s take a different tack and extract the function that surrounds it!

public static Function<Link, URI> createUserFn(String username) {
  return link -> {
    try {
      return new URI(UriTemplate.fromTemplate(link.getHref())
        .expand(ImmutableMap.of("username", username)));
    } catch (URISyntaxException | MalformedUriTemplateException | VariableExpansionException e) {
      return null;

Here we’ve extracted a method, that closes over the username argument, and returns a new Function<Link,URI>. What’s interesting to note here is that we’ve not had to declare the argument as final - another small Java 8 feature which subtly adds to the readability of the code, this can now be called with:

representation.mapLinkByRel("create-user", createUserFn("example"));

But let’s go further, realising that we can further extract the function surrounding any map and URI Template, we give ourselves:

public static Function<Link, URI> uriTemplateFn(Map data) {
  return link -> {
    try {
      return new URI(UriTemplate.fromTemplate(link.getHref()).expand(data));
    } catch (URISyntaxException | MalformedUriTemplateException | VariableExpansionException e) {
      throw new RuntimeException(e);

and while where there, let’s introduce a new, independently testable function for doing HTTP calls:

public static enum Method { GET, POST, PUT, DELETE }
public static <T> Function<URI, Future<T>> asyncHttpFn(Method method, Function<Response, T> handler) {
  AsyncHttpClient ahc = new AsyncHttpClient();
  return href -> {
    try {
      Request request = new RequestBuilder(
      return ahc.prepareRequest(request).execute(new AsyncCompletionHandler() {
        public Object onCompleted(Response response) throws Exception {
          return handler.apply(response);
    } catch (IOException e) {
      throw new RuntimeException(e);

This new function, whilst also not all that complex (aside from some boilerplate), may take a while for you get your head around if your unfamiliar with functional programming, or programming with callbacks.

The asyncHttpFn method takes a Method enum, which is the HTTP method to call, along with a function that goes from a Response to some generic type T.

What I find interesting here is that I initially tried to simply accept an AsyncCompletionHandler instance for the method - which has a single abstract method onCompleted but javac told me this was not allowed - so I opted to pass in a simple function and wrap it.

Given this new function, we can again rewrite our createUserFn function combining both uriTemplateFn and asyncHttpFn to be:

public static Function<Link, Future<User>> createUserFn(String username) {
  return uriTemplateFn(ImmutableMap.of("username", username)).andThen(
    asyncHttpFn(Method.POST, response -> new User()));

One of the powerful features that Java 8’s Function class has are the pair of functions andThen and compose which return a new function, from the composition of another function - either before or after applying the original function.

In this instance, we take our first function which expands a Link to a URI over a given Map and then applies that to the function returned by supplying another lambda to asyncHttpFn function, which converts a Response to a User, which is finally returned asynchronously as a Future - quite mind bending…

This leads us to rewriting our usage, handling the future as:

final User user = representation.mapLinkByRel("create-user", createUserFn("example"))
  .get(5, TimeUnit.SECONDS);

and finally wrapping things up inside one last Java 8 encapsulation - the ability to add static methods to an interface:

public static interface UserRepresentation {
  static User createUser(Representation representation, String username)  {
    try {
      return representation.mapLinkByRel("create-user", createUserFn(username)).get(5, TimeUnit.SECONDS);
    } catch (InterruptedException | TimeoutException | ExecutionException e) {
      throw new RuntimeException(e);

which lets us rewrite our client application as something as simple as:

User newUser = UserRepresentation.createUser(representation, "anotherexample");

Sadly default methods on interfaces don’t really help here, but this will do.

After experimenting briefly with Java 8 lambdas whilst writing this, I think they’ll be making a HUGE impact on Java code bases over the next year or so - assuming you’re project is in a position to actually migrate.

What I do find interesting is that whilst lambdas bring a hefty fresh air to Java, lambdas alone don’t bring all the love and that maybe Kotlin’s half-way measure is the way to go - type inference, extension methods, and lambdas without the epically large type system.

HalBuilder 3.1.2 (and others) released

HalBuilder 3.1.2 Released

With Christmas and the passing of the year it seems I’ve found spare time ( which I’ve sadly been promising myself for months to find ) to actually pull on a few pull requests and make a few tweaks to the HalBuilder set of libraries.

First up are some long awaited dependency updates including Google Guava 15 ( 16.0-RC1 is now out, and a new set of releases will be forth coming supporting that ) along with updates to Jackson 2.3.0 and JDOM 2.0.2 for the JSON and XML support respectively.

As part of this change, the halbuilder-core artifact itself no longer depends on JDom as the internal constant is now housed inside the XMLRepresentationFactory itself - so if you’re only using the JSON support: one less dependency for you!


A rather contentious part of the HAL Specification is that the _links map may contain either an object or an array of objects which lead end-developers to constantly check for one or the other ( unless they themselves are using a library ) so the SINGLE_ELEM_ARRAY flag has been introduced.

The initial pull request simply used the flag to configure Jackson to render a single element array property as an array, but I extended this to also expand both the _links and _embedded collections to always use arrays ( with the exception of the self rel ).

As with other flags, this is provided as a constant on the RepresentationFactory class from halbuilder-api.

getLinksByRel no longer descends

Initially added with a “TODO” comment question its value - the getLinksByRel method on a Representation no longer searches its child representations for links, without any form of result context identify which representation a link came from this made little sense - so it’s gone…


As always, the following artifacts have been released to Maven Central, if you’re using the halbuilder-standard artifact then you’ll automatically pick up the newer releases via version ranges ( hopefully those dependency updates don’t cause you grief - but if they do - update ;p ).


HalBuilder 3.0.1 Released

Last night I released version 3.0.1 of my HAL - Hypertext Application Language Java library to Maven Central.

As part of this release everything except the API was updated so everything remains the same (code wise) for users, however changes have been made to how a RepresentationFactory is created, which is the impetus for the 3.0.1 version numbering.

The following resources were released:

  • halbuilder-test-resources
  • halbuilder-core
  • halbuilder-json
  • halbuilder-xml
  • halbuilder-standard

and as usual, can be found with the following Apache Maven dependency declaration:


Extracted codec’s for XML and JSON to separate projects

One of the main changes with this release is that the HAL+JSON and HAL+XML serializer/deserializer code has been extracted to separate git repositories/artifacts - giving them there own independent release cycles.

A driving factor in this is not wanting to hold back development/pull requests/improvements to either the JSON or the XML where one may, or may not be round-trippable from one format to the other.

As part of this change, the DefaultRepresentationFactory no longer comes preconfigured with any representation support beyond interface mirroring, instead the XML and JSON artifacts contain a XmlRepresentationFactory and JsonRepresentationFactory subclass for you to use which configures HalBuilder to use the respective formats.

If you wish to continue supporting both, you can use the halbuilder-standard artifact and use StandardRepresentationFactory where you would have originally used the default.

Refactored JSON Writer for Customisation

As part of these changes, JsonRepresentationWriter has had two methods extracted as protected for third party customization - getJsonGenerator and getJsonFactory which will allow custom data formats/serialisation to be configured. Of course, with oversight - extracting getting the ObjectMapper on JsonRepresentationReader wasn’t done in this release, but thankfully now these are separate projects that’s an even quicker and simpler change.

Reworked namespace/currie support

When adding namespaces now, the URI provided MUST be a URI-Template including {rel} somewhere.

Examples Project

Since the core project no longer contained any representation support, the singular IT test in the project no longer worked, so this has now been moved to a new halbuilder-examples project along with some other example projects showing how to use HalBuilder in different contexts.

Updated dependencies

As part of this release, Google Guava was updated to version 14.0.1.

Grab the updates and report back any bugs/issues/features/adoration.

clojure-maven-plugin 1.3.17

So as it goes, the moment after I do a minor patch release someone else provides a simple bug, along with a pull request that looks good, so another minor point release is pushed to central:


This updates the clojure:run goal to use both compile and runtime dependencies on the classpath.

It’s a small change, but solves a lot of headaches for people.