Leveraging REST Messaging with Solace Schema Registry: Serializing and Deserializing Messages in Java

You can use REST messaging with the SERDES Collection to handle structured message payloads efficiently over HTTP. SERDES, which stands for Serialization and Deserialization, allows applications to convert complex data structures into a compact, transmittable format when publishing messages and to reconstruct them when consuming messages. This is particularly useful when integrating with Solace Schema Registry in REST-based messaging environments. The SERDES Collection refers to the set of Java serializer and deserializer libraries, such as Solace Avro SERDES for Java, Solace JSON Schema SERDES for Java, and Generic SERDES for Java. You can use these Java libraries to integrate REST applications with Solace Schema Registry for schema-based message serialization and deserialization.

Prerequisites

Before implementing SERDES with REST messaging, you should understand the language-agnostic concepts and configuration options covered in Serialization and Deserialization with Solace Schema Registry. Key topics include:

  • Connecting to Schema Registry (authentication and security)
  • Choosing schema resolution strategies (Destination ID, Topic ID, Topic ID with Profiles, Record ID)
  • Optimizing performance (caching and lookup options)
  • Advanced configuration (auto-registration, header formats, Avro and JSON Schema-specific settings)
  • Cross-protocol message translation

This page focuses on REST-specific implementation details, including Java imports, REST messaging considerations, and code examples for serializing and deserializing messages over HTTP.

Deploying with Maven

To use the SERDES Collection with your REST applications, you need to include the appropriate Maven dependencies in your project. The SERDES libraries are distributed as separate artifacts that you can include based on your serialization format requirements.

The following sections show you how to configure your Maven project to include the necessary dependencies for schema registry integration:

SERDES BOM Dependencies (Recommended)

The SERDES Bill of Materials (BOM) provides a centralized way to manage compatible versions of all Solace Schema Registry SERDES components. Using the BOM ensures that all SERDES dependencies are aligned with tested and compatible versions. This simplifies dependency management and reduces version conflicts in your applications.

The BOM includes version management for:

  • Avro SERDES components
  • JSON Schema SERDES components
  • Common SERDES libraries
  • Compatible versions of underlying dependencies

To use the SERDES BOM, add it to your Maven pom.xml file in the dependencyManagement section:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.solace</groupId>
            <artifactId>solace-schema-registry-serdes-bom</artifactId>
            <version>1.0.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

After importing the BOM, you can add SERDES dependencies without specifying versions:

<dependencies>
    <!-- Avro SERDES (version managed by BOM) -->
    <dependency>
        <groupId>com.solace</groupId>
        <artifactId>solace-schema-registry-avro-serde</artifactId>
    </dependency>
    
    <!-- JSON Schema SERDES (version managed by BOM) -->
    <dependency>
        <groupId>com.solace</groupId>
        <artifactId>solace-schema-registry-jsonschema-serde</artifactId>
    </dependency>
</dependencies>

Avro Serializer Dependencies

The Avro serializer provides a specific implementation for Apache Avro serialization and deserialization. This dependency includes the AvroSerializer, AvroDeserializer, and Avro-specific configuration properties, and also brings in the required common SERDES components used across all schema formats.

Add the following dependency to your Maven pom.xml file:

<dependencies>
    <dependency>
        <groupId>com.solace</groupId>
        <artifactId>solace-schema-registry-avro-serde</artifactId>
        <version>1.0.0</version>
    </dependency>
</dependencies>

For the latest version information and additional details about the Avro SERDES artifact, see the Maven Central Repository.

Important considerations when adding Maven dependencies:

  • Always use the latest compatible versions of the SERDES libraries for optimal performance and security.
  • The Avro SERDES dependency automatically includes the necessary Apache Avro libraries as transitive dependencies.

JSON Schema Serializer Dependencies

The JSON Schema serializer provides a specific implementation for JSON Schema serialization and deserialization. This dependency includes the JsonSchemaSerializer, JsonSchemaDeserializer, and JSON Schema-specific configuration properties, and also brings in the required common SERDES components used across all schema formats.

Add the following dependency to your Maven pom.xml file:

