Loading repository data…
Loading repository data…
SummerBootFramework / repository
Summer Boot Framework was initiated by a group of developers in 2004 to provide a high performance, free customizable but also lightweight Netty JAX-RS Restful, WebSocket and gRPC service with powerful reusable non-functional features, and was adopted by several Toronto law firms in 2011 to customize their back-end services.
Java 21+ · Netty 4.2 · Guice 7 · Jakarta EE · Virtual Threads
Summer Boot Framework was initiated by a group of developers in 2004 to provide a high-performance, free, customizable, and lightweight Netty JAX-RS RESTful, WebSocket, and gRPC service with JPA and other powerful reusable non-functional features. Since 2011, it has been adopted by several Toronto law firms to customize their back-end services.
Its sub-project, jExpress (a.k.a. Summer Boot Framework Core), focuses on solving the following non-functional and operational maintainability requirements.
Open Source History: jExpress was initially open-sourced on MS MySpace in Sep 2006. Due to the shutdown of MySpace, this framework was migrated to a server sponsored by one of the law firms in October 2011, then to GitLab in Dec 2016, and eventually to GitHub in Sep 2021.
Disclaimer: We really had a great time with GitLab until 2021 when we realized one of the contributor's employers was also using GitLab at that time. We decided to move to GitHub instead to avoid incurring unnecessary hassles.
<dependency>
<groupId>org.summerboot</groupId>
<artifactId>jexpress</artifactId>
<version>2.7.2</version>
</dependency>
SNAPSHOT repository:
<repositories>
<repository>
<id>maven.snapshots</id>
<name>Maven Snapshot Repository</name>
<url>https://s01.oss.sonatype.org/content/repositories/snapshots/</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
VirtualThread, CPU, IO, and Mixed modes are configurable for HTTP server, HTTP client, gRPC server, and BackOffice.step 1 — main class:
import org.summerboot.jexpress.boot.SummerApplication;
public class Main {
public static void main(String... args) {
SummerApplication.run();
}
}
step 2 — lifecycle hooks (replaces deprecated SummerRunner):
Implement AppLifecycleListener or extend AppLifecycleHandler (recommended):
import com.google.inject.Singleton;
import org.summerboot.jexpress.boot.SummerApplication;
import org.summerboot.jexpress.boot.lifecycle.app.AppInitializer;
import org.summerboot.jexpress.annotation.Order;
import org.summerboot.jexpress.boot.lifecycle.app.AppLifecycleHandler;
import org.apache.commons.cli.Options;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.File;
@Singleton
@Order(1)
public class MainLifecycle extends AppLifecycleHandler implements AppInitializer {
private static final Logger log = LogManager.getLogger(MainLifecycle.class);
@Override
public void initCLI(Options options) {
log.info("CLI options initialized");
}
@Override
public void initAppBeforeIoC(File configDir) {
log.info("before IoC: {}", configDir);
}
@Override
public void initAppAfterIoC(File configDir, com.google.inject.Injector guiceInjector) {
log.info("after IoC: {}", configDir);
}
@Override
public void beforeApplicationStart(SummerApplication.AppContext context) throws Exception {
log.debug("application about to start");
}
}
Migration note (from < 2.6.5):
SummerRunnerhas been removed. Move yourrun()logic toAppLifecycleListener.beforeApplicationStart().
step 3 — a RESTful controller:
import com.google.inject.Singleton;
import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import java.util.List;
import org.summerboot.jexpress.annotation.Controller;
import org.summerboot.jexpress.annotation.rest.Log;
import org.summerboot.jexpress.api.common.SessionContext;
import io.netty.handler.codec.http.HttpResponseStatus;
@Singleton
@Controller
@Path("/hellosummer")
public class MyController {
@GET
@Path("/account/{name}")
@Produces(MediaType.TEXT_PLAIN)
public String hello(@NotNull @PathParam("name") String myName) {
return "Hello " + myName;
}
@POST
@Path("/account/{name}")
@Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public ResponseDto hello_no_validation(@PathParam("name") String myName, RequestDto request) {
return new ResponseDto();
}
/**
* Three features:
* 1. auto-validate JSON request via Bean Validation (enabled by default in v2.6+, no @Valid needed)
* 2. mask sensitive fields in log via @Log(maskDataFields)
* 3. mark performance POI via ioc.poi(key)
*/
@POST
@Path("/hello/{name}")
@Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Log(maskDataFields = {"creditCardNumber", "clientPrivacy", "secretList"})
public ResponseDto hello(@NotNull @PathParam("name") String myName,
@NotNull RequestDto request, // @Valid not required since v2.6.0
final SessionContext context) {
context.poi("DB begin");
// ... DB access ...
context.poi("DB end");
context.status(HttpResponseStatus.CREATED);
return new ResponseDto();
}
public static class RequestDto {
@NotNull
private String creditCardNumber;
@NotNull
private List<String> shoppingList;
}
public static class ResponseDto {
private String clientPrivacy;
private final List<String> secretList = List.of("aa", "bb");
}
}
v2.6.0+: Bean Validation is enabled by default for all
@Controllermethods — no need to add@Validon request body parameters.
AlternativeNameThe controller is only activated when the app is launched with -use RoleBased:
@Controller(AlternativeName = "RoleBased")
import org.summerboot.jexpress.api.rest.PingController;
@Controller
@Path("/hellosummer")
public class MyController extends PingController {
// GET /hellosummer/ping is auto-registered
}
or use @Ping directly:
import org.summerboot.jexpress.annotation.rest.Ping;
@Controller
@Path("/hellosummer")
public class MyController {
@GET
@Path("/ping")
@Ping
public void ping() {
}
}
step 1 — annotate the controller:
import jakarta.annotation.security.PermitAll;
import jakarta.annotation.security.RolesAllowed;
import org.summerboot.jexpress.api.rest.BootController;
@Controller
@Path("/hellosummer")
public class MyController extends BootController {
@GET
@Path("/hello/anonymous")
public void anonymous() {
}
@GET
@Path("/helloAdmin/user")
@PermitAll
public void loginedUserOnly() {
}
@GET
@Path("/helloAdmin/admin")
@RolesAllowed({"AppAdmin"})
public void adminOnly() {
}
@GET
@Path("/helloAdmin/employee")
@RolesAllowed({"Employee"})
public void employeeOnly() {
}
}
step 2 — implement an Authenticator:
import com.google.inject.Singleton;
import io.netty.handler.codec.http.HttpHeaders;
import javax.naming.NamingException;
import org.summerboot.jexpress.annotation.Service;
import org.summerboot.jexpress.api.common.SessionContext;
import org.summerboot.jexpress.api.auth.Authenticator;
import org.summerboot.jexpress.boot.lifecycle.auth.AuthenticatorListener;
import org.summerboot.jexpress.security.auth.BootAuthenticator;
import org.summerboot.jexpress.api.auth.Caller;
import org.summerboot.jexpress.api.auth.User;
import handler.org.summerboot.jexpress.web.netty.RequestProcessor;
@Singleton
@Service(binding = Authenticator.class)
public class MyAuthenticator extends BootAuthenticator<Long> {
@Override
protected Caller authenticate(String username, String password, Long metaData,
AuthenticatorListener listener, SessionContext context) throws NamingException {
if ("wrongpwd".equals(password)) return null;
long tenantId = 1;
String tenantName = "jExpress Org";
long userId = 456;
User user = new User(tenantId, tenantName, userId, username);
user.addGroup("AdminGroup");
user.addGroup("EmployeeGroup");
return user;
}
@Override
public boolean customizedAuthorizationCheck(RequestProcessor processor,
HttpHeaders httpRequestHeaders,
String httpRequestPath,
SessionContext context) throws Exception {
return true;
}
}
v2.6+:
BootAuthenticatoralso implementsServerInterceptorfor unified JWT auth across both HTTP and gRPC.
step 3 — cfg_auth.properties:
roles.AppAdmin.groups=AdminGroup
#roles.AppAdmin.users=admin1, admin2
roles.Employee.groups=EmployeeGroup
#roles.Employee.users=employee1, employee2
[411043] 2025-08-17T11:50:58,429 WARN org.summerboot.jexpress.api.rest.BootHttpRequestHandler.() [Netty-HTTP.Biz-5-vt-1]
[411043-2 /127.0.0.1:8311] [200 OK, error=0, queuing=1ms, process=5798ms, response=5801ms]
HTTP/1.1 GET /hellosummer/services/appname/v1/aaa/111
POI.t0=2025-08-17T11:50:52.627-04:00 service.begin=0ms, process.begin=1ms, biz.begin=6ms, biz.end=5795ms, process.end=5798ms, service.end=5801ms,
1.client_req.headers=...
2.client_req.body(0 bytes)=null
3.server_resp.headers=...
4.server_resp.body(158 bytes)={"name":"...","value":"..."}
v2.6.6 API change:
SessionContext.uri()renamed toSessionContext.uriRawDecoded().
SessionContext.uriRawDecoded()= raw URI fromFullHttpRequest.uri()
ServiceRequest.getHttpRequestPath()= decoded path fromQueryStringDecoder.path()
| File | Purpose |
|---|---|
log4j2.xml | Async logging via |