EzyHTTP Annotations

Updated at 1783438159000
EzyHTTP uses annotations to declare controllers, request mappings, request arguments, converters, interceptors, exception handlers, and a few startup/runtime extension points.
This article summarizes the annotations currently provided by EzyHTTP and explains how they are used at runtime.
AnnotationTargetDescription
ApiClass, MethodMarks mapped URIs as API URIs so they can be recognized by the request URI manager.
ApplicationBootstrapClassMarks the application entry used to initialize and start the server runtime.
AsyncMethodMarks a request handler as asynchronous.
AuthenticatedClass, MethodMarks mapped URIs that require authentication.
AuthenticatableClass, MethodMarks mapped URIs that can be handled in an authentication-aware way without necessarily requiring authentication.
BodyConvertClassRegisters a request/response body converter for a content type.
ComponentClassesClassAdds explicit component classes to the application context.
ComponentsScanClassAdds packages to scan for EzyHTTP/EzyFox components.
ControllerClassDeclares a request controller and optionally sets its root URI.
DescriptionClass, Method, Field, Parameter, ConstructorProvides descriptive metadata that tools can read at runtime.
DoDeleteMethodMaps a method to an HTTP DELETE URI.
DoGetMethodMaps a method to an HTTP GET URI; also supports alternate GET URIs.
DoPostMethodMaps a method to an HTTP POST URI.
DoPutMethodMaps a method to an HTTP PUT URI.
ExceptionHandlerClassRegisters a global exception handler class.
EzyConfigurationAfterApplicationReadyClassRuns configuration logic after the application has started.
GraphQLQueryClass, MethodNames a GraphQL query data fetcher or query method in the GraphQL module.
InterceptorClassRegisters a request interceptor and optionally sets its priority.
PathVariableParameterBinds a URI path variable to a Java method parameter.
PropertiesSourcesClassAdds property files to the application context.
RequestArgumentParameterBinds a custom request argument to a Java method parameter.
RequestBodyParameterBinds and deserializes the request body.
RequestCookieParameterBinds a cookie value to a Java method parameter.
RequestHeaderParameterBinds a request header to a Java method parameter.
RequestParamParameterBinds a query/form parameter to a Java method parameter.
ServiceClassRegisters a service singleton in the bean context.
StringConvertClassRegisters string conversion logic used by path variables, params, headers, and cookies.
TryCatchMethodMaps one or more exception classes to a handler method.

Request mapping annotations

The core request mapping model is built from @Controller on a class and one of @DoGet, @DoPost, @DoPut, or @DoDelete on public methods.
@Controller("/api/v1/books")
public class BookController {

    @DoGet("/{bookId}")
    public BookResponse getBook(@PathVariable("bookId") long bookId) {
        // implementation
    }

    @DoPost(value = "/add", responseType = "application/json")
    public BookResponse addBook(@RequestBody AddBookRequest request) {
        // implementation
    }
}
@Controller accepts either value or uri. If both are empty, the default root URI is /. If a URI does not start with /, EzyHTTP normalizes it by adding /.
The HTTP method annotations also accept either value or uri. If both are empty, the method is mapped to the controller root URI.
@Controller("/health")
public class HealthController {

    @DoGet
    public String health() {
        return "OK";
    }
}
The example above maps GET /health.
@DoGet additionally supports otherUris, which creates extra GET mappings for the same handler:
@DoGet(value = "/profile", otherUris = {"/me", "/account"})
public UserProfile getProfile() {
    // implementation
}
All four HTTP method annotations support:
  • accept: the accepted request body content types.
  • responseType: the response body content type. If it is empty, EzyHTTP uses application/json.

API, authentication and authenticatable URIs

@Api, @Authenticated, and @Authenticatable can be placed on a controller class or on an individual handler method.
When placed on a controller, the flag applies to all request handler methods in that controller. When placed on a method, it applies only to that method.
@Api
@Authenticated
@Controller("/api/v1/users")
public class UserController {

    @DoGet("/{userId}")
    public UserResponse getUser(@PathVariable long userId) {
        // implementation
    }
}
For every mapped URI, EzyHTTP stores metadata in the request URI manager. That makes it possible for interceptors to check whether a matched URI is an API URI, authenticated URI, or authenticatable URI.
@Interceptor
public class AuthenticationInterceptor implements RequestInterceptor {

