A client connecting over HTTP (see HttpEntityConnectionProvider) uses one of two wire formats: JSON, the default, or Java serialization. The serialization format requires the exchanged classes on both ends and a deserialization filter; the JSON format exchanges typed JSON documents — entities, conditions, error envelopes — and is the format for clients outside the trusted network, such as mobile clients. This chapter covers what a JSON client must provide: its domain, and JSON type registrations for the database operations it calls.

The format is selected via the connection provider builder or the codion.client.http.json configuration value (default true):

HttpEntityConnectionProvider.builder()
        .json(true)
        ...

1. Supplying the domain

A client provides its domain model to the connection builder via HttpEntityConnectionProvider.Builder.domain(Domain). Without it, the client fetches the entity definitions from the server on connect — that reply is the one JSON-mode response carrying a Java-serialized object. A client with an injected domain skips the round trip and, if it avoids Java-serialized report results (see below), performs no Java deserialization at all.

The choice carries a trade-off in the other direction as well. The domain implementation contains the database-level details — physical table and column names (the domain API constants may well be aliases), column expressions, custom select and subquery SQL — and injecting it packages all of that inside the client, where it can be read out of the distributed application. The entity definitions served by the entities route deliberately omit these: the database-level fields are transient, so a client that fetches its definitions sees only the domain API surface.

  • A client you distribute within your own walls — the typical desktop deployment — injects the domain; the implementation details were never secret from those users.

  • For a client distributed beyond them, injecting means treating your schema and query SQL as public. If that is unacceptable, have the client fetch its definitions and accept the single Java-deserialized response, from the server it authenticated to, over TLS.

2. Registering operation types

Entities, keys and conditions serialize out of the box. The parameter and return values of database procedures, functions and reports are domain-specific types, so the domain registers how they travel, in an EntityObjectMapperFactory:

public final class ChinookObjectMapperFactory extends AbstractEntityObjectMapperFactory {

  public ChinookObjectMapperFactory() {
    super(Chinook.DOMAIN);
  }

  @Override
  public EntityObjectMapper entityObjectMapper(Entities entities) {
    EntityObjectMapper objectMapper = super.entityObjectMapper(entities);
    objectMapper.parameter(Invoice.UPDATE_TOTALS).set(new TypeReference<>() {});
    objectMapper.parameter(Track.RAISE_PRICE).set(RaisePriceParameters.class);
    objectMapper.parameter(Playlist.RANDOM_PLAYLIST).set(RandomPlaylistParameters.class);
    objectMapper.parameter(Customer.REPORT).set(new TypeReference<>() {});
    objectMapper.returnType(Customer.REPORT).set(new TypeReference<>() {});
    objectMapper.returnType(Track.RAISE_PRICE).set(new TypeReference<>() {});
    objectMapper.returnType(Playlist.RANDOM_PLAYLIST).set(Entity.class);

    return objectMapper;
  }
}

Registration is mandatory for the operations a JSON client calls — a function with an unregistered return type fails at the first call, with a message naming the function and the registration site. Both client and server resolve these registrations from the same factory, so there is one source of truth and no type name ever crosses the wire.

The factory is discovered via the ServiceLoader, so it must be registered as a service provider — in module-info.java for the module path, and in META-INF/services for clients running on the classpath, Android included, where JPMS provides clauses are inert.

Tip
Keeping the mapper factory in its own module, depending only on the domain API, lets clients that need it (HTTP/JSON) include it while others (local, RMI) skip it — and keeps Jackson off the classpaths that don’t need it.

3. Errors

Over JSON, an error response is a typed envelope, never a serialized exception. The client reconstructs the exception from a closed set of error kinds — nothing on the wire names a class — so the exceptions application code catches are the same as over any other connection type: ReferentialIntegrityException, EntityModifiedException with the conflicting entity, UniqueConstraintException, ReportException and so on, with their messages intact. An error the server does not recognize yields a generic message and a correlation id identifying the server log entry.

4. Reports

A report result travels as JSON like a function result, using a registered return type — a report exported to PDF on the server simply arrives as byte[]. A client displaying reports with the JasperReports viewer can keep working with JasperPrint over a JSON connection via JRExport.SERIALIZED and JasperReports.loadPrint() — see Reporting with JasperReports.

5. Deployment

Exposing the HTTP service outside a trusted network is a deployment decision with security consequences — authentication, authorization, rate limiting, TLS. See Internet deployment before opening the port.