Jersey MOXy is the Jersey module jersey-media-moxy, which converts Java objects to JSON and JSON back to Java objects with EclipseLink MOXy, a JAXB implementation. When the module is on the classpath, Jersey registers it by itself, so a resource method returns a plain Java object and the client receives JSON.
We use MOXy in Jersey REST APIs that read and write JSON, and in apps that need JSON and XML from the same annotated classes. MOXy has been the default JSON provider of Jersey since Jersey 2.0.
The following example is a Jersey 4.0.3 resource method on Java 25. It returns a Book object, and the comments show what the client gets back.
@GET
@Path("/{id}")
@Produces(MediaType.APPLICATION_JSON)
public Book findById(@PathParam("id") int id) {
Book book = BOOKS.get(id);
if (book == null) {
throw new NotFoundException("No book with id " + id); // 404 Not Found
}
return book; // {"author":"Robert Martin","id":1,"tags":["java","design"],"title":"Clean Code"}
}
Notice that the method contains no JSON code, and Book is a plain class without annotations. MOXy writes the properties in alphabetical order, because nothing defines another order.
Next, we look at how Jersey finds MOXy and build a small book catalog API with GET and POST endpoints. After that, we rename and hide JSON fields with JAXB annotations, configure MOXy with MoxyJsonConfig, switch it off for Jackson, and test the API with the JAX-RS client.
1. How Jersey Uses MOXy to Read and Write JSON
A JAX-RS resource method works only with Java objects. The conversion between an HTTP body and a Java object is done by entity providers, which are classes that implement MessageBodyReader (body to object) and MessageBodyWriter (object to body). Jersey picks the provider by the Java type and the media type, such as application/json.
The module jersey-media-moxy contains a MessageBodyReader and a MessageBodyWriter for JSON, plus the feature class MoxyJsonFeature that registers them. Jersey finds this feature with its auto-discovery mechanism, so we add the dependency and write no registration code.

