home.


Tagged: jersey


Jersey: Custom bean validation / ConstraintViolationException

With bean validation in Jersey, you get JSON errors like this:

[
  {
    "message":"may not be null",
    "messageTemplate":"{javax.validation.constraints.NotNull.message}",
    "path":"Hello.register.arg0.email",
    "invalidValue":null
  }
]

That’s great, but sometimes you have errors that appear after bean validation, such as when someone tries to register as an existing user.

You will only know about this when you talk to your data stores of existing users.

It would be nice to keep the above error format, but talk about more general errors. You do this by creating a custom ConstraintViolation and throwing a ConstraintViolationException.

The ConstraintViolationException takes a HashSet of ConstraintViolation. Let’s create a dummy ConstraintViolation in Scala (the Java version is obviously just about the same):

new ConstraintViolation[Object] {
  def getConstraintDescriptor(): ConstraintDescriptor[_] = null
  def getExecutableParameters(): Array[Object] = null
  def getExecutableReturnValue(): Object = null
  def getInvalidValue(): Object = "The invalid valid"
  def getLeafBean(): Object = null
  def getMessage(): String = "Something went wrong"
  def getMessageTemplate(): String = ""
  def getPropertyPath(): javax.validation.Path = new MyPropertyPath(argPath)
  def getRootBean(): Object = obj
  def getRootBeanClass(): Class[Object] = classOf[Object]
  def unwrap[U](t: Class[U]): U = t.newInstance
}

Most of the values above are empty, except for getInvalidValue, getMessage, getPropertyPath, getRootBean and getRootBeanClass

getInvalidValue displays the invalid value in the JSON above. getMessage displays the messages. getPropertyPath helps display path above: it’s just a dumy object with an dumy iterator and we use its toString method to fill out the rest of the "path"

The getRootBean gives us the name at the start of the "path" json reply. getRootBeanClass and unwrap need to be non-null but don’t seem to affect the JSON output.

Let’s put this altogether:

// Dumpy Path object: we only want its "toString"
class MyPropertyPath(var pathName: String) extends javax.validation.Path {
  def iterator():java.util.Iterator[javax.validation.Path.Node] = {
    return new java.util.Iterator[javax.validation.Path.Node] {
      def hasNext(): Boolean = false
      def next(): javax.validation.Path.Node = null
    }
  }
  override def toString:String = pathName
}

def customValidationErrorMessage(obj: Object, argPath: String, message: String, invalid: Object) = {
  var hs = new HashSet[ConstraintViolation[_]]
  hs.add(new ConstraintViolation[Object] {
    def getConstraintDescriptor(): ConstraintDescriptor[_] = null
    def getExecutableParameters(): Array[Object] = null
    def getExecutableReturnValue(): Object = null
    def getInvalidValue(): Object = invalid
    def getLeafBean(): Object = null
    def getMessage(): String = message
    def getMessageTemplate(): String = ""
    def getPropertyPath(): javax.validation.Path = new P(argPath)
    def getRootBean(): Object = obj
    def getRootBeanClass(): Class[Object] = classOf[Object]
    def unwrap[U](t: Class[U]): U = t.newInstance
  })
  throw new ConstraintViolationException("", hs)
}

If we call customValidationErrorMessage(register, "username", "Duplicate user", "dave") in a Jersey method, we’ll get this returned:

[
  {
    "message":"Duplicate user",
    "messageTemplate":"",
    "path":"RegisterUser.username",
    "invalidValue":"dave"
  }
]

Voila!

jersey bean-validation scala

Jersey: Bean validation with JSON return objects

Here’s how to get JSON error messages detailing validation errors in your REST input.

I’m doing this in Scala, but the Java version is obviously nearly the same.

First let’s look at our gradle dependencies, jaxrs, jetty, jackson, bean validation and our default logger.

 compile 'org.glassfish.jersey.bundles:jaxrs-ri:2.23.1'
 compile 'org.glassfish.jersey.containers:jersey-container-jetty-http:2.23.1'
 compile 'org.glassfish.jersey.media:jersey-media-json-jackson:2.16'
 compile 'org.glassfish.jersey.ext:jersey-bean-validation:2.23.2'
 compile "org.slf4j:slf4j-simple:1.6.1"

We need to turn bean validation error messages on in our ResourceConfig:

new ResourceConfig() {
  register(classOf[Hello])
  property(ServerProperties.BV_SEND_ERROR_IN_RESPONSE, true)
}

We’ll define a class with @NonNull contraints, specifying Jackon will deserialise looking at fields not getters and setters for convenience.

@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
class RegisterUser() {
  @NotNull var email: String = ""
  @NotNull var username: String = ""
  @NotNull var password: String = ""
}

Now the standard Jersey resource class, but now with the @Valid annotation on the POST object.

@Path("/") class Hello {
  @Path("register") @POST @Produces(Array(MediaType.APPLICATION_JSON))
  def register(@Valid register: RegisterUser) : RegisterUser = return register
}

Let’s put it altogether:

import javax.validation.constraints.{NotNull};
import javax.validation.{Valid};
import javax.ws.rs.core.{MediaType, UriBuilder}
import javax.ws.rs.{ApplicationPath, Path, Produces, POST}
import org.glassfish.jersey.server.{ResourceConfig, ServerProperties}
import org.glassfish.jersey.jetty.JettyHttpContainerFactory
import com.fasterxml.jackson.annotation.{JsonAutoDetect}

object rest {

  @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
  class RegisterUser() {
    @NotNull var email: String = ""
    @NotNull var username: String = ""
    @NotNull var password: String = ""
  }

  @Path("/") class Hello {
    @Path("register") @POST @Produces(Array(MediaType.APPLICATION_JSON))
    def register(@Valid register: RegisterUser) : RegisterUser = return register
  }

  def main(args: Array[String]) : Unit = {
    JettyHttpContainerFactory.createServer(
      UriBuilder.fromUri("http://localhost/").port(8901).build(),
      new ResourceConfig() {
        register(classOf[Hello])
        property(ServerProperties.BV_SEND_ERROR_IN_RESPONSE, true)
      }
    )
  }

}