<dependencies>
    <dependency>
        <groupId>com.solace</groupId>
        <artifactId>solace-schema-registry-jsonschema-serde</artifactId>
        <version>1.0.0</version>
    </dependency>
</dependencies>

For the latest version information and additional details about the JSON Schema SERDES artifact, see the Maven Central Repository.

Important considerations when adding Maven dependencies:

  • Always use the latest compatible versions of the SERDES libraries for optimal performance and security.
  • The JSON Schema SERDES dependency automatically includes the necessary JSON Schema validation libraries as transitive dependencies.

Using SERDES with REST Messaging

REST messaging provides a lightweight, HTTP-based approach to message publishing and consumption that integrates with the SERDES Collection. When using SERDES with REST messaging, schema information is transmitted through HTTP headers rather than being embedded in the message payload, enabling efficient schema resolution and cross-protocol compatibility.

REST-Specific SERDES Considerations

When implementing SERDES with REST messaging, there are several important considerations:

  • Header Format—REST messaging requires string-based headers, making the SCHEMA_ID_STRING header format optimal for interoperability. For more information, see SERDES Header Configuration. This ensures schema identifiers are transmitted as human-readable strings in HTTP headers.
  • Content-Type and Message Type Handling—The HTTP Content-Type header determines how Solace interprets the message payload and the resulting message type for Solace JCSMP API consumers:
    • Use application/octet-stream for binary content that results in BytesMessage objects
    • Use application/json and other text-based Content-Types for content that results in TextMessage objects
    • As of version 10.28 of the Solace JCSMP API, SerdeMessage.deserialize() supports both BytesMessage and TextMessage objects, enabling seamless consumption regardless of Content-Type
    • Note that SerdeMessage.serialize() continues to work only with BytesMessage objects
  • User Properties—SERDES headers are transmitted as Solace user properties using the solace-user-property- prefix in HTTP headers.
  • REST Delivery Point Configuration—REST consumer applications require proper configuration of a REST Delivery Point (RDP) on the Solace broker to receive messages. The RDP must be configured with the correct POST request target that matches your consumer application's endpoint. For detailed instructions, refer to Configuring REST Delivery Points.

Cross-Protocol Message Flow with SERDES

SERDES supports cross-protocol messaging between REST and Solace JCSMP API applications. For detailed information about cross-protocol message translation rules and compatibility, see Cross-Protocol Message Translation.

The following examples illustrate the two primary cross-protocol flows:

Example 1: REST Publisher to Solace JCSMP API Consumer

  1. Publisher Side—The REST client serializes the message payload using the appropriate SERDES serializer, which adds schema identification headers to the HTTP request.
  2. Message Transmission—The serialized payload and SERDES headers are transmitted via HTTP POST to the event broker REST endpoint using your preferred Message Exchange Patterns, such as one-way POST for publishing.
  3. Consumer Side—The Solace JCSMP API application receives the message, extracts the SERDES headers, and uses them to deserialize the message payload back to its original structure using SerdeMessage.deserialize().

Example 2: Solace JCSMP API Publisher to REST Consumer

  1. Publisher Side—The Solace JCSMP API application serializes the message payload using the appropriate SERDES serializer, which adds schema identification headers to the message.
  2. Message Transmission—The serialized payload and SERDES headers are published to the event broker using Solace JCSMP API APIs.
  3. Consumer Side—The REST consumer application receives the HTTP request via a REST Delivery Point (RDP), extracts the SERDES headers from the HTTP headers, and uses them to deserialize the message payload back to its original structure.

REST Consumer Application Parameters

When implementing REST consumer applications that use SERDES, you need to configure several key parameters to ensure proper message reception and deserialization:

  • Post Request Target—The endpoint path that your consumer application listens on (for example, /my-rest-endpoint). This must match the "POST Request Target" configured in your broker's REST Delivery Point (RDP).
  • Port—The port number for your local HTTP server (for example, 8080). This determines where your consumer application will listen for incoming HTTP requests from the broker.
  • HTTP Topic Header Key—(Optional) The name of a custom HTTP header that contains the message's topic information. This is only needed if your RDP is configured to add topic information to a specific header (for example, X-Solace-Topic).

Example consumer application startup with these parameters:

