CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-dropwizard--dropwizard-jersey

Dropwizard Jersey Support - Jersey integration module for the Dropwizard Java framework

Pending
Overview
Eval results
Files

optional-handling.mddocs/

Optional Handling

Support for Java 8 Optional and Guava Optional types with automatic serialization and 404 responses for empty values. Provides seamless integration between optional types and HTTP semantics in JAX-RS resources.

Capabilities

Java 8 Optional Support

Message body writers and exception handling for Java 8 Optional types with automatic HTTP status code mapping.

/**
 * Message body writer for Optional<?> types
 * Returns 404 Not Found for empty Optional values
 */
public class OptionalMessageBodyWriter implements MessageBodyWriter<Optional<?>> {
    
    /** Checks if this writer can handle the given Optional type */
    public boolean isWriteable(Class<?> type, Type genericType, 
                              Annotation[] annotations, MediaType mediaType);
    
    /** Gets content length for Optional (returns -1 for chunked encoding) */
    public long getSize(Optional<?> optional, Class<?> type, Type genericType,
                       Annotation[] annotations, MediaType mediaType);
    
    /** Writes Optional content or throws EmptyOptionalException for empty values */
    public void writeTo(Optional<?> optional, Class<?> type, Type genericType,
                       Annotation[] annotations, MediaType mediaType,
                       MultivaluedMap<String, Object> httpHeaders,
                       OutputStream entityStream) throws IOException, WebApplicationException;
}

/**
 * Message body writer for OptionalInt primitive type
 * Handles primitive int Optional values
 */
public class OptionalIntMessageBodyWriter implements MessageBodyWriter<OptionalInt> {
    
    /** Checks if this writer can handle OptionalInt */
    public boolean isWriteable(Class<?> type, Type genericType,
                              Annotation[] annotations, MediaType mediaType);
    
    /** Writes OptionalInt value or throws exception for empty values */
    public void writeTo(OptionalInt optionalInt, Class<?> type, Type genericType,
                       Annotation[] annotations, MediaType mediaType,
                       MultivaluedMap<String, Object> httpHeaders,
                       OutputStream entityStream) throws IOException, WebApplicationException;
}

/**
 * Message body writer for OptionalLong primitive type
 */
public class OptionalLongMessageBodyWriter implements MessageBodyWriter<OptionalLong> {
    
    public boolean isWriteable(Class<?> type, Type genericType,
                              Annotation[] annotations, MediaType mediaType);
    
    public void writeTo(OptionalLong optionalLong, Class<?> type, Type genericType,
                       Annotation[] annotations, MediaType mediaType,
                       MultivaluedMap<String, Object> httpHeaders,
                       OutputStream entityStream) throws IOException, WebApplicationException;
}

/**
 * Message body writer for OptionalDouble primitive type
 */
public class OptionalDoubleMessageBodyWriter implements MessageBodyWriter<OptionalDouble> {
    
    public boolean isWriteable(Class<?> type, Type genericType,
                              Annotation[] annotations, MediaType mediaType);
    
    public void writeTo(OptionalDouble optionalDouble, Class<?> type, Type genericType,
                       Annotation[] annotations, MediaType mediaType,
                       MultivaluedMap<String, Object> httpHeaders,
                       OutputStream entityStream) throws IOException, WebApplicationException;
}

Usage Examples:

import java.util.Optional;
import java.util.OptionalInt;
import java.util.OptionalLong;
import java.util.OptionalDouble;
import jakarta.ws.rs.*;

@Path("/users")
public class UserResource {
    
    @GET
    @Path("/{id}")
    public Optional<User> getUser(@PathParam("id") UUIDParam userId) {
        // Returns 404 automatically if Optional is empty
        return userService.findById(userId.get());
    }
    
    @GET
    @Path("/{id}/age")
    public OptionalInt getUserAge(@PathParam("id") UUIDParam userId) {
        // Returns 404 if user not found or age not set
        return userService.getUserAge(userId.get());
    }
    