Let’s run the application using scala -cp "dependencies/*" valid.scala (dependencies is where my dependencies are. See this.

Here’s the curl command detailing the error in JSON:

curl -H "accept: application/json" -H "content-type: application/json" -X POST -d '{"username": "me", "email": null, "password":"p"}' http://localhost:8901/register
[{"message":"may not be null","messageTemplate":"{javax.validation.constraints.NotNull.message}","path":"Hello.register.arg0.email","invalidValue":null}]
jersey bean-validation jackson scala

Scala: Simplest Jersey service with Jetty returning JSON via Jackson

If you want a quick web service with Jersey in Jetty returning JSON, you must:

  • Define the basic Jersey class with annotations.
  • Use JettyHttpContainerFactory to start the Jetty service.

Let’s put it all together, in a file called rest.scala.

import java.util.ArrayList
import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
import javax.ws.rs.{ApplicationPath, GET, Path, Produces}
import javax.ws.rs.core.{MediaType, UriBuilder}
import javax.ws.rs.{ApplicationPath, GET, Path, Produces}
import javax.ws.rs.core.{MediaType}
import org.eclipse.jetty.server.handler.AbstractHandler
import org.eclipse.jetty.server.{Server, Request}
import org.glassfish.jersey.server.ResourceConfig
import org.glassfish.jersey.jetty.JettyHttpContainerFactory

object rest {

  @Path("/") class Hello {
    @Path("hello") @GET @Produces(Array(MediaType.APPLICATION_JSON))
    // Returns ArrayList, not a scala List, since Jackson can't convert that
    def example() : ArrayList[String] = {
      var al = new ArrayList[String]()
      al.add("hmm")
      return al
    }
  }

  // Not run from a scala script, since that strangely can't find Hello
  def main(args: Array[String]) : Unit = {
    JettyHttpContainerFactory.createServer(
      UriBuilder.fromUri("http://localhost/").port(9998).build(),
      new ResourceConfig(classOf[Hello])
    )
  }
}

You need these dependencies, which you can use with this via gradle -b deps.gradle copyDependencies:

    compile 'org.glassfish.jersey.bundles:jaxrs-ri:2.23.1'
    compile 'org.glassfish.jersey.containers:jersey-container-jetty-http:2.23.1'
    compile 'org.glassfish.jersey.media:jersey-media-json-jackson:2.16'

Or just download them all via this bash script (urls obtained via the stderr output of the above script):

wget -P dependencies/ https://repo1.maven.org/maven2/javax/inject/javax.inject/1/javax.inject-1.jar
wget -P dependencies/ https://repo1.maven.org/maven2/javax/annotation/javax.annotation-api/1.2/javax.annotation-api-1.2.jar
wget -P dependencies/ https://repo1.maven.org/maven2/javax/ws/rs/javax.ws.rs-api/2.0.1/javax.ws.rs-api-2.0.1.jar
wget -P dependencies/ https://repo1.maven.org/maven2/javax/validation/validation-api/1.1.0.Final/validation-api-1.1.0.Final.jar
wget -P dependencies/ https://repo1.maven.org/maven2/javax/servlet/javax.servlet-api/3.1.0/javax.servlet-api-3.1.0.jar
wget -P dependencies/ https://repo1.maven.org/maven2/org/eclipse/jetty/jetty-http/9.2.14.v20151106/jetty-http-9.2.14.v20151106.jar
wget -P dependencies/ https://repo1.maven.org/maven2/org/eclipse/jetty/jetty-io/9.2.14.v20151106/jetty-io-9.2.14.v20151106.jar
wget -P dependencies/ https://repo1.maven.org/maven2/org/eclipse/jetty/jetty-server/9.2.14.v20151106/jetty-server-9.2.14.v20151106.jar
wget -P dependencies/ https://repo1.maven.org/maven2/org/eclipse/jetty/jetty-util/9.2.14.v20151106/jetty-util-9.2.14.v20151106.jar
wget -P dependencies/ https://repo1.maven.org/maven2/org/eclipse/jetty/jetty-continuation/9.2.14.v20151106/jetty-continuation-9.2.14.v20151106.jar
wget -P dependencies/ https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.3.2/jackson-core-2.3.2.jar
wget -P dependencies/ https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.3.2/jackson-databind-2.3.2.jar
wget -P dependencies/ https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.3.2/jackson-annotations-2.3.2.jar
wget -P dependencies/ https://repo1.maven.org/maven2/com/fasterxml/jackson/jaxrs/jackson-jaxrs-base/2.3.2/jackson-jaxrs-base-2.3.2.jar
wget -P dependencies/ https://repo1.maven.org/maven2/com/fasterxml/jackson/jaxrs/jackson-jaxrs-json-provider/2.3.2/jackson-jaxrs-json-provider-2.3.2.jar
wget -P dependencies/ https://repo1.maven.org/maven2/com/fasterxml/jackson/module/jackson-module-jaxb-annotations/2.3.2/jackson-module-jaxb-annotations-2.3.2.jar
wget -P dependencies/ https://repo1.maven.org/maven2/org/glassfish/jersey/core/jersey-server/2.23.1/jersey-server-2.23.1.jar
wget -P dependencies/ https://repo1.maven.org/maven2/org/glassfish/jersey/core/jersey-client/2.23.1/jersey-client-2.23.1.jar
wget -P dependencies/ https://repo1.maven.org/maven2/org/glassfish/jersey/core/jersey-common/2.23.1/jersey-common-2.23.1.jar
wget -P dependencies/ https://repo1.maven.org/maven2/org/glassfish/jersey/containers/jersey-container-servlet-core/2.23.1/jersey-container-servlet-core-2.23.1.jar
wget -P dependencies/ https://repo1.maven.org/maven2/org/glassfish/jersey/containers/jersey-container-servlet/2.23.1/jersey-container-servlet-2.23.1.jar
wget -P dependencies/ https://repo1.maven.org/maven2/org/glassfish/jersey/containers/jersey-container-jetty-http/2.23.1/jersey-container-jetty-http-2.23.1.jar
wget -P dependencies/ https://repo1.maven.org/maven2/org/glassfish/jersey/bundles/jaxrs-ri/2.23.1/jaxrs-ri-2.23.1.jar
wget -P dependencies/ https://repo1.maven.org/maven2/org/glassfish/jersey/bundles/repackaged/jersey-guava/2.23.1/jersey-guava-2.23.1.jar
wget -P dependencies/ https://repo1.maven.org/maven2/org/glassfish/jersey/media/jersey-media-json-jackson/2.16/jersey-media-json-jackson-2.16.jar
wget -P dependencies/ https://repo1.maven.org/maven2/org/glassfish/jersey/media/jersey-media-jaxb/2.23.1/jersey-media-jaxb-2.23.1.jar
wget -P dependencies/ https://repo1.maven.org/maven2/org/glassfish/jersey/ext/jersey-entity-filtering/2.16/jersey-entity-filtering-2.16.jar
wget -P dependencies/ https://repo1.maven.org/maven2/org/glassfish/hk2/hk2-api/2.4.0-b34/hk2-api-2.4.0-b34.jar
wget -P dependencies/ https://repo1.maven.org/maven2/org/glassfish/hk2/hk2-locator/2.4.0-b34/hk2-locator-2.4.0-b34.jar
wget -P dependencies/ https://repo1.maven.org/maven2/org/glassfish/hk2/osgi-resource-locator/1.0.1/osgi-resource-locator-1.0.1.jar
wget -P dependencies/ https://repo1.maven.org/maven2/org/glassfish/hk2/hk2-utils/2.4.0-b34/hk2-utils-2.4.0-b34.jar
wget -P dependencies/ https://repo1.maven.org/maven2/org/glassfish/hk2/external/aopalliance-repackaged/2.4.0-b34/aopalliance-repackaged-2.4.0-b34.jar
wget -P dependencies/ https://repo1.maven.org/maven2/org/glassfish/hk2/external/javax.inject/2.4.0-b34/javax.inject-2.4.0-b34.jar
wget -P dependencies/ https://repo1.maven.org/maven2/org/javassist/javassist/3.18.1-GA/javassist-3.18.1-GA.jar

Finally run the program with scala -cp "dependencies/*" rest.scala. You should see:

2016-08-14 16:34:13.759:INFO::main: Logging initialized @791ms
2016-08-14 16:34:14.160:INFO:oejs.Server:main: jetty-9.2.14.v20151106
2016-08-14 16:34:14.181:INFO:oejs.ServerConnector:main: Started ServerConnector@5efa40fe{HTTP/1.1}{0.0.0.0:9998}
2016-08-14 16:34:14.182:INFO:oejs.Server:main: Started @1213ms

You can test it via:

curl localhost:9998/hello
["hmm"]
scala jersey jetty jackson

A single page Java/Jetty/Jersey REST app with no web.xml descriptor

First create the build.gradle file in your directory:

apply plugin: 'war'
apply plugin: 'eclipse'

repositories {
   mavenCentral()
}

dependencies {
    compile 'org.glassfish.jersey.media:jersey-media-json-jackson:2.16'
    compile 'org.glassfish.jersey.bundles:jaxrs-ri:2.16'
}

compileJava {
    sourceCompatibility = JavaVersion.VERSION_1_8
    targetCompatibility = JavaVersion.VERSION_1_8
}

Then, in src/main/java/com/example, create App.java:

package com.example;

import java.util.ArrayList;
import java.util.List;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.glassfish.jersey.server.ResourceConfig;

@ApplicationPath("rest") 
public class App extends ResourceConfig {
  public App() { packages("com.example"); }

  @Path("example")
  public static class Hello {

      @GET
      @Produces(MediaType.APPLICATION_JSON)
      public List<String> example() {
        List<String> l = new ArrayList<>();
        l.add("one"); l.add("two");
        return l; 
      }

  }  

}

Now

  1. Create the war file with gradle
  2. Download the Jetty runner jar
  3. Run the war file with the jetty runner (takes about 15 seconds to init usually)
  4. Use curl to test the app

From the command line:

gradle build war
wget http://central.maven.org/maven2/org/eclipse/jetty/jetty-runner/9.3.0.M1/jetty-runner-9.3.0.M1.jar
java -jar jetty-runner-9.3.0.M1.jar --port 8081 build/libs/your-directory-name.war
curl localhost:8081/rest/example && echo

The above curl command should print:

["one","two"]
jetty jersey gradle java

PART 4: Jersey 2 and Dependency Injection with HK2

(This is part of a series http://blog.denevell.org/category_java-web-quick-start.html)

Previously, we had the JPA and adapter code in the request object. This was obviously not ideal.

Jersey 2 comes with a JSR330 (dependency injection) implementation baked in, named HK2, so we can move the JPA model code and the adapter code into separate objects and inject them in.

Let’s start with a simple object that will adapt the list of JPA entities into a resource entities (same code as before).

echo '
package com.example.YOURPROJECT;

import java.util.ArrayList;
import java.util.List;

public class ExampleResourcesAdapter {

    public List<ExampleResource> adapt(List<ExampleEntity> list) { 
       ArrayList<ExampleResource> resList = new ArrayList<ExampleResource>();
       for (ExampleEntity exampleEExntity : list) {
         ExampleResource exampleItem = new ExampleResource();
         exampleItem.setStuff(exampleEntity.getTalky());
         resList.add(exampleItem);
       }
       return resList;
    }

}
' > src/main/java/com/example/YOURPACKAGE/ExampleResourcesAdapter.java

Now let’s move the JPA stuff into its own model. In this case we’re also using an interface, although we don’t have to for the DI to work. (Same JPA code as before, but without the comments)

echo '
package com.example.YOURPROJECT;

import java.util.List;

public interface AddListModel {
    List<ExampleEntity> addAndList(String someString);
}
' > src/main/java/com/example/YOURPACKAGE/AddListModel.java

And now for the implementation of the interface.

echo '
package com.example.YOURPROJECT;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.TypedQuery;

import org.apache.log4j.Logger;

public class AddListModelImpl implements AddListModel {

    @Override
    public List<ExampleEntity> addAndList(String someString) {
       EntityManager entityManager = Persistence.createEntityManagerFactory("PERSISTENCE_UNIT_NAME").createEntityManager();
       EntityTransaction transaction = entityManager.getTransaction(); 
       List<ExampleEntity> list = null;
       try {
         transaction.begin();
         ExampleEntity entity = new ExampleEntity();
         entity.setTalky(someString);
         entityManager.persist(entity);
         TypedQuery<ExampleEntity> nq = entityManager.createNamedQuery("list", ExampleEntity.class);
         list = nq.getResultList();
         transaction.commit();
       } catch (Exception e) {
         Logger.getLogger(getClass()).error("Problem persisting", e);
         transaction.rollback();
         throw e; 
       } finally {
         entityManager.clear(); 
         entityManager.close();
       }     
       return list;
    }

}
' > src/main/java/com/example/YOURPACKAGE/AddListModelImpl.java

Now we have these objects, we need to tell the dependency injector about them. We do this using a AbstractBinder class.

(I think there’s an automatic method for this, using annotations, but this is the only way I’ve got working.)

echo '
package com.example.YOURPROJECT;

import org.glassfish.hk2.utilities.binding.AbstractBinder;

public class DependencyBinder extends AbstractBinder {

    @Override
    protected void configure() {
       bind(AddListModelImpl.class).to(AddListModel.class);
       bind(ExampleResourcesAdapter.class).to(ExampleResourcesAdapter.class);
    }

}
' > src/main/java/com/example/YOURPACKAGE/DependencyBinder.java

Note we’re binding the iplementatioon of the model interface on the first bind line. And on the second just binding two concreate classes together.

We need to tell Jersey about this AbstractBinder, so in your JerseyApplication.java class you need to register it:

register(new DependencyBinder());

Now we can see the new request has two @Inject lines and is much, much shorter (also has a new @Path)

echo '
package com.example.YOURPROJECT;

import java.util.List;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("example_jpa_di")
public class ExampleJPAWithDIRequest {

    @Inject AddListModel mModel;
    @Inject ExampleResourcesAdapter mAdapter;

    @Path("{example}")
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public List<ExampleResource> example(@PathParam("example") String example) {
       List<ExampleEntity> list = mModel.addAndList(example);
       List<ExampleResource> resList = mAdapter.adapt(list);
       return resList;
    }
}
' > src/main/java/com/example/YOURPACKAGE/ExampleJPAWithDIRequeset.java

We can run it, and it’ll have the same result as before (note the different url for the request)

gradle build
java -jar jetty-runner-9.1.0.M0.jar --port 8081 build/libs/YOUR_PROJECT_DIR.war

If you visit

http://localhost:8081/YOUR_PATH/example_jpa_di/ONE

and then visit

http://localhost:8081/YOUR_PATH/example_jpa_di/TWO

You should see the JSON, (should the database be blank before starting)

[{"stuff":"ONE"},{"stuff":"TWO"}]
java JPA java-web-quick-start jersey jsr330

Page 1 of 3
Next