The Codion server provides RMI and HTTP connection options to clients.
1. Features
-
Firewall friendly RMI; uses one way communication without callbacks, uses two ports, one for the RMI Registry and one for client connections
-
Integrated web server for serving HTTP client connections, based on Javalin and Jetty
-
All user authentication left to the database by default
-
Comprehensive administration and monitoring facilities via the ServerMonitor
-
Featherweight server with moderate memory and CPU usage
2. Security
Here’s a great overview of RMI security risks and mitigations.
2.1. Authentication
The Codion server does not perform any user authentication by default, it leaves that up the underlying database. An authentication layer can be added by implementing an Authenticator and registering it with the ServiceLoader.
2.2. RMI SSL encryption
To enable SSL encryption between client and server, create a keystore and truststore pair and set the following system properties.
2.4. Serialization filtering
The framework provides a way to configure a ObjectInputFilter for deserialization, by specifying a ObjectInputFilterFactory implementation class with the following system property.
codion.server.objectInputFilterFactory=\
my.serialization.filter.MyObjectInputFilterFactory
|
Important
|
By default, an ObjectInputFilterFactory is required for the server to start. If no filter factory is configured, the server will throw an exception on startup. This is a security measure to prevent accidental deployment without deserialization filtering.
|
To explicitly disable this requirement (not recommended for production), set:
codion.server.objectInputFilterFactoryRequired=false
2.4.1. Pattern filter
To use the built-in pattern based serialization filter, set the following system property.
codion.server.objectInputFilterFactory=\
is.codion.common.rmi.server.SerializationFilterFactory
To use serialization filter patterns specified in a string, set the following system property.
|
Important
|
SerializationFilterFactory automatically appends the exclude all pattern !* if it’s missing, but it is good practice to always include it in your pattern list.
|
codion.server.serialization.filter.pattern=pattern1;pattern2;!*
This is equivalent to setting the following:
jdk.serialFilter=pattern1;pattern2;!*
To use the serialization pattern filter based on patterns in a file, set the following system property.
The file may contain all the patterns in a single line, using the ; delimiter or one pattern per line, without a delimiter. Lines starting with '#' are skipped as comments.
codion.server.serialization.filter.patternFile=config/patterns.txt
codion.server.serialization.filter.patternFile=classpath:patterns.txt
A list of deserialized classes can be created during a server dry-run by adding the following system property. The file containing all classes deserialized during the run is written to disk on server shutdown.
codion.server.serialization.filter.dryRunFile=deserialized.txt
Example whitelist
java.lang.**
java.math.**
java.time.**
java.util.**
is.codion.common.**
is.codion.framework.**
is.codion.plugin.jasperreports.*
is.codion.demos.chinook.domain.api.*
is.codion.demos.world.domain.api.*
# ServerMonitor
ch.qos.logback.classic.Level
# For loading JasperReport from classpath
net.sf.jasperreports.compilers.**
net.sf.jasperreports.engine.**
# Reject all other classes
!*
2.4.2. Resource exhaustion limits
The pattern-based serialization filter supports JEP 290 resource limits to prevent resource exhaustion attacks during deserialization. These limits are enforced during deserialization, before objects are fully materialized in memory.
The following system properties configure the resource limits (with their default values):
codion.server.serialization.filter.maxBytes=10485760 # Maximum stream size: 10 MB
codion.server.serialization.filter.maxArray=100000 # Maximum array length: 100,000 elements
codion.server.serialization.filter.maxDepth=100 # Maximum object graph depth: 100 levels
codion.server.serialization.filter.maxRefs=1000000 # Maximum internal references: 1,000,000 refs
These limits are automatically prepended to the serialization filter patterns. For example, with a pattern file containing class names, the effective filter becomes:
maxbytes=10485760;maxarray=100000;maxdepth=100;maxrefs=1000000;java.lang.String;is.codion.**;!*
Important: These limits provide the primary defense against resource exhaustion attacks. Application-level validation (such as User credential length limits) cannot prevent memory allocation during deserialization, making these JEP 290 limits essential for security.
2.4.3. Serialization Filter Dry Run
The serialization filter dry-run mode helps you discover exactly which classes are deserialized during program execution. This is essential when creating or updating serialization filter patterns, as it eliminates guesswork about what classes need to be whitelisted.
How It Works
When dry-run mode is enabled, the framework records every class that gets deserialized during the program’s lifetime. On JVM shutdown, it writes a sorted list of unique class names to the specified file. This file can then be used directly as input for the pattern-based filter.
|
Important
|
During dry-run mode, all classes are accepted (no filtering is performed). This allows you to capture the complete set of deserialized classes without being blocked by an incomplete filter. |
Configuration
Enable dry-run mode by setting the following system property:
codion.server.serialization.filter.dryRunFile=deserialized_classes.txt
The specified file will be created (or overwritten) on JVM shutdown with the discovered class names.
Periodic Flushing
To prevent data loss if the JVM crashes during dry-run, the results are automatically flushed to disk periodically (default: every 30 seconds):
codion.server.serialization.filter.dryRunFlushInterval=30
Set dryRunFlushInterval=0 to disable periodic flushing. For dry-runs in unstable environments, consider setting a shorter interval (e.g., dryRunFlushInterval=5).
Direct Usage Example
The dry-run mode can be used outside of the server context to analyze deserialization in any Java application. Here’s an example that discovers which classes are deserialized when loading a JasperReports report:
public static void main(String[] args) {
// Set the dry-run output file
SerializationFilterFactory.SERIALIZATION_FILTER_DRYRUN_FILE.set(
"/path/to/output.txt");
// Configure the filter in dry-run mode
ObjectInputFilter.Config.setSerialFilter(
new SerializationFilterFactory().createObjectInputFilter());
// Perform operations that deserializes data
Report.REPORT_PATH.set("path/to/reports");
JRReport report = JasperReports.fileReport("customer_report.jasper");
report.load();
// Dry-run output is written on JVM shutdown
}
Workflow
-
Enable dry-run mode with the
dryRunFileproperty -
Run your application through typical usage scenarios to trigger deserialization
-
Shut down the JVM gracefully to write the discovered classes to the file
-
Review the output file containing the sorted list of class names
-
Use the output as your whitelist by setting it as the pattern file:
codion.server.serialization.filter.patternFile=deserialized_classes.txt
Array Handling
The dry-run mode automatically handles array types by extracting and recording the component type. For example, if java.lang.String[] is deserialized, the output file will contain java.lang.String, not the array type itself.
Best Practices
-
Run dry-run mode in a test environment that exercises all application features
-
Include all expected user workflows to ensure complete coverage
-
Use dry-run mode when adding new features that might deserialize new types
-
Keep the dry-run output file under version control to track changes over time
-
Re-run dry-run mode after dependency upgrades that might introduce new deserialized classes
3. HTTP
Codion provides HTTP-based connections as an alternative to RMI, accessible via the HttpEntityConnection interface. The framework supports two distinct HTTP connection types, each with different serialization strategies optimized for different scenarios.
3.1. Connection Types
3.1.1. Serialization-based Connection
The default HTTP connection uses Java serialization for all communication:
-
Performance: More efficient for complex object graphs with automatic deduplication
-
Memory: Lower memory footprint due to object reference handling
-
Security: Requires proper deserialization filtering (see Serialization filtering)
-
Configuration: Server-side enabled via
codion.server.http.serialization=true(default:false)
The serialization-based connection is ideal for trusted networks and scenarios where performance is critical.
3.1.2. JSON-based Connection
The JSON connection provides a more security-conscious approach:
-
Security: Eliminates server-side deserialization vulnerabilities by using JSON for incoming requests
-
Transparency: Human-readable request/response format for debugging
-
Compatibility: Works with non-Java clients (when using JSON throughout)
-
Configuration: Server-side enabled via
codion.server.http.json=true(default:true)
The JSON connection uses a hybrid serialization strategy:
| Operation | Request | Response |
|---|---|---|
CRUD |
JSON |
JSON |
JSON |
Java serialization |
|
JSON |
N/A |
|
JSON |
Java serialization |
|
N/A |
Java serialization |
|
Important
|
The hybrid approach protects the server from deserialization attacks while maintaining compatibility with complex return types like JasperPrint and Entities that don’t support JSON serialization.
|
3.2. Choosing a Connection Type
Use serialization-based connections when:
-
Operating in a trusted network environment
-
Performance and memory efficiency are priorities
-
You have proper deserialization filtering configured
-
All clients are Java-based
Use JSON-based connections when:
-
Security is the primary concern (avoiding server-side deserialization)
-
Operating in less trusted environments
-
You want human-readable request/response payloads
-
You need non-Java client compatibility (with custom JSON handling)
3.3. JSON Connection Requirements
When using the JSON connection with functions, procedures, or reports that have parameters, you must register parameter types with the EntityObjectMapper. This is required because Jackson needs target types to deserialize JSON, and generic type parameters are erased at runtime.
See the manual section on HTTP/JSON Serialization for details on registering parameter types via EntityObjectMapperFactory.
4. Configuration
4.1. Example configuration file
# Database configuration
codion.db.url=jdbc:h2:mem:h2db
codion.db.useOptimisticLocking=true
codion.db.countQueries=true
codion.db.initScripts=\
config/employees/create_schema.sql,\
config/chinook/create_schema.sql,\
config/petstore/create_schema.sql,\
config/world/create_schema.sql
# The admin user credentials, used by the server monitor application
codion.server.admin.user=scott:tiger
# Client method tracing disabled by default
codion.server.methodTracing=false
# A connection pool based on this user is created on startup
codion.server.connectionPoolUsers=scott:tiger
# The port used by clients
codion.server.port=2222
# The port for the admin interface, used by the server monitor
codion.server.admin.port=4444
# RMI Registry port
codion.server.registryPort=1099
# Any auxiliary servers to run alongside this server
codion.server.auxiliaryServerFactories=\
is.codion.framework.servlet.EntityServiceFactory
# Specifies whether to expose json based services (default true)
codion.server.http.json=true
# Specifies whether to expose java serialization based services (default false)
codion.server.http.serialization=true
# The http port
codion.server.http.port=8080
# Specifies whether to use https
codion.server.http.secure=false
# The ObjectInputFilterFactory class to use
codion.server.objectInputFilterFactory=\
is.codion.common.rmi.server.SerializationFilterFactory
# The serialization pattern file to use for RMI deserialization filtering
codion.server.serialization.filter.patternFile=\
../config/serialization-whitelist.txt
# RMI configuration
java.rmi.server.hostname=localhost
java.rmi.server.randomIDs=true
# SSL configuration
javax.net.ssl.keyStore=../config/keystore.jks
javax.net.ssl.keyStorePassword=crappypass
# Used to connect to the server to shut it down
#codion.client.trustStore=../config/truststore.jks
5. Code examples
Absolute bare-bones examples of how to run the EntityServer and connect to it.
5.1. RMI
Database database = H2DatabaseFactory
.createDatabase("jdbc:h2:mem:testdb",
"src/main/sql/create_schema.sql");
EntityServerConfiguration configuration =
EntityServerConfiguration.builder(SERVER_PORT, REGISTRY_PORT)
.domainClasses(List.of(Store.class.getName()))
.database(database)
.sslEnabled(false)
.build();
EntityServer server = EntityServer.startServer(configuration);
RemoteEntityConnectionProvider connectionProvider =
RemoteEntityConnectionProvider.builder()
.port(SERVER_PORT)
.registryPort(REGISTRY_PORT)
.domain(Store.DOMAIN)
.user(parse("scott:tiger"))
.build();
EntityConnection connection = connectionProvider.connection();
List<Entity> customers = connection.select(all(Customer.TYPE));
customers.forEach(System.out::println);
connection.close();
server.shutdown();
5.2. HTTP
Database database = H2DatabaseFactory
.createDatabase("jdbc:h2:mem:testdb",
"src/main/sql/create_schema.sql");
EntityService.PORT.set(HTTP_PORT);
EntityServerConfiguration configuration =
EntityServerConfiguration.builder(SERVER_PORT, REGISTRY_PORT)
.domainClasses(List.of(Store.class.getName()))
.database(database)
.sslEnabled(false)
.auxiliaryServerFactory(List.of(EntityServiceFactory.class.getName()))
.build();
EntityServer server = EntityServer.startServer(configuration);
HttpEntityConnectionProvider connectionProvider =
HttpEntityConnectionProvider.builder()
.port(HTTP_PORT)
.https(false)
.domain(Store.DOMAIN)
.user(parse("scott:tiger"))
.build();
EntityConnection connection = connectionProvider.connection();
List<Entity> customers = connection.select(all(Customer.TYPE));
customers.forEach(System.out::println);
connection.close();
server.shutdown();