    @GET
    @Path("/{id}/salary")
    public OptionalLong getUserSalary(@PathParam("id") UUIDParam userId) {
        // Returns 404 if user not found or salary not set
        return userService.getUserSalary(userId.get());
    }
    
    @GET
    @Path("/{id}/rating")
    public OptionalDouble getUserRating(@PathParam("id") UUIDParam userId) {
        // Returns 404 if user not found or rating not set
        return userService.getUserRating(userId.get());
    }
}

Empty Optional Exception Handling

Exception types and mappers for handling empty Optional values with different HTTP response strategies.

/**
 * Exception thrown when an Optional value is empty
 * Used internally by Optional message body writers
 */
public class EmptyOptionalException extends WebApplicationException {
    
    /** Creates exception with default message */
    public EmptyOptionalException();
    
    /** Creates exception with custom message */
    public EmptyOptionalException(String message);
    
    /** Creates exception with cause */
    public EmptyOptionalException(Throwable cause);
    
    /** Creates exception with message and cause */
    public EmptyOptionalException(String message, Throwable cause);
}

/**
 * Exception mapper that converts EmptyOptionalException to 404 Not Found
 * Default behavior for empty Optional values
 */
public class EmptyOptionalExceptionMapper implements ExceptionMapper<EmptyOptionalException> {
    
    /** Maps EmptyOptionalException to 404 Not Found response */
    public Response toResponse(EmptyOptionalException exception);
}

/**
 * Alternative exception mapper that converts EmptyOptionalException to 204 No Content
 * Useful when empty values should return success with no content
 */
public class EmptyOptionalNoContentExceptionMapper implements ExceptionMapper<EmptyOptionalException> {
    
    /** Maps EmptyOptionalException to 204 No Content response */
    public Response toResponse(EmptyOptionalException exception);
}

Guava Optional Support

Support for Google Guava Optional types for legacy code compatibility.

/**
 * Message body writer for Guava Optional types
 * Provides compatibility with Google Guava Optional
 */
public class OptionalMessageBodyWriter implements MessageBodyWriter<com.google.common.base.Optional<?>> {
    
    /** Checks if this writer can handle Guava Optional */
    public boolean isWriteable(Class<?> type, Type genericType,
                              Annotation[] annotations, MediaType mediaType);
    
    /** Writes Guava Optional content or returns 404 for absent values */
    public void writeTo(com.google.common.base.Optional<?> optional, Class<?> type, Type genericType,
                       Annotation[] annotations, MediaType mediaType,
                       MultivaluedMap<String, Object> httpHeaders,
                       OutputStream entityStream) throws IOException, WebApplicationException;
}

/**
 * Parameter binder for Guava Optional parameter converters
 * Enables parameter conversion for Guava Optional types
 */
public class OptionalParamBinder extends AbstractBinder {
    
    /** Configures Guava Optional parameter bindings */
    protected void configure();
}

/**
 * Parameter converter provider for Guava Optional types
 * Provides automatic conversion of query/path parameters to Guava Optional
 */
public class OptionalParamConverterProvider implements ParamConverterProvider {
    
    /** Gets parameter converter for Guava Optional types */
    public <T> ParamConverter<T> getConverter(Class<T> rawType, Type genericType, Annotation[] annotations);
}

Guava Optional Usage:

import com.google.common.base.Optional;
import jakarta.ws.rs.*;

@Path("/legacy")
public class LegacyResource {
    
    @GET
    @Path("/users/{id}")
    public Optional<User> getUser(@PathParam("id") UUIDParam userId) {
        // Using Guava Optional for legacy compatibility
        User user = userService.findById(userId.get());
        return Optional.fromNullable(user);
    }
    
    @GET
    @Path("/search")
    public List<User> searchUsers(@QueryParam("name") Optional<String> name,
                                 @QueryParam("email") Optional<String> email) {
        // Guava Optional parameters
        String nameFilter = name.orNull();
        String emailFilter = email.orNull();
        return userService.search(nameFilter, emailFilter);
    }
}

Optional Response Patterns

Standard Optional Usage

