SkillAgentSearch skills...

Proteus

Proteus is a blazing fast minimalist Java web server framework built atop Undertow.

Install / Use

/learn @noboomu/Proteus

README

Alt logo

Proteus is a blazing fast minimalist Java API server framework built atop Undertow for developing back end applications and microservices.

  • NO MAGIC
  • Incredibly easy to use and get started
  • Limited dependencies and < 340kb
  • JAX-RS compliant
  • Easy on the developer and the metal
  • Blazing fast!!! The latest Techempower benchmarks demonstrate proteus outperforming the top Java web frameworks

Top in Java Frameworks for JSON

Top in Java Frameworks for Plaintext

TL;DR

  • Proteus rewrites your controller methods into high performance Undertow response handlers at run time.
  • The goal is to provide the absolute highest performance while providing a simple and familiar API.
  • As developers, we feel a web framework should provide the essentials at minimal cost.

Getting Started

Quick Start

  • Make sure you have a JDK >= 8 and a current version of Maven installed.
  • Copy and paste into your terminal:
/bin/bash -e <(curl -fsSL https://raw.githubusercontent.com/noboomu/proteus-example/master/scripts/quickStart.sh)

As a dependency

<dependency>
    <groupId>io.sinistral</groupId>
    <artifactId>proteus-core</artifactId>
    <version>0.6.0</version>
</dependency>

OpenAPI v3 Support

<dependency>
    <groupId>io.sinistral</groupId>
    <artifactId>proteus-openapi</artifactId>
    <version>0.6.0</version>
</dependency>

Controllers

Supported Controller Annotations

Controller classes respect standard JAX-RS annotations:

@Path("/benchmarks")
@Produces((MediaType.APPLICATION_JSON)) 
@Consumes((MediaType.MEDIA_TYPE_WILDCARD))
public class DemoController

Supported Method Annotations

Controller class methods respect standard Swagger / JAX-RS annotations:



@GET
@Path("/plaintext")
@Produces((MediaType.TEXT_PLAIN))
public ServerResponse<ByteBuffer> plaintext(ServerRequest request)
{ 
	return response("Hello, World!").textPlain();
}

OpenAPI v3 annotations are supported when using the proteus-openapi module.

Proteus has three built in annotations:

  • @Blocking

    • io.sinistral.proteus.annotations.Blocking
    • Forces the request processing to block.
  • @Debug

    • io.sinistral.proteus.annotations.Debug
    • Dumps the request and response details to the log.
  • @Chain

    • io.sinistral.proteus.annotations.Chain
    • Wraps the endpoint handler in the provided array of io.undertow.server.HttpHandler classes.

Controller methods arguments support the following JAX-RS annotations:

  • @PathParam

    • javax.ws.rs.PathParam
    • Binds a url template parameter to the method parameter.
    • i.e. if the path is /dogs/{id}, @PathParam("id") binds the path segment value to the method parameter.
  • @QueryParam

    • javax.ws.rs.QueryParam
    • Binds a HTTP query parameter to the method parameter.
  • @FormParam

    • javax.ws.rs.FormParam
    • Binds the form parameter within a request body to the method parameter.
  • @HeaderParam

    • javax.ws.rs.HeaderParam
    • Binds the value of a HTTP header to the method parameter.
  • @CookieParam

    • javax.ws.rs.CookieParam
    • Binds the value of a HTTP cookie to the method parameter.
  • @BeanParam

    • javax.ws.rs.BeanParam
    • Binds and attempts to convert the request body to an instance of the method parameter.
  • @DefaultParam

    • javax.ws.rs.DefaultParam
    • Sets the default value of a method parameter.

Methods and Return Types

*The examples below assume you've included the proteus-openapi module.

Performance

For total control and maximum performance the raw HttpServerExchange can be passed to the controller method.

Methods that take an HttpServerExchange as an argument should not return a value.

In this case the method takes on full responsibility for completing the exchange.

Convenience

The static method io.sinistral.proteus.server.ServerResponse.response helps create ServerResponse<T> instances that are the preferred return type for controller methods.

If the response object's contentType is not explicitly set, the @Produces annotation is used in combination with the Accept headers to determine the Content-Type.

For methods that should return a String or ByteBuffer to the client users can create responses like this:

 ...
 import static io.sinistral.proteus.server.ServerResponse.response;
 ...
@GET
@Path("/hello-world")
@Produces((MediaType.TEXT_PLAIN)) 
@Operation(description = "Serve a plaintext message using a ServerResponse")
public ServerResponse<ByteBuffer> plaintext(ServerRequest request, @QueryParam("message") String message)
{ 
   return response("Hello, World!").textPlain();
}

By default, passing a String to the static ServerResponse.response helper function will convert it into a ByteBuffer.

For other types of responses the following demonstrates the preferred style:

  ...
 
  import static io.sinistral.proteus.server.ServerResponse.response;
   ...
@GET
@Path("/world")
@Produces((MediaType.APPLICATION_JSON)) 
@Operation(description = "Return a world JSON object")
public ServerResponse<World> getWorld(ServerRequest request, @QueryParam("id") Integer id,  @QueryParam("randomNumber") Integer randomNumber )
{ 
	return response(new World(id,randomNumber)).applicationJson();
}

The entity can be set separately as well:

this disables static type checking!

  ...
 
  import static io.sinistral.proteus.server.ServerResponse.response;
  ...
@GET
@Path("/world")
@Produces((MediaType.APPLICATION_JSON)) 
@Operation(description = "Return a world JSON object")
public ServerResponse getWorld(ServerRequest request, Integer id,  Integer randomNumber )
{ 
	return response().entity(new World(id,randomNumber));
}

CompletableFuture<ServerResponse<T>> can also be used as a response type:

  ...  
  import static io.sinistral.proteus.server.ServerResponse.response; 
  ...
@GET
@Path("/future/user")
@Operation(description = "Future user endpoint"  )
public CompletableFuture<ServerResponse<User>> futureUser( ServerRequest request )
{ 
	return CompletableFuture.completedFuture(response( new User(123L) ).applicationJson() );
}

In this case a handler will be generated with the following source code:

  ...
  import static io.sinistral.proteus.server.ServerResponse.response;
  ...
public void handleRequest(final io.undertow.server.HttpServerExchange exchange) throws java.lang.Exception { 
    CompletableFuture<ServerResponse<User>> response = examplesController.futureUser();
    response.thenAccept( r ->  r.applicationJson().send(this,exchange) )
    	.exceptionally( ex ->  {
      		throw new java.util.concurrent.CompletionException(ex);
  	} );
}

Controller Parameters

A io.sinistral.proteus.server.ServerRequest can be added as an endpoint parameter if the user wishes to access request properties that are not included in the parameter list.

Proteus is capable of parsing most types of endpoint parameters automatically so long as the type has a fromString, valueOf, or can be deserialized from JSON.

Multipart/Form file uploads can be passed to the endpoint methods as a java.io.File, a java.nio.Files.Path, or a java.nio.ByteBuffer.

Optional parameters are also supported, here is a more complex endpoint demonstrating several parameter types:

  ...
  import  static  io.sinistral.proteus.server.ServerResponse.response;
  ...
@GET
@Path("/response/parameters/complex/{pathLong}")
@Operation(description = "Complex parameters")
public ServerResponse<Map<String,Object>> complexParameters(
        ServerRequest serverRequest, 
        @PathParam("pathLong") final Long pathLong, 
        @QueryParam("optionalQueryString") Optional<String> optionalQueryString, 
        @QueryParam("optionalQueryLong") Optional<Long> optionalQueryLong, 
        @QueryParam("optionalQueryDate") Optional<OffsetDateTime> optionalQueryDate, 
        @QueryParam("optionalQueryUUID") Optional<UUID> optionalQueryUUID, 
        @HeaderParam("optionalHeaderUUID") Optional<UUID> optionalHeaderUUID,
        @QueryParam("optionalQueryEnum") Optional<User.UserType> optionalQueryEnum,
        @HeaderParam("optionalHeaderString") Optional<String> optionalHeaderString,
        @QueryParam("queryUUID") UUID queryUUID,  
        @HeaderParam("headerString") String headerString,
        @QueryParam("queryEnum") User.UserType queryEnum, 
	    @QueryParam("queryIntegerList") List<Integer> queryIntegerList, 
	    @QueryParam("queryLong") Long queryLong 
	    )
	{
		Map<String,Object> responseMap = new HashMap<>(); 
		responseMap.put("optionalQueryString", optionalQueryString.orElse(null));
		responseMap.put("optionalQueryLong", optionalQueryLong.orElse(null));
	 	responseMap.put("optionalQueryDate", optionalQueryDate.map(OffsetDateTime::toString).orElse(null));
		responseMap.put("optionalQueryUUID", optionalQueryUUID.map(UUID::toString).orElse(null));
		responseMap.put("optionalHeaderUUID", optionalHeaderUUID.map(UUID::toString).orElse(null));
		responseMap.put("optionalHeaderString", optionalHeaderString.orElse(null));
		responseMap.put("optionalQueryEnum", optionalQueryEnum.orElse(null));
		responseMap.put("queryEnum", queryEnum);
		
View on GitHub
GitHub Stars198
CategoryDevelopment
Updated26d ago
Forks18

Languages

Java

Security Score

100/100

Audited on Feb 26, 2026

No findings