We have just released version 5.64.1. Here is a list of the changes that came with the release:
Highlights
Five things worth knowing about this release. The full list of features, changes and fixes follows.
Traffic Parrot stands in for the CICS bridge
Set trafficparrot.ibmmq.cicsBridge.enabled to true and a native IBM® MQ virtual service answers CICS DPL bridge requests as the bridge does: the COMMAREA is decoded with the program's COBOL copybooks, the request is matched on the program name, the decoded fields and the MQCIH, and the reply's COMMAREA is encoded from JSON by the reply copybook. Nothing runs on the mainframe. See CICS over IBM® MQ.
Credentials stay out of a recording
Set trafficparrot.http.recording.redaction.enabled to true and the Authorization, Cookie, Set-Cookie and Proxy-Authorization headers, and any header you name, are masked in what recording saves to disk and in the per-request log entry, replaced by a placeholder or dropped. The client recording through Traffic Parrot still receives the backend's real headers. See Redacting credential headers during recording.
Your extension gets Traffic Parrot's database connections
An extension with a public constructor taking TrafficParrotServices is handed the JDBC connections named in database-connections.json, opened as the dataSource helper opens them, so a transformer that generates test data writes to the database the helper reads, with no copy of the credentials and no reflection into Traffic Parrot's classes. See Using Traffic Parrot's database connections.
Handlebars partials have a page of their own
Tools, Templates lists every .hbs file under the traffic files directory and edits it in place, offering Traffic Parrot's helpers by name as you type {{; the next response that includes the partial renders the saved version, with no restart. A partial can no longer include a file outside the traffic files directory. See Template files.
Testcontainers: your jars, your properties, and a Cypress suite
The Testcontainers module now supplies your extension jars and property overrides, withExtensionJar(Path) and withProperty(key, value), so an extension, a JDBC driver and a database-connections.json of your own load from an unedited release zip, and the example workspace shares one Traffic Parrot across every test class in the JVM. A Cypress suite does the same with the testcontainers npm package, starting Traffic Parrot once before the first spec and stopping it after the last. See Adding your extensions and configuration and From a Cypress suite.
Also in this release: SFTP and FTP connections pick up complete files only, a native IBM® MQ request matcher can read its value from a file, a webhook can read the headers of the response it follows, the Settings page shows the database connections and the extension jars, a property set on the command line is honoured when the properties file leaves it out, a signed-in user without the edit role can no longer change anything, and a mock OAuth2/OIDC provider is scaffolded into a scenario in one request.
Features
- Traffic Parrot stands in for the IBM® MQ CICS bridge. With trafficparrot.ibmmq.cicsBridge.enabled set to true, a native IBM® MQ virtual service answers CICS DPL bridge requests (format MQCICS: an MQCIH, the program name and the COMMAREA) as the bridge does. The COMMAREA is decoded with the program's COBOL copybooks, named in copybooks/programs.json, and the request is matched as a JSON view of the program name, the decoded fields, the MQMD user and the MQCIH fields, with the new matchesCicsField matcher (a field path and equalTo, matches or a comparison) or any JSON matcher. The reply is built from the mapping's JSON: the settable MQCIH fields, and the COMMAREA encoded with the reply copybook or given as hex, with the cicsField helper echoing request fields into it; it goes to the request's reply-to queue, correlated to its message id. A text message on a queue whose mapping uses CICS fields is warned about in the log, and the MQCIH Authenticator never reaches the log. The IMS bridge is not covered. See CICS over IBM® MQ.
- Credential headers can be redacted while recording. Set trafficparrot.http.recording.redaction.enabled to true in trafficparrot.properties and the Authorization, Cookie, Set-Cookie and Proxy-Authorization headers, plus any header named in trafficparrot.http.recording.redaction.headers, are masked in what recording saves to disk: the recorded response headers, and the request headers recording matches on. trafficparrot.http.recording.redaction.strategy chooses between PLACEHOLDER, which replaces each value with trafficparrot.http.recording.redaction.placeholder (***REDACTED*** unless you change it) and so changes what the saved request matcher expects, and DROP, which leaves the header out. The client recording through Traffic Parrot still receives the backend's real headers, a mapping created in the web user interface or through the admin API is saved as written, and the per-request entry in the log shows the covered headers redacted the same way, whether or not a recording is running. Off by default. See Redacting credential headers during recording.
- A custom extension can use Traffic Parrot's database connections. An extension with a public constructor taking a TrafficParrotServices, from the trafficparrot-sdk, is created with it, and services.databaseConnections().dataSource(connectionId) is a javax.sql.DataSource for the JDBC connection of that id in database-connections.json, opened as the dataSource helper opens it. So a transformer that generates test data writes to the database the helper reads, with no copy of the credentials and no reflection into Traffic Parrot's classes, and an edit to the file takes effect on the next call. This works for every extension type, and an extension without such a constructor is created as before. The SDK workspace project gains InsertRecordWithDatabaseConnection, a transformer that inserts a record and answers with its id, with a unit test that passes it an in-memory database. See Using Traffic Parrot's database connections.
- A webhook (callback) can read the headers of the response Traffic Parrot served, as {{originalResponse.headers.headerName}}, beside {{originalResponse.status}} and {{originalResponse.body}}, in every template a callback has: URL, method, headers, body and the enabled condition. A header the mapping's response template rendered is read as rendered. The shape is that of {{request.headers}}: the name matches in any case, and a header sent more than once keeps every value, {{originalResponse.headers.headerName.[1]}} being the second. See Request data.
- Handlebars partials have a page of their own. Tools, Templates lists every .hbs file under the traffic files directory by its path, templates/sql/customer-exists.hbs for example, with a filter, and Edit opens it in an editor that shows the name a template includes it with, {{> templates/sql/customer-exists.hbs}}, and offers Traffic Parrot's helpers by name when you type {{, inserting the chosen helper's snippet. Save writes the file, and the next response that includes the partial renders the saved version, with no restart. A save over a file that changed on disk since it was opened is refused with the reason shown, and Edit reloads it. The page lists, loads and saves the files through the same mechanism as the Current configuration files editor on the Settings page, so trafficparrot.gui.config.files.allowedFilesToEdit governs which files it lists, and loading and saving need traffic-parrot-gui-edit-role when the web user interface login is on; a user without the role sees the list and a notice saying so. Creating or deleting a partial is still done on disk. See Template files.
- SFTP and FTP connections pick up complete files only, and pick up a file of a name already answered. A file in a polled request directory is picked up once it has been listed with the same size and modified time at two consecutive polls, so a large file still being uploaded is left alone until it is complete. Names matching trafficparrot.file.remote.temporaryFileNames, *.tmp,*.part,*.filepart unless you change it, are never picked up, and nor are Traffic Parrot's own in-progress uploads. A file that matches a mapping is now deleted from the request directory before the response is written, on every driver, so a client that uploads its next file under the same name as soon as it sees the response has that file picked up too; previously a second file of a name already picked up in the session was never picked up. A file that matches no mapping stays and is not picked up again unless it changes. The two-poll check and the property apply to SFTP and FTP connections only. See How files on SFTP and FTP servers are picked up.
- A native IBM® MQ mapping's request bodyMatcher can now read its expected value from a file under __files, by replacing the matcher's inline value with a bodyFileName object, as a JMS or file message mapping's request matcher can. The file is read when the mapping loads, resolved against the __files directory beside the mapping's ibm-mq-mappings directory (per scenario for scenario-scoped mappings) and contained to it. Only the top-level bodyMatcher supports it: a bodyFileName nested inside and, or or not, or one naming a file that does not exist, is refused when the IBM® MQ mappings load, with an error that names the mapping file, the matcher and the file. The Static Files page counts the file as in use and exporting a scenario bundles it. The IBM® MQ mapping schema served at /schemas now describes the file form on the top-level bodyMatcher only, and the JMS and IBM® MQ schemas now require at least two operands in an and or or, which is what the mapping loader has always required, so trafficparrot validate reports a one-operand form before the mapping is loaded.
- The Testcontainers module supplies your extension jars and property overrides. withExtensionJar(Path) copies a jar into the container's lib/external directory before it starts, so an extension or a JDBC driver loads with no edited release zip, and withProperty(key, value) adds a key=value override to the start command of a container built from a release zip, as if the property were set in trafficparrot.properties. The example workspace gains two examples that share one Traffic Parrot across every test class in the JVM, one of them started with an extension, a driver and a database-connections.json of its own. See Adding your extensions and configuration and One Traffic Parrot for the whole suite.
- The Settings page shows the database connections and the extension jars. Two read-only cards. Database connections lists the selected scenario's database-connections.json, each connection with its id, type, URL, driver, user and other settings and every password shown as ****; the file is read as the dataSource helper reads it, so a file the helper would refuse shows the helper's reason. Extension jars lists the .jar files in lib/external, the directory the launcher puts on the classpath, with each jar's size and modified time. Each card opens from a deep link, /settings.html#collapseDatabaseConnections and /settings.html#collapseExtensionJars.
- A mock OAuth2/OIDC provider, scaffolded in one request. POST /api/http/oauth2-mocks writes three ordinary HTTP mappings into the selected scenario: an OpenID Connect discovery document whose endpoint URLs resolve against the address the request arrived on, a JWKS, and a token endpoint that issues RS256-signed JWTs for the client_credentials grant, so a system under test discovers the mock by its issuer, fetches its keys and obtains tokens as it would from a real provider, and a real OIDC client library completes both without special handling. The request body sets the mock's name, the issuer (which also decides where the endpoints are mounted), the audience, extra claims, the token lifetime (zero or negative issues an already-expired token, for testing expiry), the public base URL the mock advertises behind a proxy or on a published port, and an optional client id and secret the token endpoint then requires. Each mock signs with its own RSA key, kept in the token mapping, so the key id it publishes and every token it issued stay valid across a restart; a second scaffold of the same name, or onto paths other mappings already answer, is refused with 409 rather than replacing the key. When the web user interface login is on, the request needs traffic-parrot-gui-edit-role. See Mock OAuth2/OIDC provider.
Changes
- trafficparrot.http.recording.request.storage.rewrite.regex rules now also apply to multipart/form-data request bodies, part by part. Previously a multipart body was recorded unchanged, with no warning, even when rules were set, so a credential in a form field was saved raw. Each part is now decoded with the charset from its own Content-Type (UTF-8 when it names none) and the rules run on that part on its own, so a rule cannot match across a part boundary. A part that a rule matches is re-encoded in full, as a non-multipart body already was, so a rule that matches inside a file upload re-encodes that part: keep uploads out of a recording by giving the Recording URL a path rather than by relying on the rules to miss them. A rewrite that leaves a JSON part invalid is treated as one that leaves a JSON body invalid: the exchange is not recorded, and an error naming the URL and the rules is written to the log. An installation that sets no rules is unaffected. See Rewriting request bodies during recording.
- While trafficparrot.http.recording.request.storage.rewrite.regex rules are set, the Request received: entry the log writes for each request to the virtual service now shows the request body as the rules rewrite it, whether or not a recording is running, and closes with a line naming what was redacted. Previously the entry carried the body as received, so a rule that kept a credential out of the recorded mapping did not keep it out of the log. trafficparrot.virtualservice.httpRequestLoggingDisabled=true still removes the entry, an entry the rules leave unchanged is as before, the URL is logged as received, and the request journal, which the HTTP Request Log page shows, keeps the body as received. See Rewriting request bodies during recording.
- When an MCP tool fails unexpectedly, the answer is now a tool result with isError: true whose text names the tool and the reason, for example Tool 'list_scenarios' failed: Permission denied. The cause is in the Traffic Parrot log., where before it was a JSON-RPC -32603 error with a fixed message that pointed only at the log. MCP 2025-11-25 files a tool's failure under tool execution errors, which the client shows to the assistant, whereas a protocol error it may drop; so the assistant can act on the reason. The reason is a permission error, a missing file, a file that already exists, the operating system's text for any other file-system failure, or otherwise the type of the exception, and never a path, because the response goes into the assistant's context and on to its model provider. The cause, with its stack trace, is still in the Traffic Parrot log. A call in a batch that fails in dispatch, before its tool runs, is still -32603. See Running a tool.
- An MCP tool argument that the tool rejects, as missing, of the wrong type or out of range, is now answered as a tool result with isError: true whose text names the argument, for example Argument 'id' is required or Argument 'dryRun' must be true or false, but was 'no', where before it was a JSON-RPC -32602 error. MCP 2025-11-25, the revision Traffic Parrot advertises, classifies input validation as a tool execution error so that the client shows the message to the assistant, which can then correct the call, whereas a protocol error the client may drop. A call no tool gets to judge is still -32602: tools/call without a name, a tool this instance does not offer, or arguments that is not an object. The AGENTS.md brief in the install directory says the same. See the error-code table.
- A matchesXPath request body matcher given an expression alone, with no value sub-matcher, now matches only when the expression selects at least one node or evaluates to boolean true. An expression that evaluates to a number, a string or false used to match every XML body, so count(//item) = 99 matched a body with two items; it now matches none, and count(//item) = 2 matches the two-item body alone. This applies to HTTP, JMS and native IBM® MQ mappings alike, and the matcher's description in the editor's request matchers help panel now says the same. A mapping that relied on such an expression matching should select a node, or compare the value, instead. See the matchers reference.
- Upgraded bundled JRE from 21.0.11 to 21.0.12.1
- The shipped trafficparrot.gui.config.files.allowedFilesToEdit gains .hbs$, so the Current configuration files editor on the Settings page now lists Handlebars partials among the files; the Templates page under Tools is the place to edit them (see Template files). An installation upgraded with the upgrade script keeps the list its trafficparrot.properties already holds, as it keeps every setting, so add .hbs$ to it yourself to see partials on either page; a fresh installation has the new list.
- Saving a file in the Current configuration files editor on the Settings page, or a partial on the Templates page, now keeps an LF-only file's line breaks: a file that uses LF alone is saved with LF. A file with CRLF or mixed line breaks, a file with no line break, and a new file are saved with CRLF, as every saved file was before. A JSON file does not notice the difference; a plain-text partial served as a response body does.
- The gRPC mapping editor shows request metadata as rows by default, in a Request metadata matching card below the request and response panels, with a matcher per entry (equal to, contains, a regular expression or a date/time comparison), and exception metadata as rows on the Exception tab. The key: value text form of each is under Edit as text beneath its rows, as for HTTP request and response headers, and the two stay in step. What is saved is unchanged: a mapping saved without an edit is the same mapping. See Request metadata.
- The wss4j, opensaml, openws and xmltooling libraries are no longer bundled. Traffic Parrot never used them: SOAP message signing uses Apache Santuario (xmlsec) directly, which is still bundled. A plugin, or a JAR under lib/external, that relied on finding them on the classpath must now bring its own copy.
Fixes
- The HTTP Request Log now shows the status the client received, in the Status column and in the row's details alike, including when a {{modifyResponse 'statusCode' 404}} response template or a proxied backend set it after the mapping was matched. Previously the column showed the status in the mapping's response definition, so a row read 200 for a response the client received as 404 while the details panel showed the served one. A mapping with no status now reads 200, the status it is served with, where the cell was blank, and a fault that sends no status line leaves the cell blank, where it read 200. See Assert a request was made.
- With trafficparrot.gui.security.mode set to LOGIN_PROPERTIES, the log pages that every virtual service HTTP and HTTPS port serves under /__logs (/__logs/virtualservice/, the Traffic Parrot log, and /__logs/access/, the access logs; a scenario's own ports included) now ask for the web user interface login. They answered anyone who could reach the port, with no login, while the same pages on the web user interface port asked for one. Stubs and the admin API on the virtual service ports are unchanged, and so is the Logs page, which reads them through the web user interface port with the login you signed in with. Read the pages through the Logs page or the HTTPS port rather than a plain-HTTP port, which sends the password unencrypted.
- An Azure AMQP 1.0 connection in jms-connections.json loads and connects again, where one such entry used to stop every JMS connection in the file loading after an upgrade. An entry whose jmsProvider is not one Traffic Parrot knows is now skipped, with one WARN in the log naming the entry and the file it is in, rather than stopping the other connections loading. See JMS connections.
- JMS mappings recorded through an IBM® MQ 10 client now replay. That client adds a JMS_IBM_MsgToken property to every message it receives and refuses to set it again, so a mapping that had recorded it could not be replayed: each matching request was logged as skipped and no response was sent. New installations no longer record the property, which joins the default of trafficparrot.jms.skipRecordingProperties. A mapping recorded earlier still carries it, and Traffic Parrot now sends the response without it and writes a WARN naming the property to the log, where before no response was sent. The same applies to any other JMSX or JMS_ property the JMS provider refuses to set; a refused property outside those two namespaces still fails the response as before, because it is more likely a mistake in the mapping than a property the provider owns. Upgrading keeps the skip list your installation already has, so if you record through an IBM MQ 10 client, add JMS_IBM_MsgToken to it yourself.
- Saving or deleting a mapping from a mappings list page now closes the edit dialog and refreshes the list when the list's automatic reload is stopped, or is paused because a tooltip is open or text on the page is selected. The reload that a save or delete issues could be cut short by the next paused reload tick, so the dialog stayed open and the list went on showing the mapping as it was before the change until the automatic reload ran again. This applies to the HTTP, gRPC, JMS, native IBM® MQ, file message and WebSocket mappings lists alike.
- Opening the HTTP or gRPC mappings list no longer changes what a mapping with a response body file serves, and neither does an OpenAPI export, a mapping schema check at startup, or a save of a gRPC mapping whose file name Traffic Parrot generates, which read the mappings the same way. With trafficparrot.virtualservice.mapping.cache.milliseconds above 0 (the shipped performance-profile-settings/trafficparrot.performance.properties sets 10000000), listing the mappings made such a mapping answer with the text the list shows in place of its file until the cache expired: (Traffic Parrot: not a text body) for a binary file, the Problem loading body file message for a file that could not be read, or a text file's contents as they were when listed.
- A mapping whose response bodyFileName points outside the scenario's __files directory no longer breaks the HTTP and gRPC mappings lists. The list request failed, so the page showed no mappings, and adding a gRPC mapping failed too, until that mapping was deleted. The mapping is now listed with the reason its file cannot be read. trafficparrot validate now treats such a mapping as it treats one whose body file is missing, validating the reason as the response body, where before it stopped with exit code 2 on an HTTP mapping and skipped a gRPC one. The OpenAPI export, which failed on such a mapping, now completes with the reason as that mapping's response body, and the mapping schema checks at startup, which stopped Traffic Parrot starting, now report the mapping only where an OpenAPI specification or proto covers it.
- The HTTP and gRPC editors now refuse a response body file name outside the scenario's __files when the save keeps a binary body, including a re-save of an existing mapping that already names one (such a mapping could not serve its body anyway).
- The gRPC management file endpoints, /grpc/management/file/<name> on the management port, no longer put a filesystem path in an error response. A name outside the scenario's __files directory, or naming that directory itself, is refused with 400 and Invalid file name on every verb, where before a save or delete answered 500 with the directory's absolute path, a delete named at the directory removed it when it was empty, and a read of it answered its listing. A missing file answers 404 and File not found, and any other failure a fixed 500 message, with the detail in the log. The gRPC export leaves out a body file whose name is refused, where before the zip was cut short at that mapping.
- A JMS, native IBM® MQ or file message mapping whose response body file is missing from __files, or resolves outside it, is now reported without filesystem paths. The mappings list, which the Add/Edit page loads, a delete or edit of any mapping of that protocol, and the AsyncAPI and Pact specification imports answered 500 naming the body file and the mapping file by their paths; the reason now reads, for example, Body file 'reply.txt' referenced from mapping 'orders.json' was not found., with the paths in the log. The export, which answered a 200 zip with its __files entries missing, now answers that 500 too. A zip import that cannot write an entry reports Could not import zip entry '<entry>' rather than the entry's path.
- An IBM® MQ, JMS or file message mapping file with no id now shows correctly on the Add/Edit page and can be edited and deleted there. Previously the list raised a DataTables warning, the edit dialog opened empty, and while such a file was present, editing or deleting other mappings could fail.
- Loading and saving files under Settings, Current configuration files, now needs traffic-parrot-gui-edit-role when the web user interface login is on (trafficparrot.gui.security.mode=LOGIN_PROPERTIES). A signed-in user holding traffic-parrot-gui-role alone was offered Load, and the /api/configuration/files endpoints answered that user any listed file's content and accepted its changes and new files, so the shipped readonly user could read, change or create any .json, .csv or .properties file the editor lists. Those endpoints now answer 403 naming the role, and the page shows such a user the file names and a line saying which role loading and saving need; the names list stays open to every signed-in user. File names on the page are now shown as written, where a name containing markup was rendered as HTML. With the default ALLOW_ALL nothing changes. See Role based access and trafficparrot.gui.config.files.notAllowedFilesToEdit.
- ActiveMQ, RabbitMQ, FTP and SFTP connection passwords, and the internal broker's keystore password, no longer appear in trafficparrot.log. The lines that report a JMS connection being created and a messaging record or replay page being rendered printed each configured connection with its password; they now print [REDACTED] in its place, and a password that is not set is still shown as not set, so a failed login can be told from a missing password. Native IBM® MQ connection passwords were already masked. Log files written before the upgrade (logs/trafficparrot.log*) still hold them.
- An OpenAPI response keyed by a range such as 4XX no longer fails the import. The specification allows 1XX to 5XX as response keys, and importing one stopped with For input string: "4XX" after the operations before it had already saved, from the Import page, the skeletons upload and the generate_mappings_from_openapi MCP tool alike. A range response is now imported at the first code in the range, 400 for 4XX, and where the specification also defines that code explicitly the explicit example is the one imported. OpenAPI request validation (trafficparrot.openapi.request.validation.mode) answers an invalid request whose validation response is keyed by a range at that first code too, where before such a request was served unvalidated and an error was written to the log. See Matching response code.
- The Admin request received: entries in the log, one per request to the virtual service's admin API, no longer carry the value of an Authorization or Proxy-Authorization header. The value is replaced with ***** and the scheme is kept, so an entry reads Authorization: [Basic *****]. With trafficparrot.gui.security.mode set to LOGIN_PROPERTIES the web user interface sends the signed-in user's own credential with every admin API request it makes through /api/http, and Traffic Parrot sends an internal one on every start, so both were written to the log as sent. The Request received: entry for stub traffic is redacted while credential header redaction is on or request storage rewrite rules are set, and otherwise carries the request's headers as received.
- An equalToXml request body matcher with namespaceAwareness set to NONE now matches a document whose elements have siblings, such as a SOAP envelope holding a Header and a Body, and an element holding both text and child elements (mixed content) now matches under every namespaceAwareness setting. Neither matched before. See JSON-only matchers.
- A matchesJsonSchema request body matcher whose schema is written as a JSON object rather than as a string, through the admin API or in a mapping file, is now saved and read back as an object, and a schemaVersion the mapping does not declare is no longer added to it. Saving through the admin API used to rewrite the matcher, turning the object into an escaped string and appending "schemaVersion": "V202012". What the matcher matches is unchanged: a mapping that declares no version still validates against JSON Schema 2020-12. The Add/Edit mapping editors add no schemaVersion either, and a mapping whose schema is an object keeps that form through an unedited Save. See JSON-only matchers.
- Saving a mapping in the HTTP, gRPC, JMS, native IBM® MQ or file message Add/Edit mapping editor now keeps the request body matcher options the editor cannot author, whether or not the matcher value was edited: a matchesJsonSchema keeps its schemaVersion, an equalTo its caseInsensitive, an equalToJson its ignoreArrayOrder and ignoreExtraElements, and a matchesJsonPath its advanced form with a value sub-matcher. Save rebuilt the matcher from the dropdown and the text field alone, so a schema declaring V4 was saved with V202012, which can change what it matches, and the other options were dropped. An option is kept while the matcher chosen in the dropdown stays the same, and dropped when you choose another matcher. A request body matcher that reads its value from a file (bodyFileName) still loses its sibling options on Save. Also fixed in the HTTP and gRPC editors: after opening a mapping whose request body matcher reads from a file, saving a different mapping with an inline request body no longer writes that body into the first mapping's file. See JSON-only matchers.
- A Handlebars partial can no longer include a file outside the traffic files directory. A partial's name is resolved against the selected scenario's directory, or the traffic files directory when no scenario is selected, and a name that resolves outside it, or to a file that does not exist, is now reported as a missing partial, with the reason in the log. A mapping that included a file from outside the traffic files directory now answers an error: move the file under it. See Template files.
- Upgrading with the upgrade script now keeps the web user interface user store and the Handlebars partials, and prints no setting values. trafficparrot.gui.login.properties was not carried, so an installation with the login on came up with the shipped admin and readonly users and their password; it is now copied whole, so a shipped user you removed stays removed. The templates and responses directories and any .hbs file at the installation root are carried too, where before a partial had to be copied by hand. The upgrade's output, which it also writes to tp.out in the new installation, now names each carried setting without its value, where before it printed the value of every setting it merged: passwords, the MCP token and a licence passed as -Dtrafficparrot.license.content among them. If you upgraded an installation before this change, check that its trafficparrot.gui.login.properties holds only the users you want, and that its tp.out holds nothing you would not keep in a log. The standalone upgrade helper script, which takes the release zip and the previous installation as its arguments, carries the same files, merges a directory on macOS, where before it carried nothing, copies a directory reached through a symbolic link as that directory's contents, and ends with an error naming what it could not copy rather than reporting a complete upgrade. See Upgrading Traffic Parrot.
- Under the web user interface login (trafficparrot.gui.security.mode=LOGIN_PROPERTIES), a signed-in user without traffic-parrot-gui-edit-role can no longer change Traffic Parrot. The role was checked only when a page was drawn, so the mapping editors hid their controls from such a user, but nothing checked it on the way in: the shipped readonly user could create a mapping through POST /api/http/__admin/mappings, duplicate one, start a recording, switch the scenario, clear a request log, upload a static file or run an import, from the pages that still showed those controls and through every API the web user interface port serves. Every request that could change Traffic Parrot now needs the edit role: any method other than GET, HEAD and OPTIONS, and any request carrying a body. A user with traffic-parrot-gui-role alone is refused with 403 and a JSON body naming the role, which the pages show as the error. The request log queries that take their criteria in a POST body stay open to a reader: requests/find, requests/count, the near-miss queries and mappings/find-by-metadata through /api/http, the gRPC and JMS request counts, and the WebSocket messages/find and messages/count. On /mcp, a tool that changes something is refused as a tool result with isError: true naming the role, before it runs; listing the tools and calling the seven that only read is unchanged. Stub traffic forwarded to the virtual service through the web user interface port now needs the edit role too while the login is on; the virtual service port itself, its /__admin API and the management ports are not behind the login, as before. With the default ALLOW_ALL nothing changes. See Role based access.
- A -D system property, including one set through JAVA_TOOL_OPTIONS, or a key=value argument to the start script now sets a property that the loaded properties file leaves out. Such an override was ignored and the bundled default applied, which the startup log listed under Using default values from as if nothing had been set: on an installation upgraded from an earlier release, whose trafficparrot.properties lacks every property added since, or with a file named by trafficparrot.properties.filename that holds only the keys you set, -Dtrafficparrot.virtualservice.http.port=2001 left the virtual service on 8081, and -Dtrafficparrot.http.recording.redaction.enabled=true left redaction off. The startup log's Using system property overrides line now lists such a key. A fresh installation's shipped file holds every property, so it was unaffected. Precedence is unchanged: a system property wins over the file, which wins over the bundled default.
- The dataSource helper now works on a fresh installation with database-connections.json as shipped. The shipped file carries a Couchbase example, and reading any connection from a file that holds one needed the Couchbase client, which the release does not bundle, so every dataSource call failed with NoClassDefFoundError: com/couchbase/client/core/env/Authenticator, the JDBC connections beside the example included, until the example was deleted from the file. The file now reads without the client. A Couchbase connection used while its JARs are not in lib/external answers an error naming lib/external, and the JDBC connections beside it work. See Data source.
- An unmatched native IBM® MQ or file message is now listed on that protocol's Requests page with its near misses, the mappings closest to it and the field each one missed on, when trafficparrot.messaging.diagnostics.enabled is true, and Clear empties the list, as on the JMS page. The property named all three protocols but only JMS was connected, and JMS recorded nothing at the shipped mapping cache setting, trafficparrot.virtualservice.mapping.cache.milliseconds=0, so its page stayed at 0 MESSAGES however the property was set. The messaging request journal is now safe to write from several receive threads at once: a JMS virtual service with more than one request destination, or whose Requests page was read during traffic, could leave a received message unanswered, and the journal could grow past trafficparrot.virtualservice.maxRequestJournalEntries until restart. With the property false, near misses are no longer computed. Near misses are listed under the selected scenario, whichever scenario's virtual service received the message, and a file whose name does not match is shown as a Body Content mismatch.
- A JMS response property value containing ; now keeps it when the mapping is saved, including when the mapping is opened and saved without changes: everything after the second ; on a name;type;value line is the value. A line without a type or a value is refused with a message naming the line, instead of an error that pointed to the logs. See Response properties.
- The size helper now counts the entries of a map, as the WireMock helpers list says it does: {{size request.query}} and {{size (parseJson request.body)}} render the count, where before each rendered an error such as {{ size ERROR: cannot get size of 'java.util.HashMap' }} in the response. A string or a list is counted as before. A map that stands for a failed helper is not counted: when the map comes from a dataSource call that reported an error, size renders that error rather than 0, where before it rendered an error naming an internal class.
- Internalize under Tools now writes each response column of a native IBM® MQ mapping with several responses into the response it was exported from. The MappingData.csv header names each response's queue, but every column was written into every response, so after an Internalize, even of an unedited export, all the responses of such a mapping carried the body of whichever column was processed last. A column whose header names a queue the mapping no longer sends to is refused with an error naming that queue and the queues the mapping does send to, and the rest of the file is applied as before. A mapping with one response is unaffected.
- A native IBM® MQ response that could not be scheduled within a minute, because the request queue's Maximum messages in progress, the largest value among the mappings that read the queue, were all in flight, was dropped: the request had already been consumed, the log read Skipping IBM MQ message, and no response was sent. Traffic Parrot now keeps waiting for a slot and writes a WARN, Still waiting to schedule a response message to <queue>, to the log, and the requests it has not yet consumed stay on the queue. A queue is unaffected while any mapping that reads it leaves the field at its default.
- Under concurrent requests, with trafficparrot.virtualservice.mapping.cache.milliseconds at its default of 0, a request could intermittently be answered 900, no responses matched, although a mapping matched it, most often where request matcher scripts sat above a fallback mapping. Every request now matches against the complete set of mappings. The same fix covers gRPC, and JMS and IBM® MQ when a non-zero mapping cache is set.
- With trafficparrot.virtualservice.handlebars.throwErrors=true, a dataSource query that fails, because the connection id is unknown, the table is missing, the SQL is invalid or the database is down, is now answered with an HTTP 500 even when the helper is given a default, and so is every other error the helper would otherwise write into the body: no rows with no default, a placeholder with no value, and a single value asked of a query that returned several. A query that runs and returns no rows with a default still serves the default. With the property at its default of false, a failure the default covers is now logged at WARN, Returning default value '[]' because of error '...', where before it was logged at INFO. A custom helper that reports an error without throwing can honour the property through the new protected static throwErrorsEnabled() on TrafficParrotHelper.
- An HTTP extension named in trafficparrot.http.responsetransformers that cannot be loaded, such as a jar carrying its own copy of the SDK or a class that is not on the classpath, no longer stops the HTTP virtual service from starting, and the HTTP pages of the console no longer fail on it: it is left out, with an ERROR in the log naming the class and the reason, and the other extensions are loaded. A mapping file that cannot be loaded, because it is not valid JSON, is not a mapping, or names a request body file that is missing, is left out the same way, with one ERROR naming the file and the reason, and the other mappings, HTTP and gRPC, are still served, where before every mock answered 500 while the file was broken, or Traffic Parrot did not start. A validate run still fails on such a file.
- The HTTP and gRPC mapping lists show each mapping file left out that way, in a warning above the table: one line per file, or per mapping of a file whose mappings fail one at a time, naming it by its path exactly as the ERROR in the log does, with the reason, so the line can be searched for in the log. The warning is refreshed with the list's automatic reload and goes once the file loads; before, the log was the only sign. GET /http/management/skippedMappingFiles and GET /grpc/management/skippedMappingFiles on the management port answer the same list as JSON.
- A dataSource query on a database connection that contains a colon which is not a placeholder, such as the time literal '12:30' or the cast 42::varchar, now runs. The helper's own check for placeholders without a value read every :name in the SQL as a placeholder, so it refused such a query before it ran, with Parameter values not specified: [:30, :varchar]. For a database connection the check now follows the same named-parameter rules the query runs with: a colon inside a quoted literal, a quoted identifier, a comment or a :: cast is not a placeholder, and a placeholder the call gives no value is still refused by name. Because the check now reads the SQL as the database does, its refusals shift with it: a placeholder inside a comment is no longer reported, and &name, :id.json and :age2 given only age are refused as missing before the query runs, where before the query failed inside the driver and default covered it. CSV and Excel sources are unchanged, and still treat a quoted :name as a placeholder. See Data source.
- The CA truststore shipped as certificates/virtualservice-CA-certificate.jks now holds the current Traffic Parrot CA certificate, the same one as certificates/virtualservice-CA-certificate.pem and the one every shipped server certificate is issued from. It still held the CA from before the December 2025 rotation, so a client that trusted the keystore trusted none of the shipped certificates and failed the TLS handshake; a client trusting the PEM was unaffected. The alias, trafficparrot-ca, and the password are unchanged, so a client configured for the keystore needs only the new file.
- The SDK workspace project zip now builds on a machine whose Maven cache does not already hold org.unsynchronized:jdeserialize, a library the SDK depends on that Maven Central does not host: the zip's repo/ directory carries it next to the SDK, with its licence under licenses/. The workspace's build stopped at Could not find artifact org.unsynchronized:jdeserialize:jar:2.0.2 unless an earlier build had already cached it. The zip also no longer carries a .surefire-<hash> file, the test-run statistics left behind by the build that produced it.
Documentation
- Added CICS over IBM® MQ: how Traffic Parrot stands in for the CICS DPL bridge, the copybooks and their index, the request view, matching, replying, and a walkthrough that runs end to end on the free IBM® MQ developer container with a sample program and a Java client; and Traffic Parrot for CA LISA / DevTest users, which maps DevTest's IBM MQ transport and copybook data protocol onto those pieces and says what is not covered. The IBM® MQ matchers table gains matchesCicsField, and the properties reference the trafficparrot.ibmmq.cicsBridge.enabled row.
- The MCP endpoint reference now lists get_body_file: a read-only tool that returns one of the selected scenario's __files entries as text, by the name get_mapping reports as bodyFileName, so an assistant can see a response body that is stored in a file instead of inline. Only that scenario's __files directory is readable, a binary file is refused with its size, and contents past 64 KB come back cut short with truncated set. The tool counts on that page and on Generate mocks with an AI agent now read thirteen tools, seven of which only read.
- The HTTP and gRPC pages and the OpenAPI documentation now describe the POST /http/management/importMappings and POST /grpc/management/importMappings responses as the endpoints answer them: the ids of the imported mappings, plus warnings when part of an HTTP upload could not be imported. The HTTP page also says that an imported mapping records the file it came from as tp.import.source and that importing a file of the same name again replaces the earlier import, and both pages say what happens when an imported mapping is written to the file of an existing one.
- Added a Hoverfly middleware in Traffic Parrot how-to page for users migrating from Hoverfly. It maps Hoverfly's Modify/Synthesize middleware, a language-agnostic executable fed the HTTP pair as JSON on stdin and returning mutated JSON on stdout, onto Traffic Parrot's
external-processresponse transformer, with a concept-mapping table (Hoverfly's global-middlewareflag against the global opt-in plus per-mappingtransformers/transformerParametersmodel), a copy-pasteable uppercase-body Python example, the versioned envelope migration notes, and the differences to expect (response-only, one process per request, HTTP only, per-mapping opt-in). - Added Traffic Parrot in JUnit and Spring Boot tests to the user guide: how the build starts and stops Traffic Parrot around the integration-test phase with the Maven or Gradle plugin, how a test points the code under test at its port, how to reserve every port at build time when builds share a CI host, and what to clear between tests (the request journal, scenario state and key-value state). The For WireMock Users page's reset row now names those three calls; it named the mapping cleanup feature, which consolidates recorded mappings into a new scenario and is not a reset.
- Added When each extension runs: where each extension type sits relative to request matching and response rendering. A Handlebars helper runs during rendering; an HTTP response definition transformer and the JMS, Native IBM MQ and file message response transformers run before it, on the mapping's response as written; an HTTP response transformer runs after it, on the response the client is about to receive.
- Added Adding an extension jar and configuration without rebuilding the zip: the lib/external directory, and the three ways to set a property from outside the zip, on the command line, through JAVA_TOOL_OPTIONS, or in a properties file of your own that holds only the keys you set, each with its Docker form and the startup log line that confirms it.
- The templates on the Per-test data recipe page, and in its example zip, are now laid out across lines with indentation: a helper call's arguments one per line inside the tag, where they render nothing, and a JSON response one field per line. The request matcher script that decides the 409 keeps rendering exactly true or false, with a ~ on each tag beside the layout and a comment saying why. The responses they render are unchanged.
- Added From a Cypress suite to the Testcontainers page: a cypress.config.js that builds the image from the module's Dockerfile with the testcontainers npm package, starts Traffic Parrot once in setupNodeEvents with your jars and traffic files copied in and your properties on the start command, hands its URL to every spec through the returned config, and stops it on after:run, with the two specs that use it.
