Lightweight API Gateway for REST, GraphQL and legacy Web Services, easily extended with powerful plugins.
Based on the Java platform, Membrane integrates smoothly with major enterprise technologies. Load tests show that Java provides an excellent foundation for high performance and scalability. On a 2021 MacBook Pro, Membrane handles more than 20,000 requests per second, supports up to 10,000 concurrent clients, and can host over 100,000 APIs on a single instance.
The examples below demonstrate how to address a wide range of API Gateway requirements using simple configurations. Version 7.0.0 or newer is required.
Forwarding Requests from Port 2000 to a Backend:
api:
port: 2000
target:
url: https://api.predic8.dePath Rewriting with an URI Template:
api:
port: 2000
path:
uri: /fruit/{id}
target:
url: https://api.predic8.de/shop/v2/products/${pathParam.id}Deploy OpenAPI and enable Request Validation:
api:
port: 2000
specs:
- openapi:
location: "fruitshop-api.yml"
validateRequests: yesIssue JSON Web Tokens:
To issue a JWT for a user, create an API that acts as a simple token endpoint:
api:
port: 2000
path:
uri: /token
flow:
- basicAuthentication:
fileUserDataProvider:
htpasswdPath: .htpasswd
- request:
- template:
src: |
{
"sub": "${fn.user()}"
}
- jwtSign:
jwk:
location: jwk.json
- return:
status: 200Authenticated requests to '/token' return a signed JWT in which the username from Basic Authentication is used as the sub claim.
{
"typ": "JWT",
"alg": "RS256"
}
.
{
"sub": "alice",
"iat": 1765222877,
"exp": 1765223177
}
.hTL_0-AS8IZgiDUJ6Kg...
This example is intentionally minimal, but it highlights the basic building blocks: authenticate the caller, shape the token payload, and sign the result. From there, you can extend it with additional claims, custom logic, or stricter policies to implement tailored API security flows.
Learn how API Gateways work with real-world examples and insights into Membrane.
Download instantly — no registration required.
- Deploy APIs from OpenAPI specifications.
- Validate requests and responses against OpenAPI and JSON Schema.
- JSON Web Tokens, OAuth2, API Keys, NTLM, and Basic Authentication.
- Built-in OAuth2 Authorization Server.
- Rate limiting and traffic control
- Protection for GraphQL, JSON, and XML APIs against malicious inputs.
- Seamless support for SOAP message routing.
- Configure, validate, and rewrite WSDL-based services, including message validation.
- Admin Web Console for monitoring and management.
- Advanced load balancing to ensure high availability.
- Flexible message transformation for seamless data processing.
- Embeddable reverse proxy HTTP framework to build custom API gateways.
- Traffic shadowing
- Streams HTTP traffic for low-latency, non-blocking processing.
- Reuses TCP connections via HTTP Keep-Alive to reduce request overhead.
- Lightweight distribution (~55MB) compared to other Java-based gateways.
- Low memory footprint, ideal for containers and cloud-native environments.
- Java-based, yet competitive with C/C++ gateways in performance.
- Getting Started
- Basics Routing, rewriting
- OpenAPI Support
- Routing
- Scripting
- Message Transformation
- Conditionals with if
- Security
- Traffic Control Rate limiting, Load balancing
- Legacy Web Services SOAP and WSDL
- Operation
- Community and professional Support
You can run Membrane as Docker container, standalone Java application or install it on Linux as RPM.
-
Download and extract
- Download a release and unzip it.
-
Start the Gateway
- Open a terminal in the extracted folder.
- Make sure Java 21 or newer is installed:
java -version
- Start:
- Linux/Mac:
./membrane.sh - Windows:
membrane.cmd
- Linux/Mac:
-
Access the Gateway
-
Change the Configuration
Modify the preconfigured APIs or add APIs by editing the
proxies.xmlfile in theconffolder.
-
Start a Membrane container
docker run -p 2000:2000 predic8/membrane
-
Access the Gateway
Test an API by opening http://localhost:2000.
-
Change the Configuration
-
Download proxies.xml or:
wget https://raw.githubusercontent.com/membrane/api-gateway/master/distribution/router/conf/proxies.xml
-
Bind the configuration file to the container.
Mac/Linux:
docker run -v "$(pwd)/proxies.xml:/opt/membrane/conf/proxies.xml" -p 2000:2000 predic8/membraneWindows:
docker run -v %cd%\proxies.xml:/opt/membrane/conf/proxies.xml -p 2000:2000 predic8/membraneYou can now edit
proxies.xmland restart the container to apply the changes.
-
For detailed Docker setup instructions, see the Membrane Deployment Guide.
- Try the code snippets on this page.
- Run the samples in the examples folder of the distribution.
- Follow the REST API Tutorial to learn about deploying and securing APIs.
- Check out the SOAP API Tutorial for legacy web service integration.
- Read the API Gateway eBook
- Look at the documentation.
- Browse the reference
- Try the recipes from the cookbook
To define new APIs or modify the existing configuration, edit the proxies.xml file located in the conf folder. This file serves as the central configuration point for managing API behavior and routing rules.
Explore and copy the sample snippets below into the proxies.xml file and modify them to suit your needs. Then save or restart the gateway to apply the changes. Usually a save will trigger a reload automatically.
For even more samples have a look at the examples folder.
To forward requests from the API Gateway to a backend, use a simple api configuration. The example below routes requests received on port 2000 with a path starting with /shop to the backend at https://api.predic8.de:
api:
port: 2000
path:
uri: /shop
target:
url: https://api.predic8.deAfter modifying and saving the proxies.xml file, open http://localhost:2000/shop/v2/
9E88
Membrane natively supports OpenAPI, allowing you to easily configure the gateway with OpenAPI documents and automatically validate both requests and responses.
Membrane allows you to configure APIs directly from OpenAPI documents in the proxies.xml file. Backend addresses and other details are automatically derived from the OpenAPI description.
The snippet below shows how to deploy an API using an OpenAPI (openapi/fruitshop-v2-2-0.oas.yml) with request validation enabled:
api:
port: 2000
specs:
- openapi:
location: openapi/fruitshop-v2-2-0.oas.yml
validateRequests: trueOnce configured, a list of deployed APIs is available at: http://localhost:2000/api-docs
Click on an API title in the list to open the Swagger UI for interactive exploration and testing:
For additional details and a working example, check out the OpenAPI Example.
Membrane provides versatile routing with a fallthrough mechanism that applies only the first matching API rule, ensuring precise and efficient routing based on path, HTTP method, or hostname or many other criterias.
The configuration below demonstrates several routing rules:
# POST requests
api:
port: 2000
method: POST
flow:
- response:
- static:
src: POST is blocked!
- return:
statusCode: 405
---
# Regex path matching
api:
port: 2000
path:
uri: /shop/v2/products/.*
isRegExp: true
target:
url: https://api.predic8.de
---
# Requests whose HOST header is "www.predic8.de"
api:
port: 2000
host: www.predic8.de
flow:
- response:
- static:
src: "<html>Homepage</html>"
- return:
statusCode: 200
---
# Requests with a query parameter city and value Paris
api:
port: 2000
test: params.city == 'Paris'
flow:
- response:
- static:
src: Oui!
- return:
statusCode: 200| Option | Description |
|---|---|
port |
port Membrane listens for incoming connections. |
method |
- HTTP method (e.g., GET, POST, DELETE).- * matches any method. |
host |
- Hostname e.g. api.predic8.de- Supports basic globbing with * |
test |
- Custom script e.g. $pathParam.id == '42', $header.contentType == '...' |
path |
- Request path - Regular expressions can be used with isRegExp="true" |
For more routing options, see the Membrane API documentation.
Membrane lets you create endpoints that return immediately without forwarding requests to a backend.
The following configuration creates a health check endpoint that responds to requests at http://localhost:2000/health:
api:
port: 2000
path:
uri: /health
flow:
- response:
- static:
src: I'm good.
- return:
statusCode: 200Block paths (e.g., /nothing) while allowing other calls to pass through.
Routing Note: APIs are matched from top to bottom. When multiple APIs share the same port, place the APIs with stricter routing conditions higher in the configuration.
api:
port: 2000
path:
uri: /nothing
flow:
- response:
- static:
src: "Nothing to see here!"
- return:
statusCode: 404
---
api:
port: 2000
flow:
- static:
src: Other calls
- return:
statusCode: 200The URLs of request can be rewritten dynamically before forwarding them to the backend. This is useful for restructuring API paths or managing legacy endpoints.
The following configuration rewrites requests starting with /fruitshop to /shop/v2, preserving the remainder of the path:
api:
port: 2000
flow:
- rewriter:
- map:
from: ^/fruitshop/(.*)
to: /shop/v2/$1
target:
url: https://api.predic8.deA request to:
http://localhost:2000/fruitshop/products/4
will be rewritten to and forwarded to the backend at:
https://api.predic8.de/shop/v2/products/4
Membrane has powerful scripting features that allow to modify the desired of an API using Groovy or Javascript.
- Custom Responses: Tailor responses dynamically based on client requests or internal logic.
- Mocking APIs: Simulate API behavior during testing or development phases.
- Dynamic Headers: Add headers conditionally based on business rules.
- Debugging: Inspect incoming requests during development.
The following API executes a Groovy script during the request and the response.
api:
port: 2000
flow:
- groovy:
src: |
println "I'm executed in the ${flow} flow"
println "HTTP Headers:\n${header}"
target:
url: https://api.predic8.deAfter invoking http://localhost:2000 you can see the following output in the console where you have started Membrane:
I'm executed in the REQUEST flow
HTTP Headers:
Host: localhost:2000
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:133.0) Gecko/20100101 Firefox/133.0
...
I'm executed in the RESPONSE flow
HTTP Headers:
Content-Length: 390
Content-Type: application/json
You can realize a load balancer by setting the destination randomly.
api:
port: 2013
flow:
- groovy:
src: |
sites = ["https://api.predic8.de","https://membrane-api.io","https://predic8.de"]
Collections.shuffle sites
exchange.destinations = sites
target: {} # No details needed target uses destinations from exchangeThe groovy plugin in Membrane allows you to dynamically generate custom responses. The result of the last line of the Groovy script is passed to the plugin. If the result is a Response object, it will be returned to the caller.
The following example creates a custom JSON response with a status code of 200, a specific content type, and a custom header:
api:
port: 2000
flow:
- groovy:
src: |
Response.ok()
.contentType("application/json")
.header("X-Foo", "bar")
.body("""
{
"success": true
}""")
.build()- The
Response.ok()method initializes a new HTTP response with a status of200 OK. - The
contentType()method sets theContent-Typeheader, ensuring the response is identified as JSON. - The
header()method adds custom headers to the response. - The
body()method specifies the response payload. - The
build()method finalizes the response object, which is then returned by thegroovyplugin.
When accessing this API, the response will look like this:
HTTP/1.1 200 OK
Content-Type: application/json
X-Foo: bar
{
"success": true
}
For more information about using Groovy with Membrane, refer to:
In addition to Groovy, Membrane supports JavaScript for implementing custom behavior. This allows you to inspect, modify, or log details about requests and responses.
The following example logs all HTTP headers from incoming requests and responses to the console:
api:
port: 2000
flow:
- javascript:
src: |
console.log("------------ Headers: -------------");
var fields = header.getAllHeaderFields();
for (var i = 0; i < fields.length; i++) {
console.log(fields[i]);
}
target:
url: https://api.predic8.deThe CONTINUE keyword ensures that the request continues processing and is forwarded to the target URL.
When a JavaScript script returns a Response object as the last line of code, the request flow is interrupted, and the response is sent back to the client. This allows for creating custom responses dynamically.
The following example generates a JSON response and sends it directly to the client:
api:
port: 2000
flow:
- javascript:
src: |
var body = JSON.stringify({
foo: 7,
bar: 42
});
Response.ok(body).contentType("application/json").build();For more details about using JavaScript with Membrane, check the JavaScript Plugin documentation.
You can modify HTTP headers in requests or responses using Membrane's setHeader and headerFilter feature. This is particularly useful for enabling CORS or adding custom headers.
The following configuration adds CORS headers to the responses received from the backend:
api:
port: 2000
flow:
- response:
- setHeader:
name: Access-Control-Allow-Origin
value: "*"
- setHeader:
name: Access-Control-Allow-Methods
value: GET
target:
url: https://api.predic8.deMembrane allows dynamic extraction of values from the JSON body of a request or response and uses them to set HTTP headers.
The following example extracts the id and name fields from a JSON body and sets them as custom headers in the response:
api:
port: 2000
flow:
- response:
- setHeader:
name: X-Product-Id
value: ${jsonPath('$.id')}
language: spel
- setHeader:
name: X-Product-Name
value: ${$.name}
language: jsonpath
target:
url: https://api.predic8.deYou can easily remove specific HTTP headers from requests or responses (or both) using the headerFilter element. This is useful for cleaning up headers or meeting security requirements.
The following configuration demonstrates how to manage headers:
api:
port: 2000
flow:
- response:
- headerFilter:
rules:
- include:
pattern: "X-XSS-Protection"
- exclude:
pattern: "X-.*"
target:
url: https://www.predic8.de<include>: Specifies headers to retain.<exclude>: Defines headers to remove. Wildcards can be used for patterns.
The first matching rule will be acted upon by the filter.
api:
port: 2000
flow:
- request:
- template:
contentType: application/json
pretty: true
src: |
{ "answer": ${params.answer} }
- return:
status: 200Call this API with http://localhost:2000?answer=42.
Call the following APIs with this request:
curl -d '{"city":"Berlin"}' -H "Content-Type: application/json" "http://localhost:2000"
This template will transform the JSON input into plain text:
api:
port: 2000
flow:
- request:
- template:
contentType: text/plain
src: |
City: ${json.city}
- return:
status: 200...into a different JSON:
api:
port: 2000
flow:
- request:
- template:
contentType: application/json
src: |
{
"destination": "${json.city}"
}
- return:
status: 200...or into XML:
api:
port: 2000
flow:
- request:
- template:
contentType: application/xml
src: |
<places>
<place>${json.city}</place>
</places>
- return:
status: 200Using setProperty you can extract values from XML request or response bodies and store it in properties. Then the properties are available as variables inside the template plugin.
plugin.
<api port="2000">
<request>
<setProperty name="fn" value="${/person/@firstname}" language="xpath"/>
<template>Buenas Noches, ${property.fn}sito!</template>
</request>
<return/>
</api>See: message-transformation examples
Use the Javascript or Groovy plugin for more powerful yet simple transformations.
api:
port: 2000
flow:
- request:
- javascript:
src: |
({ id:7, place: json.city })
- return:
status: 200
contentType: application/jsonCall the API with this curl command:
curl -d '{"city":"Berlin"}' -H "Content-Type: application/json" "http://localhost:2000"
This script transforms the input and adds some calculations.
api:
port: 2000
flow:
- request:
- javascript:
src: |
function convertDate(d) {
return d.getFullYear() + "-" + ("0"+(d.getMonth()+1)).slice(-2) + "-" + ("0"+d.getDate()).slice(-2);
}
({
id: json.id,
date: convertDate(new Date(json.date)),
client: json.customer,
total: json.items.map(i => i.quantity * i.price).reduce((a,b) => a+b),
positions: json.items.map(i => ({
pieces: i.quantity,
price: i.price,
article: i.description
}))
})
- return:
status: 200See examples/javascript for a detailed explanation. The same transformation can also be realized with Groovy
You can beautify a JSON or XML using the <beautifier/> plugin.
<api port="2000">
<template contentType="application/xml"><![CDATA[
<foo><bar>baz</bar></foo>
]]></template>
<beautifier/>
<return statusCode="200"/>
</api>Returns:
<foo>
<bar>baz</bar>
</foo>Replace 5XX error messages from a backend:
<api port="2000">
<response>
<if test="statusCode matches '5\d\d'" language="SpEL">
<static>
Error!
</static>
</if>
</response>
<return/>
</api>Membrane offers lots of security features to protect backend servers.
You can define APIs keys directly in your configuration, and Membrane will validate incoming requests against them.
The following configuration secures the Fruitshop API by validating a key provided as a query parameter:
<api port="2000">
<apiKey>
<!-- Define valid API keys -->
<keys>
<secret value="abc123" />
<secret value="secret" />
<secret value="Paris2025" />
</keys>
<!-- Extract the API key from the query parameter -->
<queryParamExtractor paramName="api-key" />
</apiKey>
<target url="https://api.predic8.de" />
</api>To test the configuration, pass a valid API key in the query string:
curl "http://localhost:2000/shop/v2/products/4?api-key=abc123"If the key is invalid or missing, Membrane denies access and returns an error response (HTTP 401 Unauthorized).
For more complex setups, such as API keys in the HTTP header, role-based access control (RBAC) or file-based key storage, see the API Key Plugin Examples.
The API below only allows requests with valid tokens from Microsoft's Azure AD. You can also use the JWT validator for other identity providers.
<api port="8080">
<jwtAuth expectedAud="api://2axxxx16-xxxx-xxxx-xxxx-faxxxxxxxxf0">
<jwks jwksUris="https://login.microsoftonline.com/common/discovery/keys"/>
</jwtAuth>
<target url="https://your-backend"/>
</api>Use OAuth2/OpenID to secure endpoints against Google, Azure AD, GitHub, Keycloak or Membrane authentication servers.
<api port="2001">
<oauth2Resource2>
<membrane src="https://accounts.google.com"
clientId="INSERT_CLIENT_ID"
clientSecret="INSERT_CLIENT_SECRET"
scope="email profile"
subject="sub"/>
</oauth2Resource2>
<groovy>
// Get email from OAuth2 and forward it to the backend
def oauth2 = exc.properties.'membrane.oauth2'
header.setValue('X-EMAIL',oauth2.userinfo.email)
CONTINUE
</groovy>
<target url="https://backend"/>
</api>Try the tutorial OAuth2 with external OpenID Providers
Operate your own identity provider:
<api port="2000">
<oauth2authserver location="logindialog" issuer="http://localhost:2000" consentFile="consentFile.json">
<staticUserDataProvider>
<user username="john" password="password" email="john@predic8.de"/>
</staticUserDataProvider>
<staticClientList>
<client clientId="abc" clientSecret="def" callbackUrl="http://localhost:2001/oauth2callback"/>
</staticClientList>
<bearerToken/>
<claims value="aud email iss sub username">
<scope id="username" claims="username"/>
<scope id="profile" claims="username email password"/>
</claims>
</oauth2authserver>
</api>See the OAuth2 Authorization Server example.
<api port="2000">
<basicAuthentication>
<user name="bob" password="secret"/>
<user name="alice" password="secret"/>
</basicAuthentication>
<target host="localhost" port="8080"/>
</api>Route to SSL/TLS secured endpoints:
<api port="8080">
<target url="https://api.predic8.de"/> <!-- Note the s in https! -->
</api>Secure endpoints with SSL/TLS:
<api port="8443">
<ssl>
<keystore location="membrane.p12" password="secret" keyPassword="secret" />
<truststore location="membrane.p12" password="secret" />
</ssl>
<target host="localhost" port="8080" />
</api>Membrane offers protection mechanisms to secure your APIs from common risks associated with XML and JSON payloads.
The xmlProtection plugin inspects incoming XML requests and mitigates risks such as:
- External entity references (XXE attacks).
- Excessively large element names.
- High numbers of attributes or deeply nested structures.
Example:
<api port="2000">
<xmlProtection />
<target url="https://api.predic8.de"/>
</api>The jsonProtection plugin safeguards APIs from JSON-based vulnerabilities by setting limits on:
- Depth: Prevents overly nested JSON structures.
- Key Length: Restricts excessively long keys.
- Object Size: Maximum number of fields in a JSON object.
- String Length: Controls maximum length of string values.
- ...
Example:
<api port="2000">
<jsonProtection maxDepth="5" maxKeyLength="100" maxStringLength="100000"/>
<target url="https://api.predic8.de"/>
</api>See JSON Protection.
Limit the number of incoming requests:
<api port="2000">
<rateLimiter requestLimit="3" requestLimitDuration="PT30S"/>
<target host="localhost" port="8080"/>
</api>Distribute workload to multiple backend nodes. See the example
<api port="8080">
<balancer name="balancer">
<clusters>
<cluster name="Default">
<node host="my.backend-1" port="4000"/>
<node host="my.backend-2" port="4000"/>
<node host="my.backend-3" port="4000"/>
</cluster>
</clusters>
</balancer>
</api>Route and intercept WebSocket traffic:
<api port="2000">
<webSocket url="http://my.websocket.server:1234">
<wsLog/>
</webSocket>
<target port="8080" host="localhost"/>
</api>See documentation
Integrate legacy services.
SOAP proxies configure themselves by analysing WSDL:
<soapProxy wsdl="http://thomas-bayer.com/axis2/services/BLZService?wsdl"/>The validator checks SOAP messages against a WSDL document including referenced XSD schemas.
<soapProxy wsdl="http://thomas-bayer.com/axis2/services/BLZService?wsdl">
<validator/>
</soapProxy>Log data about requests and responses to a file or database as CSV or JSON file.
<api port="2000">
<log/> <!-- Logs to the console -->
<statisticsCSV file="./log.csv"/> <!-- Logs fine-grained CSV -->
<target url="https://api.predic8.de"/>
</api>This API will expose metrics for Prometheus at http://localhost:2000/metrics:
<api port="2000">
<path>/metrics</path>
<prometheus />
</api>
Grafana dashboard from Membrane metrics.
See Prometheus and Grafana example.
Membrane supports integration with OpenTelemetry traces using the openTelemetry plugin and the W3C propagation standard. This enables detailed tracing of requests across Membrane and backend services.

This diagram illustrates Membrane in a tracing setup with a backend service and a database connection.
The configuration below shows Membrane forwarding requests to a backend, while exporting OpenTelemetry data to a collector:
<api port="2000">
<openTelemetry sampleRate="1.0">
<otlpExporter host="localhost" port="4317"/>
</openTelemetry>
<target host="localhost" port="3000"/>
</api>For a working example and detailed setup, see the OpenTelemetry Example.
To get support from our community, please post your questions to our Discussions page @GitHub.
If you find a bug, please report it using GitHub Issues. Please provide a minimal example that reproduces the issue and the version of Membrane you are using.




