diff --git a/.gitignore b/.gitignore
index 48e99efe..4933baab 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,6 +24,12 @@ ingestors/java/spring/.idea/
ingestors/java/spring/gradle.properties
ingestors/java/spring/target/
+ingestors/java/spring-jakarta/.gradle/
+ingestors/java/spring-jakarta/.idea/
+ingestors/java/spring-jakarta/gradle.properties
+ingestors/java/spring-jakarta/target/
+ingestors/java/spring-jakarta/out/
+
.meta/*
ingestors/govxlan/govxlan
testing/tmp_tests
\ No newline at end of file
diff --git a/ingestors/java/spring-jakarta/pom.xml b/ingestors/java/spring-jakarta/pom.xml
new file mode 100644
index 00000000..646fb876
--- /dev/null
+++ b/ingestors/java/spring-jakarta/pom.xml
@@ -0,0 +1,165 @@
+
+
+ 4.0.0
+
+
+
+ org.sonatype.plugins
+ nexus-staging-maven-plugin
+ 1.6.13
+ true
+
+ ossrh
+ https://s01.oss.sonatype.org/
+ true
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+ 2.2.1
+
+
+ attach-sources
+
+ jar-no-fork
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ 3.4.1
+
+
+ attach-javadocs
+
+ jar
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-gpg-plugin
+ 1.5
+
+
+ sign-artifacts
+ verify
+
+ sign
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.4.1
+
+
+ package
+
+ shade
+
+
+
+
+
+
+
+ true
+
+
+
+
+
+
+
+ jar
+
+ spring
+ https://www.github.com/metlo-labs/metlo
+ Java Agent for Metlo
+
+
+ MIT License
+ http://www.opensource.org/licenses/mit-license.php
+
+
+
+
+ Ninad Sinha
+ ninad@metlo.com
+ com.metlo
+ https://www.metlo.com
+
+
+
+ scm:git:git://github.com/metlo-labs/metlo.git
+ scm:git:ssh://github.com:metlo-labs/metlo.git
+ http://github.com/metlo-labs/metlo
+
+
+
+ 17
+ 17
+ UTF-8
+
+
+
+
+ ossrh
+ https://s01.oss.sonatype.org/content/repositories/snapshots/
+
+
+
+ ossrh
+ https://s01.oss.sonatype.org/content/repositories/releases/
+
+
+
+ com.metlo
+ spring-jakarta
+ 0.0.1
+
+
+
+ com.google.code.gson
+ gson
+ 2.9.0
+ compile
+ false
+
+
+ org.springframework
+ spring-context
+ 6.0.4
+ provided
+
+
+ org.springframework
+ spring-web
+ 6.0.4
+ provided
+
+
+ jakarta.servlet
+ jakarta.servlet-api
+ 5.0.0
+ provided
+
+
+ jakarta.platform
+ jakarta.jakartaee-api
+ 9.0.0
+ provided
+
+
+
+
\ No newline at end of file
diff --git a/ingestors/java/spring-jakarta/src/main/java/com/metlo/spring/Metlo.java b/ingestors/java/spring-jakarta/src/main/java/com/metlo/spring/Metlo.java
new file mode 100644
index 00000000..ac02b06a
--- /dev/null
+++ b/ingestors/java/spring-jakarta/src/main/java/com/metlo/spring/Metlo.java
@@ -0,0 +1,185 @@
+package com.metlo.spring;
+
+import com.metlo.spring.utils.ContentCachingResponseWrapperWithHeaderNames;
+import com.metlo.spring.utils.RateLimitingRequests;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.web.filter.OncePerRequestFilter;
+import org.springframework.web.filter.OncePerRequestFilter;
+import org.springframework.web.util.ContentCachingRequestWrapper;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.*;
+import java.util.function.Function;
+
+
+public class Metlo extends OncePerRequestFilter {
+ private static final int DEFAULT_THREAD_POOL_SIZE = 2;
+
+ private static final int DEFAULT_RPS = 10;
+ private final static String endpoint = "api/v1/log-request/single";
+
+ private final Boolean enabled;
+
+ private final RateLimitingRequests req;
+
+ public Metlo(String host, String api_key) {
+ this(DEFAULT_THREAD_POOL_SIZE, host, api_key, DEFAULT_RPS);
+ }
+
+ public Metlo(String host, String api_key, Integer rps) {
+ this(DEFAULT_THREAD_POOL_SIZE, host, api_key, rps);
+ }
+
+ public Metlo(int pool_size, String host, String api_key, Integer rps) {
+
+ String METLO_ADDR = host;
+ if (host.charAt(host.length() - 1) == '/') {
+ METLO_ADDR += endpoint;
+ } else {
+ METLO_ADDR += "/" + endpoint;
+ }
+ this.req = new RateLimitingRequests(rps, pool_size, METLO_ADDR, api_key);
+
+ String enabled = System.getenv("METLO_ENABLED");
+ if (enabled != null) {
+ this.enabled = Boolean.parseBoolean(enabled);
+ } else {
+ this.enabled = true;
+ }
+ }
+
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
+ throws ServletException, IOException {
+
+ ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
+ ContentCachingResponseWrapperWithHeaderNames responseWrapper = new ContentCachingResponseWrapperWithHeaderNames(response);
+
+ filterChain.doFilter(requestWrapper, responseWrapper);
+ if (this.enabled) {
+ String requestBody = getStringValue(requestWrapper.getContentAsByteArray(),
+ request.getCharacterEncoding());
+ String responseBody = getStringValue(responseWrapper.getContentAsByteArray(),
+ response.getCharacterEncoding());
+
+ this.req.send(createDataBinding(requestWrapper, responseWrapper, requestBody, responseBody));
+ // copyBodyToResponse requires min Spring 4.2.0
+ responseWrapper.copyBodyToResponse();
+ }
+ }
+
+ private String getStringValue(byte[] contentAsByteArray, String characterEncoding) {
+ try {
+ return new String(contentAsByteArray, 0, contentAsByteArray.length, characterEncoding);
+ } catch (UnsupportedEncodingException e) {
+ e.printStackTrace();
+ }
+ return "";
+ }
+
+ private Map createDataBinding(HttpServletRequest request, ContentCachingResponseWrapperWithHeaderNames response, String request_body, String response_body) {
+ Map DATA = new HashMap();
+ Map REQUEST = new HashMap<>();
+ Map REQUEST_URL = new HashMap<>();
+ Map RESPONSE = new HashMap<>();
+ Map META = new HashMap<>();
+
+ String host = request.getHeader("host");
+ if (host == null) {
+ host = request.getRemoteHost();
+ }
+ REQUEST_URL.put("host", host);
+ REQUEST_URL.put("path", request.getRequestURI());
+ REQUEST_URL.put("parameters", listParamFormat(request.getParameterMap()));
+
+ REQUEST.put("url", REQUEST_URL);
+ try {
+ REQUEST.put("headers", listRequestHeaderFormat(request.getHeaderNames(), request::getHeader));
+ } catch (Exception e) {
+ REQUEST.put("headers", new ArrayList