// Consumer listening on port 8080, endpoint /my-rest-endpoint, with topic in X-Solace-Topic header
String postRequestTarget = "/my-rest-endpoint";
int port = 8080;
String topicHeaderKey = "X-Solace-Topic"; // Optional

// Start HTTP server with these parameters
HttpServer server = HttpServer.create(new InetSocketAddress(port), 0);
server.createContext(postRequestTarget, new MessageHandler());

Serializing and Deserializing Messages with Avro over REST

The following examples demonstrate how to implement Avro serialization and deserialization in REST messaging applications using Java HTTP clients and servers.

Publishing Avro Messages over REST

When publishing Avro messages over REST, you create an AvroSerializer, configure it with the appropriate SERDES properties, and use it to serialize message payloads before sending them via HTTP POST requests.

Key configuration for Avro REST publishing:

  • Configure SCHEMA_ID_STRING for optimal REST compatibility
  • Set appropriate encoding type (BINARY or JSON)
  • Format SERDES headers as Solace user properties

Example configuration for an Avro REST publisher:

// Configure the Avro serializer with schema registry connection details
Map<String, Object> config = new HashMap<>();
config.put(SchemaResolverProperties.REGISTRY_URL, REGISTRY_URL);
config.put(SchemaResolverProperties.AUTH_USERNAME, REGISTRY_USERNAME);
config.put(SchemaResolverProperties.AUTH_PASSWORD, REGISTRY_PASSWORD);
// Set encoding type - BINARY for compact payload, JSON for human-readable
config.put(AvroProperties.ENCODING_TYPE, AvroProperties.AvroEncoding.BINARY);
// Use SCHEMA_ID_STRING for REST compatibility (string-based headers)
config.put(SerdeProperties.SCHEMA_HEADER_IDENTIFIERS, SchemaHeaderId.SCHEMA_ID_STRING);

For information about configuring the config map with SERDES properties, see Getting Started with SERDES and Avro-Specific Configuration.

Example serialization and HTTP request:

// Create a User object to serialize
User user = new User("John Doe", "123", "support@solace.com");
Map<String, Object> headers = new HashMap<>();

// Serialize the object - this populates the headers map with schema information
byte[] payloadBytes = serializer.serialize(topic, user, headers);

// Add SERDES headers to HTTP request with solace-user-property- prefix
// This allows the broker to extract schema information for deserialization
for (String key : headers.keySet()) {
    Object value = headers.get(key);
    // Convert all header values to strings for HTTP transmission
    httpBuilder.header(String.format("solace-user-property-%s", key), value.toString());
}

// Set Content-Type for binary Avro payload
httpBuilder.header("Content-Type", "application/octet-stream");

HttpRequest request = httpBuilder.POST(HttpRequest.BodyPublishers.ofByteArray(payloadBytes)).build();

For a complete example, see AvroRestPublisherHttpClient.java on the Solace Samples Repository.

For a complete list of Avro SERDES properties and methods, see the Java Avro SERDES API Reference.

Consuming Avro Messages over REST

When consuming Avro messages over REST, you create an HTTP server that receives POST requests, extracts SERDES headers from the HTTP headers, and uses an AvroDeserializer to reconstruct the original message payload.

Key aspects of Avro REST consumption:

  • Extract SERDES headers from HTTP headers with solace-user-property- prefix.
  • Handle type conversion for schema IDs (string vs. long).
  • Use appropriate HTTP status codes for different error conditions.

Example SERDES header extraction:

/**
 * Extracts SERDES headers from HTTP headers for message deserialization.
 * Handles both string and typed values (for example, "123 ; type=int64").
 */