@Path("/api")
public class OptionalResponseExamples {
    
    @GET
    @Path("/users/{id}")
    public Optional<User> getUser(@PathParam("id") UUIDParam userId) {
        // Service returns Optional - automatically converts to 404 if empty
        return userService.findById(userId.get());
    }
    
    @GET
    @Path("/users/{id}/profile")
    public Optional<UserProfile> getUserProfile(@PathParam("id") UUIDParam userId) {
        // Nested service calls with Optional chaining
        return userService.findById(userId.get())
            .flatMap(user -> profileService.getProfile(user.getId()));
    }
    
    @GET
    @Path("/users/{id}/preferences")
    public Optional<UserPreferences> getUserPreferences(@PathParam("id") UUIDParam userId) {
        // Returns 404 if user not found or preferences not set
        return userService.findById(userId.get())
            .map(user -> user.getPreferences())
            .filter(prefs -> prefs != null);
    }
}

Conditional Optional Responses

@Path("/conditional")
public class ConditionalOptionalResource {
    
    @GET
    @Path("/data/{id}")
    public Optional<Data> getData(@PathParam("id") UUIDParam dataId,
                                 @QueryParam("includeDetails") boolean includeDetails) {
        Optional<Data> data = dataService.findById(dataId.get());
        
        if (includeDetails) {
            // Include additional details if requested
            return data.map(d -> dataService.enrichWithDetails(d));
        }
        
        return data;
    }
    
    @GET
    @Path("/users/{id}/stats")
    public Optional<UserStats> getUserStats(@PathParam("id") UUIDParam userId,
                                           @QueryParam("period") String period) {
        return userService.findById(userId.get())
            .flatMap(user -> {
                if ("month".equals(period)) {
                    return statsService.getMonthlyStats(user.getId());
                } else if ("year".equals(period)) {
                    return statsService.getYearlyStats(user.getId());
                } else {
                    return statsService.getAllTimeStats(user.getId());
                }
            });
    }
}

Optional with Custom HTTP Status

@Path("/custom")
public class CustomOptionalResource {
    
    @GET
    @Path("/data/{id}")
    public Response getDataWithCustomStatus(@PathParam("id") UUIDParam dataId) {
        Optional<Data> data = dataService.findById(dataId.get());
        
        if (data.isPresent()) {
            return Response.ok(data.get()).build();
        } else {
            // Custom response for empty Optional
            return Response.status(204) // No Content instead of 404
                .header("X-Reason", "Data not available")
                .build();
        }
    }
    
    @GET
    @Path("/users/{id}/avatar")
    public Response getUserAvatar(@PathParam("id") UUIDParam userId) {
        Optional<byte[]> avatar = userService.getAvatar(userId.get());
        
        if (avatar.isPresent()) {
            return Response.ok(avatar.get())
                .type("image/png")
                .build();
        } else {
            // Redirect to default avatar instead of 404
            return Response.seeOther(URI.create("/images/default-avatar.png")).build();
        }
    }
}

Configuration and Customization

Configuring Optional Behavior

public class OptionalConfiguration {
    
    public void configureOptionalHandling(JerseyEnvironment jersey) {
        // Use 204 No Content instead of 404 for empty Optionals
        jersey.register(EmptyOptionalNoContentExceptionMapper.class);
        
        // Or keep default 404 behavior
        // jersey.register(EmptyOptionalExceptionMapper.class); // Default
        
        // Register optional message body writers (automatically registered by DropwizardResourceConfig)
        jersey.register(OptionalMessageBodyWriter.class);
        jersey.register(OptionalIntMessageBodyWriter.class);
        jersey.register(OptionalLongMessageBodyWriter.class);
        jersey.register(OptionalDoubleMessageBodyWriter.class);
    }
    
    public void configureGuavaOptional(JerseyEnvironment jersey) {
        // Enable Guava Optional support
        jersey.register(io.dropwizard.jersey.guava.OptionalMessageBodyWriter.class);
        jersey.register(new OptionalParamBinder());
    }
}