    private final RequestURIManager requestURIManager;

    @Override
    public boolean preHandle(RequestArguments arguments, Method handler) {
        boolean required = requestURIManager.isAuthenticatedURI(
            arguments.getMethod(),
            arguments.getUriTemplate()
        );
        if (!required) {
            return true;
        }
        // verify authentication
        return true;
    }
}
EzyHTTP also registers the slash and non-slash variants of flagged URIs. For example, a mapped API URI /api/v1/users is recognized together with /api/v1/users/.
@Authenticated means the URI requires authentication. @Authenticatable is a softer marker: it lets middleware recognize that the URI supports authentication-related handling, but the annotation itself does not force a request to be rejected. The actual decision still belongs to your interceptor or controller logic.

Request argument annotations

EzyHTTP can bind different parts of a request into handler method parameters:
@DoGet("/{bookId}")
public BookResponse getBook(
    @PathVariable("bookId") long bookId,
    @RequestParam(value = "includeAuthor", defaultValue = "false") boolean includeAuthor,
    @RequestHeader(value = "X-Request-Id", defaultValue = "") String requestId,
    @RequestCookie(value = "visitorId", defaultValue = "") String visitorId
) {
    // implementation
}
The request argument annotations behave as follows:
  • @PathVariable: reads a variable from the URI template, such as {bookId}.
  • @RequestParam: reads a query or form parameter.
  • @RequestHeader: reads a request header.
  • @RequestCookie: reads a cookie value.
  • @RequestBody: reads the request body and deserializes it by content type.
  • @RequestArgument: reads a custom argument from RequestArguments.
@RequestParam, @RequestHeader, and @RequestCookie support value, name, and defaultValue. EzyHTTP resolves the key from value first, then name, then the parameter index. The built-in default value is the literal string "null".
@PathVariable supports value. If it is empty, EzyHTTP falls back to the parameter index.
@DoPost("/orders")
public OrderResponse createOrder(@RequestBody CreateOrderRequest request) {
    // implementation
}
For @RequestBody, the request must provide a content type. EzyHTTP selects a registered body deserializer for that content type. If deserialization fails, EzyHTTP raises a body deserialization exception that can be handled by @TryCatch or a global exception handler.

Converters

@BodyConvert registers a body converter for a content type. Body converters are used to serialize response bodies and deserialize request bodies.
@BodyConvert("application/xml")
public class XmlBodyConverter implements BodyConverter {

    @Override
    public byte[] serialize(Object body) throws IOException {
        // convert object to XML bytes
    }

    @Override
    public <T> T deserialize(BodyData bodyData, Class<T> bodyType) throws IOException {
        // convert XML body to Java object
    }
}
EzyHTTP already registers common converters for JSON, form data, plain text, HTML text, and multipart form data. Use @BodyConvert when your application needs another content type or custom serialization behavior.
@StringConvert registers string conversion logic. It is used when converting string values from path variables, request parameters, headers, and cookies into Java types.
@StringConvert
public class CustomStringConverter extends DefaultStringDeserializer {

    public CustomStringConverter() {
        mappers.put(List.class, value -> Arrays.asList(value.split(",")));
    }
}

Components, services and properties

EzyHTTP builds its application context by scanning packages and registering singleton components.
@ComponentsScan adds packages to scan. If its value is empty, EzyHTTP scans the package of the annotated class.
@ComponentsScan({
    "com.example.app",
    "com.example.shared"
})
public class ApplicationConfig {}
@ComponentClasses adds explicit classes to the application context:
@ComponentClasses({
    MailService.class,
    AuditService.class
})
public class ApplicationConfig {}
@PropertiesSources adds property files:
@PropertiesSources({
    "application.properties",
    "application.yaml"
})
public class ApplicationConfig {}
@Service registers a service singleton. It supports both value and name for naming the service; if no name is supplied, the standard bean naming rule is used.
@Service("bookService")
public class BookService {
    // business logic
}

Startup annotations

@ApplicationBootstrap marks the application entry that EzyHTTP uses to initialize and start the underlying server runtime.
@ApplicationBootstrap
public class MyApplicationBootstrap implements ApplicationEntry {