private static Map<String,Object> getSerdesHeaders(Headers httpHeaders) {
    final Map<String,Object> serdesHeaders = new HashMap<>();
    // Regex pattern to extract value and optional type: "<value> [; type=<type>]"
    final Pattern typePattern = Pattern.compile("(.*?)(?:\\s*;\\s*type=(\\S+))?$");
    
    httpHeaders.forEach((key, values) -> {
        // Only process Solace user property headers
        if (key.startsWith("solace-user-property-")) {
            String httpValue = values.get(0);
            Matcher matcher = typePattern.matcher(httpValue);
            
            if (matcher.find()) {
                String valueAsString = matcher.group(1).trim();
                String type = matcher.group(2); // Optional type suffix
                Object value = null;
                
                if (type != null && "int64".equals(type.trim())) {
                    // Convert to Long for SCHEMA_ID compatibility
                    value = Long.parseLong(valueAsString);
                } else {
                    // Default to string for SCHEMA_ID_STRING and other properties
                    value = valueAsString;
                }
                
                // Remove the solace-user-property- prefix to get original key
                serdesHeaders.put(key.substring("solace-user-property-".length()), value);
            }
        }
    });
    
    return serdesHeaders;
}

Example deserialization handling both message types:

// Extract SERDES headers from HTTP request
Map<String, Object> serdesHeaders = getSerdesHeaders(httpHeaders);
byte[] messagePayload = exchange.getRequestBody().readAllBytes();

// Get topic from optional header if configured
Optional<String> optionalTopic = Optional.of(httpTopicHeaderKey)
        .filter(key -> !key.isEmpty())
        .map(httpHeaders::getFirst);

// For REST consumers (HTTP-based) - direct deserialization
GenericRecord record = deserializer.deserialize(optionalTopic.orElse(""), messagePayload, serdesHeaders);

// Log the received message for debugging
System.out.printf("Received SERDES message: %s%n", record.toString());

For a complete example, see AvroRestConsumer.java on the Solace Samples Repository.

For a complete list of Avro SERDES properties and methods, see the Java Avro SERDES API Reference.

Serializing and Deserializing Messages with JSON Schema over REST

JSON Schema serialization and deserialization over REST follows similar patterns to Avro, but uses JsonSchemaSerializer and JsonSchemaDeserializer classes with JSON-specific configuration options.

Publishing JSON Schema Messages over REST

When publishing JSON Schema messages over REST, you create a JsonSchemaSerializer, configure it with appropriate validation settings, and serialize JSON data structures before transmission.

Example configuration for a JSON Schema REST publisher:

// Configure the JSON Schema serializer with validation enabled
Map<String, Object> config = new HashMap<>();
config.put(SchemaResolverProperties.REGISTRY_URL, REGISTRY_URL);
config.put(SchemaResolverProperties.AUTH_USERNAME, REGISTRY_USERNAME);
config.put(SchemaResolverProperties.AUTH_PASSWORD, REGISTRY_PASSWORD);
// Enable schema validation during serialization (recommended for data quality)
config.put(JsonSchemaProperties.VALIDATE_SCHEMA, true);
// Use string-based schema IDs for REST compatibility
config.put(SerdeProperties.SCHEMA_HEADER_IDENTIFIERS, SchemaHeaderId.SCHEMA_ID_STRING);

For information about configuring the config map with SERDES properties, see Getting Started with SERDES and JSON Schema-Specific Configuration.

Example JSON object creation and serialization:

// Create a JSON object using Jackson ObjectMapper
ObjectNode user = new ObjectMapper().createObjectNode();
user.put("name", "John Doe");
user.put("id", "123");
user.put("email", "support@solace.com");

// Serialize the JSON object - headers will be populated with schema information
Map<String, Object> headers = new HashMap<>();
byte[] payloadBytes = serializer.serialize(topic, user, headers);

Example HTTP request creation and sending:

// Construct the REST endpoint URL for topic publishing
String url = String.format("http://%s:%d/TOPIC/%s", brokerHost, port, topic);
HttpRequest.Builder httpBuilder = HttpRequest.newBuilder().uri(URI.create(url));

// Add SERDES headers as Solace user properties
for (String key : headers.keySet()) {
    Object value = headers.get(key);
    // All SERDES headers are transmitted as string values in HTTP
    httpBuilder.header(String.format("solace-user-property-%s", key), value.toString());
}

// Set Content-Type based on desired message type
// application/json → TextMessage, application/octet-stream → BytesMessage
if ("JSON".equals(CONTENT_TYPE)) {
    httpBuilder.header("Content-Type", "application/json");
} else {
    httpBuilder.header("Content-Type", "application/octet-stream");
}