Custom Optional Exception Mapper

@Provider
public class CustomOptionalExceptionMapper implements ExceptionMapper<EmptyOptionalException> {
    
    @Override
    public Response toResponse(EmptyOptionalException exception) {
        // Custom logic based on request context
        UriInfo uriInfo = getUriInfo(); // Inject UriInfo
        String path = uriInfo.getPath();
        
        if (path.contains("/users/")) {
            return Response.status(404)
                .entity(new ErrorMessage(404, "User not found"))
                .build();
        } else if (path.contains("/data/")) {
            return Response.status(204)
                .header("X-Data-Status", "Not Available")
                .build();
        } else {
            return Response.status(404)
                .entity(new ErrorMessage(404, "Resource not found"))
                .build();
        }
    }
}

Best Practices

When to Use Optional

public class OptionalBestPractices {
    
    // Good: Use Optional for single resource lookups that may not exist
    @GET
    @Path("/users/{id}")
    public Optional<User> getUser(@PathParam("id") UUIDParam userId) {
        return userService.findById(userId.get());
    }
    
    // Good: Use Optional for optional resource properties
    @GET
    @Path("/users/{id}/avatar")
    public Optional<byte[]> getUserAvatar(@PathParam("id") UUIDParam userId) {
        return userService.getAvatar(userId.get());
    }
    
    // Avoid: Don't use Optional for collections - return empty collections instead
    @GET
    @Path("/users")
    public List<User> getUsers() {
        return userService.getAllUsers(); // Returns empty list, not Optional<List<User>>
    }
    
    // Good: Use primitive Optionals for numeric values that may be absent
    @GET
    @Path("/users/{id}/age")
    public OptionalInt getUserAge(@PathParam("id") UUIDParam userId) {
        return userService.getUserAge(userId.get());
    }
}

Optional Parameter Handling

@Path("/search")
public class OptionalParameterResource {
    
    @GET
    public List<User> searchUsers(@QueryParam("name") Optional<String> name,
                                 @QueryParam("minAge") OptionalInt minAge,
                                 @QueryParam("maxAge") OptionalInt maxAge) {
        
        SearchCriteria criteria = new SearchCriteria();
        
        // Handle optional parameters
        name.ifPresent(criteria::setName);
        minAge.ifPresent(criteria::setMinAge);
        maxAge.ifPresent(criteria::setMaxAge);
        
        return userService.search(criteria);
    }
    
    // Alternative approach with null checks
    @GET
    @Path("/alt")
    public List<User> searchUsersAlt(@QueryParam("name") String name,
                                    @QueryParam("minAge") Integer minAge,
                                    @QueryParam("maxAge") Integer maxAge) {
        
        SearchCriteria criteria = new SearchCriteria();
        
        if (name != null) criteria.setName(name);
        if (minAge != null) criteria.setMinAge(minAge);
        if (maxAge != null) criteria.setMaxAge(maxAge);
        
        return userService.search(criteria);
    }
}

Optional Chaining

public class OptionalChainingExamples {
    
    @GET
    @Path("/users/{id}/organization/name")
    public Optional<String> getUserOrganizationName(@PathParam("id") UUIDParam userId) {
        // Chain optional operations
        return userService.findById(userId.get())
            .flatMap(user -> organizationService.findById(user.getOrganizationId()))
            .map(org -> org.getName());
    }
    
    @GET
    @Path("/orders/{id}/customer/email")
    public Optional<String> getOrderCustomerEmail(@PathParam("id") UUIDParam orderId) {
        return orderService.findById(orderId.get())
            .flatMap(order -> customerService.findById(order.getCustomerId()))
            .map(customer -> customer.getEmail())
            .filter(email -> email != null && !email.isEmpty());
    }
}

Install with Tessl CLI

npx tessl i tessl/maven-io-dropwizard--dropwizard-jersey

docs

error-handling.md

framework-configuration.md

http-caching.md

index.md

jsr310-parameters.md

optional-handling.md

parameter-handling.md

session-management.md

validation.md

tile.json