    @Override
    public void init() {
        // prepare runtime
    }

    @Override
    public void start() {
        // start server
    }
}
After the application starts, EzyHTTP looks for singleton objects annotated with @EzyConfigurationAfterApplicationReady. If the object implements EzyBeanConfig, its config() method is called.
@EzyConfigurationAfterApplicationReady(priority = 10)
public class CacheWarmupConfig implements EzyBeanConfig {

    @Override
    public void config() {
        // run after application ready
    }
}
Objects are sorted by priority in ascending order before they run.

Interceptors

@Interceptor registers a request interceptor. Interceptors can run logic before and after a request handler.
@Interceptor(priority = 100)
public class LoggingInterceptor implements RequestInterceptor {

    @Override
    public boolean preHandle(RequestArguments arguments, Method handler) {
        return true;
    }

    @Override
    public void postHandle(RequestArguments arguments, Method handler) {
        // after handler
    }
}
Interceptors are sorted by priority in ascending order. Returning false from preHandle stops the request from continuing to the handler.

Async handlers

@Async marks a handler method that works with servlet async processing.
@Async
@DoGet("/files/{fileName}")
public void download(
    RequestArguments arguments,
    @PathVariable("fileName") String fileName
) throws Exception {
    // start async processing and write response later
}
This is useful for long-running operations such as upload, download, or streaming. When an exception happens after async processing has started, EzyHTTP completes the async context during exception handling.

Exception handling

There are two common patterns for exception handling.
Inside a controller, you can add methods annotated with @TryCatch:
@Controller("/books")
public class BookController {

    @DoGet("/{bookId}")
    public BookResponse getBook(@PathVariable long bookId) {
        // implementation
    }

    @TryCatch({BookNotFoundException.class})
    public ErrorResponse handleBookNotFound(BookNotFoundException e) {
        return new ErrorResponse("book_not_found");
    }
}
For global exception handling, create a class annotated with @ExceptionHandler and add public methods annotated with @TryCatch:
@ExceptionHandler
public class GlobalExceptionHandler {

    @TryCatch({IllegalArgumentException.class, HttpBadRequestException.class})
    public ErrorResponse handleBadRequest(Exception e) {
        return new ErrorResponse("bad_request");
    }
}
@TryCatch accepts one or more exception classes. EzyHTTP maps those exception classes to the annotated method. The response type for exception handlers is JSON by default.

GraphQLQuery

@GraphQLQuery belongs to the EzyHTTP GraphQL module. It can be used to name a GraphQL data fetcher or query method.
@GraphQLQuery(value = "book", group = "library")
public class BookGraphQLFetcher implements GraphQLDataFetcher {

    @Override
    public Object getData(
        RequestArguments arguments,
        GraphQLQueryDefinition query
    ) {
        // fetch GraphQL data
    }
}
The annotation supports:
  • value: query name.
  • name: query name, used before value when present.
  • group: optional query group name.
If a GraphQL data fetcher does not implement getQueryName(), EzyHTTP reads the query name from @GraphQLQuery. If neither is available, the fetcher cannot provide a query name.

Description

@Description is a general metadata annotation. It can be placed on classes, methods, fields, parameters, and constructors.
@Description("Request body used to create a book")
public class CreateBookRequest {

    @Description("Book title")
    private String title;
}
It supports:
  • value: description text.
  • contentType: optional content type for the description.
This annotation is most useful for tooling and generated documentation.

Quick usage guide

Use the annotations by responsibility:
  • Routing: @Controller, @DoGet, @DoPost, @DoPut, @DoDelete.
  • URI metadata: @Api, @Authenticated, @Authenticatable.
  • Parameters and body: @PathVariable, @RequestParam, @RequestHeader, @RequestCookie, @RequestBody, @RequestArgument.
  • Conversion: @BodyConvert, @StringConvert.
  • Application context: @ComponentsScan, @ComponentClasses, @PropertiesSources, @Service.
  • Runtime hooks: @ApplicationBootstrap, @EzyConfigurationAfterApplicationReady, @Interceptor, @Async.
  • Errors: @ExceptionHandler, @TryCatch.
  • Optional GraphQL module: @GraphQLQuery.
  • Documentation metadata: @Description.

Next Step

You can also read:

Table Of Contents