A simple example of using Spring Boot with an external source of data converting it to RDF and storing it locally in a triple store (Apache Jena). The data can be exposed in various formats by this service:
- RDF; Turtle, RDF/XML and JSON-LD
- Regular formats like JSON and XML, without semantics
- Accepting GraphQL queries and corresponding responses
- A SPARQL endpoint can be activated and accessed using a SPARQL-editor or the like
- Maybe more on its way ...
This project has several purposes:
- serve as an example of how to retrieve data from an external service and store that data in a triple store after applying semantics
- base for experimentation using the latest and greatest of Java (currently 25) and other libraries used in this project
- act as a repository of "nice-to-have" stuff on how to do things Spring Boot, RDF, etc., i.e. implementation, testing, format conversion, asynch, practicies, etc.
- additional experimentation could also include performing inference using RDF and not AI (!), probably more interesting once we fetch data from more than one disparate source.
Everything has a history, even this little project. It started out as a simple demo with a slightly silly and whimsical touch (to get people's attention?). It lay dormant for some years until quite recently when it is now housed in this repository. However, in doing so it started slowly move away from some silliness and hopefully it will mature over time as it gets new features and the deployment model solidifies (Kubernetes).
The example/demo service is really simple; it will retrieve an entry from the Bored API and store it locally as RDF magic. I.e providing a key to an entry will fetch it from the external, Bored API, and apply semantics to it and then store it in an in-memory triple store. Keys are in the range [1000000, 9999999]. If same entry is requested subsequently the locally stored entry will be used (a Bloom filter is queried first to see if there might be a local entry of it stored, otherwise an external fetch is performed).
If no key is provided the service will list all locally stored entries. It will not make any attempt to retrieve any external data - for now.
Use PostMan or your client of choice to perform request invocations to the service. By altering the Accept-header you may get different formats of the response of interest.
If you prefer using curl:
curl --location 'http://localhost:8080/v1/magic?key=3943506' \
--header 'Accept: text/turtle'
By providing different values for the Accept-header when making a GET request will render corresponding format for the data retrieved. Currently supported formats:
application/json, plain ol' JSONtext/xml, plain even older XML
And, the supported RDF formats:
application/json+ld, the JSON-LD format, JavaScript Object Notation for Linked Datatext/turtle, the Terse RDF Triple Language (Turtle) format, more compact and readable than JSON-LDapplication/rdf+xml, RDF/XML to express (i.e. serialize) an RDF graph as an XML document. Not so compact and not that readable ...
Compare that with the other results when invoking GET requests with various Accept-hreaders.
As the triple store used in this experiment/demo service is an in-memory store, all its data will be lost when the service is closed. There are some ideas creating a persistent store that retains the data between sessions. Also, there are some other ideas of creating a mechanism for populating the store with a specified number of random entries when invoked.
Some example keys that can be used for interesting results:
- 4290333 - "Go on a long drive with no music"
- 6204657 - "Surprise your significant other with something considerate"
- 3943506 - "Learn Express.js"
A GraphQL end point has been added recently. The GraphiQL capability is activated in the application.properties file, thus executing queries can be done by pointing your web browser of choice to:
http://localhost:8080/graphiql
By "populating" the triple store, e.g. by providing a key "6204657" using Postman or curl, a query like below:
query magicDetails {
magicByKey(key: "6204657") {
id
magicString
type
}
.. will render an intersting result like:
q{
"data": {
"magicByKey": {
"id": "6204657",
"magicString": "Surprise your significant other with something considerate",
"type": "social"
}
}
}
Please keep in mind that the GraphQL capabilities was recently added and is a work in progress (as the entire app for that matter), as more, and more complex, things will come soon.
By activating the SPARQL capability the locally stored RDF data can be extracted and manipulated using a SPARQL endpoint. This capability comes "out-of-box" by employing Apache Fuseki.
The application.properties file holds a setting, rdf.fuseki.enabled, controlling this feature. By default it is set to false.
By setting it to true and restarting the service the Fuseki provided endpoint will be available on port 3001. You can use Postman (post the request with the SPARQL-query) or any SPARQL-editor of your choice.
There is a logging aspect taking care of logging in the services and infrastructure packages. These log statements are set to the be active for the log level INFO. Using an aspect for logging is maybe not that obvious, it can be sometimes hard to see what gets logged and when. The idea behind it, though, is that we do not want to litter the code with logging statements and keep it somewhat clean, and this is a kind of demo anyway. The project is not that large ... yet.
Also, when it comes to logging: all incoming request are furnished with Mapped Diagnostic Context (MDC) so the call chain of method invocations through the application can easily be followed if there are multiple simultaneous incoming requests. Check the logback-spring.xml file, the value appears as traceId. The class RequestLoggingFilter is invoked for each incoming request and produces a trace id for this purpose.
The controller already has logging by default in Spring Boot itself. However, in order to see it you need to set the log level to DEBUG. However, doing so will render quite a lot of printouts on the console. If you want to try logging on other levels, like INFO; you could alter the RequestLoggingFilter class; add whatever logging statments necessary.
After checking out the code from this repository, building it should be straightforward. In a Windows environment:
gradlew build
... Linux and Mac:
./gradle build
Building the code will also execute the tests in the project.
It is recommended to use a tool like Postman (https://www.postman.com/downloads/) to make invocations to the service and try different 'Accept' headers to see the results. A frontend based on React is in the making.
Running the service in an Windows environment:
gradlew bootRun
... and for Linux and Mac:
./gradle bootRun
It is also possible to load and execute this code in your IDE of choice. It has been tested on IntelliJ IDEA 2025.1.1.1 (Ultimate Edition) and Eclipse SDK (2025-03 4.35.0).
Containerizing the application is done using the following steps. Once a Docker image is available it can be deployed as single service in e.g Docker Desktop, or in a Kubernetes cluster.
Building a Docker image is pretty straight forward:
gradlew bootBuildImage --imageName=autocorrect/spring-boot-magic
Once the container is built, it is time for deployment:
docker run -p 8080:8080 -t autocorrect/spring-boot-magic:latest
The Tomcat web container bundled with Spring Boot exposes the service on port 8080 by default.
Use PostMan or your client of choice to check that the service is responding. If you prefer using curl:
curl --location 'http://localhost:8080/v1/magic?key=3943506' \
--header 'Accept: text/turtle'
Sometimes, like using the 'cmder' command line tool on Windows might render an error indicating the port number setting not bing a number, despite being an integer.
curl: (3) URL rejected: Port number was not a decimal number between 0 and 65535
Try replacing the single (') quotes with double quotes (") and it should work.
Once the the Docker image is verified it can also be deployed to Kubernetes cluster. In this example we will make a simple deploy to a locally running Minikube installation.
More on minikube
For our convenience a deployment YAML-file for Kubernetes is already created in this repository. Further, the latest version of the Docker image is available on Docker Hub. The deployment.yaml points to this image on Docker Hub. However, if you want to create your own you can either:
- push your image to a local Docker repository (can be a bit cumbersome as you also need a proxy running for our container), or ..
- ... push it to your own repository on Docker Hub
If you want to check the details on how to create and deploy a Kubernetes deployment for Spring Boot you can read more: Guide: Spring Boot Kubernetes
Start your Minikube cluster:
minikube start
Running the Minikube Dashboard will render a UI in your default browser. It is not necessary as all commands can be executed on the command line, but maybe visuaully more pleasing.
minikube dashboard
Once you have a Docker image available, locally or on Docker Hub, it can be deployed:
kubectl apply -f deployment.yaml
Check that your pod has started and is up and running (or check in the dashboard).
kubectl get pods
Executing the following command will tell us whether our service is available:
kubectl get all
The console printout will look like something like this:
NAME READY STATUS RESTARTS AGE
pod/demo-6476c974bd-rxkjx 1/1 Running 0 68m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/demo ClusterIP 10.109.8.245 <none> 8080/TCP 2d23h
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 2d23h
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/demo 1/1 1 1 114m
NAME DESIRED CURRENT READY AGE
replicaset.apps/demo-6476c974bd 1 1 1 101m
replicaset.apps/demo-bf9496f7f 0 0 0 114m
The service is running, but there is no port forwarding so the following is needed (recommendation: execute the following command in a separate console window or tab as it locks the console):
kubectl port-forward svc/demo 8080:8080
... accessing the service using e.g. PostMan or the 'curl' command as above will render something like this on the console with port forwarding:
Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080
Handling connection for 8080
Handling connection for 8080
Handling connection for 8080
This is an experimental project, however, improvements will be made, including use of the latest features in the Java platform (currently Java 24).
Items on the current TODO-list:
- create a mechanism for populating the triple store with a specified number of random entries
- introduce version 2 (v2) of the service with greater capabilities and asynchronous behavior
- apply query time inferencing (RDF, not using AI as that is a separate repo) once we have sufficient amounts of inersting data, preferrably from disparate sources
- add a client/UI for exploring data visually; currently a React based front-end is being worked on. It is harbored in a repository of its own.
- separate out the triple store to a separate service
- maybe add OAuth2 authentication to make the whole thing more production like? One could, e.g., login using one's account on GitHub.
- if parts of this service is split up, it could be deployed as several pods in a Kubernetes cluster (executed locally in e.g. MiniKube)
- add a mechanism that reads any stored entries from previous sessions into the Bloom filter when using TDB (file persistence)
- see if we can make use of some interesting new features in Java:
- switch with pattern matching (to a large extent already done)
- primitive types in patterns (instanceof and switch, on its way ...)
- maybe use a SequencedCollection where applicable
- ... more ...
- combine data from more sources? "Convert" Chuck Norris jokes to magic as well?
- introduce Futures for handling incoming requests (to experiment codewise, this service is not really required to be that performant in any way)
- collect utilities common to other projects into their own repository (expose a util-library for reuse and avoid code duplication over several repos)
- String templates was introduced as a preview in 21, but as of 23 the feature seems to have been axed due to alleged design flaws. So, we will unfortunately not se any of those for now ...