Dropwizard is a REST-oriented framework that draws together several Java packages into a cohesive whole. It is an alternative to Spring (and Spring’s WebMVC package). Dropwizard delivers a more streamlined experience. It adopts even more configuration by convention than Spring, and eliminates most of the API surface that is unrelated specifically to delivering REST APIs.
Start a new Dropwizard project
Let’s begin by scaffolding a new project via the official Dropwizard Maven archetype. Find a comfortable place on your local system, and from the command line, enter the command in Listing 1.
Listing 1. Run the archetype
mvn archetype:generate -DarchetypeGroupId=io.dropwizard.archetypes -DarchetypeArtifactId=java-simple -DarchetypeVersion=2.0.0
This will run in interactive mode. I used a group ID of com.infoworld and an artifact ID of Demo. I also used Demo for the name.
Once the archetype finishes deploying itself, you can cd
into your directory (in my case, cd Demo
). Now you install the dependencies with mvn clean package
.
Now the app can be run with
java -jar target/Demo-1.0-SNAPSHOT.jar server
(Remember to use the app name you gave it if different from Demo.)
If you visit localhost:8080 now, you’ll be greeted with a default JSON-formatted “not found” error:
{"code":404,"message":"HTTP 404 Not Found"}
Map an endpoint
So far, the app only returns 404s because no endpoints are mapped. I want to give you a clear understanding of how this is accomplished with Dropwizard. We’ll isolate the process by mapping a simple string endpoint.
Step 1 is to create a class that acts as the path handler. Remember, Dropwizard’s calling in life is to unite several best-in-class libraries into an easy-to-use package. For supporting RESTful endpoints, Dropwizard uses Jersey. Your app already has Jersey fully set up. The path mappings are accomplished with the Jersey syntax.
Step 2 is to register the handler class with the app. Let’s take these two steps in turn.
Create a path handler class
By convention, Dropwizard endpoint handlers go in the /src/main/java/com/infoworld/resources folder (or whatever group ID you choose). We’re going to add an endpoint handler for returning a list of songwriters. As you can see in Listing 2, we’ll return only the best. Create a new SongWriters.java file in the /resources directory.
Listing 2. The SongWriters endpoint
package com.infoworld.resources;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import java.util.List;
import java.util.Optional;
@Path("/songwriters")
@Produces(MediaType.APPLICATION_JSON)
public class SongWriters {
public SongWriters() {
}
@GET
public String getBrands() {
return "{'name':'Roger Waters','name':'Tom Petty'}";
}
}
Listing 2 is fairly straightforward. The class itself is given a path via the @Path
annotation, and JSON is specified as the media type via the @Produces
annotation. Next, the getBrands()
method, which returns a string, is mapped to the GET HTTP
method via the @GET
annotation. The method just returns a hard-coded string representing the set of songwriters.
Register the handler
Now to register the handler. Open the /src/main/java/com/infoworld/DemoApplication.java file and modify the run()
method to look like Listing 3.
Listing 3. Registering the SongWriters handler
import com.infoworld.resources.SongWriters;
// ...
public void run(final DemoConfiguration configuration, final Environment environment) {
SongWriters songWriters = new SongWriters();
environment.jersey().register(songWriters);
}
Again, fairly straightforward. Listing 3 imports the class you just created, then uses the Environment
object to access Jersey and register the handler. Notice the run
method also receives a DemoConfiguration
object, which you’ll see in action momentarily.
Test the handler
Now if you visit localhost:8080/songwriters, you’ll get the JSON that you hard coded. Notice the system has seamlessly handled returning the string as JSON.
Model the domain
Now you want to move from a hard-coded string to using actual domain objects. Start by creating a /src/main/java/com/infoworld/domain/SongWriter.java file, and give it the contents of Listing 4.
Listing 4. The SongWriter domain model class
package com.infoworld.domain;
import java.util.ArrayList;
import java.util.List;
public class SongWriter {
private final String name;
private List songs = new ArrayList<>();
public SongWriter(final String name, final List songs) {
this.name = name;
this.songs = songs;
}
public String getName() {
return name;
}
public List getSongs(){
return this.songs;
}
}
Add a repository class
Now we’ll add a repository class to represent the data access layer. Create a new folder and file: /src/main/java/com/infoworld/repo/SongWriterRepo.java.
Listing 5. The SongWriterRepo class
package com.infoworld.repo;
import com.infoworld.domain.SongWriter;
import java.util.List;
import java.util.ArrayList;
import java.util.Optional;
import java.util.stream.Collectors;
public class SongWriterRepo {
private final List songWriters;
public SongWriterRepo(){
this.songWriters = new ArrayList();
}
public SongWriterRepo(List songWriters){
this.songWriters = songWriters;
}
public List findAll(){
return songWriters;
}
public Optional getByName(final String name){
return songWriters.stream().filter(sw -> sw.getName().equals(name)).findFirst();
}
}
As you can see in Listing 5, SongWriterRepo is responsible for returning all the SongWriter objects in a List
via the findAll()
method, while the getByName()
method grabs the songwriter specified by the passed in string name.
Use the domain layer
Now we’ll use the new layer to drive the JSON returned by the resource endpoint. Modify com.infoworld.resource.SongWriters to look like Listing 6.
Listing 6. The modified SongWriters class
package com.infoworld.resources;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import java.util.List;
import java.util.Optional;
import com.infoworld.repo.SongWriterRepo;
import com.infoworld.domain.SongWriter;
@Path("/songwriters")
@Produces(MediaType.APPLICATION_JSON)
public class SongWriters {
private final SongWriterRepo repo;
public SongWriters(SongWriterRepo repo) {
this.repo = repo;
}
@GET
public List getBrands() {
return repo.findAll();
}
}
Listing 6 has eliminated the hard-coded string in favor of referencing the SongWriterRepo
class, which is set in the constructor. Now take a look at Listing 7, where these classes are wired together in the DemoApplication
class.
Listing 7. Wiring the domain class in DemoApplication
package com.infoworld;
import io.dropwizard.Application;
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment;
import com.infoworld.resources.SongWriters;
import com.infoworld.repo.SongWriterRepo;
public class DemoApplication extends Application {
// ...
@Override
public void run(final DemoConfiguration configuration, final Environment environment) {
SongWriterRepo swRepo = new SongWriterRepo();
SongWriters songWriters = new SongWriters(swRepo);
environment.jersey().register(songWriters);
}
}
Return to the DemoApplication
class, and modify it as shown in Listing 7. Notice that you now instantiate the SongWriterRepo
object and parameterize the SongWriters
resource object before handing it off to Jersey.
Now if you rebuild the project and visit localhost:8080/songwriters, you will find an empty array. Let’s add a hard-coded songwriter as shown in Listing 8.
Listing 8. Adding a songwriter
public void run(final DW2Configuration configuration,
final Environment environment) {
List preload = new ArrayList();
preload.add(new SongWriter("Mark Knopfler", new ArrayList()));
SongWriterRepo swRepo = new SongWriterRepo(preload);
SongWriters songWriters = new SongWriters(swRepo);
environment.jersey().register(songWriters);
}
Now you’ll get a result when you hit the endpoint.
Use the config file
Add a new config file at src/main/resources/Demo-Config.yml and put the contents of Listing 9 in it.
Listing 9. The config YAML
---
songWriters:
- John Lennon
- Jeff Lynne
Now change the com.infoworld.DemoConfiguration.java file to look like Listing 10. This file is used to model the config file via Jackson.
Listing 10. Config java
package com.infoworld;
import io.dropwizard.Configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.hibernate.validator.constraints.*;
import javax.validation.constraints.*;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
import java.util.ArrayList;
public class DW2Configuration extends Configuration {
private List<String> songWriters;
@JsonCreator
public DW2Configuration(@JsonProperty("songWriters") List<String> songWriters){
this.songWriters = songWriters;
}
public List<String> getSongWriters(){
return this.songWriters;
}
}
Now use the file in DemoApplication
, as demonstrated in Listing 11.
Listing 11. Using the config file to preload songwriters in DemoApplication.java
@Override
public void run(final DemoConfiguration configuration,
final Environment environment) {
List preload = new ArrayList();
configuration.getSongWriters().forEach(sw -> preload.add(new SongWriter(sw, new ArrayList())));
SongWriterRepo swRepo = new SongWriterRepo(preload);
SongWriters songWriters = new SongWriters(swRepo);
environment.jersey().register(songWriters);
}
The main point of the change in Listing 11 is that the configuration file is hydrated into the DemoConfiguration
class, populating the getSongWriters()
method that is used to preload the repository class.
Run the app with the config file
A small change is required to run this app with the config file. Notice in Listing 12 that the file is referenced after the call to server
.
Listing 12. Running the app with the config file
java -jar target/DW2-1.0-SNAPSHOT.jar server src/main/resources/DW2-config.yml
Now when you visit the app at localhost:8080/songwriters, you’ll see the preloaded songwriters.
Dropwizard is a lean alternative to Spring-based RESTful APIs. As you’ve seen here, it focuses on providing a simple but sufficient stack to address these demands.
The sources for the demo app are available here.