HttpRequest request = httpBuilder.POST(HttpRequest.BodyPublishers.ofByteArray(payloadBytes)).build();

// Send the request and handle response
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println("Published message with status: " + response.statusCode());
if (response.statusCode() != 200) {
    System.err.println("Error response: " + response.body());
}

For a complete example, see JsonSchemaRestPublisherHttpClient.java on the Solace Samples Repository.

For a complete list of JSON Schema SERDES properties and methods, see the Java JSON SERDES API Reference.

Consuming JSON Schema Messages over REST

JSON Schema REST consumption includes additional error handling for schema validation failures, which can return specific HTTP status codes to indicate validation errors.

Example configuration for a JSON Schema REST consumer:

// Configure the JSON Schema deserializer for message consumption
Map<String, Object> config = new HashMap<>();
config.put(SchemaResolverProperties.REGISTRY_URL, REGISTRY_URL);
config.put(SchemaResolverProperties.AUTH_USERNAME, REGISTRY_USERNAME);
config.put(SchemaResolverProperties.AUTH_PASSWORD, REGISTRY_PASSWORD);
// Enable validation to ensure incoming messages conform to schema
config.put(JsonSchemaProperties.VALIDATE_SCHEMA, true);

Example error handling with appropriate HTTP status codes and message type support:

try {
    // Extract SERDES headers and message payload
    Map<String, Object> serdesHeaders = getSerdesHeaders(httpHeaders);
    byte[] messagePayload = exchange.getRequestBody().readAllBytes();
    
    // Get topic from optional header if configured
    Optional<String> optionalTopic = Optional.of(httpTopicHeaderKey)
            .filter(key -> !key.isEmpty())
            .map(httpHeaders::getFirst);
    
    // For REST consumers (HTTP-based) - direct deserialization
    User object = deserializer.deserialize(optionalTopic.orElse(""), messagePayload, serdesHeaders);
    System.out.println("Deserialized REST message: " + object);
    
    // Send success response
    String response = "Message received successfully";
    exchange.sendResponseHeaders(200, response.length());
    try (OutputStream os = exchange.getResponseBody()) {
        os.write(response.getBytes());
    }
} catch (JsonSchemaValidationException ve) {
    // Schema validation failed - return 422 Unprocessable Entity
    System.err.println("Schema validation error: " + ve.getMessage());
    exchange.sendResponseHeaders(422, -1);
} catch (SerializationException se) {
    // General serialization/deserialization error - return 400 Bad Request
    System.err.println("Serialization error: " + se.getMessage());
    exchange.sendResponseHeaders(400, -1);
} catch (Exception e) {
    // Other unexpected errors - return 500 Internal Server Error
    System.err.println("Unexpected error: " + e.getMessage());
    e.printStackTrace();
    exchange.sendResponseHeaders(500, -1);
}

For a complete example, see JsonSchemaRestConsumer.java on the Solace Samples Repository.

For a complete list of JSON Schema SERDES properties and methods, see the Java JSON SERDES API Reference.

Troubleshooting Cross-Protocol SERDES

When working with mixed REST and Solace JCSMP API environments using SERDES, you may encounter specific issues related to message type handling and cross-protocol compatibility. This section provides guidance for common troubleshooting scenarios.

Common Issues and Solutions

  • SCHEMA_ID not found in message headers—Verify that the REST publisher is correctly configured with SERDES headers using the SCHEMA_ID_STRING format for optimal REST compatibility. Check that SERDES headers are being transmitted as solace-user-property- prefixed HTTP headers.
  • Content-Type mismatch issues—Ensure that REST publishers use appropriate Content-Type headers:
    • Use application/json for JSON Schema messages that should be delivered as a TextMessage.
    • Use application/octet-stream for Avro binary messages that should be delivered as a BytesMessage.
    • Verify that Solace JCSMP API consumers can handle the expected message type.
  • SERDES header format compatibility—For cross-protocol scenarios, configure serializers to use SchemaHeaderId.SCHEMA_ID_STRING instead of the default SchemaHeaderId.SCHEMA_ID to ensure string-based headers work correctly across REST and Solace JCSMP API protocols.