jExpress focuses on addressing non-functional and operational maintainability needs, some of which Spring Boot may not yet provide.
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.6.6</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>- Apache Central: https://repo.maven.apache.org/maven2/org/summerboot/jexpress/2.6.6
- Sonatype Central: https://central.sonatype.com/artifact/org.summerboot/jexpress/2.6.6
- mvnrepository.com: https://mvnrepository.com/artifact/org.summerboot/jexpress/2.6.6
- Solve the performance bottleneck of traditional multi-threading at the I/O layer.
- Quickly develop a RESTful Web Service with JAX-RS with minimal code.
- Application servers are always heavy, and some are not free (IBM WebSphere, Oracle Glassfish, Payara, Red Hat JBoss, Tomcat).
- Netty Reactor's multiplexing approach provides incredible power for socket-level custom communication protocols.
- Virtual Thread support (Java 21):
VirtualThread,CPU,IO, andMixedmodes 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.SummerInitializer;
import org.summerboot.jexpress.boot.annotation.Order;
import org.summerboot.jexpress.boot.event.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 SummerInitializer {
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.Valid;
import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import java.util.List;
import org.summerboot.jexpress.boot.annotation.Controller;
import org.summerboot.jexpress.boot.annotation.Log;
import org.summerboot.jexpress.nio.server.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 context.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.
The controller is only activated when the app is launched with -use RoleBased:
@Controller(AlternativeName = "RoleBased")import org.summerboot.jexpress.nio.server.ws.rs.PingController;
@Controller
@Path("/hellosummer")
public class MyController extends PingController {
// GET /hellosummer/ping is auto-registered
}or use @Ping directly:
import org.summerboot.jexpress.boot.annotation.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.nio.server.ws.rs.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.boot.annotation.Service;
import org.summerboot.jexpress.nio.server.RequestProcessor;
import org.summerboot.jexpress.nio.server.SessionContext;
import org.summerboot.jexpress.security.auth.*;
@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.nio.server.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()
- Keep configuration files clean and in sync with your code.
| File | Purpose |
|---|---|
log4j2.xml |
Async logging via Log4j2 + Disruptor |
cfg_smtp.properties |
SMTP / email alert settings |
cfg_auth.properties |
JWT signing, role/group mapping |
etc/boot.ini |
Master security algorithms, keystore type, thread pool modes |
log4j2.xmlrequires JVM arg:-Dlog4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
| File | Condition |
|---|---|
cfg_nio.properties |
Application contains @Controller |
cfg_grpc.properties |
Application contains a gRPC service |
Any @ImportResource config |
Annotated with @ImportResource, extends BootConfig, or implements JExpressConfig |
Key new sections in etc/boot.ini:
#######################
# 3. Default Settings #
#######################
#default.ConfigChangeMonitor.Throttle.Milliseconds=100
#####################################################
# 5.1 Security Settings: keystore type and provider #
#####################################################
## PKCS12 (default), PKCS11, JCEKS, JKS, BCFKS
#keystore.type=PKCS12
#keystore.provider=
#########################################
# 5.2 Security Settings: message digest #
#########################################
## SHA3-256 (default), SHA3-384, SHA3-512, SHA-256, SHA-384, SHA-512
#algorithm.Messagedigest=SHA3-256
###################################################################
# 5.3 Security Settings: asymmetric key #
###################################################################
#algorithm.Asymmetric=RSA
#transformation.Asymmetric=RSA/None/OAEPWithSHA-256AndMGF1Padding
######################################################
# 5.4 Security Settings: symmetric key #
######################################################
#algorithm.Symmetric=AES
#length.SymmetricKey.Bits=256
#transformation.Symmetric=AES/GCM/NoPadding
#length.SymmetricKey.AuthenticationTag.Bits=128
#length.symmetricKey.InitializationVector.Bytes=12
#####################################################
# 5.5 Security Settings: secret key (with password) #
#####################################################
#algorithm.SecretKey=PBKDF2WithHmacSHA256
#length.algorithm.SecretKey.Bits=256
#length.algorithm.SecretKey.Salt.Bits=16
#count.algorithm.SecretKey.iteration=310000- Guarantee service continuity when configuration changes (3rd-party tokens, license keys, etc.).
import com.fasterxml.jackson.annotation.JsonIgnore;
import java.io.File;
import java.util.Properties;
import org.summerboot.jexpress.boot.config.BootConfig;
import org.summerboot.jexpress.boot.config.ConfigUtil;
import org.summerboot.jexpress.boot.config.annotation.Config;
import org.summerboot.jexpress.boot.config.annotation.ConfigHeader;
import org.summerboot.jexpress.boot.config.annotation.ImportResource;
@ImportResource("cfg_app.properties")
public class MyConfig extends BootConfig {
public static final MyConfig cfg = new MyConfig();
private MyConfig() {
}
@ConfigHeader(title = "My Header description")
@JsonIgnore
@Config(key = "my.licenseKey", validate = Config.Validate.Encrypted, required = true)
protected volatile String licenseKey;
@Override
protected void loadCustomizedConfigs(File cfgFile, boolean isNotMock, ConfigUtil helper, Properties props) throws Exception {
}
@Override
public void shutdown() {
}
public String getLicenseKey() {
return licenseKey;
}
}Generate the template at any time:
public static void main(String[] args) {
String template = MyConfig.generateTemplate(MyConfig.class);
System.out.println(template);
}Generated cfg_app.properties:
#########################
# My Header description #
#########################
my.licenseKey=DEC(plain password)- Sensitive data (passwords, license keys, JWT signing keys, 3rd-party tokens) must not be plain text.
- Two-Level Protection: root admin controls the encryption key; app admin manages the config values.
| Level | Role | Capability |
|---|---|---|
| Level 1 | Application Admin | Writes plain DEC(...) values; app auto-encrypts them |
| Level 2 | Root (OS) Admin | Holds the root password file used to encrypt/decrypt |
Launch the app with:
java -jar jExpressApp.jar -authfile /etc/security/my-service.root_pwdRoot password file format:
APP_ROOT_PASSWORD=<base64 encoded root password>v2.6.0+: The default master password is no longer hardcoded. It is loaded from
etc/master.password(auto-created if absent) when-authfileis not provided.
Auto-encrypt: Wrap plain text with DEC() and save. The app encrypts it within 5 seconds:
datasource.password=DEC(plain password)
# becomes →
datasource.password=ENC(encrypted password)Manual batch encrypt:
java -jar my-service.jar -cfgdir <config folder> -encrypt -authfile <root pwd file>
java -jar my-service.jar -cfgdir <config folder> -encryptManual batch decrypt (root password required):
java -jar my-service.jar -cfgdir <config folder> -decryptDefault
<app root password>ischangeitwhen-authfileis provided.
Enable GET /hellosummer/ping without polluting your application log:
import org.summerboot.jexpress.boot.annotation.Ping;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import org.summerboot.jexpress.boot.annotation.Controller;
import org.summerboot.jexpress.nio.server.ws.rs.BootController;
@Controller
@Path("/hellosummer")
public class WebController extends BootController {
@GET
@Ping
@Path("/ping")
public void ping() {
}
@GET
@Path("/hello/{name}")
public String hello(@PathParam("name") String name) {
return "Hello " + name;
}
}ping.sync.HealthStatus.requiredHealthChecks=
ping.sync.PauseStatus=
ping.sync.showRootCause=- Automatically respond with an error to the load balancer when a dependency (DB, 3rd-party service) is down.
import org.summerboot.jexpress.boot.annotation.Inspector;
import org.summerboot.jexpress.boot.annotation.Service;
import org.summerboot.jexpress.boot.instrumentation.HealthInspector;
import org.summerboot.jexpress.nio.server.domain.Err;
import java.util.List;
@Inspector(name = "myDB")
@Service(binding = HealthInspector.class)
public class MyHealthInspector implements HealthInspector<Void> {
@Override
public List<Err> ping(Void... param) {
List<Err> errors = new java.util.ArrayList<>();
// check DB connectivity, add Err on failure
// errors.add(new Err(123, "DB_DOWN", "Database is unreachable", null));
return errors;
}
}Daemon mode: Annotate a
@Controllerclass or method with@Daemonto keep it accessible even when the service is paused or health-check fails:@Daemon(requiredHealthChecks = {"myDB"}) @GET @Path("/admin/status") public void adminStatus() {}
- Get notified before someone knocks on your door. Debounce repeated alerts.
####################
# 1. SMTP Settings #
####################
mail.smtp.host=smtpserver
mail.smtp.user=abc_service@email.addr
mail.smtp.user.displayname=ABC Service
mail.smtp.user.password=DEC(changeit)
###########################################
# 2. Alert Recipients (CSV format) #
###########################################
#email.to.AppSupport=
#email.to.Development=
#email.to.ReportViewer=
## Same-title alerts suppressed within this many minutes
debouncing.emailalert_minute=30No code changes required — just update cfg_smtp.properties.
- Request and response logged together in a single log entry.
- Client receives response without waiting for logging.
- Log file auto-rotated and named with the server hostname.
context.poi("DB begin");
// ... DB access ...
context.
poi("DB end");Sample POI output:
POI: service.begin=4ms, auth.begin=4ms, process.begin=4ms, biz.begin=4ms,
biz.end=18ms, process.end=18ms, service.end=18ms
- Request log — Security, performance, full client↔server conversation.
- App status/event log — Version, start/stop events, config-change events, TPS counters.
SummerRunner and IdleEventMonitor.IdleEventListener have been consolidated into:
public interface AppLifecycleListener extends IdleEventMonitor.IdleEventListener {
void beforeApplicationStart(SummerApplication.AppContext context) throws Exception;
void onApplicationStart(SummerApplication.AppContext context, String appVersion, String fullConfigInfo) throws Exception;
void onApplicationStop(SummerApplication.AppContext context, String appVersion);
void onApplicationStatusUpdated(SummerApplication.AppContext context, boolean healthOk, boolean paused,
boolean serviceStatusChanged, String reason) throws Exception;
void onHealthInspectionFailed(SummerApplication.AppContext context, boolean healthOk, boolean paused,
long retryIndex, int nextInspectionIntervalSeconds) throws Exception;
void onConfigChangeBefore(File configFile, JExpressConfig cfg);
void onConfigChangedAfter(File configFile, JExpressConfig cfg, Throwable ex);
// from IdleEventMonitor.IdleEventListener:
void onIdle(IdleEventMonitor idleEventMonitor) throws Exception;
}Extend the default adapter AppLifecycleHandler and override only what you need.
Configure idle thresholds in configuration files:
# cfg_nio.properties
nio.server.idle.threshold.second=60
# cfg_grpc.properties
gRpc.server.idle.threshold.second=60@Service // default implementation
public class MyServiceImpl implements MyService { ...
}
@Service(AlternativeName = "impl1")
public class MyServiceImpl_1 implements MyService { ...
}
@Service(AlternativeName = "impl2")
public class MyServiceImpl_2 implements MyService { ...
}java -jar my-service.jar -?
# shows: -use <items> launch application in mock mode, valid values <impl1, impl2>
java -jar my-service.jar -use impl1import org.summerboot.jexpress.boot.BootErrorCode;
import org.summerboot.jexpress.boot.annotation.Unique;
import org.summerboot.jexpress.boot.annotation.UniqueIgnore;
@Unique(name = "ErrorCode", type = int.class)
public interface AppErrorCode extends BootErrorCode {
int APP_UNEXPECTED_FAILURE = 1001;
int BAD_REQUEST = 1002;
int AUTH_CUSTOMER_NOT_FOUND = 1003;
int DB_SP_ERROR = 1004;
// suppress false-positive duplicate alert:
@UniqueIgnore
int ALIAS_BAD_REQUEST = BAD_REQUEST;
}
@Unique(name = "POI", type = String.class)
public interface AppPOI extends BootPOI {
String FILE_BEGIN = "file.begin";
String FILE_END = "file.end";
}java -jar my-service.jar -unique ErrorCode
java -jar my-service.jar -unique POIimport org.summerboot.jexpress.boot.annotation.Scheduled;
public class MyJob {
@Scheduled(cron = "0 15 10 ? * 6L 2024-2030") // every last Friday at 10:15am
public void monthlyReport() { ...}
@Scheduled(hour = 2, minute = 0) // daily at 2:00am
public void dailyMaintenance() { ...}
@Scheduled(fixedRateMs = 10_000, initialDelayMs = 5_000) // every 10s
public void polling() { ...}
@Scheduled(fixedDelayMs = 10_000) // 10s after completion
public void delayedPolling() { ...}
}Dynamic cron from a static field:
private static String MY_CRON = "0 0 * * * ?";
@Scheduled(cronField = "MY_CRON")
public void dynamicJob() { ...}Annotate your gRPC service implementation with @GrpcService:
import org.summerboot.jexpress.boot.annotation.GrpcService;
@GrpcService
public class MyGrpcServiceImpl extends MyGrpcService.MyGrpcServiceImplBase {
// implement gRPC methods
}Configure in cfg_grpc.properties.
import org.summerboot.jexpress.nio.grpc.GRPCClient;
import org.summerboot.jexpress.nio.grpc.GRPCClientConfig;
GRPCClient client = new GRPCClient(GRPCClientConfig.cfg);Supports:
- 2-way TLS (mutual authentication)
- Client-side load balancing via
BootLoadBalancerProvider - Bearer token authentication via
BearerAuthCredential - Dynamic configuration reload
import org.summerboot.jexpress.nio.grpc.GRPCTestHelper;
// see https://github.com/SummerBootFramework/jExpressDemo-HelloSummerimport org.summerboot.jexpress.integration.httpclient.RPCDelegate;
import org.summerboot.jexpress.integration.httpclient.RPCResult;
// inject or create an RPCDelegate instance
RPCResult<MyResponseDto> result = rpcDelegate.get(url, MyResponseDto.class, context);
if(result.
hasError()){
// handle errors
}
MyResponseDto dto = result.getResult();Configure in cfg_httpclient.properties (proxy, TLS, timeout, thread pool mode, etc.).
import org.summerboot.jexpress.integration.jpa.JPAHibernateConfig;
import org.summerboot.jexpress.integration.jpa.AbstractEntity;Configure in a JPA config file that extends JPAHibernateConfig. DB credentials use DEC()/ENC():
jakarta.persistence.jdbc.url=jdbc:mysql://localhost:3306/mydb
jakarta.persistence.jdbc.user=myuser
jakarta.persistence.jdbc.password=DEC(changeit)
jakarta.persistence.jdbc.driver=com.mysql.jdbc.Driverimport org.summerboot.jexpress.integration.mqtt.MqttClientConfig;Configure in a MQTT config file. TLS settings follow the same pattern as HTTP/gRPC clients.
import org.summerboot.jexpress.integration.cache.AuthTokenCache;Jedis (Redis) is a provided dependency. Add it to your app's dependencies if needed:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>7.2.1</version>
</dependency>Requests with illegal characters or path traversal in the URL are automatically rejected with 400 Bad Request:
/../../../../windows/win.ini?q=<script>alert(1)</script> → 400
/?action:%{(new java.lang.ProcessBuilder(...)).start()} → 400
UrlSanitizer.cleanUrl(String url) returns:
public record UrlSanitized(String cleanPath, String cleanQuery, String cleanedURL, boolean isPathTraversal) {
}Whitelist/blacklist with regex support (no prefix required since v2.6.1):
# cfg_nio.properties
CallerAddressFilter.option=String # String, Regex, or HostName
# cfg_grpc.properties
gRpc.server.CallerAddressFilter.option=Stringimport org.summerboot.jexpress.security.SecurityUtil;
String safeFilename = SecurityUtil.escape4Filename(userInput);| Setting | Default |
|---|---|
| Keystore type | PKCS12 |
| Message digest | SHA3-256 |
| Asymmetric | RSA/None/OAEPWithSHA-256AndMGF1Padding |
| Symmetric | AES/GCM/NoPadding |
| Secret key | PBKDF2WithHmacSHA256 |
| EC curve | secp256r1 |
Place plugin JARs in the plugin/ folder. The framework picks them up at startup, allowing you to:
- Add new features without modifying the core application.
- Override existing logic via the Visitor pattern.
- Deploy logic developed by separate teams as independent plugins.
| Option | Description |
|---|---|
-? |
Show help |
-cfgdir <path> |
Config folder path |
-authfile <path> |
Root password file |
-encrypt |
Batch encrypt all DEC(...) values |
-decrypt |
Batch decrypt all ENC(...) values |
-use <name> |
Launch with alternative service implementation |
-unique <name> |
List/validate unique codes |
-domain <name> |
Domain name |
-psv <envId> |
Print service version for environment |
-debug |
Enable debug mode (logs all requests/responses, ignores @Log) |
| Library | Version |
|---|---|
| Java | 21 |
| Netty | 4.2.10.Final |
| gRPC | 1.79.0 |
| Guice (IoC) | 7.0.0 |
| Jackson | 2.21.0 |
| Hibernate ORM | 7.2.4.Final |
| HikariCP | 7.0.2 |
| Log4j2 | 2.25.3 |
| BouncyCastle | 1.83 |
| jjwt | 0.13.0 |
| Hibernate Validator | 9.1.0.Final |
| Quartz | 2.5.2 |
| PDFBox | 3.0.6 |
| openhtmltopdf | 1.1.37 |
| iText | 9.5.0 |
| Apache Tika | 3.2.3 |
| ZXing (barcode) | 3.5.4 |
| Jedis (Redis) | 7.2.1 |
| Freemarker | 2.3.34 |
- Delete classes implementing
SummerRunnerorIdleEventMonitor.IdleEventListener. - Implement
AppLifecycleListeneror extendAppLifecycleHandler. - Move
SummerRunner.run()logic →AppLifecycleListener.beforeApplicationStart(). - Move idle listener logic →
AppLifecycleListener.onIdle(). - Set
nio.server.idle.threshold.second/gRpc.server.idle.threshold.secondin config.
SessionContext.uri()→SessionContext.uriRawDecoded()
The encryption format changed in 2.6.0. Before upgrading, either:
java -jar your-app.jar -decryptor redeploy with DEC(...) values and let the new version re-encrypt them.
| Old | New |
|---|---|
@Controller.implTag |
@Controller.AlternativeName |
@Service.implTag |
@Service.AlternativeName |
@Log.hideJsonStringFields / hideJsonNumberFields / hideJsonArrayFields |
@Log.maskDataFields |
ServiceContext |
SessionContext |
@ImportResource.checkImplTagUsed |
@ImportResource.whenUseAlternative |
@ImportResource.loadWhenImplTagUsed |
@ImportResource.thenLoadConfig |
BootHealthInspectorImpl |
@DefaultHealthInspector |
ServiceContext.reset() |
SessionContext.resetResponseData() |
DB config hibernate.* |
jakarta.persistence.* |
