End-to-end image classification system using:
- TensorFlow Serving (gRPC) for model serving
- Apache Camel 4.x for orchestration
- ResNet50 pretrained model (ImageNet)
- Java 21 and Gradle multi-module project
┌─────────────────┐
│ input-images/ │ ──┐
│ (*.jpg, *.png)│ │
└─────────────────┘ │
│ (1) File component reads images
│
▼
┌────────────────────┐
│ Camel Client │
│ (Java 21) │
│ │
│ - Read image bytes│
│ - Build Request │ (2) Build PredictRequest (protobuf)
│ - Parse Response │ containing raw JPEG bytes
└────────┬───────────┘
│
│ (3) gRPC call (localhost:8500)
▼
┌────────────────────┐
│ TensorFlow Serving │
│ (Docker) │
│ │
│ - Decode JPEG │ (4) Model processes:
│ - Resize 224x224 │ - Decoding
│ - Preprocess │ - Resizing
│ - ResNet50 │ - Preprocessing
│ - Inference │ - Inference
└────────┬───────────┘
│
│ (5) PredictResponse (1000 class probs)
▼
┌────────────────────┐
│ Camel Client │
│ │
│ - Parse response │ (6) Log results:
│ - Map to labels │ - Top-5 predictions
│ - Log results │ - Cat detection
│ - Move file │ - Move to .done/
└────────────────────┘
- TensorFlow Serving is the official TF model serving framework
- Production-ready with gRPC support
- Handles model versioning, batching, and optimization
- DJL is NOT used - all inference happens in TensorFlow Serving
- gRPC is more efficient for binary data (protobufs)
- Lower latency and bandwidth compared to JSON/REST
- Better type safety with protocol buffers
- Camel's
camel-tensorflow-servingcomponent uses gRPC
- SavedModel includes preprocessing (decode, resize, normalize)
- Java client sends raw JPEG bytes (no preprocessing in Java)
- Simpler client code, less error-prone
- Model is self-contained and portable
camel-tensorflow-image-classification/
├── tools/ # Python scripts
│ ├── export_resnet50_savedmodel.py # Export model with custom signature
│ └── download_imagenet_labels.py # Download ImageNet labels
├── models/
│ └── resnet50/1/ # SavedModel directory
│ ├── saved_model.pb # Model graph
│ ├── variables/ # Model weights
│ └── assets/ # (empty)
├── input-images/ # Image input directory
│ └── .done/ # Processed images
├── docker-compose.yml # TensorFlow Serving container
├── settings.gradle # Gradle multi-module config
├── build.gradle # Root build file
└── camel-client/ # Camel application
├── build.gradle
└── src/main/
├── java/com/example/camel/tensorflow/
│ ├── ImageClassificationApp.java # Main entry point
│ ├── ImageClassificationRoute.java # Camel route
│ ├── TensorFlowServingHelper.java # Protobuf utilities
│ └── ImageNetLabels.java # Label mapping
└── resources/
├── application.properties # Configuration
├── logback.xml # Logging config
└── imagenet_class_index.json # ImageNet labels
- Java 21
- Python 3.8+ with TensorFlow 2.x
- Docker and Docker Compose
- Gradle (or use
gradlew.bat)
# Install TensorFlow
pip install tensorflow
# Export ResNet50 model with custom serving signature
python tools/export_resnet50_savedmodel.py
# Download ImageNet labels
python tools/download_imagenet_labels.pyThis creates:
models/resnet50/1/- SavedModel with preprocessingcamel-client/src/main/resources/imagenet_class_index.json- Labels
# Start TensorFlow Serving container
docker-compose up -d
# Check logs to verify model is loaded
docker-compose logs -f tensorflow-serving
# Look for: "Successfully loaded servable version"TensorFlow Serving now listens on:
- gRPC:
localhost:8500(used by Camel) - REST:
localhost:8501(not used in this project)
# Build the project
gradlew.bat build
# Run the application
gradlew.bat :camel-client:run# Copy test images to input directory
copy path\to\cat.jpg input-images\
copy path\to\dog.jpg input-images\
# Watch the console for classification resultsThe exported model has the following signature:
signature_name: "serving_default"
inputs:
image_bytes: TensorSpec(shape=(), dtype=tf.string)
# Raw JPEG bytes (no preprocessing in client)
outputs:
predictions: TensorSpec(shape=(1, 1000), dtype=tf.float32)
# Class probabilities for 1000 ImageNet classes-
Client builds PredictRequest:
PredictRequest request = PredictRequest.newBuilder() .setModelSpec(ModelSpec.newBuilder() .setName("resnet50") .setSignatureName("serving_default")) .putInputs("image_bytes", imageTensorProto) .build();
-
TensorFlow Serving processes:
- Receives gRPC request
- Extracts
image_bytestensor - Runs SavedModel inference (decode, resize, ResNet50)
- Returns
predictionstensor
-
Client parses PredictResponse:
TensorProto predictionsTensor = response.getOutputsOrThrow("predictions"); List<Float> probabilities = predictionsTensor.getFloatValList();
The application logs whether an image is a cat:
boolean isCat = topPredictions.stream()
.anyMatch(p -> labels.getLabel(p.classIndex()).toLowerCase().contains("cat"));ImageNet has multiple cat-related classes:
- tabby cat
- tiger cat
- Persian cat
- Siamese cat
- Egyptian cat
- etc.
Edit camel-client/src/main/resources/application.properties:
# TensorFlow Serving endpoint
tensorflow.serving.host=localhost
tensorflow.serving.port=8500
tensorflow.serving.model.name=resnet50
# Input directory
camel.component.file.input.directory=input-images
# Number of top predictions to log
classification.top.predictions=5Error: Model not found: resnet50
Solution: Run python tools/export_resnet50_savedmodel.py first
Error: UNAVAILABLE: io exception
Solution: Ensure TensorFlow Serving is running: docker-compose up -d
Error: imagenet_class_index.json not found
Solution: Run python tools/download_imagenet_labels.py
Error: Unsupported class file major version
Solution: Ensure Java 21 is installed and JAVA_HOME is set
- First inference is slow (~2-3 seconds) - model loading
- Subsequent inferences are fast (~50-200ms)
- gRPC is faster than REST for binary data
- Batching is not implemented in this demo (processes one image at a time)
tensorflow>=2.13.0- Model export and SavedModel API
apache-camel:4.4.0- Routing and orchestrationcamel-tensorflow-serving- gRPC client for TensorFlow Servingtensorflow-core-platform:0.5.0- TensorFlow Java API (protobufs)jackson-databind:2.16.0- JSON parsing for labels
tensorflow/serving:latest- Official TensorFlow Serving image
This is a demonstration project for educational purposes.
Created as an example of TensorFlow Serving integration with Apache Camel 4.x.