Chapter 14: Troubleshooting#
When Things Go Wrong#
The app compiles. It launches. You click the button to fetch users. Nothing happens. No error message. No crash. Just silence. The UI sits there. You check the endpoint -- it is correct. You check the auth -- it is set. You add a ShowMessage after the REST call and discover the status code is 0. Zero. The request never completed. You look at the SSL DLLs and realize you put 32-bit DLLs in System32 and 64-bit DLLs in SysWOW64. Backwards.
Every problem in this chapter has been hit by a real developer. Every fix has been verified. The format is the same throughout: Problem, Cause, Fix. Find your symptom, read the cause, apply the fix.
1. SSL Errors#
"Could not load SSL library"#
Problem: REST calls fail with the error "Could not load SSL library" or the HTTP client raises an ENetHTTPClientException mentioning SSL.
Cause: The OpenSSL DLLs are missing or have the wrong bitness. Delphi's HTTP client needs platform-matched DLLs:
- The IDE is 32-bit, so it needs 32-bit DLLs
- Your compiled 64-bit app needs 64-bit DLLs
- Windows has confusing folder names:
SysWOW64is for 32-bit,System32is for 64-bit
Fix:
32-bit DLLs (libeay32.dll, ssleay32.dll) --> C:\Windows\SysWOW64\ (for the IDE, design-time testing) --> Your app's output directory (if compiling as Win32)โ64-bit DLLs (libcrypto-3-x64.dll, libssl-3-x64.dll) --> C:\Windows\System32\ (for compiled 64-bit apps) --> Your app's output directory (alternative)Verify you have the correct files:
// Add this to FormCreate to diagnose SSL at startupprocedure TFormMain.CheckSSL;begin {$IFDEF WIN64} if not FileExists('libcrypto-3-x64.dll') and not FileExists('C:\Windows\System32\libcrypto-3-x64.dll') then ShowMessage('WARNING: 64-bit SSL DLLs not found'); {$ENDIF} {$IFDEF WIN32} if not FileExists('libeay32.dll') and not FileExists('C:\Windows\SysWOW64\libeay32.dll') then ShowMessage('WARNING: 32-bit SSL DLLs not found'); {$ENDIF}end;Certificate Verification Failures#
Problem: HTTPS calls fail with "certificate verify failed" or "unable to get local issuer certificate."
Cause: The server's SSL certificate cannot be verified against a trusted CA bundle. Common with internal APIs, self-signed certificates, or corporate proxies.
Fix for development -- disable certificate validation (never do this in production):
uses System.Net.HttpClient;โprocedure TFormMain.FormCreate(Sender: TObject);begin { Development only -- accepts any certificate } REST.ValidateServerCertificate := False;end;Fix for production -- ensure the server has a valid certificate from a trusted CA (Let's Encrypt, DigiCert, etc.).
Self-Signed Certificate Handling#
Problem: You need to connect to an internal API that uses a self-signed certificate.
Cause: The HTTP client rejects certificates not signed by a trusted CA.
Fix: Accept the specific certificate by validating its thumbprint:
procedure TFormMain.OnValidateServerCertificate( const Sender: TObject; const ARequest: TURLRequest; const Certificate: TCertificate; var Accepted: Boolean);begin { Accept only your known self-signed certificate } if Certificate.Subject.Contains('CN=myapi.internal') then Accepted := True else Accepted := Certificate.IsValid;end;2. REST Issues#
401 Unauthorized#
Problem: Every REST call returns status 401.
Cause 1: Bearer token expired.
// Check if token is setif REST.GetBearer.IsEmpty then ShowMessage('No bearer token set -- user needs to log in');Cause 2: Wrong authentication type. The API expects Basic Auth but you are sending Bearer, or vice versa.
// Basic AuthREST.Username := 'admin';REST.Password := 'secret';โ// Bearer Auth -- do NOT set Username/PasswordREST.SetBearer('eyJhbGciOiJIUzI1NiJ9...');โ// Both set? Bearer takes priority, but the server might reject// the extra Authorization header. Clear one:REST.Username := '';REST.Password := '';REST.SetBearer(Token);Cause 3: Token format wrong. Some APIs want Bearer <token>, others want just the token. TTina4REST adds the Bearer prefix automatically via SetBearer. Do not add it yourself:
// WRONG -- double prefix: "Bearer Bearer eyJ..."REST.SetBearer('Bearer eyJhbGciOiJIUzI1NiJ9...');โ// RIGHTREST.SetBearer('eyJhbGciOiJIUzI1NiJ9...');404 Not Found#
Problem: Status code 404 on a valid endpoint.
Cause 1: Trailing slash mismatch. The API expects /users but you send /users/.
// Try bothRESTUsers.EndPoint := '/users'; // no trailing slashRESTUsers.EndPoint := '/users/'; // with trailing slashCause 2: BaseUrl includes a path that is duplicated in the EndPoint.
// WRONG -- requests /v1/v1/usersREST.BaseUrl := 'https://api.example.com/v1';RESTUsers.EndPoint := '/v1/users';โ// RIGHTREST.BaseUrl := 'https://api.example.com/v1';RESTUsers.EndPoint := '/users';500 Internal Server Error#
Problem: Server returns 500 on POST/PATCH requests.
Cause: The request body is malformed or missing required fields.
Fix: Log the request body before sending:
var Body := '{"name": "Andre"}';// Log itMemo1.Lines.Add('Sending: ' + Body);โvar Response := REST.Post(StatusCode, '/users', '', Body);Common body issues:
- Missing
Content-Type: application/jsonheader (TTina4REST sets this automatically) - Unescaped quotes in string values
- Wrong field names (camelCase vs snake_case)
Timeout Errors#
Problem: REST calls hang for 30+ seconds, then fail.
Cause: Default timeout is too long, or the server is unresponsive.
Fix:
// Set a reasonable timeout (milliseconds)REST.Timeout := 10000; // 10 secondsโ// Handle timeout in async callsRESTUsers.OnExecuteDone := procedure(Sender: TObject)begin TThread.Synchronize(nil, procedure begin if RESTUsers.LastStatusCode = 0 then ShowMessage('Request timed out or network error') else ProcessResponse; end);end;CORS Issues When Calling Web APIs#
Problem: Browser-based APIs return CORS errors. Your Delphi app gets unexpected responses.
Cause: CORS is a browser security feature. Desktop applications do not have CORS restrictions. If you are getting errors, the issue is something else (wrong URL, authentication, SSL).
Fix: CORS is not relevant for Delphi desktop apps. Check the actual error message -- it is likely a network, SSL, or auth issue disguised by a generic error message.
3. JSON Issues#
Access Violation Parsing Malformed JSON#
Problem: StrToJSONObject crashes with an access violation.
Cause: The input string is not valid JSON, and you are using the result without checking for nil.
Fix:
// WRONGvar Obj := StrToJSONObject(ResponseText);ShowMessage(Obj.GetValue<string>('name')); // AV if Obj is nilโ// RIGHTvar Obj := StrToJSONObject(ResponseText);try if Assigned(Obj) then ShowMessage(Obj.GetValue<string>('name')) else ShowMessage('Failed to parse JSON: ' + ResponseText.Substring(0, 100));finally Obj.Free;end;Field Names Not Matching#
Problem: FieldByName('firstName') raises "Field not found" but the data is there.
Cause: PopulateMemTableFromJSON and GetFieldDefsFromJSONObject convert camelCase to snake_case when the ASnakeCase parameter is True (which is the default in some contexts).
Fix: Use the correct field name:
// If JSON has "firstName" and snake_case conversion is on:MemTable.FieldByName('first_name').AsString; // RIGHTMemTable.FieldByName('firstName').AsString; // WRONG -- raises exceptionโ// Check what fields exist:for var I := 0 to MemTable.FieldCount - 1 do Memo1.Lines.Add(MemTable.Fields[I].FieldName);Nested Objects Becoming ftMemo#
Problem: A nested JSON object like {"address": {"city": "Cape Town"}} becomes a single ftMemo field instead of separate fields.
Cause: GetFieldDefsFromJSONObject stores nested objects and arrays as ftMemo fields containing the JSON string. This is by design -- MemTables do not support nested structures.
Fix: Parse the nested JSON from the memo field:
var AddressJSON := MemTable.FieldByName('address').AsString;var Addr := StrToJSONObject(AddressJSON);try if Assigned(Addr) then ShowMessage('City: ' + Addr.GetValue<string>('city'));finally Addr.Free;end;Or use a separate TTina4JSONAdapter to extract the nested data into its own MemTable.
Large JSON Causing Out of Memory#
Problem: Parsing a very large JSON response (100MB+) causes the app to run out of memory.
Cause: Delphi's TJSONObject loads the entire document into memory. Combined with string copies during parsing, memory usage can be 3-5x the JSON size.
Fix: Use pagination to limit response size:
RESTUsers.QueryParams := 'page=1&limit=100'; // Fetch 100 at a timeIf the API does not support pagination, process the response in chunks or use streaming JSON parsers.
4. MemTable Issues#
"Field not found"#
Problem: MemTable.FieldByName('user_name') raises "Field 'user_name' not found."
Cause 1: The API changed its response format and the field name is different.
Fix: List all fields to see what actually exists:
procedure TFormMain.DebugMemTableFields(ATable: TFDMemTable);begin if not ATable.Active then begin ShowMessage('MemTable is not active'); Exit; end;โ var Fields := ''; for var I := 0 to ATable.FieldCount - 1 do Fields := Fields + ATable.Fields[I].FieldName + ' (' + ATable.Fields[I].ClassName + ')' + #13#10; ShowMessage(Fields);end;Cause 2: The MemTable was never populated -- ExecuteRESTCall failed silently.
Fix: Check LastStatusCode before accessing MemTable data:
RESTUsers.ExecuteRESTCall;if RESTUsers.LastStatusCode = 200 thenbegin // Safe to access MemTable MemUsers.First;endelse ShowMessage('REST call failed: ' + RESTUsers.LastStatusCode.ToString);Duplicate Key Violations in Sync Mode#
Problem: PopulateMemTableFromJSON raises an index violation error when using Sync mode.
Cause: IndexFieldNames is set to a field that has duplicate values in the data, or the index was not created properly.
Fix:
// Make sure the index field is unique in the dataRESTUsers.SyncMode := TTina4RestSyncMode.Sync;RESTUsers.IndexFieldNames := 'id'; // 'id' must be uniqueโ// If you need a compound key:RESTUsers.IndexFieldNames := 'user_id;order_id';IndexFieldNames Not Set for Sync Mode#
Problem: Sync mode does not update existing records -- it keeps appending duplicates.
Cause: IndexFieldNames is empty. Without it, the MemTable cannot match existing rows.
Fix:
// WRONG -- Sync mode without index just appendsRESTUsers.SyncMode := TTina4RestSyncMode.Sync;// IndexFieldNames is emptyโ// RIGHTRESTUsers.SyncMode := TTina4RestSyncMode.Sync;RESTUsers.IndexFieldNames := 'id';Data Type Mismatches#
Problem: FieldByName('price').AsFloat returns 0 even though the JSON has "price": "29.99".
Cause: The JSON value is a string "29.99", not a number 29.99. The field was created as ftString.
Fix: Convert explicitly:
var PriceStr := MemTable.FieldByName('price').AsString;var Price := StrToFloatDef(PriceStr, 0.0);Or use AsFloat which does automatic conversion for most cases, but verify the field type:
// Debug the field typeShowMessage(MemTable.FieldByName('price').DataType.ToString);// If it shows ftString, the JSON sent "29.99" as a string5. HTML Renderer Issues#
Elements Not Displaying#
Problem: You set HTML.Text but nothing appears in the renderer.
Cause 1: The renderer has zero width or height.
// Check dimensionsShowMessage(Format('Renderer size: %dx%d', [Round(Renderer.Width), Round(Renderer.Height)]));Cause 2: The HTML has a syntax error that prevents rendering.
Fix: Start with minimal HTML and build up:
// Test with minimal HTML firstRenderer.HTML.Text := '<p>Test</p>';// If this works, add more content graduallyCSS Not Applying#
Problem: CSS classes or styles are ignored.
Cause: The renderer supports a subset of CSS. Some properties are not implemented.
Fix: Check the supported CSS list in the documentation. Use inline styles as a fallback:
// If a CSS class does not work:'<div class="my-custom-class">Text</div>' // might not workโ// Use inline styles instead:'<div style="color: red; font-size: 18px;">Text</div>' // worksSupported CSS includes: color, background-color, font-size, font-family, font-weight, padding, margin, border, border-radius, width, height, display, text-align, and more. Complex selectors like :nth-child or @media queries are not supported.
Form Controls Not Appearing#
Problem: <input> or <select> elements are not visible.
Cause: The form control type is not supported, or the element is hidden by CSS.
Fix: Check supported form elements:
// Supported input types:'<input type="text">' // works'<input type="password">' // works'<input type="email">' // works'<input type="checkbox">' // works'<input type="radio">' // works'<input type="submit">' // works'<input type="button">' // works'<input type="file">' // works'<input type="date">' // might not render as date picker'<input type="range">' // might not render as slider'<textarea>' // works'<select><option>' // worksonclick Not Firing#
Problem: Clicking an element with onclick="App:MyMethod('test')" does nothing.
Cause 1: The object was not registered with RegisterObject.
// Must be called before setting HTMLRenderer.RegisterObject('App', Self);Cause 2: The method does not exist or has the wrong signature.
// The method must be published or public// Parameter types must match what the onclick passesโ// WRONG -- private method, not found by RTTIprivate procedure MyMethod(const Value: string);โ// RIGHT -- public methodpublic procedure MyMethod(Value: String);Cause 3: Wrong quote escaping in the onclick attribute.
// WRONG -- mismatched quotes'<span onclick="App:MyMethod("test")">Click</span>'โ// RIGHT -- use single quotes inside double, or escape'<span onclick="App:MyMethod(''test'')">Click</span>'Cause 4: The element is overlapped by another element. The click hits the wrong element.
Fix: Add a distinct style="cursor: pointer; z-index: 10;" and ensure nothing overlaps.
Images Not Loading#
Problem: <img src="https://..."> shows a broken image or empty space.
Cause 1: Cache directory not set or not writable.
Renderer.CacheEnabled := True;Renderer.CacheDir := TPath.Combine(TPath.GetDocumentsPath, 'MyAppCache');ForceDirectories(Renderer.CacheDir); // Create if it does not existCause 2: HTTPS image URL but SSL DLLs not installed.
Fix: Install SSL DLLs as described in Section 1.
Cause 3: The image URL returns a redirect that the renderer does not follow.
Fix: Use the final URL directly, or download the image separately and use a data: URI:
var B64 := FileToBase64('downloaded_image.jpg');Renderer.HTML.Text := '<img src="data:image/jpeg;base64,' + B64 + '">';6. Page Navigation Issues#
Default Page Not Showing#
Problem: The app starts with a blank renderer. No page is displayed.
Cause: No page has IsDefault := True.
Fix:
var LoginPage := Pages.Pages.Add;LoginPage.PageName := 'login';LoginPage.IsDefault := True; // This page shows on startupLoginPage.HTMLContent.Text := '<h1>Login</h1>';Links Not Navigating#
Problem: Clicking <a href="#dashboard"> does not change the page.
Cause 1: The href value does not match any PageName.
// Link href: #dashboard// Page.PageName: Dashboard <-- case mismatchโ// Fix: make them match exactlyPage.PageName := 'dashboard'; // lowercase// HTML: <a href="#dashboard"> // matchesCause 2: The renderer is not linked to the Pages component.
Pages.Renderer := Renderer; // Must be setOnBeforeNavigate Not Cancelling#
Problem: Setting Allow := False in OnBeforeNavigate does not prevent navigation.
Cause: The Allow parameter is a var parameter. Make sure your event handler signature matches:
// WRONG -- parameter not passed by referenceprocedure TFormMain.BeforeNav(Sender: TObject; const FromPage, ToPage: string; Allow: Boolean); // missing 'var'โ// RIGHTprocedure TFormMain.BeforeNav(Sender: TObject; const FromPage, ToPage: string; var Allow: Boolean);begin if (ToPage <> 'login') and (not IsAuthenticated) then Allow := False; // Prevents navigationend;7. Twig Template Issues#
Template Not Rendering#
Problem: Setting Twig.Text produces empty output or the raw template text.
Cause 1: Wrong template path for file-based templates.
// Check the path existsShowMessage(Pages.TwigTemplatePath);ShowMessage(BoolToStr(DirectoryExists(Pages.TwigTemplatePath), True));Cause 2: Template syntax error. A missing {% endif %} or unmatched braces.
Fix: Test with a minimal template:
Renderer.Twig.Text := '<p>{{ name }}</p>';// If this works, the issue is in your complex templateVariables Empty#
Problem: Template renders but variables show as empty.
Cause: Variables were not set before the template was assigned.
// WRONG -- template renders before variables are setRenderer.Twig.Text := '<h1>{{ title }}</h1>';Renderer.SetTwigVariable('title', 'Hello');โ// RIGHT -- set variables firstRenderer.SetTwigVariable('title', 'Hello');Renderer.Twig.Text := '<h1>{{ title }}</h1>';Includes Failing#
Problem: {% include 'header.html' %} does not work. The template renders without the included content.
Cause: TwigTemplatePath is not set or points to the wrong directory.
Fix:
// Set the base path for includesPages.TwigTemplatePath := 'C:\MyApp\templates';// orRenderer.TwigTemplatePath := 'C:\MyApp\templates';โ// The included file must exist at:// C:\MyApp\templates\header.html8. WebSocket Issues#
Connection Refused#
Problem: WebSocket connection fails immediately.
Cause 1: Wrong URL scheme. Use wss:// for secure or ws:// for insecure.
// WRONGFWebSocket.URL := 'https://api.example.com/ws';โ// RIGHTFWebSocket.URL := 'wss://api.example.com/ws';Cause 2: Server not running or firewall blocking the port.
Fix: Test the WebSocket URL with a browser-based tool (like websocat or a browser extension) to verify the server is reachable.
Messages Not Receiving#
Problem: WebSocket connects successfully but OnMessage never fires.
Cause 1: OnMessage event not wired up.
// WRONG -- event handler not assignedFWebSocket.Connect;โ// RIGHTFWebSocket.OnMessage := procedure(Sender: TObject; const Msg: string)begin TThread.Synchronize(nil, procedure begin ProcessMessage(Msg); end);end;FWebSocket.Connect;Cause 2: The server sends binary messages, not text. Check server documentation.
Cause 3: The server requires a subscription message after connecting.
FWebSocket.OnConnect := procedure(Sender: TObject)begin { Some servers require subscribing to channels } FWebSocket.Send('{"action": "subscribe", "channel": "notifications"}');end;Auto-Reconnect Not Working#
Problem: After a disconnect, the WebSocket does not reconnect.
Cause: Auto-reconnect properties not configured.
Fix:
FWebSocket.AutoReconnect := True;FWebSocket.ReconnectInterval := 5000; // 5 seconds between attemptsโFWebSocket.OnDisconnect := procedure(Sender: TObject)begin TThread.Synchronize(nil, procedure begin ShowNotification('Connection lost. Reconnecting...'); end);end;โFWebSocket.OnReconnect := procedure(Sender: TObject)begin TThread.Synchronize(nil, procedure begin ShowNotification('Reconnected.'); { Re-authenticate if needed } FWebSocket.Send('{"action": "auth", "token": "' + FBearerToken + '"}'); end);end;9. Diagnostic Checklists#
TTina4REST Checklist#
When REST calls are not working:
- Is
BaseUrlset and correct (no trailing slash)? - Is the endpoint path correct (starts with
/)? - Are SSL DLLs installed for the correct bitness?
- Is authentication set (
SetBearerorUsername/Password)? - What status code comes back? (0 = network error, 401 = auth, 404 = wrong path)
- Can you reach the URL from a browser or curl?
TTina4HTMLRender Checklist#
When the renderer is not displaying correctly:
- Does the renderer have non-zero width and height?
- Is
RegisterObjectcalled for RTTI onclick? - Are the methods public (not private)?
- Is the HTML valid (all tags closed)?
- Are images using HTTPS (SSL DLLs needed)?
- Is
CacheDirwritable?
TTina4HTMLPages Checklist#
When pages are not navigating:
- Is
Rendererlinked to a TTina4HTMLRender? - Does at least one page have
IsDefault := True? - Do
hrefvalues matchPageNamevalues (case-sensitive)? - Is
OnBeforeNavigateblocking withAllow := False? - Is the page collection empty?
TTina4RESTRequest Checklist#
When data is not loading into MemTable:
- Is
Tina4RESTlinked to a TTina4REST component? - Is
EndPointset correctly? - Is
DataKeyset to the correct JSON key? - Is
MemTablelinked to a TFDMemTable? - Does the API response contain the expected
DataKey? - Is
SyncModeset correctly? (Sync needsIndexFieldNames)
10. Quick Reference: Error to Fix#
| Symptom | Likely Cause | Section |
|---|---|---|
| "Could not load SSL library" | Wrong bitness SSL DLLs | 1 |
| Status code 0 | Network error or timeout | 2 |
| Status code 401 | Token expired or wrong auth | 2 |
| Status code 404 | Wrong endpoint or double path | 2 |
| Access violation on JSON parse | nil result not checked | 3 |
| "Field not found" on MemTable | snake_case conversion | 4 |
| Blank renderer | Zero width/height or empty HTML | 5 |
| onclick does nothing | RegisterObject missing | 5 |
| No default page shown | IsDefault not set | 6 |
| Twig variables empty | Set after template assigned | 7 |
| WebSocket not connecting | Wrong URL scheme (https vs wss) | 8 |
| UI freezes on REST call | Synchronous call, use async | Ch. 13 |
| Memory leak | TJSONObject not freed | Ch. 13 |
Summary#
Most Tina4 Delphi issues fall into five categories:
- SSL configuration -- wrong DLL bitness, missing DLLs, certificate issues
- REST communication -- wrong endpoints, auth problems, missing error handling
- JSON structure -- nil checks, field name mismatches, nested object handling
- HTML rendering -- registration, event wiring, CSS support limits
- Threading -- synchronous calls blocking UI, missing TThread.Synchronize
When something does not work, start with the diagnostic checklist for the component involved. Check the status code. Log the response. Verify the field names. Nine times out of ten, the fix is in this chapter.