Jersey also supports other JSON libraries, each in its own module. In Jersey 4, all of these modules register themselves when their jar is on the classpath, so we keep only one JSON module in the project. When we register another JSON feature by hand, Jersey turns off the automatic MOXy setup.
| Library | Jersey module | Feature class | Annotations it reads |
|---|---|---|---|
| EclipseLink MOXy | jersey-media-moxy | MoxyJsonFeature | JAXB (@XmlElement, @XmlTransient) |
| Jackson | jersey-media-json-jackson | JacksonFeature | Jackson (@JsonProperty, @JsonIgnore) |
| JSON-B (Yasson) | jersey-media-json-binding | JsonBindingFeature | JSON-B (@JsonbProperty, @JsonbTransient) |
| JSON-P | jersey-media-json-processing | JsonProcessingFeature | none, works with JsonObject trees |
2. Jersey MOXy JSON Example
The example is a small library catalog API with a list of books. It uses Jersey 4.0.3, which implements Jakarta REST 4.0 from Jakarta EE 11, with MOXy 5.0.2 and the Grizzly HTTP server, so the app runs from a main() method without an application server.
2.1. Maven Dependencies
The Jersey BOM keeps all Jersey modules on the same version. Jersey needs an injection module (jersey-hk2), a container (jersey-container-grizzly2-http) and the MOXy module, which brings org.eclipse.persistence.moxy 5.0.2 and jakarta.xml.bind-api 4.0.5 with it.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.glassfish.jersey</groupId>
<artifactId>jersey-bom</artifactId>
<version>4.0.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-grizzly2-http</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-moxy</artifactId>
</dependency>
</dependencies>
Old tutorials use Jersey 2.x with javax.ws.rs imports. Jersey 3.x and 4.x use the jakarta.ws.rs package, so a Jersey 2 project must change its imports when it upgrades.
2.2. The Book Model Class
MOXy needs a public no-argument constructor to create the object when it reads JSON, and it reads and writes the properties through the getters and setters. No annotation is required.
public class Book {
private int id;
private String title;
private String author;
private List<String> tags;
public Book() {
}
public Book(int id, String title, String author, List<String> tags) {
this.id = id;
this.title = title;
this.author = author;
this.tags = tags;
}
// getters and setters for all four fields
}
2.3. The Resource Class
The annotations @Produces and @Consumes on the class set application/json for every method. The method findById() from the intro throws a NotFoundException for an unknown id, and Jersey turns it into a 404 response. The method create() returns 201 Created with a Location header that UriInfo builds from the request URL.
@Path("/books")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class BookResource {
private static final Map<Integer, Book> BOOKS = new ConcurrentHashMap<>(Map.of(
1, new Book(1, "Clean Code", "Robert Martin", List.of("java", "design")),
2, new Book(2, "Effective Java", "Joshua Bloch", List.of("java"))));
@GET
public List<Book> findAll() {
return BOOKS.values().stream().sorted((a, b) -> a.getId() - b.getId()).toList();
}
@POST
public Response create(Book book, @Context UriInfo uriInfo) {
if (book == null || book.getId() <= 0 || book.getTitle() == null || book.getTitle().isBlank()) {
throw new BadRequestException("A book needs a positive id and a title");
}
BOOKS.put(book.getId(), book);
return Response.created(uriInfo.getAbsolutePathBuilder().path(String.valueOf(book.getId())).build())
.entity(book)
.build();
}
}
The check at the start of create() is there for a reason, which we explain in section 3.
2.4. Starting the Server
A ResourceConfig lists the resource classes, and GrizzlyHttpServerFactory starts an HTTP server with them. We do not register MOXy here, because auto-discovery does it.
URI baseUri = URI.create("http://localhost:8080/api/");
ResourceConfig config = new ResourceConfig(BookResource.class);
HttpServer server = GrizzlyHttpServerFactory.createHttpServer(baseUri, config);
We start the app with mvn exec:java -Dexec.mainClass=com.howtodoinjava.jersey.Main. In a WAR file on a servlet container such as Tomcat, the same ResourceConfig class works through Jersey’s ServletContainer, as the Jersey deployment guide describes.
2.5. Calling the API With curl
A GET request on /api/books returns the list as a JSON array, and a single book as a JSON object.
curl http://localhost:8080/api/books
curl -i -X POST -H "Content-Type: application/json" \
-d '{"id":3,"title":"Java Concurrency in Practice","author":"Brian Goetz","tags":["java","threads"]}' \
http://localhost:8080/api/books
[{"author":"Robert Martin","id":1,"tags":["java","design"],"title":"Clean Code"},{"author":"Joshua Bloch","id":2,"tags":["java"],"title":"Effective Java"}]
HTTP/1.1 201 Created
Location: http://localhost:8080/api/books/3
Content-Type: application/json
Content-Length: 96
{"author":"Brian Goetz","id":3,"tags":["java","threads"],"title":"Java Concurrency in Practice"}
We can see that the list with one tag, [“java”], is still a JSON array. A request for /api/books/9 returns 404 Not Found with an empty body.
3. What MOXy Does With Unexpected JSON
MOXy reads JSON leniently. It ignores properties that the class does not have, and it does not fail on a value of the wrong type. For example, a mobile app sends a book with an extra pages field, and a buggy client sends the text “abc” as the id.
| Request body | What MOXy creates |
|---|---|
| {“id”:4,”title”:”Refactoring”,”pages”:448} | Book with id 4 and title “Refactoring”; pages is ignored |
| {“id”:”abc”} | Book with id 0 and no title |
| {“id”:5,”title”: (broken JSON) | nothing; Jersey returns 400 Bad Request |
Because MOXy accepts wrong values without an error, the resource method must validate the object itself. Without the check in create(), the second request would store a book with id 0. With the check, the API rejects it.
$ curl -i -X POST -H "Content-Type: application/json" -d '{"id":"abc"}' http://localhost:8080/api/books
HTTP/1.1 400 Bad Request
Connection: close
Content-Length: 0
For larger models, the jersey-bean-validation module lets Jakarta Bean Validation annotations, such as @NotBlank on the fields and @Valid on the parameter, replace the manual check.
4. Renaming and Hiding JSON Fields With JAXB Annotations
MOXy reads the standard JAXB annotations from the jakarta.xml.bind.annotation package. So the same annotations control the JSON names, the property order and the hidden fields. A magazine class shows the most useful ones.
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(propOrder = {"id", "title", "issueDate", "price"})
public class Magazine {
private int id;
private String title;
@XmlElement(name = "issue_date")
private String issueDate;
private Double price;
@XmlTransient
private int copiesInStock;
// constructors and getters
}
A resource returns a magazine with the price null and 25 copies in stock. The JSON has the property order from @XmlType, the name issue_date, no copiesInStock field and no price, because MOXy leaves out null values.
{"id":1,"title":"Java Monthly","issue_date":"2026-10-01"}
| Annotation | Effect on the JSON |
|---|---|
| @XmlAccessorType(XmlAccessType.FIELD) | MOXy reads the fields instead of the getters and setters |
| @XmlType(propOrder = {…}) | sets the order of the properties |
| @XmlElement(name = “issue_date”) | sets the JSON name of a property |
| @XmlTransient | leaves the property out of the JSON |
| @XmlRootElement | needed for XML output, see section 4.1 |
4.1. JSON and XML From the Same Class
MOXy is a full JAXB implementation, so the annotated class also works as XML. We add the jersey-media-jaxb module and list both media types in @Produces. The client chooses the format with the Accept header.
@GET
@Path("/1")
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public Magazine findOne() {
return new Magazine(1, "Java Monthly", LocalDate.of(2026, 10, 1), null, 25);
}
<?xml version="1.0" encoding="UTF-8"?><magazine><id>1</id><title>Java Monthly</title><issue_date>2026-10-01</issue_date></magazine>
Without jersey-media-jaxb, the XML request fails with MessageBodyWriter not found for media type=application/xml and a 500 response. For XML, @XmlRootElement is required, whereas the JSON output works without it.
5. Configuring MOXy With MoxyJsonConfig
The class MoxyJsonConfig changes how MOXy writes and reads JSON for the whole app. The defaults come from the Jersey 4 user guide.
| Setting | Method | Default |
|---|---|---|
| Pretty-printed output | setFormattedOutput(true) | false |
| Root element name around the object | setIncludeRoot(true) | false |
| Empty collections as [] | setMarshalEmptyCollections(false) | true |
MOXy finds the configuration through a JAX-RS ContextResolver. We register the resolver class in the ResourceConfig, or mark it with @Provider when we use package scanning.
@Provider
public class MoxyConfigResolver implements ContextResolver<MoxyJsonConfig> {
private final MoxyJsonConfig config = new MoxyJsonConfig()
.setFormattedOutput(true) // pretty-print the JSON
.setMarshalEmptyCollections(false); // leave out empty lists
@Override
public MoxyJsonConfig getContext(Class<?> type) {
return config;
}
}
ResourceConfig config = new ResourceConfig(BookResource.class, MoxyConfigResolver.class);
For a draft book with an empty tag list, the default output has “tags”:[]. With the resolver, the output is pretty-printed and the empty list is gone.
{"id":5,"tags":[],"title":"Draft"}
{
"id" : 5,
"title" : "Draft"
}
Pretty-printed JSON is larger, so we turn it on for development or for APIs where people read the responses.
6. Turning MOXy Off or Switching to Jackson
Some teams want Jackson, because the rest of their code already uses Jackson annotations. The cleanest switch is to replace jersey-media-moxy with jersey-media-json-jackson in the pom, and Jersey 4 discovers the Jackson module by itself. When both modules must stay on the classpath, we register JacksonFeature, and Jersey stops the automatic MOXy setup.
ResourceConfig config = new ResourceConfig(BookResource.class)
.register(JacksonFeature.class);
{"id":2,"title":"Effective Java","author":"Joshua Bloch","tags":["java"]}
Notice that Jackson keeps the order of the fields in the class, whereas MOXy sorts them alphabetically. To turn MOXy off without another JSON provider, we set the property CommonProperties.MOXY_JSON_FEATURE_DISABLE to true. With no JSON provider left, every JSON response fails with a 500 error and the log message MessageBodyWriter not found for media type=application/json.
ResourceConfig config = new ResourceConfig(BookResource.class)
.property(CommonProperties.MOXY_JSON_FEATURE_DISABLE, true);
7. Reading JSON With the JAX-RS Client and Testing It
MOXy also works in the Jersey client, where auto-discovery registers it in the same way. The test starts the Grizzly server once, calls the API with a JAX-RS Client, and reads the JSON back into Book objects. The example uses JUnit 6 (version 6.1.3).
@BeforeAll
static void start() {
server = Main.startServer(new ResourceConfig(BookResource.class));
client = ClientBuilder.newClient();
}
@Test
void readsList() {
List<Book> books = client.target(Main.BASE_URI).path("books")
.request(MediaType.APPLICATION_JSON)
.get(new GenericType<List<Book>>() {});
assertEquals("Effective Java", books.get(1).getTitle());
}
@Test
void createsBook() {
Book newBook = new Book(3, "Java Concurrency in Practice", "Brian Goetz", List.of("java", "threads"));
try (Response response = client.target(Main.BASE_URI).path("books")
.request(MediaType.APPLICATION_JSON)
.post(Entity.json(newBook))) {
assertEquals(201, response.getStatus());
assertEquals("http://localhost:8080/api/books/3", response.getLocation().toString());
}
}
A GenericType keeps the type List<Book> at runtime, so MOXy knows the element type of the list. The Response is closed in try-with-resources, because an open response keeps the HTTP connection busy.
8. Jersey MOXy FAQs
8.1. Does MOXy Support Java Records?
No. MOXy 5.0.2 needs a no-argument constructor, so a record fails with the message requires a zero argument constructor or a specified factory method, and Jersey returns a 500 error. Jackson and JSON-B (Yasson) both write records without problems, so a project that wants records as JSON types switches to one of them, as in section 6.
8.2. Why Do I Get “MessageBodyWriter Not Found for Media Type=application/json”?
Jersey has no JSON provider for the response. Either no JSON module, such as jersey-media-moxy, is on the classpath, or MOXy was turned off with MOXY_JSON_FEATURE_DISABLE. We check the dependencies with mvn dependency:tree. The same error with a records type means MOXy is present but cannot handle the class.
8.3. Should I Use MOXy or Jackson With Jersey?
For a new JSON-only API, we pick Jackson or JSON-B, because both support records and keep up with new Java features. MOXy is the right choice when the classes already carry JAXB annotations, or when the API must return both JSON and XML from the same classes, as in section 4.1. Jersey also has a Gson integration example for teams that use Gson.
9. Conclusion
Jersey MOXy converts between JSON and Java objects with the jersey-media-moxy module, and Jersey registers it as soon as the jar is on the classpath. A plain class with a no-argument constructor and getters and setters is enough for JSON, and the properties appear in alphabetical order unless @XmlType sets another order.
JAXB annotations rename, order and hide properties, and the same class can produce XML with the jersey-media-jaxb module. A ContextResolver for MoxyJsonConfig changes app-wide settings such as pretty-printing and empty collections.
MOXy ignores unknown properties and wrong value types, so we validate the input in the resource method. When the project uses records or Jackson annotations, we replace MOXy with the Jackson or JSON-B module, or register JacksonFeature to turn off the automatic MOXy setup. For the basics of Jersey resources, see the Jersey hello world example.
10. References
- Jersey 4 User Guide: JSON support (MOXy)
- Jersey 4 User Guide: Client API
- Jakarta RESTful Web Services 4.0
- Jakarta XML Binding 4.0
- EclipseLink documentation
Happy Learning !!
Thank you for this article!
Hi Lokesh, I want to download your code, but can’t find any option to sign up.
Hi Lokesh , Thanks for this easy explanation .
How to consume a JSON request ? I am using below code . and same configuration as explained above .
@Path(“/fun”)
public class MyResource {
@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public Test getTestResponse(Test test){
Test test= new Test ();
test.setName(“ABC”);
test.setDate(“Jan 1,2016”);
test.setApp(“AppV2”);
return test;
}
=================================================================
Output :
HTTP Status 415 – Unsupported Media Type
=================================================================